From 09b10d3f2c47ba09a8549ef1268d63d4ce9a66e0 Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 30 Jun 2015 18:56:10 -0700 Subject: [PATCH] Convert getPathFind and add unit test --- src/api/common/schemas/pathfind.json | 5 +- src/api/common/utils.js | 9 +- src/api/index.js | 4 +- src/api/ledger/balances.js | 19 +- src/api/ledger/parse/pathfind.js | 10 +- src/api/ledger/pathfind.js | 226 +++++-------- src/api/ledger/utils.js | 9 + src/api/transaction/settings.js | 1 + src/api/transaction/utils.js | 5 +- test/api-test.js | 22 +- test/fixtures/get-pathfind-response.json | 58 ++++ test/fixtures/paths.js | 410 +++++++++++++++++++++++ test/mock-rippled.js | 8 + 13 files changed, 627 insertions(+), 159 deletions(-) create mode 100644 test/fixtures/get-pathfind-response.json create mode 100644 test/fixtures/paths.js diff --git a/src/api/common/schemas/pathfind.json b/src/api/common/schemas/pathfind.json index 2998c965..2302f2c7 100644 --- a/src/api/common/schemas/pathfind.json +++ b/src/api/common/schemas/pathfind.json @@ -7,14 +7,13 @@ "type": "object", "properties": { "address": {"$ref": "address"}, - "amounts": { + "currencies": { "type": "array", "items": { "type": "object", "properties": { "currency": {"$ref": "currency"}, - "counterparty": {"$ref": "address"}, - "value": {"$ref": "value"} + "counterparty": {"$ref": "address"} }, "required": ["currency"], "additionalProperties": false diff --git a/src/api/common/utils.js b/src/api/common/utils.js index 71f9b1f6..cf0b2cde 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -38,7 +38,14 @@ function composeAsync(wrapper, callback) { callback(error); return; } - callback(null, wrapper(data)); + let result; + try { + result = wrapper(data); + } catch (exception) { + callback(exception); + return; + } + callback(null, result); }; } diff --git a/src/api/index.js b/src/api/index.js index 27520f85..0afbc09a 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -10,7 +10,7 @@ const getTransaction = require('./ledger/transaction'); const getAccountTransactions = require('./ledger/transactions'); const getTrustlines = require('./ledger/trustlines'); const getBalances = require('./ledger/balances'); -// const getPathFind = require('./ledger/pathfind'); +const getPathFind = require('./ledger/pathfind'); const getOrders = require('./ledger/orders'); const getOrderBook = require('./ledger/orderbook'); const getSettings = require('./ledger/settings'); @@ -39,7 +39,7 @@ RippleAPI.prototype = { getAccountTransactions, getTrustlines, getBalances, - // getPathFind, + getPathFind, getOrders, getOrderBook, getSettings, diff --git a/src/api/ledger/balances.js b/src/api/ledger/balances.js index 980dad5b..9fa651e7 100644 --- a/src/api/ledger/balances.js +++ b/src/api/ledger/balances.js @@ -5,12 +5,6 @@ const utils = require('./utils'); const getTrustlines = require('./trustlines'); const validate = utils.common.validate; const composeAsync = utils.common.composeAsync; -const dropsToXrp = utils.common.dropsToXrp; - -function getXRPBalance(remote, address, ledgerVersion, callback) { - remote.requestAccountInfo({account: address, ledger: ledgerVersion}, - composeAsync((data) => dropsToXrp(data.account_data.Balance), callback)); -} function getTrustlineBalanceAmount(trustline) { return { @@ -23,9 +17,10 @@ function getTrustlineBalanceAmount(trustline) { function formatBalances(balances) { const xrpBalance = { currency: 'XRP', - value: balances[0] + value: balances.xrp }; - return [xrpBalance].concat(balances[1].map(getTrustlineBalanceAmount)); + return [xrpBalance].concat( + balances.trustlines.map(getTrustlineBalanceAmount)); } function getBalances(account, options, callback) { @@ -34,10 +29,10 @@ function getBalances(account, options, callback) { const ledgerVersion = options.ledgerVersion || this.remote.getLedgerSequence(); - async.parallel([ - _.partial(getXRPBalance, this.remote, account, ledgerVersion), - _.partial(getTrustlines.bind(this), account, options) - ], composeAsync(formatBalances, callback)); + async.parallel({ + xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion), + trustlines: _.partial(getTrustlines.bind(this), account, options) + }, composeAsync(formatBalances, callback)); } module.exports = utils.wrapCatch(getBalances); diff --git a/src/api/ledger/parse/pathfind.js b/src/api/ledger/parse/pathfind.js index b99d87a2..7ffaf214 100644 --- a/src/api/ledger/parse/pathfind.js +++ b/src/api/ledger/parse/pathfind.js @@ -1,7 +1,13 @@ /* @flow */ 'use strict'; +const _ = require('lodash'); const parseAmount = require('./amount'); +function parsePaths(paths) { + return paths.map(steps => steps.map(step => + _.omit(step, ['type', 'type_hex']))); +} + function parsePathfind(sourceAddress: string, destinationAmount: Object, pathfindResult: Object): Object { return pathfindResult.alternatives.map(function(alternative) { @@ -14,9 +20,7 @@ function parsePathfind(sourceAddress: string, address: pathfindResult.destination_account, amount: destinationAmount }, - paths: JSON.stringify(alternative.paths_computed), - allowPartialPayment: false, - noDirectRipple: false + paths: JSON.stringify(parsePaths(alternative.paths_computed)) }; }); } diff --git a/src/api/ledger/pathfind.js b/src/api/ledger/pathfind.js index 85506d7c..b60c3f90 100644 --- a/src/api/ledger/pathfind.js +++ b/src/api/ledger/pathfind.js @@ -1,147 +1,105 @@ -/* eslint-disable valid-jsdoc */ 'use strict'; +const _ = require('lodash'); const async = require('async'); -const asyncify = require('simple-asyncify'); -const bignum = require('bignumber.js'); +const BigNumber = require('bignumber.js'); const utils = require('./utils'); const validate = utils.common.validate; const parsePathfind = require('./parse/pathfind'); const NotFoundError = utils.common.errors.NotFoundError; -const TimeOutError = utils.common.errors.TimeOutError; +const composeAsync = utils.common.composeAsync; + +function addParams(params, result) { + return _.assign({}, result, { + source_account: params.src_account, + source_currencies: params.src_currencies, + destination_amount: params.dst_amount + }); +} + +function requestPathFind(remote, pathfind, callback) { + const params = { + src_account: pathfind.source.address, + dst_account: pathfind.destination.address, + dst_amount: utils.common.toRippledAmount(pathfind.destination.amount) + }; + if (typeof params.dst_amount === 'object' && !params.dst_amount.issuer) { + // Convert blank issuer to sender's address + // (Ripple convention for 'any issuer') + // https://ripple.com/build/transactions/ + // #special-issuer-values-for-sendmax-and-amount + // https://ripple.com/build/ripple-rest/#counterparties-in-payments + params.dst_amount.issuer = params.dst_account; + } + if (pathfind.source.currencies && pathfind.source.currencies.length > 0) { + params.src_currencies = pathfind.source.currencies.map(amount => + _.omit(utils.common.toRippledAmount(amount), 'value')); + } + + remote.requestRipplePathFind(params, + composeAsync(_.partial(addParams, params), callback)); +} + +function addDirectXrpPath(paths, xrpBalance) { + // Add XRP "path" only if the source acct has enough XRP to make the payment + const destinationAmount = paths.destination_amount; + if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) { + paths.alternatives.unshift({ + paths_computed: [], + source_amount: paths.destination_amount + }); + } + return paths; +} + +function isRippledIOUAmount(amount) { + // rippled XRP amounts are specified as decimal strings + return (typeof amount === 'object') && + amount.currency && (amount.currency !== 'XRP'); +} + +function conditionallyAddDirectXRPPath(remote, address, paths, callback) { + if (isRippledIOUAmount(paths.destination_amount) + || !_.includes(paths.destination_currencies, 'XRP')) { + callback(null, paths); + } else { + utils.getXRPBalance(remote, address, undefined, + composeAsync(_.partial(addDirectXrpPath, paths), callback)); + } +} + +function formatResponse(pathfind, paths) { + if (paths.alternatives && paths.alternatives.length > 0) { + const address = pathfind.source.address; + return parsePathfind(address, pathfind.destination.amount, paths); + } + if (!_.includes(paths.destination_currencies, + pathfind.destination.amount.currency)) { + throw new NotFoundError('No paths found. ' + + 'The destination_account does not accept ' + + pathfind.destination.amount.currency + ', they only accept: ' + + paths.destination_currencies.join(', ')); + } else if (paths.source_currencies && paths.source_currencies.length > 0) { + throw new NotFoundError('No paths found. Please ensure' + + ' that the source_account has sufficient funds to execute' + + ' the payment in one of the specified source_currencies. If it does' + + ' there may be insufficient liquidity in the network to execute' + + ' this payment right now'); + } else { + throw new NotFoundError('No paths found.' + + ' Please ensure that the source_account has sufficient funds to' + + ' execute the payment. If it does there may be insufficient liquidity' + + ' in the network to execute this payment right now'); + } +} -/** - * Get a ripple path find, a.k.a. payment options, - * for a given set of parameters and respond to the - * client with an array of fully-formed Payments. - * - * @param {Remote} remote - * @param {RippleAddress} req.params.source_account - * @param {Amount Array ["USD r...,XRP,..."]} req.query.source_currencies - * - Note that Express.js middleware replaces "+" signs with spaces. - * Clients should use "+" signs but the values here will end up - * as spaces - * @param {RippleAddress} req.params.destination_account - * @param {Amount "1+USD+r..."} req.params.destination_amount_string - */ function getPathFind(pathfind, callback) { - const self = this; validate.pathfind(pathfind); - function prepareOptions() { - const pathfindParams = { - src_account: pathfind.source.address, - dst_account: pathfind.destination.address, - dst_amount: utils.common.toRippledAmount(pathfind.destination.amount) - }; - if (typeof pathfindParams.dst_amount === 'object' - && !pathfindParams.dst_amount.issuer) { - // Convert blank issuer to sender's address - // (Ripple convention for 'any issuer') - // https://ripple.com/build/transactions/ - // #special-issuer-values-for-sendmax-and-amount - // https://ripple.com/build/ripple-rest/#counterparties-in-payments - pathfindParams.dst_amount.issuer = pathfindParams.dst_account; - } - if (pathfind.source.amounts && pathfind.source.amounts.length > 0) { - pathfindParams.src_currencies = pathfind.source.amounts; - } - return pathfindParams; - } - - function findPath(pathfindParams, _callback) { - const request = self.remote.requestRipplePathFind(pathfindParams); - request.once('error', _callback); - request.once('success', function(pathfindResults) { - pathfindResults.source_account = pathfindParams.src_account; - pathfindResults.source_currencies = pathfindParams.src_currencies; - pathfindResults.destination_amount = pathfindParams.dst_amount; - _callback(null, pathfindResults); - }); - - function reconnectRippled() { - self.remote.disconnect(function() { - self.remote.connect(); - }); - } - request.timeout(utils.common.server.CONNECTION_TIMEOUT, function() { - request.removeAllListeners(); - reconnectRippled(); - _callback(new TimeOutError('Path request timeout')); - }); - request.request(); - } - - function addDirectXrpPath(pathfindResults, _callback) { - // Check if destination_amount is XRP and if destination_account accepts XRP - if (typeof pathfindResults.destination_amount.currency === 'string' - || pathfindResults.destination_currencies.indexOf('XRP') === -1) { - return _callback(null, pathfindResults); - } - // Check source_account balance - self.remote.requestAccountInfo({account: pathfindResults.source_account}, - function(error, result) { - if (error) { - return _callback(new Error( - 'Cannot get account info for source_account. ' + error)); - } - if (!result || !result.account_data || !result.account_data.Balance) { - return _callback(new Error('Internal Error. Malformed account info : ' - + JSON.stringify(result))); - } - // Add XRP "path" only if the source_account has enough money - // to execute the payment - if (bignum(result.account_data.Balance).greaterThan( - pathfindResults.destination_amount)) { - pathfindResults.alternatives.unshift({ - paths_canonical: [], - paths_computed: [], - source_amount: pathfindResults.destination_amount - }); - } - _callback(null, pathfindResults); - }); - } - - function formatPath(pathfindResults) { - const alternatives = pathfindResults.alternatives; - if (alternatives && alternatives.length > 0) { - return parsePathfind(pathfindResults); - } - if (pathfindResults.destination_currencies.indexOf( - pathfind.destination.amount.currency) === -1) { - throw new NotFoundError('No paths found. ' + - 'The destination_account does not accept ' + - pathfind.destination.amount.currency + - ', they only accept: ' + - pathfindResults.destination_currencies.join(', ')); - } else if (pathfindResults.source_currencies - && pathfindResults.source_currencies.length > 0) { - throw new NotFoundError('No paths found. Please ensure' + - ' that the source_account has sufficient funds to execute' + - ' the payment in one of the specified source_currencies. If it does' + - ' there may be insufficient liquidity in the network to execute' + - ' this payment right now'); - } else { - throw new NotFoundError('No paths found.' + - ' Please ensure that the source_account has sufficient funds to' + - ' execute the payment. If it does there may be insufficient liquidity' + - ' in the network to execute this payment right now'); - } - } - - function formatResponse(payments) { - return {payments: payments}; - } - - const steps = [ - asyncify(prepareOptions), - findPath, - addDirectXrpPath, - asyncify(formatPath), - asyncify(formatResponse) - ]; - - async.waterfall(steps, callback); + const address = pathfind.source.address; + async.waterfall([ + _.partial(requestPathFind, this.remote, pathfind), + _.partial(conditionallyAddDirectXRPPath, this.remote, address) + ], composeAsync(_.partial(formatResponse, pathfind), callback)); } module.exports = utils.wrapCatch(getPathFind); diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index 3e805e4d..e196336d 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -1,6 +1,13 @@ 'use strict'; const _ = require('lodash'); const common = require('../common'); +const dropsToXrp = common.dropsToXrp; +const composeAsync = common.composeAsync; + +function getXRPBalance(remote, address, ledgerVersion, callback) { + remote.requestAccountInfo({account: address, ledger: ledgerVersion}, + composeAsync((data) => dropsToXrp(data.account_data.Balance), callback)); +} // If the marker is omitted from a response, you have reached the end // getter(marker, limit, callback), callback(error, {marker, results}) @@ -56,6 +63,7 @@ function signum(num) { * @param {Object} second * @returns {Number} [-1, 0, 1] */ + function compareTransactions(first, second) { if (first.ledgerVersion === second.ledgerVersion) { return signum(Number(first.indexInLedger) - Number(second.indexInLedger)); @@ -64,6 +72,7 @@ function compareTransactions(first, second) { } module.exports = { + getXRPBalance: getXRPBalance, compareTransactions: compareTransactions, renameCounterpartyToIssuer: renameCounterpartyToIssuer, renameCounterpartyToIssuerInOrder: renameCounterpartyToIssuerInOrder, diff --git a/src/api/transaction/settings.js b/src/api/transaction/settings.js index 84811943..d8cd4a80 100644 --- a/src/api/transaction/settings.js +++ b/src/api/transaction/settings.js @@ -78,6 +78,7 @@ function setTransactionFields(transaction, input) { * @returns {Number|String} numbers will be converted while strings * are returned */ + function convertTransferRate(transferRate) { if (_.isNumber(transferRate)) { return transferRate * Math.pow(10, 9); diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index 0568e211..4b9aff43 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -23,9 +23,8 @@ function getFeeDrops(remote) { return remote.feeTx(feeUnits).to_text(); } -/*:: type Callback = (err: ?(typeof Error), data: {tx_json: any}) => void */ -function createTxJSON(transaction: any, remote: any, - instructions: any, callback: Callback): void { +function createTxJSON(transaction: any, remote: any, instructions: any, + callback: (err: ?(typeof Error), data: {tx_json: any}) => void): void { common.validate.instructions(instructions); transaction.complete(); diff --git a/test/api-test.js b/test/api-test.js index 38cc0fc2..3b7e360f 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const assert = require('assert-diff'); const setupAPI = require('./setup-api'); -const address = require('./fixtures/addresses').ACCOUNT; +const addresses = require('./fixtures/addresses'); const hashes = require('./fixtures/hashes'); const paymentSpecification = require('./fixtures/payment-specification'); const paymentResponse = require('./fixtures/payment-response'); @@ -30,6 +30,8 @@ const getSettingsResponse = require('./fixtures/get-settings-response'); const getOrdersResponse = require('./fixtures/get-orders-response'); const getOrderBookResponse = require('./fixtures/get-orderbook-response'); const getServerInfoResponse = require('./fixtures/get-server-info-response'); +const getPathFindResponse = require('./fixtures/get-pathfind-response'); +const address = addresses.ACCOUNT; function checkResult(expected, done, error, response) { if (error) { @@ -170,4 +172,22 @@ describe('RippleAPI', function() { done(); }); }); + + it('getPathFind', function(done) { + const pathfind = { + source: { + address: address + }, + destination: { + address: addresses.OTHER_ACCOUNT, + amount: { + currency: 'USD', + counterparty: addresses.ISSUER, + value: '100' + } + } + }; + this.api.getPathFind(pathfind, + _.partial(checkResult, getPathFindResponse, done)); + }); }); diff --git a/test/fixtures/get-pathfind-response.json b/test/fixtures/get-pathfind-response.json new file mode 100644 index 00000000..1fe5ac9e --- /dev/null +++ b/test/fixtures/get-pathfind-response.json @@ -0,0 +1,58 @@ +[ + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "JPY", + "value": "0.1117218827811721", + "counterparty": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + } + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "amount": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "100" + } + }, + "paths": "[[{\"account\":\"rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6\"},{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rHHa9t2kLQyXRbdLkSzEgkzwf9unmFgZs9\"},{\"account\":\"rHHa9t2kLQyXRbdLkSzEgkzwf9unmFgZs9\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}]]" + }, + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "0.001002", + "counterparty": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + } + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "amount": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "100" + } + }, + "paths": "[[{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"currency\":\"XRP\"},{\"currency\":\"USD\",\"issuer\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}]]" + }, + { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "XRP", + "value": "0.207669" + } + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "amount": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "100" + } + }, + "paths": "[[{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"currency\":\"USD\",\"issuer\":\"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc\"},{\"account\":\"rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc\"},{\"account\":\"rf9X8QoYnWLHMHuDfjkmRcD2UE5qX5aYV\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"currency\":\"USD\",\"issuer\":\"rDVdJ62foD1sn7ZpxtXyptdkBSyhsQGviT\"},{\"account\":\"rDVdJ62foD1sn7ZpxtXyptdkBSyhsQGviT\"},{\"account\":\"rfQPFZ3eLcaSUKjUy7A3LAmDNM4F9Hz9j1\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}],[{\"currency\":\"USD\",\"issuer\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT\"},{\"account\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"}]]" + } +] diff --git a/test/fixtures/paths.js b/test/fixtures/paths.js new file mode 100644 index 00000000..3868725a --- /dev/null +++ b/test/fixtures/paths.js @@ -0,0 +1,410 @@ +'use strict'; + +module.exports.generateIOUPaymentPaths = +function(request_id, sendingAccount, destinationAccount, destinationAmount) { + return JSON.stringify({ + 'id': request_id, + 'status': 'success', + 'type': 'response', + 'result': { + 'alternatives': [ + { + 'paths_canonical': [], + 'paths_computed': [ + [ + { + 'account': 'rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': destinationAmount.currency, + 'issuer': destinationAmount.issuer, + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + }, + { + 'currency': destinationAmount.currency, + 'issuer': destinationAmount.issuer, + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + }, + { + 'currency': destinationAmount.currency, + 'issuer': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + }, + { + 'currency': destinationAmount.currency, + 'issuer': 'rHHa9t2kLQyXRbdLkSzEgkzwf9unmFgZs9', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rHHa9t2kLQyXRbdLkSzEgkzwf9unmFgZs9', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ] + ], + 'source_amount': { + 'currency': 'JPY', + 'issuer': sendingAccount, + 'value': '0.1117218827811721' + } + }, + { + 'paths_canonical': [], + 'paths_computed': [ + [ + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': destinationAmount.currency, + 'issuer': destinationAmount.issuer, + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + }, + { + 'currency': destinationAmount.currency, + 'issuer': destinationAmount.issuer, + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + }, + { + 'currency': destinationAmount.currency, + 'issuer': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ] + ], + 'source_amount': { + 'currency': 'USD', + 'issuer': sendingAccount, + 'value': '0.001002' + } + }, + { + 'paths_canonical': [], + 'paths_computed': [ + [ + { + 'currency': destinationAmount.currency, + 'issuer': destinationAmount.issuer, + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'currency': destinationAmount.currency, + 'issuer': 'rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': 'rf9X8QoYnWLHMHuDfjkmRcD2UE5qX5aYV', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'currency': destinationAmount.currency, + 'issuer': 'rDVdJ62foD1sn7ZpxtXyptdkBSyhsQGviT', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rDVdJ62foD1sn7ZpxtXyptdkBSyhsQGviT', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': 'rfQPFZ3eLcaSUKjUy7A3LAmDNM4F9Hz9j1', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ], + [ + { + 'currency': destinationAmount.currency, + 'issuer': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 48, + 'type_hex': '0000000000000030' + }, + { + 'account': 'rpHgehzdpfWRXKvSv6duKvVuo1aZVimdaT', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': destinationAmount.issuer, + 'type': 1, + 'type_hex': '0000000000000001' + } + ] + ], + 'source_amount': '207669' + } + ], + 'destination_account': destinationAccount, + 'destination_currencies': [ + 'USD', + 'JOE', + 'BTC', + 'DYM', + 'CNY', + 'EUR', + '015841551A748AD2C1F76FF6ECB0CCCD00000000', + 'MXN', + 'XRP' + ] + } + }); +}; + +module.exports.generateXRPPaymentPaths = +function(request_id, sendingAccount, destinationAccount) { + return JSON.stringify({ + 'id': request_id, + 'status': 'success', + 'type': 'response', + 'result': { + 'alternatives': [ + { + 'paths_canonical': [], + 'paths_computed': [ + [ + { + 'account': 'rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + } + ] + ], + 'source_amount': { + 'currency': 'JPY', + 'issuer': sendingAccount, + 'value': '0.00005460001' + } + }, + { + 'paths_canonical': [], + 'paths_computed': [ + [ + { + 'account': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + } + ], + [ + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + } + ], + [ + { + 'account': destinationAccount, + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + } + ], + [ + { + 'account': 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'account': 'r3MeEnYZY9fAd5pGjAWf4dfJsQBVY9FZRL', + 'type': 1, + 'type_hex': '0000000000000001' + }, + { + 'currency': 'XRP', + 'type': 16, + 'type_hex': '0000000000000010' + } + ] + ], + 'source_amount': { + 'currency': 'USD', + 'issuer': sendingAccount, + 'value': '0.0000005158508428100899' + } + } + ], + 'destination_account': destinationAccount, + 'destination_currencies': [ + 'USD', + 'XRP' + ] + } + }); +}; diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 28dffb5b..d7996335 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -8,6 +8,7 @@ const addresses = require('./fixtures/addresses'); const hashes = require('./fixtures/hashes'); const accountOffersResponse = require('./fixtures/acct-offers-response'); const bookOffers = require('./fixtures/book-offers-response'); +const paths = require('./fixtures/paths'); function isUSD(json) { return json === 'USD' || json === '0000000000000000000000005553440000000000'; @@ -150,5 +151,12 @@ module.exports = function(port) { } }); + mock.on('request_ripple_path_find', function(request, conn) { + const response = paths.generateIOUPaymentPaths(request.id, + request.source_account, request.destination_account, + request.destination_amount); + conn.send(response); + }); + return mock; };