From 933c0389b811878e0653cccf3c98ace2efed4207 Mon Sep 17 00:00:00 2001 From: Ben Sharafian Date: Wed, 7 Mar 2018 04:53:56 -0800 Subject: [PATCH] feat: use btp plugin (#23) --- .gitignore | 2 + README.md | 58 +- circle.yml | 2 +- index.js | 751 +++--- package-lock.json | 3051 +++++++++++++---------- package.json | 20 +- store-wrapper.js | 32 + test/channelSpec.js | 456 ---- test/integration/rippled-integration.js | 160 +- test/pluginSpec.js | 384 +++ test/scripts/test.js | 89 + 11 files changed, 2735 insertions(+), 2270 deletions(-) create mode 100644 store-wrapper.js delete mode 100644 test/channelSpec.js create mode 100644 test/pluginSpec.js create mode 100644 test/scripts/test.js diff --git a/.gitignore b/.gitignore index 91fa8cf..6c71859 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /node_modules/ +/coverage/ +/.nyc_output/ npm-debug.log diff --git a/README.md b/README.md index e1a3138..cec06a3 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,52 @@ # ilp-plugin-xrp-paychan Uses payment channels on ripple to do fast ILP transactions between you and a -peer. Current in-flight payments are at risk (your peer can choose not to give -you claims for them), but if the amount in-flight exceeds your `inFlightLimit`, -you won't acknowledge incoming transfers until you're paid. +peer. Current in-flight payments are at risk (your peer can choose not to give +you claims for them), but are secured against the ledger as soon as you get a +claim. -**Warning: This plugin is still in a development state, and should not be used with -live accounts.** +**Warning: This plugin is still in a development state.** # Example This is how to instantiate a plugin: ```js -const PluginRipple = require('ilp-plugin-xrp-paychan') +const PluginXrpPaychan = require('ilp-plugin-xrp-paychan') const Store = require('ilp-plugin-payment-channel-framework/test/helpers/objStore') -const plugin = new PluginRipple({ - // This is the server that ripple-lib submits transactions to. You can - // configure this to point at the altnet or to point at the live net. - rippledServer: 'wss://s.altnet.rippletest.net:51233', +const plugin = new PluginXrpPaychan({ - // Your ripple address and secret - address: 'r33L6z6LMD8Lk39iEQhyXeSWqNN7pFVaM6', - secret: 'ssyFYib1wv4tKrYfQEARxGREH6T3b', + // If you want your peer to connect to you as a ws client (which doesn't + // change the nature of the liquidity relationship) set the `listener` + // argument in the constructor. + listener: { + port: 666, + secret: 'its_a_secret' // this is the token that your peer must authenticate with. + }, - // The peer you want to start a payment channel with - peerAddress: 'rhxcezvTxiANA3TkxBWpx923M5zQ4RZ9gJ', + // If you wish to connect to your peer as a ws client, specify the server option. + // You may specify both the server and client options; in that case it is not deterministic + // which peer will end up as the ws client. + server: 'btp+ws://:its_a_secret@localhost:666', - // limit of how much can be owed in-flight to you at once before you stop - // accepting more incoming transfers. (in XRP drops) - maxUnsecured: '50000', + // Specify the server that you submit XRP transactions to. + xrpServer: 'wss://s.altnet.rippletest.net:51233', - // how much to fund your payment channel. (in XRP drops) - maxAmount: '10000000', + // XRP address and secret + secret: 's...', + address: 'r...', - // highest balance allowed - maxBalance: 'Infinity', + // Peer's XRP address + peerAddress: 'r...', - // Our peer's BTP server address. - server: 'btp+ws://alice:password@mypeer.example', - - // store is used to keep local state, which is necessary because the plugin - // works based on off-chain payment channel claims. `get`, `put`, and `del` - // are asynchronous functions for accessing a key-value store. See - // https://github.com/interledger/rfcs/blob/master/0004-ledger-plugin-interface/0004-ledger-plugin-interface.md#class-pluginoptions + // Store in which to save claims and channel details. This will be passed in + // automatically if you're using the ILP connector. _store: new Store() }) plugin.connect().then(() => { // do something with your plugin + return plugin.sendData(/* ... */) }) ``` - -For an example of how to send a payment refer to [examples/send-payment.js](https://github.com/ripple/ilp-plugin-xrp-paychan/blob/master/examples/send-payment.js). \ No newline at end of file diff --git a/circle.yml b/circle.yml index e8f39ba..b9378db 100644 --- a/circle.yml +++ b/circle.yml @@ -2,7 +2,7 @@ machine: services: - docker node: - version: 7.7.1 + version: 8.9.4 dependencies: pre: diff --git a/index.js b/index.js index 18ef7ea..1350a4a 100644 --- a/index.js +++ b/index.js @@ -1,500 +1,409 @@ 'use strict' // imports +const debug = require('debug')('ilp-plugin-xrp-paychan') +const BtpPacket = require('btp-packet') const { RippleAPI } = require('ripple-lib') -const addressCodec = require('ripple-address-codec') -const { makePaymentChannelPlugin } = require('ilp-plugin-payment-channel-framework') -const uuid = require('uuid') +const { deriveAddress, deriveKeypair } = require('ripple-keypairs') +const PluginBtp = require('ilp-plugin-btp') const nacl = require('tweetnacl') -const crypto = require('crypto') -const bignum = require('bignum') // required in order to convert to buffer const BigNumber = require('bignumber.js') -const assert = require('assert') -const { ChannelWatcher } = require('ilp-plugin-xrp-paychan-shared') +const StoreWrapper = require('./store-wrapper') +const { + ChannelWatcher, + util +} = require('ilp-plugin-xrp-paychan-shared') // constants const CHANNEL_KEYS = 'ilp-plugin-xrp-paychan-channel-keys' -const DEFAULT_REFUND_THRESHOLD = 0.9 -const { - DEFAULT_WATCHER_INTERVAL, - STATE_NO_CHANNEL, - STATE_CREATING_CHANNEL, - STATE_CHANNEL, - POLLING_INTERVAL_OUTGOING_PAYCHAN, - MIN_SETTLE_DELAY, - xrpToDrops, - dropsToXrp, - sleep -} = require('./src/lib/constants') - -// utility functions -const randomTag = () => bignum.fromBuffer(crypto.randomBytes(4), { - endian: 'big', - size: 4 -}).toNumber() - -const encodeClaim = (amount, id) => Buffer.concat([ - Buffer.from('CLM\0'), - Buffer.from(id, 'hex'), - bignum(amount).toBuffer({ - endian: 'big', - size: 8 - }) -]) - -const computeChannelId = (src, dest, sequence) => { - const preimage = Buffer.concat([ - Buffer.from('\0x', 'ascii'), - Buffer.from(addressCodec.decodeAccountID(src)), - Buffer.from(addressCodec.decodeAccountID(dest)), - bignum(sequence).toBuffer({ endian: 'big', size: 4 }) - ]) - - return crypto.createHash('sha512') - .update(preimage) - .digest() - .slice(0, 32) // first half sha512 - .toString('hex') - .toUpperCase() -} - -const claimFunds = async (ctx) => { - const self = ctx.state - - const bestClaim = await self.incomingClaim.getMax() - const signature = bestClaim.data - const amount = bestClaim.value - const claimedValue = (await self.incomingClaimSubmitted.getMax()).value - if (claimedValue === amount) return - - const tx = await self.api.preparePaymentChannelClaim(self.address, { - balance: dropsToXrp(amount), - channel: self.incomingPaymentChannelId, - signature: signature.toUpperCase(), - publicKey: self.incomingPaymentChannel.publicKey - }) - - const signedTx = self.api.sign(tx.txJSON, self.secret) - ctx.plugin.debug('submitting claim transaction ', tx) - const {resultCode, resultMessage} = await self.api.submit(signedTx.signedTransaction) - if (resultCode !== 'tesSUCCESS') { - ctx.plugin.debug('Error submitting claim: ', resultMessage) - throw new Error('Could not claim funds: ', resultMessage) +const DEFAULT_CHANNEL_AMOUNT = 1000000 +const DEFAULT_FUND_THRESHOLD = 0.9 + +class PluginXrpPaychan extends PluginBtp { + constructor (opts) { + super(opts) + + this._xrpServer = opts.xrpServer || opts.rippledServer // TODO: deprecate rippledServer + this._api = new RippleAPI({ server: this._xrpServer }) + this._secret = opts.secret + this._address = opts.address || deriveAddress(deriveKeypair(this._secret).publicKey) + + this._peerAddress = opts.peerAddress // TODO: try to get this over the paychan? + this._fundThreshold = opts.fundThreshold || DEFAULT_FUND_THRESHOLD + this._channelAmount = opts.channelAmount || DEFAULT_CHANNEL_AMOUNT + this._claimInterval = opts.claimInterval || util.DEFAULT_CLAIM_INTERVAL + this._settleDelay = opts.settleDelay || util.MIN_SETTLE_DELAY + + const keyPairSeed = util.hmac(this._secret, CHANNEL_KEYS + this._peerAddress) + this._keyPair = nacl.sign.keyPair.fromSeed(keyPairSeed) + + this._store = new StoreWrapper(opts._store) + this._outgoingChannel = null + this._incomingChannel = null + this._incomingChannelDetails = null + this._incomingClaim = null + this._outgoingClaim = null + + this._watcher = new ChannelWatcher(60 * 1000, this._api) + this._watcher.on('channelClose', () => { + debug('channel closing; triggering auto-disconnect') + // TODO: should we also close our own channel? + this.disconnect() + }) } - return new Promise((resolve) => { - const handleTransaction = async function (ev) { - if (ev.transaction.Account !== self.address) return - if (ev.transaction.Channel !== self.incomingPaymentChannelId) return - if (ev.transaction.Balance !== amount) return + async _handleData (from, { requestId, data }) { + const { ilp, protocolMap } = this.protocolDataToIlpAndCustom(data) - if (ev.engine_result === 'tesSUCCESS') { - ctx.plugin.debug('successfully submitted claim', signature, 'for amount', amount) - await self.incomingClaimSubmitted.setIfMax({value: amount, data: signature}) - } else { - ctx.plugin.debug('claiming funds failed ', ev) - } + if (protocolMap.ripple_channel_id) { + this._incomingChannel = protocolMap.ripple_channel_id + await this._reloadIncomingChannelDetails() - setImmediate(() => self.api.connection - .removeListener('transaction', handleTransaction)) - resolve() + return [{ + protocolName: 'ripple_channel_id', + contentType: BtpPacket.MIME_TEXT_PLAIN_UTF8, + data: Buffer.from(this._outgoingChannel) + }] } - self.api.connection.on('transaction', handleTransaction) - }) -} - -async function reloadIncomingChannelDetails (ctx) { - const self = ctx.state - let chanId = self.incomingPaymentChannelId - let incomingChan = null - if (!chanId) { - ctx.plugin.debug('quering peer for incoming channel id') - try { - chanId = await ctx.rpc.call('ripple_channel_id', self.prefix, []) - } catch (err) { ctx.plugin.debug(err) } - if (!chanId) { - ctx.plugin.debug('cannot load incoming channel. Peer did not return incoming channel id') - return + if (!this._dataHandler) { + throw new Error('no request handler registered') } - } - // look up channel on ledger - ctx.plugin.debug('retrieving details for incoming channel', chanId) - try { - incomingChan = await self.api.getPaymentChannel(chanId) - ctx.plugin.debug('incoming channel details are:', incomingChan) - } catch (err) { - if (err.name === 'RippledError' && err.message === 'entryNotFound') { - ctx.plugin.debug('incoming payment channel does not exist:', chanId) - } else { - ctx.plugin.debug(err) - } - return - } - - const expectedBalance = (await self.incomingClaimSubmitted.getMax()).value - const actualBalance = new BigNumber(incomingChan.balance) - if (!actualBalance.equals(expectedBalance)) { - ctx.plugin.debug('Expected the balance of the incoming payment channel to be ' + expectedBalance + - ', but it was ' + actualBalance.toString()) - throw new Error('Unexpected balance of incoming payment channel') + const response = await this._dataHandler(ilp) + return this.ilpAndCustomToProtocolData({ ilp: response }) } - // Make sure the watcher has enough time to submit the best - // claim before the channel closes - const settleDelay = incomingChan.settleDelay - if (settleDelay < MIN_SETTLE_DELAY) { - ctx.plugin.debug(`incoming payment channel has a too low settle delay of ${settleDelay.toString()}` + - ` seconds. Minimum settle delay is ${MIN_SETTLE_DELAY} seconds.`) - throw new Error('settle delay of incoming payment channel too low') - } - - if (incomingChan.cancelAfter) { - throw new Error('incoming paychan expires (.cancelAfter is set)') - } - - if (incomingChan.expiration) { - throw new Error('incoming paychan expires (.expiration is set)') - } + async _reloadIncomingChannelDetails () { + if (!this._incomingChannel) { + debug('quering peer for incoming channel id') + try { + const response = await this._call(null, { + type: BtpPacket.TYPE_MESSAGE, + requestId: await util._requestId(), + data: { protocolData: [{ + protocolName: 'ripple_channel_id', + contentType: BtpPacket.MIME_TEXT_PLAIN_UTF8, + data: Buffer.from(this._outgoingChannel) + }] } + }) - if (incomingChan.destination !== self.address) { - ctx.plugin.debug('incoming channel destination is not our address: ' + - incomingChan.destination) - throw new Error('Channel destination address wrong') - } + // TODO: should this send raw bytes instead of text in the future + debug('got ripple_channel_id response:', response) + this._incomingChannel = response + .protocolData + .filter(p => p.protocolName === 'ripple_channel_id')[0] + .data + .toString() + this._store.set('incoming_channel', this._incomingChannel) + } catch (err) { debug(err) } + + if (!this._incomingChannel) { + debug('cannot load incoming channel. Peer did not return incoming channel id') + return + } + } - self.incomingPaymentChannelId = chanId - self.incomingPaymentChannel = incomingChan + // look up channel on ledger + debug('retrieving details for incoming channel', this._incomingChannel) + try { + this._incomingChannelDetails = await this._api.getPaymentChannel(this._incomingChannel) + debug('incoming channel details are:', this._incomingChannelDetails) + } catch (err) { + if (err.name === 'RippledError' && err.message === 'entryNotFound') { + debug('incoming payment channel does not exist:', this._incomingChannel) + } else { + debug(err) + } + return + } - self.watcher.watch(chanId) -} + // Make sure the watcher has enough time to submit the best + // claim before the channel closes + const settleDelay = this._incomingChannelDetails.settleDelay + if (settleDelay < util.MIN_SETTLE_DELAY) { + debug(`incoming payment channel has a too low settle delay of ${settleDelay.toString()} + seconds. Minimum settle delay is ${util.MIN_SETTLE_DELAY} seconds.`) + throw new Error('settle delay of incoming payment channel too low') + } -async function fund (ctx, fundOpts) { - const self = ctx.state - ctx.plugin.debug('outgoing channel threshold reached, adding more funds') - const xrpAmount = dropsToXrp(self.maxAmount) - const tx = await self.api.preparePaymentChannelFund(self.address, Object.assign({ - amount: xrpAmount, - channel: self.outgoingPaymentChannelId - }, fundOpts)) - - ctx.plugin.debug('submitting channel fund tx', tx) - const signedTx = self.api.sign(tx.txJSON, self.secret) - const {resultCode, resultMessage} = await self.api.submit(signedTx.signedTransaction) - if (resultCode !== 'tesSUCCESS') { - ctx.plugin.debug(`Failed to add ${xrpAmount} XRP to channel ${self.channelId}: `, resultMessage) - } + if (this._incomingChannelDetails.cancelAfter) { + debug(`channel has cancelAfter set`) + throw new Error('cancelAfter must not be set') + } - const handleTransaction = async function (ev) { - if (ev.transaction.hash !== signedTx.id) return + if (this._incomingChannelDetails.expiration) { + debug(`channel has expiration set`) + throw new Error('expiration must not be set') + } - if (ev.engine_result === 'tesSUCCESS') { - ctx.plugin.debug(`successfully funded channel for ${xrpAmount} XRP`) - const { amount } = await self.api.getPaymentChannel(self.outgoingPaymentChannelId) - self.maxAmount = xrpToDrops(amount) // TODO: no side-effects - } else { - ctx.plugin.debug('funding channel failed ', ev) + if (this._incomingChannelDetails.destination !== this._address) { + debug('incoming channel destination is not our address: ' + + this._incomingChannelDetails.destination) + throw new Error('Channel destination address wrong') } - setImmediate(() => self.api.connection - .removeListener('transaction', handleTransaction)) + this._lastClaimedAmount = new BigNumber(util.xrpToDrops(this._incomingChannelDetails.balance)) + this._claimIntervalId = setInterval(async () => { + if (this._lastClaimedAmount.isLessThan(this._incomingClaim.amount)) { + debug('starting automatic claim. amount=' + this._incomingClaim.amount) + this._lastClaimedAmount = new BigNumber(this._incomingClaim.amount) + await this._claimFunds() + debug('claimed funds.') + } + }, this._claimInterval) } - // TODO: handleTransaction is not called back - self.api.connection.on('transaction', handleTransaction) -} - -function validateOpts (opts) { - // TODO: validate plugin options - // mandatory - assert(opts.rippledServer, 'rippledServer is required') - assert(opts.address, 'address is required') - assert(opts.secret, 'secret is required') - assert(opts.peerAddress, 'peerAddress is required') - assert(opts.maxAmount, 'maxAmount is required') - assert(opts.maxUnsecured, 'maxUnsecured is required') - - // optional - if (opts.fundThreshold) { - assert(parseFloat(opts.fundThreshold) > 0 && parseFloat(opts.fundThreshold) <= 1) - } -} + // run after connections are established, but before connect resolves + async _connect () { + debug('connecting to rippled') + await this._api.connect() + await this._api.connection.request({ + command: 'subscribe', + accounts: [ this._address, this._peerAddress ] + }) + debug('connected to rippled') + + await this._store.load('outgoing_channel') + await this._store.load('incoming_claim') + await this._store.load('outgoing_claim') + + this._outgoingChannel = this._store.get('outgoing_channel') + this._incomingClaim = JSON.parse(this._store.get('incoming_claim') || '{"amount":"0"}') + this._outgoingClaim = JSON.parse(this._store.get('outgoing_claim') || '{"amount":"0"}') + debug('loaded incoming claim:', this._incomingClaim) + + if (!this._outgoingChannel) { + debug('creating new payment channel') + + const txTag = util.randomTag() + const tx = await this._api.preparePaymentChannelCreate(this._address, { + amount: util.dropsToXrp(this._channelAmount), + destination: this._peerAddress, + settleDelay: this._settleDelay, + publicKey: 'ED' + Buffer.from(this._keyPair.publicKey).toString('hex').toUpperCase(), + sourceTag: txTag + }) -function hmac (key, message) { - const h = crypto.createHmac('sha256', key) - h.update(message) - return h.digest() -} + debug('created paymentChannelCreate tx', tx.txJSON) -module.exports = makePaymentChannelPlugin({ - pluginName: 'xrp-paychan', - - constructor: function (ctx, opts) { - validateOpts(opts) - - const self = ctx.state - self.rippledServer = opts.rippledServer - self.api = new RippleAPI({ server: opts.rippledServer }) - self.address = opts.address - self.secret = opts.secret - self.peerAddress = opts.peerAddress - self.maxAmount = opts.maxAmount - self.maxUnsecured = opts.maxUnsecured - self.fundThreshold = opts.fundThreshold || DEFAULT_REFUND_THRESHOLD - // self.claimThreshold = opts.claimThreshold // TODO: implement automatic claiming if threshold is reached - self.prefix = opts.prefix // TODO: auto-generate prefix? - self.authToken = opts.token - self.settleDelay = opts.settleDelay || MIN_SETTLE_DELAY - - const keyPairSeed = hmac(self.secret, CHANNEL_KEYS + self.peerAddress) - self.keyPair = nacl.sign.keyPair.fromSeed(keyPairSeed) - self.outgoingChannel = ctx.backend.getMaxValueTracker('outgoing_channel') - self.incomingClaim = ctx.backend.getMaxValueTracker('incoming_claim') - self.incomingClaimSubmitted = ctx.backend.getMaxValueTracker('incoming_claim_submitted') - - self.watcher = new ChannelWatcher(DEFAULT_WATCHER_INTERVAL, self.api) - self.watcher.on('channelClose', async (id, paychan) => { - ctx.plugin.debug('incoming channel closing; triggering disconnect') - self.incomingPaymentChannel = paychan + const signedTx = this._api.sign(tx.txJSON, this._secret) + let resultCode + let resultMessage try { - ctx.plugin.disconnect() + const result = await this._api.submit(signedTx.signedTransaction) + resultCode = result.resultCode + resultMessage = result.resultMessage } catch (err) { - ctx.plugin.debug('Fatal Error: failed to disconnect. ' + - 'Operating on an incoming channel that is about to close.') + debug('error submitting paymentChannelCreate', err) + throw err } - }) - - ctx.rpc.addMethod('ripple_channel_id', () => { - if (!self.incomingPaymentChannelId) { - setTimeout(async () => { - try { - await reloadIncomingChannelDetails(ctx) - } catch (err) { - ctx.plugin.debug('error loading incoming channel', err) - } - }, 100) + if (resultCode !== 'tesSUCCESS') { + const message = 'Error creating the payment channel: ' + resultCode + ' ' + resultMessage + debug(message) + throw new Error(message) } - return self.outgoingPaymentChannelId || null - }) - }, - - getAuthToken: (ctx) => (ctx.state.authToken), - connect: async function (ctx) { - const self = ctx.state - ctx.plugin.debug('connecting to rippled server:', self.rippledServer) - try { - await self.api.connect() + debug('submitted paymentChannelCreate, waiting for tx to be validated (this may take a few seconds)') + await new Promise((resolve) => { + const handleTransaction = (ev) => { + if (ev.transaction.SourceTag !== txTag) return + if (ev.transaction.Account !== this._address) return + + this._outgoingChannel = util.computeChannelId( + ev.transaction.Account, + ev.transaction.Destination, + ev.transaction.Sequence) + this._store.set('outgoing_channel', this._outgoingChannel) + + setImmediate(() => this._api.connection + .removeListener('transaction', handleTransaction)) + resolve() + } - await self.api.connection.request({ - command: 'subscribe', - accounts: [ self.address, self.peerAddress ] + this._api.connection.on('transaction', handleTransaction) }) - } catch (err) { - ctx.plugin.debug('error connecting to rippled server', err) - throw new Error('Error connecting to rippled server: ' + err.message) + debug('payment channel successfully created: ', this._outgoingChannel) } - ctx.plugin.debug('connected to rippled server') - - // open payment channel - let channelId - const highest = await self.outgoingChannel.getMax() - if (highest.value === STATE_CHANNEL) { // channel exists - channelId = highest.data - ctx.plugin.debug('using existing payment channel:', channelId) - } else { // create channel - const pluginId = uuid() - const tryToCreate = { value: STATE_CREATING_CHANNEL, data: pluginId } - const result = await self.outgoingChannel.setIfMax(tryToCreate) - - // if the payment channel has not been created and this process must create it - if (result.value === STATE_NO_CHANNEL) { - ctx.plugin.debug('creating new payment channel') - const txTag = randomTag() - let tx - try { - tx = await self.api.preparePaymentChannelCreate(self.address, { - amount: dropsToXrp(self.maxAmount), - destination: self.peerAddress, - settleDelay: self.settleDelay, - publicKey: 'ED' + Buffer.from(self.keyPair.publicKey).toString('hex').toUpperCase(), - sourceTag: txTag - }) - } catch (e) { - ctx.plugin.debug('Error preparing payment channel.', e) - throw e - } - ctx.plugin.debug('created paymentChannelCreate tx', tx.txJSON) - - const signedTx = self.api.sign(tx.txJSON, self.secret) - let resultCode - let resultMessage - try { - const result = await self.api.submit(signedTx.signedTransaction) - resultCode = result.resultCode - resultMessage = result.resultMessage - } catch (err) { - ctx.plugin.debug('error submitting paymentChannelCreate', err) - throw err - } - if (resultCode !== 'tesSUCCESS') { - const message = 'Error creating the payment channel: ' + resultCode + ' ' + resultMessage - ctx.plugin.debug(message) - throw new Error(message) - } - - ctx.plugin.debug('submitted paymentChannelCreate, waiting for tx to be validated (this may take a few seconds)') - await new Promise((resolve) => { - function handleTransaction (ev) { - if (ev.transaction.SourceTag !== txTag) return - if (ev.transaction.Account !== self.address) return + this._outgoingChannelDetails = await this._api.getPaymentChannel(this._outgoingChannel) + await this._reloadIncomingChannelDetails() + } - channelId = computeChannelId( - ev.transaction.Account, - ev.transaction.Destination, - ev.transaction.Sequence) + async _claimFunds () { + if (!this._incomingClaim.signature) { + return + } - setImmediate(() => self.api.connection - .removeListener('transaction', handleTransaction)) - resolve() - } + const tx = await this._api.preparePaymentChannelClaim(this._address, { + balance: util.dropsToXrp(this._incomingClaim.amount), + channel: this._incomingChannel, + signature: this._incomingClaim.signature.toUpperCase(), + publicKey: this._incomingChannelDetails.publicKey + }) - self.api.connection.on('transaction', handleTransaction) - }) - ctx.plugin.debug('payment channel successfully created: ', channelId) - - await self.outgoingChannel.setIfMax({ value: STATE_CHANNEL, data: channelId }) - } else if (result.value === STATE_CREATING_CHANNEL) { - // if another process is currently creating the channel poll for channelId - ctx.plugin.debug(`polling for channelId (plugin id ${pluginId})`) - while ((await self.outgoingChannel.getMax()).value !== STATE_CHANNEL) { - await sleep(POLLING_INTERVAL_OUTGOING_PAYCHAN) - } - channelId = (await self.outgoingChannel.getMax()).data - } + const signedTx = this._api.sign(tx.txJSON, this._secret) + debug('submitting claim transaction ', tx) + const {resultCode, resultMessage} = await this._api.submit(signedTx.signedTransaction) + if (resultCode !== 'tesSUCCESS') { + debug('Error submitting claim: ', resultMessage) + throw new Error('Could not claim funds: ', resultMessage) } - self.outgoingPaymentChannelId = channelId - self.outgoingPaymentChannel = await self.api.getPaymentChannel(channelId) + return new Promise((resolve) => { + const handleTransaction = (ev) => { + if (ev.transaction.Account !== this._address) return + if (ev.transaction.Channel !== this._incomingChannel) return - await reloadIncomingChannelDetails(ctx) - }, + if (ev.engine_result === 'tesSUCCESS') { + debug('successfully submitted claim', this._incomingClaim) + } else { + debug('claiming funds failed ', ev) + } - disconnect: async function (ctx) { - const self = ctx.state - ctx.plugin.debug('disconnecting payment channel') + setImmediate(() => this._api.connection + .removeListener('transaction', handleTransaction)) + resolve() + } + this._api.connection.on('transaction', handleTransaction) + }) + } + + async _disconnect () { + debug('disconnecting payment channel') + clearInterval(this._claimIntervalId) try { - await claimFunds(ctx) - } catch (err) { - ctx.plugin.debug(err) + await this._claimFunds() + } catch (e) { + debug('claim error on disconnect:', e) } try { - self.api.disconnect() - } catch (err) { - ctx.plugin.debug('Error disconnecting from rippled', err) - } - }, - - getAccount: ctx => ctx.plugin._prefix + ctx.state.address, - getPeerAccount: ctx => ctx.plugin._prefix + ctx.state.peerAddress, - getInfo: ctx => ({ - currencyCode: 'XRP', - currencyScale: 6, - prefix: ctx.plugin._prefix, - connectors: [ ctx.plugin._prefix + ctx.state.peerAddress ] - }), - - handleIncomingPrepare: async function (ctx, transfer) { - const self = ctx.state - - if (!self.incomingPaymentChannel) { - throw new Error('incoming payment channel must be established ' + - 'before incoming transfers are processed') + this._api.disconnect() + } catch (e) { + debug('error disconnecting from rippled:', e) } + } - if (self.incomingPaymentChannel.expiration) { - throw new Error('cannot process transfer for a payment channel that is closing') + async sendMoney (amount) { + const claimAmount = new BigNumber(this._outgoingClaim.amount).plus(amount) + const encodedClaim = util.encodeClaim(claimAmount, this._outgoingChannel) + const signature = nacl.sign.detached(encodedClaim, this._keyPair.secretKey) + + debug(`signed outgoing claim for ${claimAmount.toString()} drops on + channel ${this._outgoingChannel}`) + + if (!this._funding && claimAmount.isGreaterThan(new BigNumber(util.xrpToDrops(this._outgoingChannelDetails.amount)).times(this._fundThreshold))) { + this._funding = true + util.fundChannel({ + api: this._api, + channel: this._outgoingChannel, + amount: this._channelAmount, + address: this._address, + secret: this._secret + }) + .then(() => { + this._funding = false + }) + .catch((e) => { + this._funding = false + debug('error issuing fund tx:', e) + }) } - // TODO: - // 1) check that the transfer does not exceed the incoming chan amount - - // check that the unsecured amount does not exceed the limit - const incoming = await ctx.transferLog.getIncomingFulfilledAndPrepared() - const amountSecured = await self.incomingClaim.getMax() - const exceeds = new BigNumber(incoming) - .minus(amountSecured.value) - .greaterThan(self.maxUnsecured) - - if (exceeds) { - throw new Error(transfer.id + ' exceeds max unsecured balance of: ', self.maxUnsecured) + this._outgoingClaim = { + amount: claimAmount.toString(), + signature: Buffer.from(signature).toString('hex') } - }, - - createOutgoingClaim: async function (ctx, outgoingBalance) { - // generate claim for amount - const self = ctx.state - const encodedClaim = encodeClaim(outgoingBalance, self.outgoingPaymentChannelId) - - // sign a claim - const signature = nacl.sign.detached(encodedClaim, self.keyPair.secretKey) - - ctx.plugin.debug(`signing outgoing claim for ${outgoingBalance} drops on ` + - `channel ${self.outgoingPaymentChannelId}`) + this._store.set('outgoing_claim', JSON.stringify(this._outgoingClaim)) + + await this._call(null, { + type: BtpPacket.TYPE_TRANSFER, + requestId: await util._requestId(), + data: { + amount, + protocolData: [{ + protocolName: 'claim', + contentType: BtpPacket.MIME_APPLICATION_JSON, + data: Buffer.from(JSON.stringify(this._outgoingClaim)) + }] + } + }) + } - // TODO: issue a fund tx if self.fundPercent is reached and tell peer about fund tx - if (outgoingBalance > self.maxAmount * self.fundThreshold) { - await fund(ctx) + async _handleMoney (from, { requestId, data }) { + const amount = data.amount + const protocolData = data.protocolData + const claim = JSON.parse(protocolData + .filter(p => p.protocolName === 'claim')[0] + .data + .toString()) + + const claimAmount = new BigNumber(claim.amount) + const encodedClaim = util.encodeClaim(claim.amount, this._incomingChannel) + const addedMoney = claimAmount.minus(this._incomingClaim.amount) + + if (addedMoney.lte(0)) { + throw new Error('new claim is less than old claim. new=' + claim.amount + + ' old=' + this._incomingClaim.amount) } - // return claim+amount - return { - amount: outgoingBalance, - signature: Buffer.from(signature).toString('hex') + // Don't throw an error here; we'll just emit the addedMoney amount and keep going. + // This can happen during high throughput when transfers may get out of sync with + // settlements. So long as one peer doesn't crash before balances are written, the + // discrepency should go away automatically. + if (!addedMoney.isEqualTo(amount)) { + debug('warning: peer balance is out of sync with ours. peer thinks they sent ' + + amount + '; we got ' + addedMoney.toString()) } - }, - handleIncomingClaim: async function (ctx, claim) { - // get claim+amount - const self = ctx.state - if (!self.incomingPaymentChannel) await reloadIncomingChannelDetails(ctx) - const { amount, signature } = claim - ctx.plugin.debug(`received claim for ${amount} drops on channel ${self.incomingPaymentChannelId}`) + debug(`received claim for ${addedMoney.toString()} drops on channel ${this._incomingChannel}`) - const encodedClaim = encodeClaim(amount, self.incomingPaymentChannelId) let valid = false try { valid = nacl.sign.detached.verify( encodedClaim, - Buffer.from(signature, 'hex'), - Buffer.from(self.incomingPaymentChannel.publicKey.substring(2), 'hex') + Buffer.from(claim.signature, 'hex'), + Buffer.from(this._incomingChannelDetails.publicKey.substring(2), 'hex') ) } catch (err) { - ctx.plugin.debug('verifying signature failed:', err.message) + debug('verifying signature failed:', err.message) } + // TODO: better reconciliation if claims are invalid if (!valid) { - ctx.plugin.debug(`got invalid claim signature ${signature} for amount ${amount} drops`) + debug(`got invalid claim signature ${claim.signature} for amount + ${claimAmount.toString()} drops total`) throw new Error('got invalid claim signature ' + - signature + ' for amount ' + amount + ' drops') + claim.signature + ' for amount ' + claimAmount.toString() + + ' drops total') } // validate claim against balance - const channelBalance = xrpToDrops(self.incomingPaymentChannel.amount) - if (new BigNumber(amount).gt(channelBalance)) { - const message = 'got claim for amount higher than channel balance. amount: ' + amount + ', incoming channel balance: ' + channelBalance - ctx.plugin.debug(message) + const channelAmount = util.xrpToDrops(this._incomingChannelDetails.amount) + if (claimAmount.isGreaterThan(channelAmount)) { + const message = 'got claim for amount higher than channel balance. amount: ' + + claimAmount.toString() + + ' incoming channel amount: ' + + channelAmount + + debug(message) throw new Error(message) } - // TODO: issue claim tx if self.claimPercent is exceeded - // store in max value tracker, throw error if invalid - await self.incomingClaim.setIfMax({ - value: amount, - data: signature - }) + this._incomingClaim = { + amount: claimAmount.toString(), + signature: claim.signature.toUpperCase() + } + this._store.set('incoming_claim', JSON.stringify(this._incomingClaim)) + + if (this._moneyHandler) { + await this._moneyHandler(addedMoney.toString()) + } + + return [] } -}) +} + +PluginXrpPaychan.version = 2 +module.exports = PluginXrpPaychan diff --git a/package-lock.json b/package-lock.json index 7da11e0..110427e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,16 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/node": { - "version": "6.0.92", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.92.tgz", - "integrity": "sha512-awEYSSTn7dauwVCYSx2CJaPTu0Z1Ht2oR1b2AD3CYao6ZRb+opb6EL43fzmD7eMFgMHzTBWSUzlWSD+S8xN0Nw==" - }, - "abbrev": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz", - "integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8=" - }, "acorn": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz", @@ -50,6 +40,7 @@ "version": "4.11.8", "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, "requires": { "co": "4.6.0", "json-stable-stringify": "1.0.1" @@ -61,23 +52,6 @@ "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", "dev": true }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, "ansi-escapes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", @@ -87,7 +61,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "2.2.1", @@ -95,20 +70,6 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, - "aproba": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", - "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==" - }, - "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.3" - } - }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -139,48 +100,17 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" - }, "assertion-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", "dev": true }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" - }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", @@ -236,7 +166,8 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true }, "base-x": { "version": "1.1.0", @@ -248,70 +179,21 @@ "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" }, - "base64url-adhoc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/base64url-adhoc/-/base64url-adhoc-2.0.1.tgz", - "integrity": "sha1-j5bFIlgHi6yEWziVCF20vUuth+I=", - "requires": { - "@types/node": "6.0.92" - } - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - }, - "dependencies": { - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - } - } - }, - "bignum": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/bignum/-/bignum-0.12.5.tgz", - "integrity": "sha1-II8rDhjrj9aVCRfjcY5M2FzM/80=", - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.37" - } - }, "bignumber.js": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", - "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "2.0.3" - } + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-6.0.0.tgz", + "integrity": "sha512-x247jIuy60/+FtMRvscqfxtVHQf8AGx2hm9c6btkgC0x/hp9yt+teISNhvF8WlwRkCc5yF2fDECH8SIMe8j+GA==" }, "bn.js": { "version": "4.11.8", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "requires": { - "hoek": "2.16.3" - } - }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, "requires": { "balanced-match": "1.0.0", "concat-map": "0.0.1" @@ -329,21 +211,21 @@ "dev": true }, "btp-packet": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btp-packet/-/btp-packet-1.0.0.tgz", - "integrity": "sha1-W4k+pEaYuMky4ZzoeIzamg0o/6I=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/btp-packet/-/btp-packet-1.2.0.tgz", + "integrity": "sha1-52w4bNLC/EL6I7azQjCys3/Zghk=", "requires": { "base64url": "2.0.0", - "bignumber.js": "4.0.4", + "bignumber.js": "4.1.0", "dateformat": "2.2.0", - "oer-utils": "1.3.3", + "oer-utils": "1.3.4", "uuid-parse": "1.0.0" }, "dependencies": { "bignumber.js": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz", - "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" } } }, @@ -394,29 +276,6 @@ "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", "dev": true }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, "chai": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", @@ -507,31 +366,11 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - } - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "co-mocha": { "version": "1.2.0", @@ -543,11 +382,6 @@ "is-generator": "1.0.3" } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "color-convert": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", @@ -569,14 +403,6 @@ "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, - "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "requires": { - "delayed-stream": "1.0.0" - } - }, "commander": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", @@ -586,15 +412,11 @@ "graceful-readlink": "1.0.1" } }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.0", @@ -607,22 +429,12 @@ "typedarray": "0.0.6" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, - "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" - }, "core-js": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", @@ -631,7 +443,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "create-hash": { "version": "1.1.3", @@ -655,14 +468,6 @@ "which": "1.3.0" } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "requires": { - "boom": "2.10.1" - } - }, "cssmin": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/cssmin/-/cssmin-0.3.2.tgz", @@ -675,21 +480,6 @@ "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, "dateformat": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", @@ -703,13 +493,6 @@ "ms": "2.0.0" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "optional": true - }, "decimal.js": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-5.0.8.tgz", @@ -727,12 +510,8 @@ "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true }, "deep-is": { "version": "0.1.3", @@ -740,20 +519,6 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, "del": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", @@ -769,16 +534,6 @@ "rimraf": "2.6.2" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, "diff": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", @@ -795,15 +550,6 @@ "isarray": "1.0.0" } }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, "elliptic": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz", @@ -831,61 +577,12 @@ "is-arrayish": "0.2.1" } }, - "es-abstract": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.2.tgz", - "integrity": "sha512-dvhwFL3yjQxNNsOWx6exMlaDrRHCRGMQlnx5lsXDCZ/J7G/frgIIl94zhZSp/galVAYp7VzPi1OrAHta89/yGQ==", - "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" - } - }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } - } - }, "eslint": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.9.0.tgz", @@ -1133,15 +830,21 @@ "dev": true }, "eventemitter2": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-2.2.2.tgz", - "integrity": "sha1-QH6nHCAgzVdTggOrfnpr3Pt2ktU=" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=" }, "extend": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, + "extensible-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extensible-error/-/extensible-error-1.0.2.tgz", + "integrity": "sha512-kXU1FiTsGT8PyMKtFM074RK/VBpzwuQJicAHqBpsPDeTXBQiSALPjkjKXlyKdG/GP6lR7bBaEkq8qdoO2geu9g==", + "dev": true + }, "external-editor": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.5.tgz", @@ -1153,11 +856,6 @@ "tmp": "0.0.33" } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, "eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -1233,34 +931,6 @@ "write": "0.2.1" } }, - "for-each": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.2.tgz", - "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", - "requires": { - "is-function": "1.0.1" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", @@ -1270,41 +940,17 @@ "samsam": "1.3.0" } }, - "formidable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", - "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz", - "integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=", - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true }, "functional-red-black-tree": { "version": "1.0.1", @@ -1312,46 +958,17 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "1.1.2", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -1384,7 +1001,8 @@ "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true }, "graceful-readlink": { "version": "1.0.1", @@ -1398,47 +1016,11 @@ "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", "dev": true }, - "handlebars": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", - "dev": true, - "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, "has": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, "requires": { "function-bind": "1.1.1" } @@ -1458,11 +1040,6 @@ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, "hash-base": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", @@ -1480,44 +1057,18 @@ "minimalistic-assert": "1.0.0" } }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" - }, "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", "dev": true }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" - } - }, "https-proxy-agent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", @@ -1541,63 +1092,43 @@ "dev": true }, "ilp-packet": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ilp-packet/-/ilp-packet-1.5.0.tgz", - "integrity": "sha1-W8b9pNgh3jdNHJivGwRPw0ds95w=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ilp-packet/-/ilp-packet-2.2.0.tgz", + "integrity": "sha1-qHJcwmMxxuLGU1OKEGPVUBQwXjE=", + "dev": true, "requires": { - "base64url": "2.0.0", - "base64url-adhoc": "2.0.1", - "bignumber.js": "4.1.0", - "lodash": "4.17.4", + "bignumber.js": "5.0.0", + "extensible-error": "1.0.2", "long": "3.2.0", - "oer-utils": "1.3.3" + "oer-utils": "1.3.4" }, "dependencies": { "bignumber.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", - "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" - }, - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==", + "dev": true } } }, - "ilp-plugin-payment-channel-framework": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/ilp-plugin-payment-channel-framework/-/ilp-plugin-payment-channel-framework-1.3.2.tgz", - "integrity": "sha1-ZZQw2V1D9JQn+ybUjyXJzNtj694=", + "ilp-plugin-btp": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ilp-plugin-btp/-/ilp-plugin-btp-1.1.8.tgz", + "integrity": "sha1-/lrUMK6Pcuva4wmpaVzBF85gSLA=", "requires": { "base64url": "2.0.0", - "bignumber.js": "2.4.0", - "btp-packet": "git+https://github.com/interledgerjs/btp-packet.git#095760e486974094df2ce6bfbef6aa5325f15da3", - "debug": "2.6.8", - "deep-equal": "1.0.1", - "eventemitter2": "2.2.2", - "ilp-packet": "1.5.0", - "int64": "0.0.5", - "superagent": "3.8.1", - "uuid4": "1.0.0", - "ws": "3.3.2" + "btp-packet": "1.2.0", + "debug": "3.1.0", + "eventemitter2": "5.0.1", + "ws": "3.3.3" }, "dependencies": { - "btp-packet": { - "version": "git+https://github.com/interledgerjs/btp-packet.git#095760e486974094df2ce6bfbef6aa5325f15da3", + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "requires": { - "base64url": "2.0.0", - "bignumber.js": "4.1.0", - "dateformat": "2.2.0", - "oer-utils": "1.3.3", - "uuid-parse": "1.0.0" - }, - "dependencies": { - "bignumber.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", - "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" - } + "ms": "2.0.0" } }, "ultron": { @@ -1606,11 +1137,11 @@ "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==" }, "ws": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.2.tgz", - "integrity": "sha512-t+WGpsNxhMR4v6EClXS8r8km5ZljKJzyGhJf7goJz9k5Ye3+b5Bvno5rjqPuIBn5mnn5GBb7o8IrIWHxX1qOLQ==", - "requires": { - "async-limiter": "1.0.0", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "1.0.0", "safe-buffer": "5.1.1", "ultron": "1.1.1" } @@ -1618,10 +1149,14 @@ } }, "ilp-plugin-xrp-paychan-shared": { - "version": "git+https://git@github.com/dappelt/ilp-plugin-xrp-paychan-shared.git#020e27dcef728bc49cfcabfcd2143b05464d0af3", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ilp-plugin-xrp-paychan-shared/-/ilp-plugin-xrp-paychan-shared-2.0.0.tgz", + "integrity": "sha512-q7H/J2a0hjAnmKePtYF5+sS2sOoNZk+IoF18Ul+WA3zpeId25ZkH5Y7CwbkgnH/uvZkBFQ/0IuarkFuVhivIAw==", "requires": { + "bignumber.js": "5.0.0", "debug": "3.1.0", - "eventemitter2": "5.0.0", + "eventemitter2": "5.0.1", + "ripple-address-codec": "2.0.1", "ripple-lib": "0.17.9" }, "dependencies": { @@ -1634,9 +1169,9 @@ } }, "bignumber.js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", - "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-5.0.0.tgz", + "integrity": "sha512-KWTu6ZMVk9sxlDJQh2YH1UOnfDP8O8TpxUxgQG/vKASoSnEjK9aVuOueFaPcQEYQ5fyNXNTOYwYw3099RYebWg==" }, "bn.js": { "version": "3.3.0", @@ -1656,15 +1191,10 @@ "ms": "2.0.0" } }, - "eventemitter2": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.0.tgz", - "integrity": "sha1-1/fiVrYxSTgxjl7U/RKsZXJJYsY=" - }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "ripple-hashes": { "version": "0.3.1", @@ -1675,6 +1205,13 @@ "create-hash": "1.1.3", "ripple-address-codec": "2.0.1", "ripple-binary-codec": "0.1.11" + }, + "dependencies": { + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + } } }, "ripple-keypairs": { @@ -1698,13 +1235,20 @@ "bignumber.js": "4.1.0", "https-proxy-agent": "1.0.0", "jsonschema": "1.2.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "ripple-address-codec": "2.0.1", "ripple-binary-codec": "0.1.11", "ripple-hashes": "0.3.1", "ripple-keypairs": "0.10.1", "ripple-lib-transactionparser": "0.6.2", "ws": "3.3.3" + }, + "dependencies": { + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + } } }, "ripple-lib-transactionparser": { @@ -1713,7 +1257,14 @@ "integrity": "sha1-6xF4NIFsqzOYRFp07Dys7JW2tfo=", "requires": { "bignumber.js": "4.1.0", - "lodash": "4.17.4" + "lodash": "4.17.5" + }, + "dependencies": { + "bignumber.js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.1.0.tgz", + "integrity": "sha512-eJzYkFYy9L4JzXsbymsFn3p54D+llV27oTQ+ziJG7WFRheJcNZilgVXMG0LoZtlQSKBsJdWtLFqOD0u+U0jZKA==" + } } }, "ultron": { @@ -1733,6 +1284,11 @@ } } }, + "ilp-store-memory": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ilp-store-memory/-/ilp-store-memory-1.0.0.tgz", + "integrity": "sha512-OxGD//mRTmw2zauj2ZrDvPcozfl8WHJPOgpwrjYc9i+nBxECwIJZKd8JxHAUwf/lQuRAoyY4rxPq0hZ6rpcIvA==" + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -1743,6 +1299,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "1.4.0", "wrappy": "1.0.2" @@ -1753,11 +1310,6 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" - }, "inquirer": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", @@ -1819,23 +1371,12 @@ } } }, - "int64": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/int64/-/int64-0.0.5.tgz", - "integrity": "sha1-JTmm/O653MEZu0VDinIQtTzG/Eo=" - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, "is-builtin-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", @@ -1845,29 +1386,6 @@ "builtin-modules": "1.1.1" } }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=" - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" - }, "is-generator": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz", @@ -1910,14 +1428,6 @@ "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", "dev": true }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "requires": { - "has": "1.0.1" - } - }, "is-resolvable": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", @@ -1927,20 +1437,11 @@ "tryit": "1.0.3" } }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -1951,86 +1452,8 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.10", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" - }, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1.0.9" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } - } - } + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "js-tokens": { "version": "3.0.2", @@ -2048,12 +1471,6 @@ "esprima": "4.0.0" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, "jschardet": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", @@ -2066,11 +1483,6 @@ "integrity": "sha1-570NzWSWw79IYyNb9GGj2YqjuYw=", "dev": true }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", @@ -2081,6 +1493,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, "requires": { "jsonify": "0.0.0" } @@ -2088,7 +1501,8 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json3": { "version": "3.3.2", @@ -2099,31 +1513,14 @@ "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true }, "jsonschema": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.0.tgz", "integrity": "sha512-XDJApzBauMg0TinJNP4iVcJl99PQ4JbWKK7nwzpOIkAOVveDKgh/2xm41T3x7Spu4PWMhnnQpNJmUSIUgl6sKg==" }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, "just-extend": { "version": "1.1.27", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", @@ -2150,22 +1547,6 @@ } } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -2300,12 +1681,7 @@ "long": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", - "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", "dev": true }, "lru-cache": { @@ -2333,29 +1709,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" - }, - "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", - "requires": { - "mime-db": "1.30.0" - } - }, "mimic-fn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", @@ -2371,6 +1724,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "1.1.8" } @@ -2378,12 +1732,14 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } @@ -2445,11 +1801,6 @@ "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", "dev": true }, - "moment": { - "version": "2.19.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.2.tgz", - "integrity": "sha512-Rf6jiHPEfxp9+dlzxPTmRHbvoFXsh2L/U8hOupUMpnuecHQmI6cF6lUbJl3QqKPko1u6ujO+FxtcajLVfLpAtA==" - }, "moo-server": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/moo-server/-/moo-server-1.3.0.tgz", @@ -2467,11 +1818,6 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" - }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -2562,104 +1908,1636 @@ "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", "dev": true }, - "type-detect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", - "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", - "dev": true - } - } - }, - "node-pre-gyp": { - "version": "0.6.37", - "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.37.tgz", - "integrity": "sha1-PIcrI2suJm5BQFeP4e6I9pMyOgU=", - "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.2", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.2", - "semver": "5.4.1", - "tape": "4.8.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - }, - "dependencies": { - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.0.3", + "validate-npm-package-license": "3.0.1" + } + }, + "nyc": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.4.1.tgz", + "integrity": "sha512-5eCZpvaksFVjP2rt1r60cfXmt3MUtsQDw8bAzNqNEr4WLvUMLgiVENMf/B9bE9YAX0mGVvaGA3v9IS9ekNqB1Q==", + "dev": true, + "requires": { + "archy": "1.0.0", + "arrify": "1.0.1", + "caching-transform": "1.0.1", + "convert-source-map": "1.5.1", + "debug-log": "1.0.1", + "default-require-extensions": "1.0.0", + "find-cache-dir": "0.1.1", + "find-up": "2.1.0", + "foreground-child": "1.5.6", + "glob": "7.1.2", + "istanbul-lib-coverage": "1.1.1", + "istanbul-lib-hook": "1.1.0", + "istanbul-lib-instrument": "1.9.1", + "istanbul-lib-report": "1.1.2", + "istanbul-lib-source-maps": "1.2.2", + "istanbul-reports": "1.1.3", + "md5-hex": "1.3.0", + "merge-source-map": "1.0.4", + "micromatch": "2.3.11", + "mkdirp": "0.5.1", + "resolve-from": "2.0.0", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "spawn-wrap": "1.4.2", + "test-exclude": "4.1.1", + "yargs": "10.0.3", + "yargs-parser": "8.0.0" + }, + "dependencies": { + "align-text": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "1.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "babel-generator": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "detect-indent": "4.0.0", + "jsesc": "1.3.0", + "lodash": "4.17.4", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "2.5.3", + "regenerator-runtime": "0.11.1" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "lodash": "4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "2.0.2", + "lodash": "4.17.4", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "1.3.0", + "mkdirp": "0.5.1", + "write-file-atomic": "1.3.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.3", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "which": "1.3.0" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "2.0.0" + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "2.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + } + } + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "bundled": true, + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "1.0.1", + "mkdirp": "0.5.1", + "pkg-dir": "1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "4.0.2", + "signal-exit": "3.0.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.5.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.2", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "istanbul-lib-coverage": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "6.26.0", + "babel-template": "6.26.0", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "istanbul-lib-coverage": "1.1.1", + "semver": "5.4.1" + } + }, + "istanbul-lib-report": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "path-parse": "1.0.5", + "supports-color": "3.2.3" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "3.1.0", + "istanbul-lib-coverage": "1.1.1", + "mkdirp": "0.5.1", + "rimraf": "2.6.2", + "source-map": "0.5.7" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "4.0.11" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.4", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "3.0.2" + } + }, + "lru-cache": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "merge-source-map": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "source-map": "0.5.7" + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, + "mimic-fn": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.4.1", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "remove-trailing-separator": "1.1.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "1.1.0" + } + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "preserve": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "randomatic": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-cache": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "1.0.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "semver": { + "version": "5.4.1", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "1.5.6", + "mkdirp": "0.5.1", + "os-homedir": "1.0.2", + "rimraf": "2.6.2", + "signal-exit": "3.0.2", + "which": "1.3.0" + } + }, + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "1.0.1", + "micromatch": "2.3.11", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "require-main-filename": "1.0.1" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "validate-npm-package-license": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "imurmurhash": "0.1.4", + "slide": "1.1.6" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "10.0.3", + "bundled": true, + "dev": true, + "requires": { + "cliui": "3.2.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "8.0.0" + }, + "dependencies": { + "cliui": { + "version": "3.2.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + } + } + }, + "yargs-parser": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } } } }, - "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.0.3", - "validate-npm-package-license": "3.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.3.0.tgz", - "integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg==" - }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "oer-utils": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/oer-utils/-/oer-utils-1.3.3.tgz", - "integrity": "sha1-Uf9svvAm9i6wGoUhF8zgdQS6E5s=" + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/oer-utils/-/oer-utils-1.3.4.tgz", + "integrity": "sha1-sqmtvJK8GRVaKgDwRWg9Hm1KyCM=" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1.0.2" } @@ -2673,24 +3551,6 @@ "mimic-fn": "1.1.0" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -2710,24 +3570,11 @@ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=" }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.4.tgz", - "integrity": "sha1-Qv5tWVPfBsgGS+bxdsPQWqqjRkQ=", - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true }, "p-limit": { "version": "1.1.0", @@ -2765,7 +3612,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-is-inside": { "version": "1.0.2", @@ -2776,7 +3624,8 @@ "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true }, "path-to-regexp": { "version": "1.7.0", @@ -2810,11 +3659,6 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2860,7 +3704,8 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true }, "progress": { "version": "2.0.0", @@ -2905,33 +3750,11 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, "qs": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" - }, - "rc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } + "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", + "dev": true }, "read-pkg": { "version": "2.0.0", @@ -2969,6 +3792,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -2984,41 +3808,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.1.0" - } - }, "require-uncached": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", @@ -3033,6 +3822,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "dev": true, "requires": { "path-parse": "1.0.5" } @@ -3053,28 +3843,11 @@ "signal-exit": "3.0.2" } }, - "resumer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", - "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", - "requires": { - "through": "2.3.8" - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4" - } - }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, "requires": { "glob": "7.1.2" } @@ -3127,6 +3900,13 @@ "create-hash": "1.1.3", "ripple-address-codec": "2.0.1", "ripple-binary-codec": "0.1.11" + }, + "dependencies": { + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" + } } }, "ripple-keypairs": { @@ -3179,6 +3959,13 @@ "ripple-keypairs": "0.10.0", "ripple-lib-transactionparser": "0.6.0", "ws": "1.1.4" + }, + "dependencies": { + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" + } } }, "ripple-lib-transactionparser": { @@ -3188,6 +3975,13 @@ "requires": { "bignumber.js": "2.4.0", "lodash": "3.10.1" + }, + "dependencies": { + "bignumber.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.4.0.tgz", + "integrity": "sha1-g4qZLan51zfg9LLbC+YrsJ3Qxeg=" + } } }, "run-async": { @@ -3230,11 +4024,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=" }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, "sha.js": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", @@ -3261,7 +4050,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "sinon": { "version": "3.3.0", @@ -3294,24 +4084,6 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "requires": { - "hoek": "2.16.3" - } - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": "1.0.1" - } - }, "spdx-correct": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", @@ -3339,77 +4111,26 @@ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, - "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - } - } - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string.prototype.trim": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", - "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", - "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.8.2", - "function-bind": "1.1.1" - } - }, "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, "requires": { "safe-buffer": "5.1.1" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "2.1.1" } @@ -3423,49 +4144,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "superagent": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", - "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.1", - "formidable": "1.1.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.3.3" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - } - } + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "supports-color": { "version": "2.0.0", @@ -3541,58 +4221,6 @@ } } }, - "tape": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/tape/-/tape-4.8.0.tgz", - "integrity": "sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA==", - "requires": { - "deep-equal": "1.0.1", - "defined": "1.0.0", - "for-each": "0.3.2", - "function-bind": "1.1.1", - "glob": "7.1.2", - "has": "1.0.1", - "inherits": "2.0.3", - "minimist": "1.2.0", - "object-inspect": "1.3.0", - "resolve": "1.4.0", - "resumer": "0.0.0", - "string.prototype.trim": "1.1.2", - "through": "2.3.8" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - } - } - }, - "tar": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", - "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.0.tgz", - "integrity": "sha1-I74tf2cagzk3bL2wuP4/3r8xeYQ=", - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.3.3", - "rimraf": "2.6.2", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", @@ -3608,7 +4236,8 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true }, "timespan": { "version": "2.3.0", @@ -3631,28 +4260,12 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, - "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", - "requires": { - "punycode": "1.4.1" - } - }, "tryit": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", "dev": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "5.1.1" - } - }, "tweetnacl": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz", @@ -3679,39 +4292,6 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "optional": true - } - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" - }, "ultron": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz", @@ -3720,23 +4300,14 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "uuid-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.0.0.tgz", "integrity": "sha1-9GV3F2JLDkuIrzb5jYlYmlu+5Wk=" }, - "uuid4": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/uuid4/-/uuid4-1.0.0.tgz", - "integrity": "sha1-gTqurxHqL2iQnFrVfYlPgyAtZyA=" - }, "validate-npm-package-license": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", @@ -3747,23 +4318,6 @@ "spdx-expression-parse": "1.0.4" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - } - } - }, "walker": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", @@ -3782,21 +4336,6 @@ "isexe": "2.0.0" } }, - "wide-align": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", - "requires": { - "string-width": "1.0.2" - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, "winston": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.0.tgz", @@ -3828,7 +4367,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "wrench": { "version": "1.3.9", @@ -3867,19 +4407,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", "dev": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } } } } diff --git a/package.json b/package.json index d078711..808a4a3 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "istanbul test -- _mocha", - "integration": "istanbul test -- _mocha test/integration", + "test": "nyc --reporter=lcov mocha --", + "integration": "nyc --reporter=lcov mocha test/integration --", "lint": "eslint ." }, "author": "", @@ -21,21 +21,21 @@ "eslint-plugin-node": "^5.1.0", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-standard": "^3.0.1", - "istanbul": "^0.4.5", + "ilp-packet": "^2.2.0", "mocha": "^3.5.3", "nock": "^9.0.20", + "nyc": "^11.4.1", "proxyquire": "^1.8.0", "sinon": "^3.3.0", "sinon-chai": "^2.13.0" }, "dependencies": { - "bignum": "^0.12.5", - "btp-packet": "^1.0.0", - "ilp-plugin-payment-channel-framework": "^1.3.1", - "ilp-plugin-xrp-paychan-shared": "git+https://git@github.com/dappelt/ilp-plugin-xrp-paychan-shared.git", - "moment": "^2.19.2", + "bignumber.js": "^6.0.0", + "btp-packet": "^1.2.0", + "ilp-plugin-btp": "^1.1.8", + "ilp-plugin-xrp-paychan-shared": "^2.0.0", + "ilp-store-memory": "^1.0.0", "ripple-lib": "^0.17.7", - "tweetnacl": "^1.0.0", - "uuid": "^3.1.0" + "tweetnacl": "^1.0.0" } } diff --git a/store-wrapper.js b/store-wrapper.js new file mode 100644 index 0000000..a27d18a --- /dev/null +++ b/store-wrapper.js @@ -0,0 +1,32 @@ +class StoreWrapper { + constructor (store) { + this._store = store + this._cache = new Map() + this._write = Promise.resolve() + } + + async load (key) { + if (!this._store) return + if (this._cache.get(key)) return + const value = await this._store.get(key) + + if (!this._cache.get(key)) this._cache.set(key, value) + } + + get (key) { + return this._cache.get(key) + } + + set (key, value) { + this._cache.set(key, value) + this._write = this._write.then(() => { + return this._store.put(key, value) + }) + } + + setCache (key, value) { + this._cache.set(key, value) + } +} + +module.exports = StoreWrapper diff --git a/test/channelSpec.js b/test/channelSpec.js deleted file mode 100644 index 2eea9db..0000000 --- a/test/channelSpec.js +++ /dev/null @@ -1,456 +0,0 @@ -'use strict' - -const chai = require('chai') -const chaiAsPromised = require('chai-as-promised') -chai.use(chaiAsPromised) -const sinon = require('sinon') -const sinonChai = require('sinon-chai') -chai.use(sinonChai) - -const assert = chai.assert -const expect = chai.expect - -const MockSocket = require('ilp-plugin-payment-channel-framework/test/helpers/mockSocket') -const Store = require('ilp-plugin-payment-channel-framework/test/helpers/objStore') - -const realSetTimeout = setTimeout -const sleep = (time) => new Promise((resolve) => realSetTimeout(resolve, time)) - -const apiHelper = require('./helper/apiHelper') -const btpPacket = require('btp-packet') -const proxyquire = require('proxyquire') -const { - DEFAULT_WATCHER_INTERVAL, - STATE_CREATING_CHANNEL, - STATE_CHANNEL, - POLLING_INTERVAL_OUTGOING_PAYCHAN, - dropsToXrp -} = require('../src/lib/constants') - -async function _connectPlugin () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() -} - -describe('channelSpec', function () { - beforeEach(function () { - // Plugin Options - this.opts = { - maxBalance: '1000000000', - server: 'btp+wss://btp-server.example', - prefix: 'g.eur.mytrustline.', - info: { - prefix: 'g.eur.mytrustline.', - currencyScale: 9, - currencyCode: 'XRP', - connector: [] - }, - settleDelay: 60 * 60, // 1 hour - token: 'shared_secret', - rippledServer: 'wss://s.altnet.rippletest.net:51233', - address: 'rQBHyjckUFGZK1nPDKzTU8Zyvd2niqHcpo', - secret: 'shDssKGbxxpJacxpQzfacKcnutYGU', - peerAddress: 'rPZUg1NH7gAfkpRpKbcwyn8ET7EPqhTFiv', - channelSecret: 'shh its a secret', - maxUnsecured: '5000', - maxAmount: '100000', - rpcUri: 'btp+wss://peer.example', - _store: new Store() - } - this.claim = { - amount: 5, - signature: '87e36f2f3c470f0b9f3dcb81c0ddacc77000a208e861b519716179805a55d5784dfae2a6ec4d68a75c3f190ee7352bcbc9b7191a43d435da598da4d98b544c03' - } - this.exceedingClaim = { - amount: 5000000, - signature: '428b5744c282895d631da6af8d7472d308227fa885c2b5c2bb587d2a021efaa50b1bae30e60ff3197c894f8f8325835bea9e38631e270ea6d75c8240f2927d04' - } - - const ApiStub = apiHelper.makeApi(this.opts) - const PluginRipple = proxyquire('../index', { - 'ripple-lib': { - 'RippleAPI': ApiStub - } - }) - - this.clock = sinon.useFakeTimers({toFake: ['setTimeout', 'setInterval'], shouldAdvanceTime: true}) - this.plugin = new PluginRipple(this.opts) - this.pluginContext = this.plugin._paychanContext - this.pluginState = this.plugin._paychanContext.state - this.mockSocket = new MockSocket() - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, []) // reply to auth message - }) - this.plugin.addSocket(this.mockSocket, {username: '', token: ''}) - this.incomingPaymentChannelId = '1234567890ABCDEF' - - this.payChanIdRequest = [{ - protocolName: 'ripple_channel_id', - contentType: btpPacket.MIME_APPLICATION_JSON, - data: Buffer.from('[]') - }] - this.payChanIdResponse = [{ - protocolName: 'ripple_channel_id', - contentType: btpPacket.MIME_APPLICATION_JSON, - data: Buffer.from(`"${this.incomingPaymentChannelId}"`) - }] - this.payChanIdResponseNull = [{ - protocolName: 'ripple_channel_id', - contentType: btpPacket.MIME_APPLICATION_JSON, - data: Buffer.from(JSON.stringify(null)) - }] - }) - - afterEach(async function () { - this.clock.restore() - assert(await this.mockSocket.isDone(), 'request handlers must have been called') - }) - - describe('connect', function () { - it('creates a payment channel', async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - - const connectSpy = sinon.spy(this.pluginState.api, 'connect') - const prepareSpy = sinon.spy(this.pluginState.api, 'preparePaymentChannelCreate') - const signSpy = sinon.spy(this.pluginState.api, 'sign') - const submitSpy = sinon.spy(this.pluginState.api, 'submit') - - await this.plugin.connect() - - expect(prepareSpy).to.have.been.calledAfter(connectSpy).and - .calledWith(this.opts.address) - expect(signSpy).to.have.been.calledAfter(prepareSpy).and - .calledWith('"some JSON string"') - expect(submitSpy).to.have.been.calledAfter(signSpy).and - .calledWith('1234567890ABCDEF') - - const [ , payChanCreate ] = prepareSpy.firstCall.args - delete payChanCreate.sourceTag - const expectedPubKey = 'ED' + Buffer.from(this.pluginState.keyPair.publicKey) - .toString('hex').toUpperCase() - assert.deepEqual(payChanCreate, { - amount: dropsToXrp(this.opts.maxAmount), - destination: this.opts.peerAddress, - settleDelay: this.opts.settleDelay, - publicKey: expectedPubKey - }) - }) - - it('uses existing payment channel', async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - - const outgoingChannel = this.plugin._paychanContext.backend - .getMaxValueTracker('outgoing_channel') - await outgoingChannel.setIfMax({ - value: STATE_CHANNEL, - data: '1234567890ABCDEF' - }) - - const submitSpy = sinon.spy(this.pluginState.api, 'submit') - await this.plugin.connect() - expect(submitSpy).to.have.not.been.called - expect(this.pluginState.outgoingPaymentChannelId) - .to.be.equal('1234567890ABCDEF') - }) - - it('waits for another instance to create a channel', async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - - const outgoingChannel = this.pluginState.outgoingChannel - await outgoingChannel.setIfMax({ - value: STATE_CREATING_CHANNEL, - data: 'ade66de4-9cbf-4f0d-8084-42e10247a4fb' - }) - - const submitSpy = sinon.spy(this.pluginState.api, 'submit') - this.plugin.connect() - - await sleep(10) // wait for the plugin to start polling - await outgoingChannel.setIfMax({ - value: STATE_CHANNEL, - data: '1234567890ABCDEF' - }) - this.clock.tick(POLLING_INTERVAL_OUTGOING_PAYCHAN + 1000) - await sleep(10) // wait for the plugin to retrieve the channelId - - expect(submitSpy).to.have.not.been.called - expect(this.pluginState.outgoingPaymentChannelId) - .to.be.equal('1234567890ABCDEF') - }) - - it('requests incoming payment channel id on connect()', async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId, data}) => { - assert.nestedProperty(data, 'protocolData') - assert.deepEqual(data.protocolData, this.payChanIdRequest) - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - - assert.equal(this.pluginState.incomingPaymentChannelId, - this.incomingPaymentChannelId) - }) - - it('requests incoming payment channel id on ripple_channel_id request', async function () { - // The first ripple_channel_id request is anwsered with null. - // Only on the second ripple_channel_id request the mockSocket returns - // the channel id. - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId, data}) => { - assert.nestedProperty(data, 'protocolData') - assert.deepEqual(data.protocolData, this.payChanIdRequest) - return btpPacket.serializeResponse(requestId, this.payChanIdResponseNull) - }).reply(btpPacket.TYPE_RESPONSE) - .reply(btpPacket.TYPE_MESSAGE, ({requestId, data}) => { - assert.nestedProperty(data, 'protocolData') - assert.deepEqual(data.protocolData, this.payChanIdRequest) - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - - // the plugin should not yet have an incoming pay chan id after connect() - assert.equal(this.pluginState.incomingPaymentChannelId, null) - - // a ripple_channel_id request should trigger the plugin to try again - // to get the incoming payment channel id - const btpMessage = btpPacket.serializeMessage(1234, this.payChanIdRequest) - - this.mockSocket.emit('message', btpMessage) - this.clock.tick(150) - await sleep(10) - assert.equal(this.pluginState.incomingPaymentChannelId, - this.incomingPaymentChannelId) - }) - - it('sends outgoing payment channel id', async function () { - this.mockSocket - .reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - .reply(btpPacket.TYPE_RESPONSE, ({requestId, data}) => { - assert.equal(requestId, 1234) - assert.lengthOf(data.protocolData, 1) - assert.equal(data.protocolData[0].protocolName, 'ripple_channel_id') - assert.equal(data.protocolData[0].contentType, btpPacket.MIME_APPLICATION_JSON) - const actualChanId = JSON.parse(data.protocolData[0].data.toString('utf8')) - assert.equal(actualChanId, this.pluginState.outgoingPaymentChannelId) - }) - - await this.plugin.connect() - - const btpMessage = btpPacket.serializeMessage(1234, this.payChanIdRequest) - this.mockSocket.emit('message', btpMessage) - }) - - describe('validates incoming pay chan', function () { - beforeEach(function () { - this.originalIncomingChannel = this.pluginState.api - .getPaymentChannel(this.incomingPaymentChannelId) - - this.wrongDestinationAddress = Object.assign({}, this.originalIncomingChannel, - { destination: 'rNotThisPluginsAddress' }) - this.cancelAfterTooSoon = Object.assign({}, this.originalIncomingChannel, - { cancelAfter: new Date().toISOString() }) - this.expirationTooSoon = Object.assign({}, this.originalIncomingChannel, - { expiration: new Date().toISOString() }) - this.settleDelayTooLow = Object.assign({}, this.originalIncomingChannel, - { settleDelay: 10 }) - - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - }) - - it(`throws if the incoming channel's settleDelay is too low`, function () { - sinon.stub(this.pluginState.api, 'getPaymentChannel').onSecondCall() - .returns(this.settleDelayTooLow) - - return expect(this.plugin.connect()).to.be - .rejectedWith('settle delay of incoming payment channel too low') - }) - - it('throws if cancelAfter is set', function () { - // onSecondCall() because the first call loads the outoing channel. Only the second loads the incoming channel. - sinon.stub(this.pluginState.api, 'getPaymentChannel').onSecondCall().returns(this.cancelAfterTooSoon) - return expect(this.plugin.connect()).to.be.rejectedWith('incoming paychan expires (.cancelAfter is set)') - }) - - it('throws if expiration is too soon', function () { - // onSecondCall() because the first call loads the outoing channel. Only the second loads the incoming channel. - sinon.stub(this.pluginState.api, 'getPaymentChannel').onSecondCall().returns(this.expirationTooSoon) - return expect(this.plugin.connect()).to.be.rejectedWith('incoming paychan expires (.expiration is set)') - }) - - it('throws if destination address is not the plugin\'s', function () { - // onSecondCall() because the first call loads the outoing channel. Only the second loads the incoming channel. - sinon.stub(this.pluginState.api, 'getPaymentChannel').onSecondCall().returns(this.wrongDestinationAddress) - return expect(this.plugin.connect()).to.be.rejectedWith('Channel destination address wrong') - }) - }) - }) - - describe('handleIncomingPrepare', function () { - beforeEach(function () { - this.transfer = { - id: '1d4958c6-eb7f-44ac-8ab7-2d87dee96002', - amount: 123456, - executionCondition: Buffer.from('27E73D639C4739E9B209BECC9FAED2026F09236E899747B29A6EEBFE6A91C53A', 'hex'), - expiresAt: '2017-12-20T16:03:03.763Z', - to: 'g.eur.mytrustline.rQBHyjckUFGZK1nPDKzTU8Zyvd2niqHcpo', - from: 'g.eur.mytrustline.rPZUg1NH7gAfkpRpKbcwyn8ET7EPqhTFiv', - ledger: 'g.eur.mytrustline.' - } - }) - - it('does not accept an incoming prepare without incoming paychan', async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId, data}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponseNull) - }) - - await this.plugin.connect() - return expect(this.plugin._paychan.handleIncomingPrepare(this.pluginContext, this.transfer)) - .to.eventually.be.rejectedWith('incoming payment channel must be established before incoming transfers are processed') - }) - - it('does not accept an incoming prepare if incoming paychan is closing', async function () { - await _connectPlugin.call(this) - const closingChannel = Object.assign({}, this.pluginState.api.getPaymentChannel(this.incomingPaymentChannelId), - { expiration: new Date().toISOString() }) - sinon.stub(this.pluginState.api, 'getPaymentChannel').returns(closingChannel) - sinon.stub(this.plugin, 'disconnect').throws('something went very wrong') - - this.clock.tick(DEFAULT_WATCHER_INTERVAL) - await sleep(50) - - return expect(this.plugin._paychan.handleIncomingPrepare(this.pluginContext, this.transfer)) - .to.eventually.be.rejectedWith('cannot process transfer for a payment channel that is closing') - }) - }) - - describe('incoming claim', function () { - beforeEach(async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - }) - - it('handles an incoming claim', async function () { - await this.plugin._paychan.handleIncomingClaim(this.pluginContext, this.claim) - - const max = await this.pluginState.incomingClaim.getMax() - assert.equal(max.value, this.claim.amount) - assert.equal(max.data, this.claim.signature) - }) - - it('rejects an incoming claim with invalid signature', async function () { - this.claim.signature = 'INVALID' - try { - await this.plugin._paychan.handleIncomingClaim(this.pluginContext, this.claim) - } catch (err) { - assert.equal(err.message, 'got invalid claim signature INVALID for amount 5 drops') - return - } - assert(false, 'should reject claim') - }) - - it('rejects an incoming claim that exceeds the channel amount', async function () { - try { - await this.plugin._paychan.handleIncomingClaim(this.pluginContext, this.exceedingClaim) - } catch (err) { - assert.equal(err.message, 'got claim for amount higher than channel balance. amount: 5000000, incoming channel balance: 100000') - return - } - assert(false, 'should reject claim') - }) - }) - - describe('outgoing claim', function () { - beforeEach(async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - }) - - it('creates a claim', async function () { - const expectClaim = { - amount: this.pluginState.maxAmount, - signature: '7d23bdbe1fcec7e1b1a535e9df07cc7f50fa6d0e148db3b16d43619fc5e8254085ad622afd1bcee0ef5311d51f0bc23a6d9aacb6893a6ba00b340718420a160d' - } - const claim = await this.plugin._paychan.createOutgoingClaim(this.pluginContext, this.pluginState.maxAmount) - assert.deepEqual(claim, expectClaim) - }) - - it('adds funding if channel runs dry', async function () { - const prepareSpy = sinon.spy(this.pluginState.api, 'preparePaymentChannelFund') - const submitSpy = sinon.spy(this.pluginState.api, 'submit') - - await this.plugin._paychan.createOutgoingClaim(this.pluginContext, this.pluginState.maxAmount) // exceeds the configured funding threshold - - expect(prepareSpy).to.have.been.calledWith(this.opts.address, { - amount: dropsToXrp(this.opts.maxAmount), - channel: String(this.pluginState.outgoingPaymentChannelId) - }) - expect(submitSpy).to.have.been.calledWith('1234567890ABCDEF') - setImmediate(() => { // wait for the callback to update maxAmount - expect(this.pluginState.maxAmount).to.be.equal('200000000') - }) - }) - }) - - describe('submits claims', function () { - beforeEach(async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - }) - - it.skip('submits a claim if maxUnsecured is exceeded', function () { - - }) - }) - - describe('disconnect', function () { - beforeEach(async function () { - this.mockSocket.reply(btpPacket.TYPE_MESSAGE, ({requestId}) => { - return btpPacket.serializeResponse(requestId, this.payChanIdResponse) - }) - await this.plugin.connect() - }) - - it('submits claim on disconnect', async function () { - const prepareSpy = sinon.spy(this.pluginState.api, 'preparePaymentChannelClaim') - const submitSpy = sinon.spy(this.pluginState.api, 'submit') - - await this.plugin._paychan.handleIncomingClaim(this.pluginContext, this.claim) - await this.plugin.disconnect() - - expect(prepareSpy).to.be.calledWith(this.opts.address, { - balance: dropsToXrp(this.claim.amount), - channel: String(this.incomingPaymentChannelId), - signature: this.claim.signature.toUpperCase(), - publicKey: 'ED' + apiHelper.PEER_PUBLIC_KEY - }) - expect(submitSpy).to.be.calledAfter(prepareSpy) - .and.calledWith('1234567890ABCDEF') - }) - - it('disconnects if incoming channel closes', async function () { - const closingChannel = Object.assign({}, this.pluginState.api.getPaymentChannel(this.incomingPaymentChannelId), - { expiration: new Date().toISOString() }) - sinon.stub(this.pluginState.api, 'getPaymentChannel').returns(closingChannel) - - this.clock.tick(DEFAULT_WATCHER_INTERVAL) - await sleep(50) - assert.isFalse(this.plugin.isConnected()) - }) - }) -}) diff --git a/test/integration/rippled-integration.js b/test/integration/rippled-integration.js index 073706c..8c51382 100644 --- a/test/integration/rippled-integration.js +++ b/test/integration/rippled-integration.js @@ -6,13 +6,12 @@ chai.use(chaiAsPromised) const assert = chai.assert const expect = chai.expect +const ilpPacket = require('ilp-packet') const crypto = require('crypto') -const uuid = require('uuid') -const base64url = require('base64url') const BigNumber = require('bignumber.js') const PluginRipple = require('../../index.js') const RippleAPI = require('ripple-lib').RippleAPI -const Store = require('ilp-plugin-payment-channel-framework/test/helpers/objStore') +const Store = require('ilp-store-memory') const {payTo, ledgerAccept} = require('./utils') const { sleep, dropsToXrp } = require('../../src/lib/constants') @@ -23,7 +22,7 @@ const COMMON_OPTS = { token: 'shared_secret', rippledServer: SERVER_URL, maxUnsecured: '5000', - maxAmount: '100000', + channelAmount: 100000, fundThreshold: '0.9' } @@ -88,42 +87,26 @@ describe('plugin integration', function () { beforeEach(async function () { await setup.call(this, SERVER_URL) - const sharedSecret = 'shh its a secret' + const sharedSecret = 'secret' const serverHost = 'localhost' const serverPort = 3000 this.plugin = new PluginRipple(Object.assign({}, COMMON_OPTS, { - // This plugin's ripple address and secret. - // Get testnet credentials at https://ripple.com/build/xrp-test-net/ address: this.newWallet.address, secret: this.newWallet.secret, - - // The peer you want to start a payment channel with peerAddress: this.peer.address, - - // Our peer acts as BTP server. This is the address he is listening on server: `btp+ws://:${sharedSecret}@${serverHost}:${serverPort}`, - - // Other options. For details see: - // https://github.com/interledger/rfcs/blob/master/0004-ledger-plugin-interface/0004-ledger-plugin-interface.md#class-pluginoptions _store: new Store() })) this.peerPlugin = new PluginRipple(Object.assign({}, COMMON_OPTS, { - // This plugin's ripple address and secret. - // Get testnet credentials at https://ripple.com/build/xrp-test-net/ address: this.peer.address, secret: this.peer.secret, - - // The peer you want to start a payment channel with peerAddress: this.newWallet.address, - - // Other options. For details see: - // https://github.com/interledger/rfcs/blob/master/0004-ledger-plugin-interface/0004-ledger-plugin-interface.md#class-pluginoptions _store: new Store(), listener: { - port: serverPort + port: serverPort, + secret: sharedSecret }, - incomingSecret: sharedSecret, prefix: 'g.xrp.mypaychan.', info: { prefix: 'g.xrp.mypaychan.', @@ -133,7 +116,8 @@ describe('plugin integration', function () { } })) - this.pluginState = this.plugin._paychanContext.state + // this.plugin.registerDataHandler(() => {}) + // this.peerPlugin.registerDataHandler(() => {}) // automatically accept the ledger when api.submit() is called const autoAcceptLedger = (api) => { @@ -145,9 +129,8 @@ describe('plugin integration', function () { }) } } - - autoAcceptLedger(this.plugin._paychanContext.state.api) - autoAcceptLedger(this.peerPlugin._paychanContext.state.api) + autoAcceptLedger(this.plugin._api) + autoAcceptLedger(this.peerPlugin._api) }) afterEach(teardown) @@ -155,22 +138,21 @@ describe('plugin integration', function () { it('is eventually fulfilled', async function () { const connectPromise = Promise.all([this.peerPlugin.connect(), this.plugin.connect()]) - await sleep(10) return expect(connectPromise).to.be.eventually.fulfilled }) it('creates an outgoing paychan', async function () { await connectPlugins(this.peerPlugin, this.plugin) - const paychanid = await this.pluginState.outgoingChannel.getMax() - const chan = await this.api.getPaymentChannel(paychanid.data) + const paychanid = this.plugin._outgoingChannel + const chan = await this.api.getPaymentChannel(paychanid) assert.strictEqual(chan.account, this.newWallet.address) - assert.strictEqual(chan.amount, dropsToXrp(COMMON_OPTS.maxAmount)) + assert.strictEqual(chan.amount, dropsToXrp(COMMON_OPTS.channelAmount)) assert.strictEqual(chan.balance, '0') assert.strictEqual(chan.destination, this.peer.address) assert.strictEqual(chan.settleDelay, COMMON_OPTS.settleDelay) - const expectedPubKey = 'ED' + Buffer.from(this.pluginState.keyPair.publicKey) + const expectedPubKey = 'ED' + Buffer.from(this.plugin._keyPair.publicKey) .toString('hex').toUpperCase() assert.strictEqual(chan.publicKey, expectedPubKey) }) @@ -178,17 +160,16 @@ describe('plugin integration', function () { it('has an incoming paychan', async function () { await connectPlugins(this.peerPlugin, this.plugin) - const paychanid = this.pluginState.incomingPaymentChannelId + const paychanid = this.plugin._incomingChannel const chan = await this.api.getPaymentChannel(paychanid) assert.strictEqual(chan.account, this.peer.address) - assert.strictEqual(chan.amount, dropsToXrp(COMMON_OPTS.maxAmount)) + assert.strictEqual(chan.amount, dropsToXrp(COMMON_OPTS.channelAmount)) assert.strictEqual(chan.balance, '0') assert.strictEqual(chan.destination, this.newWallet.address) assert.strictEqual(chan.settleDelay, COMMON_OPTS.settleDelay) - const pubKeyUIntA = this.peerPlugin._paychanContext.state.keyPair.publicKey - const expectedPubKey = 'ED' + Buffer.from(pubKeyUIntA) + const expectedPubKey = 'ED' + Buffer.from(this.peerPlugin._keyPair.publicKey) .toString('hex').toUpperCase() assert.strictEqual(chan.publicKey, expectedPubKey) }) @@ -202,79 +183,80 @@ describe('plugin integration', function () { accounts: [ this.newWallet.address, this.peer.address ] }) - this.fulfillment = crypto.randomBytes(32) + this.fulfillment = Buffer.from('E4840A1A3C50A1635CC53F637721114BEBAF3EAB02FE1AC7C97A6F100311A3ED', 'hex') this.transfer = { - id: uuid(), - ledger: this.plugin.getInfo().prefix, - from: this.plugin.getAccount(), - to: this.plugin.getPeerAccount(), - expiresAt: new Date(Date.now() + 10000).toISOString(), - amount: '5', - custom: { - field: 'some stuff' - }, - executionCondition: base64url(crypto - .createHash('sha256') - .update(this.fulfillment) - .digest()) + amount: '10', + expiresAt: new Date(Date.now() + 10000), + executionCondition: crypto.createHash('sha256').update(this.fulfillment).digest(), + destination: 'peer.example', + data: Buffer.from('hello world') } - this.autoFulfill = new Promise((resolve, reject) => { - this.peerPlugin.on('incoming_prepare', async (transfer) => { - await this.peerPlugin.fulfillCondition( - transfer.id, - base64url(this.fulfillment) - ) - resolve() + // this.transfer = { + // id: uuid(), + // ledger: this.plugin.getInfo().prefix, + // from: this.plugin.getAccount(), + // to: this.plugin.getPeerAccount(), + // expiresAt: new Date(Date.now() + 10000).toISOString(), + // amount: '5', + // custom: { + // field: 'some stuff' + // }, + // executionCondition: base64url(crypto + // .createHash('sha256') + // .update(this.fulfillment) + // .digest()) + // } + + // this.autoFulfill = new Promise((resolve, reject) => { + // this.peerPlugin.on('incoming_prepare', async (transfer) => { + // await this.peerPlugin.fulfillCondition( + // transfer.id, + // base64url(this.fulfillment) + // ) + // resolve() + // }) + // }) + // + this.peerPlugin.registerDataHandler((ilp) => { + return ilpPacket.serializeIlpFulfill({ + fulfillment: this.fulfillment, + data: Buffer.from('hello world again') }) }) }) it('submits a claim on disconnect', async function () { - await this.plugin.sendTransfer(this.transfer) - await this.autoFulfill - const peerBalance = await this.peerPlugin.getBalance() + await this.plugin.sendMoney(this.transfer.amount) // disconnect() makes the plugin submit a claim await this.peerPlugin.disconnect() // assert that the balance on-ledger was adjusted - const paychanid = await this.pluginState.outgoingChannel.getMax() - const chan = await this.api.getPaymentChannel(paychanid.data) + const paychanid = this.plugin._outgoingChannel + const chan = await this.api.getPaymentChannel(paychanid) assert.strictEqual(chan.balance, dropsToXrp(this.transfer.amount)) - - // assert that the plugins' stored balance matches the on-ledger balance - assert.strictEqual(peerBalance, this.transfer.amount) - assert.equal(await this.plugin.getBalance(), -1 * parseInt(this.transfer.amount)) }) it('funds a paychan', async function () { - const chanId = this.pluginState.outgoingPaymentChannelId - this.transfer.amount = COMMON_OPTS.maxUnsecured - const expectedFundingThreshold = parseInt(COMMON_OPTS.maxAmount) * - parseFloat(COMMON_OPTS.fundThreshold) - - // send transfers until the funding threshold is exceeded - while (parseFloat(await this.peerPlugin.getBalance()) <= expectedFundingThreshold) { - // assert that the plugin has not yet issued a funding tx - const chan = await this.api.getPaymentChannel(chanId) - assert(chan.amount, dropsToXrp(COMMON_OPTS.maxAmount)) - - this.transfer.id = uuid() - await this.plugin.sendTransfer(this.transfer) - await new Promise((resolve, reject) => { - this.plugin.on('outgoing_fulfill', () => resolve()) - }) - } - - const expectedAmount = new BigNumber(dropsToXrp(COMMON_OPTS.maxAmount)) - .mul(2).toString() - await new Promise((resolve, reject) => { + const expectedAmount = new BigNumber(dropsToXrp(COMMON_OPTS.channelAmount)).times(2) + const ledgerClose = new Promise((resolve, reject) => { this.api.connection.on('ledgerClosed', async (ev) => { - const chan = await this.api.getPaymentChannel(chanId) - if (chan.amount === expectedAmount) resolve() + const chan = await this.api.getPaymentChannel(this.plugin._outgoingChannel) + assert.equal(chan.amount, expectedAmount, 'Channel does not have expected amount') + resolve() }) }) + + // assert that the channel has the initial amount + const chan = await this.api.getPaymentChannel(this.plugin._outgoingChannel) + assert.equal(chan.amount, dropsToXrp(COMMON_OPTS.channelAmount)) + + // send a transfer that triggers a funding tx + const expectedFundingThreshold = parseInt(COMMON_OPTS.channelAmount) * + parseFloat(COMMON_OPTS.fundThreshold) + 1 + await this.plugin.sendMoney(expectedFundingThreshold) + return ledgerClose }) }) }) diff --git a/test/pluginSpec.js b/test/pluginSpec.js new file mode 100644 index 0000000..2b2320f --- /dev/null +++ b/test/pluginSpec.js @@ -0,0 +1,384 @@ +'use strict' +const Plugin = require('..') +const Store = require('ilp-store-memory') +const BtpPacket = require('btp-packet') +const { util } = require('ilp-plugin-xrp-paychan-shared') +const nacl = require('tweetnacl') + +const EventEmitter = require('events') +const chai = require('chai') +const chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) +const sinon = require('sinon') +const sinonChai = require('sinon-chai') +chai.use(sinonChai) + +const assert = chai.assert + +// peer secretn spCWi5W9SmYYsaDin9Zqe64C27i + +describe('Plugin XRP Paychan Symmetric', function () { + beforeEach(function () { + this.sinon = sinon.sandbox.create() + this.plugin = new Plugin({ + xrpServer: 'wss://s.altnet.rippletest.net:51233', + secret: 'sahNtietWCRzmX7Z2Zy7Z3EsvFDjv', + address: 'ra3h9tzcipHTZCdQesMthfx4iBZNEEuHXG', + peerAddress: 'rKwCnwtM6et7BVaCZm97hbU8oXkoohReea', + _store: new Store() + }) + + this.sinon.stub(util, 'encodeClaim').returns('abcdefg') + this.sinon.stub(nacl.sign, 'detached').returns('abcdefg') + + this.ilpData = { + protocolData: [{ + protocolName: 'ilp', + contentType: BtpPacket.MIME_APPLICATION_OCTET_STREAM, + data: Buffer.alloc(0) + }] + } + + this.channel = { + settleDelay: util.MIN_SETTLE_DELAY + 1, + destination: this.plugin._address, + publicKey: 'abcdefg', + balance: '100', + amount: '1000' + } + + this.signStub = this.sinon.stub(this.plugin._api, 'sign').returns({ signedTransaction: '123' }) + + this.submitStub = this.sinon.stub(this.plugin._api, 'submit').callsFake(() => { + setImmediate(() => { + this.plugin._api.connection.emit('transaction', { + transaction: { + Sequence: 1, + SourceTag: 1, + Account: this.plugin._address, + Destination: this.plugin._peerAddress, + Channel: null + }, + engine_result: 'tesSUCCESS' + }) + }) + return { + resultCode: 'tesSUCCESS', + resultMessage: 'Successful' + } + }) + }) + + afterEach(function () { + this.sinon.restore() + }) + + describe('_handleData', function () { + it('should handle ilp data', async function () { + let handled = false + this.plugin.registerDataHandler(data => { + assert.deepEqual(data, Buffer.alloc(0)) + handled = true + return Buffer.from('test_result') + }) + + const result = await this.plugin._handleData(null, { + requestId: 1, + data: this.ilpData + }) + + assert.isTrue(handled, 'handler should have been called') + assert.deepEqual(result, [{ + protocolName: 'ilp', + contentType: BtpPacket.MIME_APPLICATION_OCTET_STREAM, + data: Buffer.from('test_result') + }], 'result should contain the buffer returned by data handler') + }) + + it('should throw an error if there is no data handler', async function () { + await assert.isRejected(this.plugin._handleData(null, { + requestId: 1, + data: this.ilpData + }), /no request handler registered/) + }) + + it('should handle ripple_channel_id protocol', async function () { + // no need to look at the ledger in this test + this.sinon.stub(this.plugin, '_reloadIncomingChannelDetails').callsFake(() => Promise.resolve()) + this.plugin._outgoingChannel = 'my_channel_id' + + const result = await this.plugin._handleData(null, { + requestId: 1, + data: { + protocolData: [{ + protocolName: 'ripple_channel_id', + contentType: BtpPacket.MIME_TEXT_PLAIN_UTF8, + data: Buffer.from('peer_channel_id') + }] + } + }) + + assert.equal(this.plugin._incomingChannel, 'peer_channel_id', 'incoming channel should be set') + assert.deepEqual(result, [{ + protocolName: 'ripple_channel_id', + contentType: BtpPacket.MIME_TEXT_PLAIN_UTF8, + data: Buffer.from(this.plugin._outgoingChannel) + }], 'result should contain outgoing channel') + }) + }) + + describe('_reloadIncomingChannelDetails', function () { + beforeEach(function () { + this.plugin._outgoingChannel = 'my_channel_id' + this.plugin._incomingChannel = 'peer_channel_id' + }) + + it('should return if it cannot query the peer for a channel', async function () { + // simulate the plugin not being able to get incoming channel + this.plugin._incomingChannel = null + const stub = this.sinon.stub(this.plugin, '_call').callsFake(() => Promise.resolve({ protocolData: [] })) + const spy = this.sinon.spy(this.plugin._api, 'getPaymentChannel') + + await this.plugin._reloadIncomingChannelDetails() + + assert.isTrue(stub.called, 'should have tried to query peer for balance') + assert.isTrue(spy.notCalled, 'should not have reached getPaymentChannel') + }) + + it('should return if getPaymentChannel gives a rippled error', async function () { + const stub = this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .callsFake(() => { + throw new Error('there was an error!') + }) + + await this.plugin._reloadIncomingChannelDetails() + assert.isTrue(stub.called, 'should have queried ledger for paychan') + }) + + it('should throw if settleDelay is too soon', async function () { + this.channel.settleDelay = util.MIN_SETTLE_DELAY - 1 + this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .resolves(this.channel) + + await assert.isRejected(this.plugin._reloadIncomingChannelDetails(), + /settle delay of incoming payment channel too low/) + }) + + it('should throw if cancelAfter is specified', async function () { + this.channel.cancelAfter = Date.now() + 1000 + this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .resolves(this.channel) + + await assert.isRejected(this.plugin._reloadIncomingChannelDetails(), + /cancelAfter must not be set/) + }) + + it('should throw if expiration is specified', async function () { + this.channel.expiration = Date.now() + 1000 + this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .resolves(this.channel) + + await assert.isRejected(this.plugin._reloadIncomingChannelDetails(), + /expiration must not be set/) + }) + + it('should throw if destination does not match our account', async function () { + this.channel.destination = this.plugin._peerAddress + this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .resolves(this.channel) + + await assert.isRejected(this.plugin._reloadIncomingChannelDetails(), + /Channel destination address wrong/) + }) + + it('should return if all details are ok', async function () { + this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .resolves(this.channel) + + await this.plugin._reloadIncomingChannelDetails() + assert.isTrue(!!this.plugin._claimIntervalId, + 'claim interval should be started if reload was successful') + }) + }) + + describe('_connect', function () { + beforeEach(function () { + // mock out the rippled connection + this.plugin._api.connection = new EventEmitter() + this.plugin._api.connection.request = () => Promise.resolve(null) + this.sinon.stub(this.plugin._api, 'connect').resolves(null) + + this.channelId = '945BB98D2F03DFA2AED810F8917B2BC344C0AA182A5DB506C16F84593C24244F' + this.tagStub = this.sinon.stub(util, 'randomTag').returns(1) + this.prepareStub = this.sinon.stub(this.plugin._api, 'preparePaymentChannelCreate').resolves({ txJSON: '{}' }) + + this.loadStub = this.sinon.stub(this.plugin._api, 'getPaymentChannel') + .callsFake(id => { + assert.equal(id, this.channelId) + return Promise.resolve(this.channel) + }) + + this.reloadStub = this.sinon.stub(this.plugin, '_reloadIncomingChannelDetails') + .resolves(null) + }) + + it('should load outgoing channel if exists', async function () { + this.plugin._store.load('outgoing_channel') + this.plugin._store.set('outgoing_channel', this.channelId) + + await this.plugin._connect() + + assert.isTrue(this.loadStub.called, 'should have loaded outgoing channel') + assert.isTrue(this.reloadStub.called, 'should have reloaded incoming channel') + }) + + it('should prepare a payment channel', async function () { + await this.plugin._connect() + + assert.isTrue(this.tagStub.called, 'should have generated source tag') + assert.isTrue(this.prepareStub.called, 'should have generated prepare tx') + assert.isTrue(this.signStub.called, 'should have signed xrp tx') + assert.isTrue(this.submitStub.called, 'should have submitted tx to ledger') + assert.isTrue(this.loadStub.called, 'should have loaded outgoing channel') + assert.isTrue(this.reloadStub.called, 'should have reloaded incoming channel') + }) + }) + + describe('_claimFunds', function () { + beforeEach(function () { + this.plugin._incomingClaim = { + amount: '100', + signature: 'some signature' + } + + this.plugin._incomingChannelDetails = this.channel + this.prepareStub = this.sinon.stub(this.plugin._api, 'preparePaymentChannelClaim') + .resolves({ txJSON: '{}' }) + }) + + it('should return if incomingClaim has no signature', async function () { + delete this.plugin._incomingClaim.signature + + await this.plugin._claimFunds() + + assert.isFalse(this.prepareStub.called, 'should not have prepared a claim tx with no signature') + }) + + it('should submit tx if incomingClaim is valid', async function () { + await this.plugin._claimFunds() + + assert.isTrue(this.prepareStub.called, 'tx should be prepared') + assert.isTrue(this.signStub.called, 'tx should be signed') + assert.isTrue(this.submitStub.called, 'transaction should be submitted to ledger') + }) + }) + + describe('_disconnect', function () { + beforeEach(function () { + this.plugin._claimIntervalId = setInterval(() => {}, 5000) + this.claimStub = this.sinon.stub(this.plugin, '_claimFunds') + this.disconnectStub = this.sinon.stub(this.plugin._api, 'disconnect') + }) + + afterEach(function () { + assert.isTrue(this.claimStub.called, 'should have claimed funds') + assert.isTrue(this.disconnectStub.called, 'should have disconnected api') + }) + + it('should claim and disconnect the api', async function () { + await this.plugin._disconnect() + }) + + it('should still disconnect if claim fails', async function () { + this.claimStub.throws() + await this.plugin._disconnect() + }) + + it('should still disconnect if api disconnect fails', async function () { + this.disconnectStub.throws() + await this.plugin._disconnect() + }) + }) + + describe('_sendMoney', function () { + beforeEach(function () { + this.plugin._funding = true // turn off the funding path + this.plugin._outgoingChannel = 'my_channel_id' + this.plugin._outgoingClaim = { amount: '0' } + this._outgoingClaim = { + amount: '100', + signature: '61626364656667' + } + }) + + it('should sign a claim and submit it to the other side', async function () { + this.sinon.stub(this.plugin, '_call').callsFake((from, data) => { + // forgive me + assert.deepEqual(data.data.protocolData[0].data, + Buffer.from(JSON.stringify(this._outgoingClaim))) + }) + + await this.plugin.sendMoney(100) + }) + }) + + describe('_handleMoney', function () { + beforeEach(function () { + this.claimAmount = '100' + this.claimSignature = 'abcdefg' + this.claimData = () => ({ + amount: '100', + protocolData: [{ + protocolName: 'claim', + contentType: BtpPacket.MIME_APPLICATION_JSON, + data: JSON.stringify({ + amount: this.claimAmount, + signature: this.claimSignature + }) + }] + }) + + this.plugin._incomingChannelDetails = this.channel + this.plugin._incomingClaim = { + amount: '0', + signature: 'abcdefg' + } + + this.naclStub = this.sinon.stub(nacl.sign.detached, 'verify').returns(true) + }) + + it('throws an error if new claim is less than old claim', async function () { + this.plugin._incomingClaim.amount = '100' + await assert.isRejected( + this.plugin._handleMoney(null, { requestId: 1, data: this.claimData() }), + /new claim is less than old claim. new=100 old=100/) + }) + + it('throws an error if the signature is not valid', async function () { + this.naclStub.throws() + await assert.isRejected( + this.plugin._handleMoney(null, { requestId: 1, data: this.claimData() }), + /got invalid claim signature abcdefg for amount 100 drops total/) + }) + + it('throws an error if the claim is for more than the channel capacity', async function () { + this.plugin._incomingChannelDetails.amount = '0.000001' + await assert.isRejected( + this.plugin._handleMoney(null, { requestId: 1, data: this.claimData() }), + /got claim for amount higher than channel balance. amount: 100 incoming channel amount: 1/) + }) + + it('calls the money handler on success', async function () { + let handled = false + this.plugin.registerMoneyHandler(amount => { + assert.deepEqual(amount, '100') + handled = true + return Buffer.from('test_result') + }) + + await this.plugin._handleMoney(null, { requestId: 1, data: this.claimData() }) + + assert.isTrue(handled, 'handler should have been called') + }) + }) +}) diff --git a/test/scripts/test.js b/test/scripts/test.js new file mode 100644 index 0000000..9f4eccc --- /dev/null +++ b/test/scripts/test.js @@ -0,0 +1,89 @@ +const BtpPlugin = require('../..') +const IlpPacket = require('ilp-packet') + +process.on('unhandledRejection', (e) => { + console.error('unhandledRejection', e) +}) + +class Store { + constructor () { + this._store = {} + } + + async get (k) { + return this._store[k] + } + + async put (k, v) { + this._store[k] = v + } + + async del (k) { + delete this._store[k] + } +} + +const server = new BtpPlugin({ + listener: { + port: 9000, + secret: 'secret' + }, + rippledServer: 'wss://s.altnet.rippletest.net:51233', + peerAddress: 'raYsh5o2YXvuZKj6xYyuHNitUTzT8dWKYE', + secret: 'snUNeZeq4QRqefSh7oydUSWZv1uh3', + _store: new Store() +}) +const client = new BtpPlugin({ + server: 'btp+ws://:secret@localhost:9000', + rippledServer: 'wss://s.altnet.rippletest.net:51233', + peerAddress: 'rW1XrgCvURLdPkdVsQhmWsRqtgMnApepK', + secret: 'sspGuk1g9KPX4ac8oq9MDsW5QUsQM', + _store: new Store() +}) + +async function run () { + await Promise.all([ + server.connect(), + client.connect() + ]) + + server.registerDataHandler((ilp) => { + console.log('server got:', IlpPacket.deserializeIlpPacket(ilp)) + return IlpPacket.serializeIlpFulfill({ + fulfillment: Buffer.alloc(32), + data: Buffer.from('hello world again') + }) + }) + + const response = await client.sendData(IlpPacket.serializeIlpPrepare({ + amount: '10', + expiresAt: new Date(), + executionCondition: Buffer.alloc(32), + destination: 'peer.example', + data: Buffer.from('hello world') + })) + + console.log('client got:', IlpPacket.deserializeIlpPacket(response)) + + await server.sendMoney(10) + await client.sendMoney(10) + + console.log('sent money') + console.log('testing reconciliation') + + server._incomingClaim = { amount: '0' } // simulate out of sync behavior + await client.sendMoney(10) // this call should warn about discrepency + await client.sendMoney(10) // this call should not + + console.log('done; disconnecting') + + await client.disconnect() + await server.disconnect() + process.exit(0) +} + +run() + .catch((e) => { + console.error(e) + process.exit(1) + })