diff --git a/src/api/common/schema-validator.js b/src/api/common/schema-validator.js index db4f99dba0..0f16470ca0 100644 --- a/src/api/common/schema-validator.js +++ b/src/api/common/schema-validator.js @@ -31,6 +31,8 @@ function loadSchemas() { require('./schemas/currency.json'), require('./schemas/get-account-info.json'), require('./schemas/get-balances.json'), + require('./schemas/get-balance-sheet'), + require('./schemas/balance-sheet-options.json'), require('./schemas/get-ledger.json'), require('./schemas/get-orderbook.json'), require('./schemas/get-orders.json'), diff --git a/src/api/common/schemas/balance-sheet-options.json b/src/api/common/schemas/balance-sheet-options.json new file mode 100644 index 0000000000..85a8547cce --- /dev/null +++ b/src/api/common/schemas/balance-sheet-options.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "balance-sheet-options", + "description": "Options for getBalanceSheet", + "type": "object", + "properties": { + "excludeAddresses": { + "type": "array", + "items": {"$ref": "address"}, + "uniqueItems": true + }, + "ledgerVersion": {"$ref": "ledgerVersion"} + }, + "additionalProperties": false +} diff --git a/src/api/common/schemas/get-balance-sheet.json b/src/api/common/schemas/get-balance-sheet.json new file mode 100644 index 0000000000..5d74494aa9 --- /dev/null +++ b/src/api/common/schemas/get-balance-sheet.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "getBalanceSheet", + "description": "getBalanceSheet response", + "type": "object", + "properties": { + "balances": { + "type": "array", + "items": {"$ref": "amount"} + }, + "assets": { + "type": "array", + "items": {"$ref": "amount"} + }, + "obligations": { + "type": "array", + "items": { + "type": "object", + "required": ["currency", "value"], + "additionalProperties": false, + "properties": { + "currency": {"$ref": "currency"}, + "value": {"$ref": "value"} + } + } + } + }, + "additionalProperties": false +} diff --git a/src/api/common/validate.js b/src/api/common/validate.js index 3c700080bd..ce5f2d2ea9 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -82,6 +82,7 @@ module.exports = { getAccountInfoOptions: _.partial(validateOptions, 'settings-options'), getTrustlinesOptions: _.partial(validateOptions, 'trustlines-options'), getBalancesOptions: _.partial(validateOptions, 'trustlines-options'), + getBalanceSheetOptions: _.partial(validateOptions, 'balance-sheet-options'), getOrdersOptions: _.partial(validateOptions, 'orders-options'), getOrderbookOptions: _.partial(validateOptions, 'orders-options'), getTransactionOptions: _.partial(validateOptions, 'transaction-options'), diff --git a/src/api/index.js b/src/api/index.js index 0fc6ee5c95..d5a6b98373 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -16,6 +16,7 @@ const getTransaction = require('./ledger/transaction'); const getTransactions = require('./ledger/transactions'); const getTrustlines = require('./ledger/trustlines'); const getBalances = require('./ledger/balances'); +const getBalanceSheet = require('./ledger/balance-sheet'); const getPaths = require('./ledger/pathfind'); const getOrders = require('./ledger/orders'); const getOrderbook = require('./ledger/orderbook'); @@ -66,6 +67,7 @@ _.assign(RippleAPI.prototype, { getTransactions, getTrustlines, getBalances, + getBalanceSheet, getPaths, getOrders, getOrderbook, diff --git a/src/api/ledger/balance-sheet.js b/src/api/ledger/balance-sheet.js new file mode 100644 index 0000000000..608ae5080d --- /dev/null +++ b/src/api/ledger/balance-sheet.js @@ -0,0 +1,68 @@ +'use strict'; + +const _ = require('lodash'); +const utils = require('./utils'); +const validate = utils.common.validate; +const composeAsync = utils.common.composeAsync; +const convertErrors = utils.common.convertErrors; + +function formatBalanceSheet(balanceSheet) { + const result = {}; + + if (!_.isUndefined(balanceSheet.balances)) { + result.balances = []; + _.forEach(balanceSheet.balances, (balances, counterparty) => { + _.forEach(balances, (balance) => { + result.balances.push(Object.assign({counterparty}, balance)); + }); + }); + } + if (!_.isUndefined(balanceSheet.assets)) { + result.assets = []; + _.forEach(balanceSheet.assets, (assets, counterparty) => { + _.forEach(assets, (balance) => { + result.assets.push(Object.assign({counterparty}, balance)); + }); + }); + } + if (!_.isUndefined(balanceSheet.obligations)) { + result.obligations = _.map(balanceSheet.obligations, (value, currency) => + ({currency, value})); + } + + return result; +} + +function getBalanceSheetAsync(address, options, callback) { + validate.address(address); + validate.getBalanceSheetOptions(options); + + const requestOptions = Object.assign({}, { + account: address, + strict: true, + hotwallet: options.excludeAddresses, + ledger: options.ledgerVersion + }); + + const requestCallback = composeAsync( + formatBalanceSheet, convertErrors(callback)); + + this.remote.getLedgerSequence((err, ledgerVersion) => { + if (err) { + callback(err); + return; + } + + if (_.isUndefined(requestOptions.ledger)) { + requestOptions.ledger = ledgerVersion; + } + + this.remote.requestGatewayBalances(requestOptions, requestCallback); + }); +} + +function getBalanceSheet(address: string, options = {}) { + return utils.promisify(getBalanceSheetAsync).call(this, address, options); +} + +module.exports = getBalanceSheet; diff --git a/src/core/remote.js b/src/core/remote.js index 7268c7ad2d..fff2fb9295 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -2280,6 +2280,29 @@ Remote.prototype.requestConnect = function(ip, port, callback) { return request; }; +Remote.prototype.requestGatewayBalances = function(options, callback) { + assert(_.isObject(options), 'Options missing'); + assert(options.account, 'Account missing'); + + const request = new Request(this, 'gateway_balances'); + + request.message.account = UInt160.json_rewrite(options.account); + + if (!_.isUndefined(options.hotwallet)) { + request.message.hotwallet = options.hotwallet; + } + if (!_.isUndefined(options.strict)) { + request.message.strict = options.strict; + } + if (!_.isUndefined(options.ledger)) { + request.selectLedger(options.ledger); + } + + request.callback(callback); + + return request; +}; + /** * Create a Transaction * diff --git a/test/api-test.js b/test/api-test.js index 16a8fc6aa0..53fc437051 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -29,7 +29,6 @@ const orderbook = { }; function checkResult(expected, schemaName, response) { - // console.log(JSON.stringify(response, null, 2)); assert.deepEqual(response, expected); if (schemaName) { schemaValidator.schemaValidate(schemaName, response); @@ -202,6 +201,11 @@ describe('RippleAPI', function() { _.partial(checkResult, responses.getBalances, 'getBalances')); }); + it('getBalanceSheet', function() { + return this.api.getBalanceSheet(address).then( + _.partial(checkResult, responses.getBalanceSheet, 'getBalanceSheet')); + }); + describe('getTransaction', () => { it('getTransaction - payment', function() { return this.api.getTransaction(hashes.VALID_TRANSACTION_HASH).then( diff --git a/test/fixtures/api/responses/get-balance-sheet.json b/test/fixtures/api/responses/get-balance-sheet.json new file mode 100644 index 0000000000..650f07c3fc --- /dev/null +++ b/test/fixtures/api/responses/get-balance-sheet.json @@ -0,0 +1,54 @@ +{ + "balances": [ + { + "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", + "currency": "EUR", + "value": "29826.1965999999" + }, + { + "counterparty": "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ", + "currency": "USD", + "value": "10.0" + }, + { + "counterparty": "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt", + "currency": "USD", + "value": "13857.70416" + } + ], + "assets": [ + { + "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", + "currency": "BTC", + "value": "5444166510000000e-26" + }, + { + "counterparty": "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH", + "currency": "USD", + "value": "100.0" + }, + { + "counterparty": "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6", + "currency": "BTC", + "value": "8700000000000000e-30" + } + ], + "obligations": [ + { + "currency": "BTC", + "value": "5908.324927635318" + }, + { + "currency": "EUR", + "value": "992471.7419793958" + }, + { + "currency": "GBP", + "value": "4991.38706013193" + }, + { + "currency": "USD", + "value": "1997134.20229482" + } + ] +} diff --git a/test/fixtures/api/responses/index.js b/test/fixtures/api/responses/index.js index 5080660ab2..0770fd6fb6 100644 --- a/test/fixtures/api/responses/index.js +++ b/test/fixtures/api/responses/index.js @@ -4,6 +4,7 @@ module.exports = { generateAddress: require('./generate-address.json'), getAccountInfo: require('./get-account-info.json'), getBalances: require('./get-balances.json'), + getBalanceSheet: require('./get-balance-sheet.json'), getOrderbook: require('./get-orderbook.json'), getOrders: require('./get-orders.json'), getPaths: { diff --git a/test/fixtures/api/rippled/gateway-balances.json b/test/fixtures/api/rippled/gateway-balances.json new file mode 100644 index 0000000000..26b5518d59 --- /dev/null +++ b/test/fixtures/api/rippled/gateway-balances.json @@ -0,0 +1,52 @@ +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "assets": { + "r9F6wk8HkXrgYWoJ7fsv4VrUBVoqDVtzkH": [ + { + "currency": "BTC", + "value": "5444166510000000e-26" + }, + { + "currency": "USD", + "value": "100.0" + } + ], + "rwmUaXsWtXU4Z843xSYwgt1is97bgY8yj6": [ + { + "currency": "BTC", + "value": "8700000000000000e-30" + } + ] + }, + "balances": { + "rKm4uWpg9tfwbVSeATv4KxDe6mpE9yPkgJ": [ + { + "currency": "EUR", + "value": "29826.1965999999" + }, + { + "currency": "USD", + "value": "10.0" + } + ], + "ra7JkEzrgeKHdzKgo4EUUVBnxggY4z37kt": [ + { + "currency": "USD", + "value": "13857.70416" + } + ] + }, + "obligations": { + "BTC": "5908.324927635318", + "EUR": "992471.7419793958", + "GBP": "4991.38706013193", + "USD": "1997134.20229482" + }, + "ledger_current_index": 9592219, + "validated": true + } +} diff --git a/test/fixtures/api/rippled/index.js b/test/fixtures/api/rippled/index.js index fdac6f4e58..a8ca70e46e 100644 --- a/test/fixtures/api/rippled/index.js +++ b/test/fixtures/api/rippled/index.js @@ -16,6 +16,7 @@ module.exports = { }, account_offers: require('./account-offers'), account_tx: require('./account-tx'), + gateway_balances: require('./gateway-balances'), book_offers: require('./book-offers'), server_info: require('./server-info'), server_info_error: require('./server-info-error'), diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 3db74eb6e3..ce5abc0ce9 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -283,5 +283,9 @@ module.exports = function(port) { setTimeout(() => conn.send(response), 20); }); + mock.on('request_gateway_balances', function(request, conn) { + conn.send(createResponse(request, fixtures.gateway_balances)); + }); + return mock; }; diff --git a/test/remote-test.js b/test/remote-test.js index f44c91e286..4307cf5cdb 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -1966,6 +1966,22 @@ describe('Remote', function() { }); }); + it('Construct gateway_balances request', function() { + const request = remote.requestGatewayBalances({ + account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', + hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', + strict: true + }); + + assert.deepEqual(request.message, { + command: 'gateway_balances', + id: undefined, + account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54', + hotwallet: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5', + strict: true + }); + }); + it('Construct Payment transaction', function() { const tx = remote.createTransaction('Payment', { account: TX_JSON.Account,