diff --git a/src/api/common/constants.js b/src/api/common/constants.js index 7c2f038d..d6f88c0a 100644 --- a/src/api/common/constants.js +++ b/src/api/common/constants.js @@ -1,44 +1,22 @@ 'use strict'; -const ripple = require('./core'); +const Transaction = require('./core').Transaction; +const flagIndices = Transaction.set_clear_flags.AccountSet; -const AccountRootFlags = { - PasswordSpent: { - name: 'password_spent', - value: ripple.Remote.flags.account_root.PasswordSpent - }, - RequireDestTag: { - name: 'require_destination_tag', - value: ripple.Remote.flags.account_root.RequireDestTag - }, - RequireAuth: { - name: 'require_authorization', - value: ripple.Remote.flags.account_root.RequireAuth - }, - DisallowXRP: { - name: 'disallow_xrp', - value: ripple.Remote.flags.account_root.DisallowXRP - }, - DisableMaster: { - name: 'disable_master', - value: ripple.Remote.flags.account_root.DisableMaster - }, - NoFreeze: { - name: 'no_freeze', - value: ripple.Remote.flags.account_root.NoFreeze - }, - GlobalFreeze: { - name: 'global_freeze', - value: ripple.Remote.flags.account_root.GlobalFreeze - }, - DefaultRipple: { - name: 'default_ripple', - value: ripple.Remote.flags.account_root.DefaultRipple - } +const AccountFlagIndices = { + requireDestinationTag: flagIndices.asfRequireDest, + requireAuthorization: flagIndices.asfRequireAuth, + disallowIncomingXRP: flagIndices.asfDisallowXRP, + disableMasterKey: flagIndices.asfDisableMaster, + enableTransactionIDTracking: flagIndices.asfAccountTxnID, + noFreeze: flagIndices.asfNoFreeze, + globalFreeze: flagIndices.asfGlobalFreeze, + defaultRipple: flagIndices.asfDefaultRipple }; -const AccountRootFields = { +const AccountFields = { Sequence: {name: 'sequence'}, - EmailHash: {name: 'emailHash', encoding: 'hex', length: 32, defaults: '0'}, + EmailHash: {name: 'emailHash', encoding: 'hex', + length: 32, defaults: '0'}, WalletLocator: {name: 'walletLocator', encoding: 'hex', length: 64, defaults: '0'}, WalletSize: {name: 'walletSize', defaults: 0}, @@ -48,52 +26,7 @@ const AccountRootFields = { Signers: {name: 'signers'} }; -const AccountSetIntFlags = { - noFreeze: ripple.Transaction.set_clear_flags.AccountSet.asfNoFreeze, - globalFreeze: ripple.Transaction.set_clear_flags.AccountSet.asfGlobalFreeze, - defaultRipple: ripple.Transaction.set_clear_flags.AccountSet.asfDefaultRipple -}; - -const AccountSetFlags = { - requireDestinationTag: {set: 'RequireDestTag', unset: 'OptionalDestTag'}, - requireAuthorization: {set: 'RequireAuth', unset: 'OptionalAuth'}, - disallowIncomingXRP: {set: 'DisallowXRP', unset: 'AllowXRP'} -}; - -const AccountSetResponseFlags = { - RequireDestTag: {name: 'require_destination_tag', - value: ripple.Transaction.flags.AccountSet.RequireDestTag}, - RequireAuth: {name: 'require_authorization', - value: ripple.Transaction.flags.AccountSet.RequireAuth}, - DisallowXRP: {name: 'disallow_xrp', - value: ripple.Transaction.flags.AccountSet.DisallowXRP} -}; - -const OfferCreateFlags = { - Passive: {name: 'passive', - value: ripple.Transaction.flags.OfferCreate.Passive}, - ImmediateOrCancel: {name: 'immediate_or_cancel', - value: ripple.Transaction.flags.OfferCreate.ImmediateOrCancel}, - FillOrKill: {name: 'fill_or_kill', - value: ripple.Transaction.flags.OfferCreate.FillOrKill}, - Sell: {name: 'sell', value: ripple.Transaction.flags.OfferCreate.Sell} -}; - -const TrustSetResponseFlags = { - NoRipple: {name: 'prevent_rippling', - value: ripple.Transaction.flags.TrustSet.NoRipple}, - SetFreeze: {name: 'account_trustline_frozen', - value: ripple.Transaction.flags.TrustSet.SetFreeze}, - SetAuth: {name: 'authorized', - value: ripple.Transaction.flags.TrustSet.SetAuth} -}; - module.exports = { - AccountRootFlags: AccountRootFlags, - AccountRootFields: AccountRootFields, - AccountSetIntFlags: AccountSetIntFlags, - AccountSetFlags: AccountSetFlags, - AccountSetResponseFlags: AccountSetResponseFlags, - OfferCreateFlags: OfferCreateFlags, - TrustSetResponseFlags: TrustSetResponseFlags + AccountFields, + AccountFlagIndices }; diff --git a/src/api/common/errors.js b/src/api/common/errors.js index d9264b25..53c07d97 100644 --- a/src/api/common/errors.js +++ b/src/api/common/errors.js @@ -51,6 +51,12 @@ function NotFoundError(message) { NotFoundError.prototype = new RippleError(); NotFoundError.prototype.name = 'NotFoundError'; +function MissingLedgerHistoryError(message) { + this.message = message; +} +MissingLedgerHistoryError.prototype = new RippleError(); +MissingLedgerHistoryError.prototype.name = 'MissingLedgerHistoryError'; + /** * Request timed out */ @@ -75,6 +81,7 @@ module.exports = { TransactionError: TransactionError, RippledNetworkError: RippledNetworkError, NotFoundError: NotFoundError, + MissingLedgerHistoryError: MissingLedgerHistoryError, TimeOutError: TimeOutError, ApiError: ApiError, RippleError: RippleError diff --git a/src/api/common/index.js b/src/api/common/index.js index f05a053f..f922e4fc 100644 --- a/src/api/common/index.js +++ b/src/api/common/index.js @@ -9,5 +9,6 @@ module.exports = { server: require('./server'), dropsToXrp: utils.dropsToXrp, xrpToDrops: utils.xrpToDrops, - convertAmount: utils.convertAmount + toRippledAmount: utils.toRippledAmount, + wrapCatch: utils.wrapCatch }; diff --git a/src/api/common/schemas/settings.json b/src/api/common/schemas/settings.json index d5add67a..183e5159 100644 --- a/src/api/common/schemas/settings.json +++ b/src/api/common/schemas/settings.json @@ -17,8 +17,10 @@ "messageKey": {"type": "string"}, "domain": {"type": "string"}, "transferRate": {"type": "integer"}, - "signers": {"type": "string"} + "signers": {"type": "string"}, + "regularKey": {"$ref": "address"} }, "minProperties": 1, + "maxProperties": 1, "additionalProperties": false } diff --git a/src/api/common/schemas/trustline.json b/src/api/common/schemas/trustline.json index ebe12b6d..fe5a4965 100644 --- a/src/api/common/schemas/trustline.json +++ b/src/api/common/schemas/trustline.json @@ -9,6 +9,7 @@ "qualityIn": {"$ref": "quality"}, "qualityOut": {"$ref": "quality"}, "allowRippling": {"type": "boolean"}, + "authorized": {"type": "boolean"}, "frozen": {"type": "boolean"} }, "required": ["currency", "counterparty", "limit"], diff --git a/src/api/common/utils.js b/src/api/common/utils.js index 2af920b5..2ea76bd5 100644 --- a/src/api/common/utils.js +++ b/src/api/common/utils.js @@ -9,7 +9,7 @@ function xrpToDrops(xrp) { return (new BigNumber(xrp)).times(1000000.0).floor().toString(); } -function convertAmount(amount) { +function toRippledAmount(amount) { if (amount.currency === 'XRP') { return xrpToDrops(amount.value); } @@ -20,8 +20,20 @@ function convertAmount(amount) { }; } +function wrapCatch(asyncFunction: () => void): () => void { + return function() { + try { + asyncFunction.apply(this, arguments); + } catch (error) { + const callback = arguments[arguments.length - 1]; + callback(error); + } + }; +} + module.exports = { - dropsToXrp: dropsToXrp, - xrpToDrops: xrpToDrops, - convertAmount: convertAmount + dropsToXrp, + xrpToDrops, + toRippledAmount, + wrapCatch }; diff --git a/src/api/common/validate.js b/src/api/common/validate.js index c6de30a0..c1909696 100644 --- a/src/api/common/validate.js +++ b/src/api/common/validate.js @@ -25,9 +25,10 @@ function validateAddressAndSecret(obj) { } function validateLedgerRange(options) { - if (_.isUndefined(options.minLedger) && _.isUndefined(options.maxLedger)) { - if (Number(options.minLedger) > Number(options.maxLedger)) { - throw error('minLedger must not be greater than maxLedger'); + if (!_.isUndefined(options.minLedgerVersion) + && !_.isUndefined(options.maxLedgerVersion)) { + if (Number(options.minLedgerVersion) > Number(options.maxLedgerVersion)) { + throw error('minLedgerVersion must not be greater than maxLedgerVersion'); } } } diff --git a/src/api/ledger/notifications.js b/src/api/ledger/notifications.js index fbb2f355..9bd93285 100644 --- a/src/api/ledger/notifications.js +++ b/src/api/ledger/notifications.js @@ -3,8 +3,8 @@ const _ = require('lodash'); const async = require('async'); const transactions = require('./transactions'); -const NotificationParser = require('./notification_parser.js'); -const utils = require('./utils.js'); +const NotificationParser = require('./parse/notification'); +const utils = require('./utils'); const validate = utils.common.validate; const server = utils.common.server; diff --git a/src/api/ledger/orders.js b/src/api/ledger/orders.js index fb54906a..3043d800 100644 --- a/src/api/ledger/orders.js +++ b/src/api/ledger/orders.js @@ -4,11 +4,11 @@ const _ = require('lodash'); const bignum = require('bignumber.js'); const asyncify = require('simple-asyncify'); -const TxToRestConverter = require('./tx-to-rest-converter.js'); const utils = require('./utils'); const ripple = utils.common.core; const errors = utils.common.errors; const validate = utils.common.validate; +const parseTransaction = require('./parse/transaction'); const DefaultPageLimit = 200; @@ -302,18 +302,14 @@ function getOrder(account, identifier, callback) { }); txRequest.once('error', callback); - txRequest.once('transaction', function(response) { - if (response.TransactionType !== 'OfferCreate' - && response.TransactionType !== 'OfferCancel') { + txRequest.once('transaction', function(tx) { + if (tx.TransactionType !== 'OfferCreate' + && tx.TransactionType !== 'OfferCancel') { callback(new errors.InvalidRequestError('Invalid parameter: identifier. ' + 'The transaction corresponding to the given identifier ' + 'is not an order')); } else { - const options = { - account: account, - identifier: identifier - }; - asyncify(TxToRestConverter.parseOrderFromTx)(response, options, callback); + asyncify(parseTransaction)(tx, callback); } }); txRequest.request(); diff --git a/src/api/ledger/parse/amount.js b/src/api/ledger/parse/amount.js new file mode 100644 index 00000000..089c27e5 --- /dev/null +++ b/src/api/ledger/parse/amount.js @@ -0,0 +1,18 @@ +'use strict'; +const utils = require('./utils'); + +function parseAmount(amount) { + if (typeof amount === 'string') { + return { + currency: 'XRP', + value: utils.dropsToXrp(amount) + }; + } + return { + currency: amount.currency, + value: amount.value, + counterparty: amount.issuer || amount.counterparty + }; +} + +module.exports = parseAmount; diff --git a/src/api/ledger/parse/cancellation.js b/src/api/ledger/parse/cancellation.js new file mode 100644 index 00000000..1ce6c984 --- /dev/null +++ b/src/api/ledger/parse/cancellation.js @@ -0,0 +1,11 @@ +'use strict'; +const assert = require('assert'); + +function parseOrderCancellation(tx) { + assert(tx.TransactionType === 'OfferCancel'); + return { + orderSequence: tx.OfferSequence + }; +} + +module.exports = parseOrderCancellation; diff --git a/src/api/ledger/parse/fields.js b/src/api/ledger/parse/fields.js new file mode 100644 index 00000000..685bef6b --- /dev/null +++ b/src/api/ledger/parse/fields.js @@ -0,0 +1,23 @@ +'use strict'; +const AccountFields = require('./utils').constants.AccountFields; + +function parseField(info, value) { + if (info.encoding === 'hex' && !info.length) { + return new Buffer(value, 'hex').toString('ascii'); + } + return value; +} + +function parseFields(data) { + const settings = {}; + for (const fieldName in AccountFields) { + const fieldValue = data[fieldName]; + if (fieldValue !== undefined) { + const info = AccountFields[fieldName]; + settings[info.name] = parseField(info, fieldValue); + } + } + return settings; +} + +module.exports = parseFields; diff --git a/src/api/ledger/notification_parser.js b/src/api/ledger/parse/notification.js similarity index 98% rename from src/api/ledger/notification_parser.js rename to src/api/ledger/parse/notification.js index a85d2abd..fec685b8 100644 --- a/src/api/ledger/notification_parser.js +++ b/src/api/ledger/parse/notification.js @@ -1,6 +1,6 @@ /* eslint-disable valid-jsdoc */ 'use strict'; -const ripple = require('./utils').common.core; +const ripple = require('../utils').common.core; /** * Convert a Ripple transaction in the JSON format, diff --git a/src/api/ledger/parse/order.js b/src/api/ledger/parse/order.js new file mode 100644 index 00000000..fcc41935 --- /dev/null +++ b/src/api/ledger/parse/order.js @@ -0,0 +1,26 @@ +'use strict'; +const assert = require('assert'); +const utils = require('./utils'); +const parseAmount = require('./amount'); +const flags = utils.core.Transaction.flags.OfferCreate; + +function parseOrder(tx) { + assert(tx.TransactionType === 'OfferCreate'); + + const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'; + const takerGets = parseAmount(tx.TakerGets); + const takerPays = parseAmount(tx.TakerPays); + const quantity = (direction === 'buy') ? takerPays : takerGets; + const totalPrice = (direction === 'buy') ? takerGets : takerPays; + + return { + direction: direction, + quantity: quantity, + totalPrice: totalPrice, + passive: (tx.Flags & flags.Passive) !== 0, + immediateOrCancel: (tx.Flags & flags.ImmediateOrCancel) !== 0, + fillOrKill: (tx.Flags & flags.FillOrKill) !== 0 + }; +} + +module.exports = parseOrder; diff --git a/src/api/ledger/parse/pathfind.js b/src/api/ledger/parse/pathfind.js new file mode 100644 index 00000000..7ccc37bf --- /dev/null +++ b/src/api/ledger/parse/pathfind.js @@ -0,0 +1,22 @@ +'use strict'; +const parseAmount = require('./amount'); + +function parsePathfind(sourceAddress, destinationAmount, pathfindResult) { + return pathfindResult.alternatives.map(function(alternative) { + return { + source: { + address: sourceAddress, + amount: parseAmount(alternative.source_amount) + }, + destination: { + address: pathfindResult.destination_account, + amount: destinationAmount + }, + paths: JSON.stringify(alternative.paths_computed), + allowPartialPayment: false, + noDirectRipple: false + }; + }); +} + +module.exports = parsePathfind; diff --git a/src/api/ledger/parse/payment.js b/src/api/ledger/parse/payment.js new file mode 100644 index 00000000..11213b7c --- /dev/null +++ b/src/api/ledger/parse/payment.js @@ -0,0 +1,48 @@ +'use strict'; +const assert = require('assert'); +const utils = require('./utils'); +const parseAmount = require('./amount'); +const Transaction = utils.core.Transaction; + +function isPartialPayment(tx) { + return (tx.Flags & Transaction.flags.Payment.PartialPayment) !== 0; +} + +function isNoDirectRipple(tx) { + return (tx.Flags & Transaction.flags.Payment.NoRippleDirect) !== 0; +} + +function parsePaymentMemos(tx) { + if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) { + return undefined; + } + return tx.Memos.map((m) => m.Memo); +} + +function parsePayment(tx) { + assert(tx.TransactionType === 'Payment'); + + const source = { + address: tx.Account, + amount: parseAmount(tx.SendMax || tx.Amount), + tag: tx.SourceTag + }; + + const destination = { + address: tx.Destination, + amount: parseAmount(tx.Amount), + tag: tx.DestinationTag + }; + + return { + source: utils.removeUndefined(source), + destination: utils.removeUndefined(destination), + memos: parsePaymentMemos(tx), + invoiceID: tx.InvoiceID, + paths: JSON.stringify(tx.Paths || []), + allowPartialPayment: isPartialPayment(tx), + noDirectRipple: isNoDirectRipple(tx) + }; +} + +module.exports = parsePayment; diff --git a/src/api/ledger/parse/settings.js b/src/api/ledger/parse/settings.js new file mode 100644 index 00000000..1d27e435 --- /dev/null +++ b/src/api/ledger/parse/settings.js @@ -0,0 +1,27 @@ +'use strict'; +const _ = require('lodash'); +const assert = require('assert'); +const AccountSetFlags = require('./utils').constants.AccountSetFlags; +const parseFields = require('./fields'); + +function getName(flagNumber) { + return _.findKey(AccountSetFlags, (v) => v === flagNumber); +} + +function parseSettings(tx) { + const txType = tx.TransactionType; + assert(txType === 'AccountSet' || txType === 'SetRegularKey'); + const settings = {}; + if (tx.SetFlag) { + settings[getName(tx.SetFlag)] = true; + } + if (tx.ClearFlag) { + settings[getName(tx.ClearFlag)] = false; + } + if (tx.RegularKey) { + settings.regularKey = tx.RegularKey; + } + return _.assign(settings, parseFields(tx)); +} + +module.exports = parseSettings; diff --git a/src/api/ledger/parse/transaction.js b/src/api/ledger/parse/transaction.js new file mode 100644 index 00000000..f086942f --- /dev/null +++ b/src/api/ledger/parse/transaction.js @@ -0,0 +1,42 @@ +'use strict'; +const assert = require('assert'); +const utils = require('./utils'); +const parsePayment = require('./payment'); +const parseTrustline = require('./trustline'); +const parseOrder = require('./order'); +const parseOrderCancellation = require('./cancellation'); +const parseSettings = require('./settings'); + +function parseTransactionType(type) { + const mapping = { + Payment: 'payment', + TrustSet: 'trustline', + OfferCreate: 'order', + OfferCancel: 'orderCancellation', + AccountSet: 'settings', + SetRegularKey: 'settings' + }; + return mapping[type] || null; +} + +function parseTransaction(tx) { + const type = parseTransactionType(tx.TransactionType); + const mapping = { + 'payment': parsePayment, + 'trustline': parseTrustline, + 'order': parseOrder, + 'orderCancellation': parseOrderCancellation, + 'settings': parseSettings + }; + const parser = mapping[type]; + assert(parser !== undefined, 'Unrecognized transaction type'); + const specification = parser(tx); + return utils.removeUndefined({ + type: type, + address: tx.Account, + specification: utils.removeUndefined(specification), + outcome: utils.removeUndefined(utils.parseOutcome(tx)) + }); +} + +module.exports = parseTransaction; diff --git a/src/api/ledger/parse/trustline.js b/src/api/ledger/parse/trustline.js new file mode 100644 index 00000000..140bada9 --- /dev/null +++ b/src/api/ledger/parse/trustline.js @@ -0,0 +1,21 @@ +'use strict'; +const assert = require('assert'); +const utils = require('./utils'); +const flags = utils.core.Transaction.flags.TrustSet; + +function parseTrustline(tx) { + assert(tx.TransactionType === 'TrustSet'); + + return { + limit: tx.LimitAmount.value, + currency: tx.LimitAmount.currency, + counterparty: tx.LimitAmount.issuer, + qualityIn: tx.QualityIn, + qualityOut: tx.QualityOut, + allowRippling: tx.Flags & flags.NoRipple === 0, + frozen: tx.Flags & flags.SetFreeze !== 0, + authorized: tx.Flags & flags.SetAuth !== 0 + }; +} + +module.exports = parseTrustline; diff --git a/src/api/ledger/parse/utils.js b/src/api/ledger/parse/utils.js new file mode 100644 index 00000000..87dd3ee4 --- /dev/null +++ b/src/api/ledger/parse/utils.js @@ -0,0 +1,40 @@ +'use strict'; +const _ = require('lodash'); +const transactionParser = require('ripple-lib-transactionparser'); +const toTimestamp = require('../../../core/utils').toTimestamp; +const utils = require('../utils'); + +function parseTimestamp(tx) { + return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined; +} + +function removeUndefined(obj) { + return obj ? _.omit(obj, _.isUndefined) : obj; +} + +function parseOutcome(tx) { + if (!tx.validated) { + return undefined; + } + + const balanceChanges = transactionParser.parseBalanceChanges(tx.meta); + const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta); + + return { + result: tx.meta.TransactionResult, + timestamp: parseTimestamp(tx), + fee: utils.common.dropsToXrp(tx.Fee), + balanceChanges: balanceChanges, + orderbookChanges: orderbookChanges, + ledgerVersion: tx.ledger_index, + sequence: tx.Sequence + }; +} + +module.exports = { + parseOutcome, + removeUndefined, + dropsToXrp: utils.common.dropsToXrp, + constants: utils.common.constants, + core: utils.common.core +}; diff --git a/src/api/ledger/payments.js b/src/api/ledger/payments.js index 61b75424..377f9440 100644 --- a/src/api/ledger/payments.js +++ b/src/api/ledger/payments.js @@ -6,9 +6,10 @@ const async = require('async'); const asyncify = require('simple-asyncify'); const bignum = require('bignumber.js'); const transactions = require('./transactions'); -const TxToRestConverter = require('./tx-to-rest-converter.js'); const utils = require('./utils'); const validate = utils.common.validate; +const parseTransaction = require('./parse/transaction'); +const parsePathfind = require('./parse/pathfind'); const ValidationError = utils.common.errors.ValidationError; const NotFoundError = utils.common.errors.NotFoundError; @@ -32,16 +33,7 @@ function formatPaymentHelper(account, txJSON) { throw new ValidationError('Not a payment. The transaction ' + 'corresponding to the given identifier is not a payment.'); } - const metadata = { - hash: txJSON.hash || '', - ledger: String(!_.isUndefined(txJSON.inLedger) ? - txJSON.inLedger : txJSON.ledger_index), - state: txJSON.validated === true ? 'validated' : 'pending' - }; - const message = {tx_json: txJSON}; - const meta = txJSON.meta; - const parsed = TxToRestConverter.parsePaymentFromTx(account, message, meta); - return _.assign({payment: parsed.payment}, metadata); + return parseTransaction(txJSON); } /** @@ -153,7 +145,7 @@ function getPathFind(pathfind, callback) { const pathfindParams = { src_account: pathfind.source.address, dst_account: pathfind.destination.address, - dst_amount: utils.common.convertAmount(pathfind.destination.amount) + dst_amount: utils.common.toRippledAmount(pathfind.destination.amount) }; if (typeof pathfindParams.dst_amount === 'object' && !pathfindParams.dst_amount.issuer) { @@ -227,7 +219,7 @@ function getPathFind(pathfind, callback) { function formatPath(pathfindResults) { const alternatives = pathfindResults.alternatives; if (alternatives && alternatives.length > 0) { - return TxToRestConverter.parsePaymentsFromPathFind(pathfindResults); + return parsePathfind(pathfindResults); } if (pathfindResults.destination_currencies.indexOf( pathfind.destination.amount.currency) === -1) { diff --git a/src/api/ledger/settings.js b/src/api/ledger/settings.js index 8ba26f34..71f5934b 100644 --- a/src/api/ledger/settings.js +++ b/src/api/ledger/settings.js @@ -1,33 +1,31 @@ -/* eslint-disable valid-jsdoc */ 'use strict'; const _ = require('lodash'); -const TxToRestConverter = require('./tx-to-rest-converter.js'); const utils = require('./utils'); +const flags = utils.common.core.Remote.flags.account_root; const validate = utils.common.validate; -const constants = utils.common.constants; +const parseFields = require('./parse/fields'); -function parseFieldsFromResponse(responseBody, fields) { - let parsedBody = {}; +const AccountFlags = { + passwordSpent: flags.PasswordSpent, + requireDestinationTag: flags.RequireDestTag, + requireAuthorization: flags.RequireAuth, + disallowIncomingXRP: flags.DisallowXRP, + disableMasterKey: flags.DisableMaster, + noFreeze: flags.NoFreeze, + globalFreeze: flags.GlobalFreeze, + defaultRipple: flags.DefaultRipple +}; - for (let fieldName in fields) { - const field = fields[fieldName]; - let value = responseBody[fieldName] || ''; - if (field.encoding === 'hex' && !field.length) { - value = new Buffer(value, 'hex').toString('ascii'); +function parseFlags(value) { + const settings = {}; + for (const flagName in AccountFlags) { + if (value & AccountFlags[flagName]) { + settings[flagName] = true; } - parsedBody[field.name] = value; } - - return parsedBody; + return settings; } -/** - * Retrieves account settings for a given account - * - * @url - * @param {String} request.params.account - * - */ function getSettings(account, callback) { validate.address(account); @@ -35,24 +33,11 @@ function getSettings(account, callback) { if (error) { return callback(error); } - const data = info.account_data; - const settings = { - account: data.Account, - transfer_rate: '0' - }; - - // Attach account flags - _.extend(settings, TxToRestConverter.parseFlagsFromResponse(data.Flags, - constants.AccountRootFlags)); - - // Attach account fields - _.extend(settings, parseFieldsFromResponse(data, - constants.AccountRootFields)); - - settings.transaction_sequence = String(settings.transaction_sequence); - - callback(null, {settings: settings}); + const parsedFlags = parseFlags(data.Flags); + const parsedFields = parseFields(data); + const settings = _.assign({}, parsedFlags, parsedFields); + callback(null, settings); }); } diff --git a/src/api/ledger/transactions.js b/src/api/ledger/transactions.js index d8d95b68..995d34ad 100644 --- a/src/api/ledger/transactions.js +++ b/src/api/ledger/transactions.js @@ -1,102 +1,77 @@ /* eslint-disable valid-jsdoc */ 'use strict'; const _ = require('lodash'); -const assert = require('assert'); const async = require('async'); const utils = require('./utils'); +const parseTransaction = require('./parse/transaction'); const validate = utils.common.validate; const errors = utils.common.errors; const DEFAULT_RESULTS_PER_PAGE = 10; +const MIN_LEDGER_VERSION = 32570; // earlier versions have been completely lost -/** - * Retrieve a transaction from the Remote based on the account and hash - * - * Note that if any errors are encountered while executing this function - * they will be sent back to the client through the res. If the query is - * successful it will be passed to the callback function - * - * @global - * @param {Remote} remote - * - * @param {RippleAddress} account - * @param {Hex-encoded String|ASCII printable character String} identifier - * @param {Object} options - * @param {Function} callback - * - * @callback - * @param {Error} error - * @param {Transaction} transaction - */ -function getTransaction(api, account, identifier, requestOptions, callback) { - try { - assert.strictEqual(typeof requestOptions, 'object'); - validate.address(account); - validate.identifier(identifier); - validate.options(requestOptions); - } catch(err) { - return callback(err); +function hasCompleteLedgerRange(remote, options) { + const minLedgerVersion = options.minLedgerVersion || MIN_LEDGER_VERSION; + const maxLedgerVersion = options.maxLedgerVersion + || remote.getLedgerSequence(); + for (let i = minLedgerVersion; i <= maxLedgerVersion; i++) { + if (!remote.getServer().hasLedger(i)) { // TODO: optimize this + return false; + } + } + return true; +} + +function attachTransactionDate(remote, tx, callback) { + if (tx.date) { + callback(null, tx); + return; + } + if (!tx.ledger_index) { + callback(new errors.ApiError('ledger_index not found in tx')); + return; } - const options = {}; - options.hash = identifier; + remote.requestLedger(tx.ledger_index, (error, data) => { + if (error) { + callback(new errors.NotFoundError('Transaction ledger not found')); + } else if (typeof data.ledger.close_time === 'number') { + callback(null, _.assign({date: data.ledger.close_time, tx})); + } else { + callback(new errors.ApiError('Ledger missing close_time')); + } + }); +} - const isLedgerRangeRequest = !_.isUndefined(requestOptions.min_ledger) - && !_.isUndefined(requestOptions.max_ledger); +function isTransactionInRange(tx, options) { + return (!options.minLedgerVersion + || tx.ledger_index >= options.minLedgerVersion) + && (!options.maxLedgerVersion + || tx.ledger_index <= options.maxLedgerVersion); +} - if (isLedgerRangeRequest) { - const minLedger = Number(options.min_ledger); - const maxLedger = Number(options.max_ledger); - for (let i = minLedger; i <= maxLedger; i++) { - if (!api.remote.getServer().hasLedger(i)) { - return callback(new errors.NotFoundError('Ledger not found')); - } +function getTransaction(identifier, options, callback) { + validate.identifier(identifier); + validate.options(options); + + const remote = this.remote; + + function callbackWrapper(error, tx) { + if (error instanceof errors.NotFoundError + && !hasCompleteLedgerRange(remote, options)) { + callback(new errors.MissingLedgerHistoryError('Transaction not found,' + + ' but the server\'s ledger history is incomplete')); + } else if (!error && !isTransactionInRange(tx, options)) { + callback(new errors.NotFoundError('Transaction not found')); + } else { + callback(error, parseTransaction(tx)); } } - function queryTransaction(async_callback) { - api.remote.requestTx({hash: options.hash}, async_callback); - } - - function checkIfRelatedToAccount(transaction, async_callback) { - if (options.account) { - const transactionString = JSON.stringify(transaction); - const account_regex = new RegExp(options.account); - if (!account_regex.test(transactionString)) { - return async_callback(new errors.InvalidRequestError( - 'Transaction specified did not affect the given account')); - } - } - async_callback(null, transaction); - } - - function attachDate(transaction, async_callback) { - if (!transaction || transaction.date || !transaction.ledger_index) { - return async_callback(null, transaction); - } - - api.remote.requestLedger(transaction.ledger_index, - function(error, ledgerRequest) { - if (error) { - return async_callback(new errors.NotFoundError( - 'Transaction ledger not found')); - } - - if (typeof ledgerRequest.ledger.close_time === 'number') { - transaction.date = ledgerRequest.ledger.close_time; - } - - async_callback(null, transaction); - }); - } - - const steps = [ - queryTransaction, - checkIfRelatedToAccount, - attachDate - ]; - - async.waterfall(steps, callback); + async.waterfall([ + _.partial(remote.requestTx.bind(remote), {hash: identifier}), + _.partial(attachTransactionDate, remote) + ], callbackWrapper); } /** @@ -132,7 +107,7 @@ function getAccountTx(api, options, callback) { if (error) { return callback(error); } - let transactions = []; + const transactions = []; account_tx_results.transactions.forEach(function(tx_entry) { if (!tx_entry.validated) { return; @@ -321,6 +296,6 @@ module.exports = { DEFAULT_RESULTS_PER_PAGE: DEFAULT_RESULTS_PER_PAGE, NUM_TRANSACTION_TYPES: 5, DEFAULT_LEDGER_BUFFER: 3, - getTransaction: getTransaction, + getTransaction: utils.wrapCatch(getTransaction), getAccountTransactions: getAccountTransactions }; diff --git a/src/api/ledger/tx-to-rest-converter.js b/src/api/ledger/tx-to-rest-converter.js deleted file mode 100644 index cf805ce3..00000000 --- a/src/api/ledger/tx-to-rest-converter.js +++ /dev/null @@ -1,369 +0,0 @@ -/* eslint-disable valid-jsdoc */ -'use strict'; -const _ = require('lodash'); -const transactionParser = require('ripple-lib-transactionparser'); -const utils = require('./utils'); -const ripple = utils.common.core; -const constants = utils.common.constants; -const parseBalanceChanges = transactionParser.parseBalanceChanges; -const parseOrderBookChanges = transactionParser.parseOrderBookChanges; -const dropsToXrp = utils.common.dropsToXrp; - -// This is just to support the legacy naming of "counterparty", this -// function should be removed when "issuer" is eliminated -function renameCounterpartyToIssuerInOrderChanges(orderChanges) { - return _.mapValues(orderChanges, function(changes) { - return _.map(changes, function(change) { - return utils.renameCounterpartyToIssuerInOrder(change); - }); - }); -} - -function renameCounterpartyToIssuerInBalanceChanges(balanceChanges) { - return _.mapValues(balanceChanges, function(changes) { - return _.map(changes, function(change) { - return utils.renameCounterpartyToIssuer(change); - }); - }); -} - -/** - * Helper that parses bit flags from ripple response - * - * @param {Number} responseFlags - Integer flag on the ripple response - * @param {Object} flags - Object with parameter name and bit flag value pairs - * - * @returns {Object} parsedFlags - Object with parameter name and boolean - * flags depending on response flag - */ -function parseFlagsFromResponse(responseFlags, flags) { - const parsedFlags = {}; - - for (let flagName in flags) { - const flag = flags[flagName]; - parsedFlags[flag.name] = Boolean(responseFlags & flag.value); - } - - return parsedFlags; -} - -/** - * Convert a transaction in rippled tx format - * to a ripple-rest payment - * - * @param {transaction} tx - * @param {Function} callback - * @param {Object} options - * - * @callback - * @param {Error} error - * @param {Object} payment - */ - -function isPartialPayment(tx) { - return (tx.Flags & ripple.Transaction.flags.Payment.PartialPayment) !== 0; -} - -function isNoDirectRipple(tx) { - return (tx.Flags & ripple.Transaction.flags.Payment.NoRippleDirect) !== 0; -} - -function convertAmount(amount) { - if (typeof amount === 'string') { - return { - value: dropsToXrp(amount), - currency: 'XRP', - issuer: '' - }; - } - return amount; -} - -function parsePaymentMeta(account, tx, meta) { - if (_.isUndefined(meta) || _.isEmpty(meta)) { - return {}; - } - if (meta.TransactionResult === 'tejSecretInvalid') { - throw new Error('Invalid secret provided.'); - } - - const balanceChanges = renameCounterpartyToIssuerInBalanceChanges( - parseBalanceChanges(meta)); - - const order_changes = renameCounterpartyToIssuerInOrderChanges( - parseOrderBookChanges(meta))[account]; - - const partialPayment = (isPartialPayment(tx) && meta.DeliveredAmount) ? { - destination_amount_submitted: convertAmount(tx.Amount), - source_amount_submitted: convertAmount(tx.SendMax || tx.Amount) - } : {}; - - return _.assign({ - result: meta.TransactionResult, - balance_changes: balanceChanges[account] || [], - source_balance_changes: balanceChanges[tx.Account] || [], - destination_balance_changes: balanceChanges[tx.Destination] || [], - order_changes: order_changes || [] - }, partialPayment); -} - -function parsePaymentFromTx(account, message, meta) { - if (!account) { - throw new Error('Internal Error. must supply options.account'); - } - - const tx = message.tx_json; - if (tx.TransactionType !== 'Payment') { - throw new Error('Not a payment. The transaction corresponding to ' - + 'the given identifier is not a payment.'); - } - - let amount; - // if there is a DeliveredAmount we should use it over Amount there should - // always be a DeliveredAmount if the partial payment flag is set. also - // there shouldn't be a DeliveredAmount if there's no partial payment flag - if (isPartialPayment(tx) && meta && meta.DeliveredAmount) { - amount = meta.DeliveredAmount; - } else { - amount = tx.Amount; - } - - const source_amount = utils.parseCurrencyAmount(tx.SendMax || amount, true); - const destination_amount = utils.parseCurrencyAmount(amount, true); - - const payment = { - // User supplied - source_account: tx.Account, - source_tag: (tx.SourceTag ? '' + tx.SourceTag : ''), - source_amount: source_amount, - source_slippage: '0', // TODO: why is this hard-coded? - destination_account: tx.Destination, - destination_tag: (tx.DestinationTag ? '' + tx.DestinationTag : ''), - destination_amount: destination_amount, - // Advanced options - invoice_id: tx.InvoiceID || '', - paths: JSON.stringify(tx.Paths || []), - no_direct_ripple: isNoDirectRipple(tx), - partial_payment: isPartialPayment(tx), - // TODO: Update to use `unaffected` when perspective account in URI - // is not affected - direction: (account === tx.Account ? 'outgoing' : - (account === tx.Destination ? 'incoming' : 'passthrough')), - timestamp: (tx.date - ? new Date(ripple.utils.toTimestamp(tx.date)).toISOString() : ''), - fee: dropsToXrp(tx.Fee) || '' - }; - - if (Array.isArray(tx.Memos) && tx.Memos.length > 0) { - payment.memos = []; - for (let m = 0; m < tx.Memos.length; m++) { - payment.memos.push(tx.Memos[m].Memo); - } - } - - const fullPayment = _.assign(payment, parsePaymentMeta(account, tx, meta)); - return _.assign({payment: fullPayment}, meta); -} - -/** - * Convert an OfferCreate or OfferCancel transaction in rippled tx format - * to a ripple-rest order_change - * - * @param {Object} tx - * @param {Object} options - * @param {String} options.account - The account to use as perspective when - * parsing the transaction. - * - * @returns {Promise.} - resolves to a parsed OrderChange - * transaction or an Error - */ - -function parseOrderFromTx(tx, options) { - if (!options.account) { - throw new Error('Internal Error. must supply options.account'); - } - if (tx.TransactionType !== 'OfferCreate' - && tx.TransactionType !== 'OfferCancel') { - throw new Error('Invalid parameter: identifier. The transaction ' - + 'corresponding to the given identifier is not an order'); - } - if (tx.meta !== undefined && tx.meta.TransactionResult !== undefined) { - if (tx.meta.TransactionResult === 'tejSecretInvalid') { - throw new Error('Invalid secret provided.'); - } - } - - let order; - const flags = parseFlagsFromResponse(tx.flags, constants.OfferCreateFlags); - const action = tx.TransactionType === 'OfferCreate' - ? 'order_create' : 'order_cancel'; - const balance_changes = tx.meta - ? parseBalanceChanges(tx.meta)[options.account] || [] : []; - const timestamp = tx.date - ? new Date(ripple.utils.toTimestamp(tx.date)).toISOString() : ''; - const order_changes = tx.meta ? - parseOrderBookChanges(tx.meta)[options.account] : []; - - let direction; - if (options.account === tx.Account) { - direction = 'outgoing'; - } else if (balance_changes.length && order_changes.length) { - direction = 'incoming'; - } else { - direction = 'passthrough'; - } - - if (action === 'order_create') { - order = { - account: tx.Account, - taker_pays: utils.parseCurrencyAmount(tx.TakerPays), - taker_gets: utils.parseCurrencyAmount(tx.TakerGets), - passive: flags.passive, - immediate_or_cancel: flags.immediate_or_cancel, - fill_or_kill: flags.fill_or_kill, - type: flags.sell ? 'sell' : 'buy', - sequence: tx.Sequence - }; - } else { - order = { - account: tx.Account, - type: 'cancel', - sequence: tx.Sequence, - cancel_sequence: tx.OfferSequence - }; - } - - return { - hash: tx.hash, - ledger: tx.ledger_index, - validated: tx.validated, - timestamp: timestamp, - fee: dropsToXrp(tx.Fee), - action: action, - direction: direction, - order: order, - balance_changes: balance_changes, - order_changes: order_changes || [] - }; -} - -/** - * Convert the pathfind results returned from rippled into an - * array of payments in the ripple-rest format. The client should be - * able to submit any of the payments in the array back to ripple-rest. - * - * @param {rippled Pathfind results} pathfindResults - * @param {Amount} options.destination_amount Since this is not returned by - * rippled in the pathfind results it can either be added - * to the results or included in the options here - * @param {RippleAddress} options.source_account Since this is not returned - * by rippled in the pathfind results it can either be added - * to the results or included in the options here - * - * @returns {Array of Payments} payments - */ -function parsePaymentsFromPathFind(pathfindResults) { - return pathfindResults.alternatives.map(function(alternative) { - return { - source_account: pathfindResults.source_account, - source_tag: '', - source_amount: (typeof alternative.source_amount === 'string' ? - { - value: dropsToXrp(alternative.source_amount), - currency: 'XRP', - issuer: '' - } : - { - value: alternative.source_amount.value, - currency: alternative.source_amount.currency, - issuer: (typeof alternative.source_amount.issuer !== 'string' - || alternative.source_amount.issuer === pathfindResults.source_account - ? '' : alternative.source_amount.issuer) - }), - source_slippage: '0', - destination_account: pathfindResults.destination_account, - destination_tag: '', - destination_amount: ( - typeof pathfindResults.destination_amount === 'string' ? - { - value: dropsToXrp(pathfindResults.destination_amount), - currency: 'XRP', - issuer: '' - } : - { - value: pathfindResults.destination_amount.value, - currency: pathfindResults.destination_amount.currency, - issuer: pathfindResults.destination_amount.issuer - }), - invoice_id: '', - paths: JSON.stringify(alternative.paths_computed), - partial_payment: false, - no_direct_ripple: false - }; - }); -} - -function parseOrderCancellationResponse(message, meta) { - const order = { - account: message.tx_json.Account, - fee: dropsToXrp(message.tx_json.Fee), - offer_sequence: message.tx_json.OfferSequence, - sequence: message.tx_json.Sequence - }; - return _.assign({order: order}, meta); -} - -function parseOrderResponse(message, meta) { - const order = { - account: message.tx_json.Account, - taker_gets: utils.parseCurrencyAmount(message.tx_json.TakerGets), - taker_pays: utils.parseCurrencyAmount(message.tx_json.TakerPays), - fee: dropsToXrp(message.tx_json.Fee), - type: (message.tx_json.Flags & ripple.Transaction.flags.OfferCreate.Sell) - > 0 ? 'sell' : 'buy', - sequence: message.tx_json.Sequence - }; - return _.assign({order: order}, meta); -} - -function parseTrustLineResponse(message, meta) { - const limit = message.tx_json.LimitAmount; - const parsedFlags = parseFlagsFromResponse(message.tx_json.Flags, - constants.TrustSetResponseFlags); - const trustline = { - account: message.tx_json.Account, - limit: limit.value, - currency: limit.currency, - counterparty: limit.issuer, - account_allows_rippling: !parsedFlags.prevent_rippling, - account_trustline_frozen: parsedFlags.account_trustline_frozen, - authorized: parsedFlags.authorized ? parsedFlags.authorized : undefined - }; - return _.assign({trustline: trustline}, meta); -} - -function parseSettingsResponse(settings, message, meta) { - const _settings = {}; - for (let flagName in constants.AccountSetIntFlags) { - _settings[flagName] = settings[flagName]; - } - - for (let fieldName in constants.AccountRootFields) { - _settings[fieldName] = settings[fieldName]; - } - - _.assign(_settings, parseFlagsFromResponse(message.tx_json.Flags, - constants.AccountSetResponseFlags)); - return _.assign({settings: _settings}, meta); -} - -module.exports = { - parsePaymentFromTx: parsePaymentFromTx, - parsePaymentsFromPathFind: parsePaymentsFromPathFind, - parseOrderFromTx: parseOrderFromTx, - parseCancelOrderFromTx: parseOrderCancellationResponse, - parseSubmitOrderFromTx: parseOrderResponse, - parseTrustResponseFromTx: parseTrustLineResponse, - parseSettingsResponseFromTx: parseSettingsResponse, - parseFlagsFromResponse: parseFlagsFromResponse -}; diff --git a/src/api/ledger/utils.js b/src/api/ledger/utils.js index 02bc1769..47fc83d5 100644 --- a/src/api/ledger/utils.js +++ b/src/api/ledger/utils.js @@ -43,30 +43,6 @@ function parseLedger(ledger) { return 'validated'; } -function parseCurrencyAmount(rippledAmount, useIssuer) { - const amount = {}; - - if (typeof rippledAmount === 'string') { - amount.currency = 'XRP'; - amount.value = common.dropsToXrp(rippledAmount); - if (useIssuer) { - amount.issuer = ''; - } else { - amount.counterparty = ''; - } - } else { - amount.currency = rippledAmount.currency; - amount.value = rippledAmount.value; - if (useIssuer) { - amount.issuer = rippledAmount.issuer; - } else { - amount.counterparty = rippledAmount.issuer; - } - } - - return amount; -} - function signum(num) { return (num === 0) ? 0 : (num > 0 ? 1 : -1); } @@ -121,11 +97,11 @@ function attachDate(api, baseTransactions, callback) { module.exports = { parseLedger: parseLedger, - parseCurrencyAmount: parseCurrencyAmount, compareTransactions: compareTransactions, renameCounterpartyToIssuer: renameCounterpartyToIssuer, renameCounterpartyToIssuerInOrder: renameCounterpartyToIssuerInOrder, attachDate: attachDate, + wrapCatch: common.wrapCatch, common: common }; diff --git a/src/api/transaction/payment.js b/src/api/transaction/payment.js index 65f794aa..dbaf9362 100644 --- a/src/api/transaction/payment.js +++ b/src/api/transaction/payment.js @@ -5,7 +5,7 @@ const BigNumber = require('bignumber.js'); const utils = require('./utils'); const ripple = utils.common.core; const validate = utils.common.validate; -const convertAmount = utils.common.convertAmount; +const toRippledAmount = utils.common.toRippledAmount; function isSendMaxAllowed(payment) { const srcAmt = payment.source.amount; @@ -58,7 +58,7 @@ function createPaymentTransaction(account, payment) { const transactionData = { from: payment.source.address, to: payment.destination.address, - amount: convertAmount(payment.destination.amount) + amount: toRippledAmount(payment.destination.amount) }; if (payment.invoiceID) { @@ -101,7 +101,7 @@ function createPaymentTransaction(account, payment) { } } - let flags = []; + const flags = []; if (payment.allowPartialPayment) { flags.push('PartialPayment'); } diff --git a/src/api/transaction/settings.js b/src/api/transaction/settings.js index 89bffea1..d8cb791c 100644 --- a/src/api/transaction/settings.js +++ b/src/api/transaction/settings.js @@ -2,35 +2,28 @@ /* eslint-disable valid-jsdoc */ 'use strict'; const _ = require('lodash'); +const assert = require('assert'); const utils = require('./utils'); const ripple = utils.common.core; const validate = utils.common.validate; -const constants = utils.common.constants; -const InvalidRequestError = utils.common.errors.InvalidRequestError; +const AccountFlagIndices = utils.common.constants.AccountFlagIndices; +const AccountFields = utils.common.constants.AccountFields; +const ValidationError = utils.common.errors.ValidationError; // Emptry string passed to setting will clear it const CLEAR_SETTING = ''; -/** - * Set integer flags on a transaction based on input and a flag map - * - * @param {Transaction} transaction - * @param {Object} input - Object whose properties determine whether - * to update the transaction's SetFlag or ClearFlag property - * @param {Object} flags - Object that maps property names to transaction - * integer flag values - * - * @returns undefined - */ -function setTransactionIntFlags(transaction, values, flags) { - for (let flagName in flags) { - const value = values[flagName]; - const code = flags[flagName]; - - if (value === true) { - transaction.tx_json.SetFlag = code; - } else if (value === false) { - transaction.tx_json.ClearFlag = code; +function setTransactionFlags(transaction, values) { + const keys = Object.keys(values); + assert(keys.length === 1, 'ERROR: can only set one setting per transaction'); + const flagName = keys[0]; + const value = values[flagName]; + const index = AccountFlagIndices[flagName]; + if (index !== undefined) { + if (value) { + transaction.tx_json.SetFlag = index; + } else { + transaction.tx_json.ClearFlag = index; } } } @@ -45,8 +38,9 @@ function setTransactionIntFlags(transaction, values, flags) { * * @returns undefined */ -function setTransactionFields(transaction, input, fieldSchema) { - for (let fieldName in fieldSchema) { +function setTransactionFields(transaction, input) { + const fieldSchema = AccountFields; + for (const fieldName in fieldSchema) { const field = fieldSchema[fieldName]; let value = input[field.name]; @@ -66,8 +60,7 @@ function setTransactionFields(transaction, input, fieldSchema) { // Field is fixed length, why are we checking here though? // We could move this to validateInputs if (value.length > field.length) { - throw new InvalidRequestError( - 'Parameter length exceeded: ' + fieldName); + throw new ValidationError('Parameter length exceeded: ' + fieldName); } else if (value.length < field.length) { value = _.padLeft(value, field.length); } @@ -113,10 +106,8 @@ function createSettingsTransaction(account, settings) { const transaction = new ripple.Transaction(); transaction.accountSet(account); - utils.setTransactionBitFlags(transaction, settings, - constants.AccountSetFlags); - setTransactionIntFlags(transaction, settings, constants.AccountSetIntFlags); - setTransactionFields(transaction, settings, constants.AccountRootFields); + setTransactionFlags(transaction, settings); + setTransactionFields(transaction, settings); if (transaction.tx_json.TransferRate !== undefined) { transaction.tx_json.TransferRate = convertTransferRate( diff --git a/src/api/transaction/utils.js b/src/api/transaction/utils.js index 11f753c7..df25c3ff 100644 --- a/src/api/transaction/utils.js +++ b/src/api/transaction/utils.js @@ -21,7 +21,7 @@ const common = require('../common'); */ function setTransactionBitFlags(transaction: any, values: any, flags: any): void { - for (let flagName in flags) { + for (const flagName in flags) { const flagValue = values[flagName]; const flagConversions = flags[flagName]; @@ -79,20 +79,9 @@ function createTxJSON(transaction: any, remote: any, } } -function wrapCatch(asyncFunction: any): any { - return function() { - try { - asyncFunction.apply(this, arguments); - } catch (error) { - const callback = arguments[arguments.length - 1]; - callback(error); - } - }; -} - module.exports = { setTransactionBitFlags: setTransactionBitFlags, createTxJSON: createTxJSON, - wrapCatch: wrapCatch, + wrapCatch: common.wrapCatch, common: common }; diff --git a/src/core/remote.js b/src/core/remote.js index ff2a8039..739ab738 100644 --- a/src/core/remote.js +++ b/src/core/remote.js @@ -210,9 +210,9 @@ Remote.flags = { RequireAuth: 0x00040000, // require a authorization to hold IOUs DisallowXRP: 0x00080000, // disallow sending XRP DisableMaster: 0x00100000, // force regular key - DefaultRipple: 0x00800000, NoFreeze: 0x00200000, // permanently disallowed freezing trustlines - GlobalFreeze: 0x00400000 // trustlines globally frozen + GlobalFreeze: 0x00400000, // trustlines globally frozen + DefaultRipple: 0x00800000 }, // Offer offer: { @@ -448,7 +448,7 @@ Remote.prototype.disconnect = function(callback_) { throw new Error('No servers available, not disconnecting'); } - let callback = lodash.isFunction(callback_) + const callback = lodash.isFunction(callback_) ? callback_ : function() {}; @@ -980,7 +980,7 @@ Remote.prototype.requestLedgerEntry = function(type, callback_) { const self = this; - let request = new Request(this, 'ledger_entry'); + const request = new Request(this, 'ledger_entry'); const callback = lodash.isFunction(type) ? type : callback_; // Transparent caching. When .request() is invoked, look in the Remote object @@ -1232,7 +1232,7 @@ Remote.accountRequest = function(command, options_, callback_) { }; } - let {account, ledger, peer, limit, marker} = options; + const {account, ledger, peer, limit, marker} = options; // if a marker is given, we need a ledger // check if a valid ledger_index or ledger_hash is provided @@ -1246,8 +1246,7 @@ Remote.accountRequest = function(command, options_, callback_) { const request = new Request(this, command); if (account) { - account = UInt160.json_rewrite(account); - request.message.account = account; + request.message.account = UInt160.json_rewrite(account); } request.selectLedger(ledger); @@ -1257,20 +1256,20 @@ Remote.accountRequest = function(command, options_, callback_) { } if (!isNaN(limit)) { - limit = Number(limit); + let _limit = Number(limit); // max for 32-bit unsigned int is 4294967295 // we'll clamp to 1e9 - if (limit > 1e9) { - limit = 1e9; + if (_limit > 1e9) { + _limit = 1e9; } // min for 32-bit unsigned int is 0 // we'll clamp to 0 - if (limit < 0) { - limit = 0; + if (_limit < 0) { + _limit = 0; } - request.message.limit = limit; + request.message.limit = _limit; } if (marker) { @@ -1551,7 +1550,7 @@ Remote.prototype.requestTxHistory = function(start_, callback_) { const request = new Request(this, 'tx_history'); let start = start_; - let callback = callback_; + const callback = callback_; if (lodash.isPlainObject(start)) { start = start.start; @@ -1606,7 +1605,7 @@ Remote.prototype.requestBookOffers = function(options_, callback_) { }; } - let {gets, pays, taker, ledger, limit} = options; + const {gets, pays, taker, ledger, limit} = options; const request = new Request(this, 'book_offers'); @@ -1630,20 +1629,20 @@ Remote.prototype.requestBookOffers = function(options_, callback_) { request.selectLedger(ledger); if (!isNaN(limit)) { - limit = Number(limit); + let _limit = Number(limit); // max for 32-bit unsigned int is 4294967295 // we'll clamp to 1e9 - if (limit > 1e9) { - limit = 1e9; + if (_limit > 1e9) { + _limit = 1e9; } // min for 32-bit unsigned int is 0 // we'll clamp to 0 - if (limit < 0) { - limit = 0; + if (_limit < 0) { + _limit = 0; } - request.message.limit = limit; + request.message.limit = _limit; } request.callback(callback); @@ -1661,7 +1660,8 @@ Remote.prototype.requestBookOffers = function(options_, callback_) { Remote.prototype.requestWalletAccounts = function(options_, callback_) { utils.assert(this.trusted); // Don't send secrets. - let options, callback = callback_; + let options; + const callback = callback_; if (lodash.isPlainObject(options_)) { options = lodash.merge({}, options_); diff --git a/test/api-test.js b/test/api-test.js index 8a594de4..5e2717e1 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -3,6 +3,7 @@ const _ = require('lodash'); const assert = require('assert-diff'); const setupAPI = require('./setup-api'); const address = require('./fixtures/addresses').ACCOUNT; +const hashes = require('./fixtures/hashes'); const paymentSpecification = require('./fixtures/payment-specification'); const paymentResponse = require('./fixtures/payment-response'); const orderSpecification = require('./fixtures/order-specification'); @@ -20,6 +21,7 @@ const signOutput = require('./fixtures/sign-output'); const MockPRNG = require('./mock-prng'); const sjcl = require('../src').sjcl; const submitResponse = require('./fixtures/submit-response'); +const transactionResponse = require('./fixtures/transaction-response'); function checkResult(expected, done, error, response) { if (error) { @@ -84,4 +86,9 @@ describe('RippleAPI', function() { this.api.getBalances(address, {}, _.partial(checkResult, balancesResponse, done)); }); + + it('getTransaction', function(done) { + this.api.getTransaction(hashes.VALID_TRANSACTION_HASH, {}, + _.partial(checkResult, transactionResponse, done)); + }); }); diff --git a/test/fixtures/mock.js b/test/fixtures/mock.js index 446c14b2..7856bd74 100644 --- a/test/fixtures/mock.js +++ b/test/fixtures/mock.js @@ -638,7 +638,8 @@ const BINARY_TRANSACTION_SYNTH = module.exports.BINARY_TRANSACTION_SYNTH = { date: 416447810, hash: 'F4AB442A6D4CBB935D66E1DA7309A5FC71C7143ED4049053EC14E3875B0CF9BF', inLedger: 348860, - ledger_index: 348860 + ledger_index: 348860, + validated: true }; module.exports.transactionResponse = function(request) { diff --git a/test/fixtures/settings-response.json b/test/fixtures/settings-response.json index ebb2cc42..2433e8ac 100644 --- a/test/fixtures/settings-response.json +++ b/test/fixtures/settings-response.json @@ -2,9 +2,8 @@ "Flags": 1048576, "TransactionType": "AccountSet", "Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", - "SetFlag": 6, - "EmailHash": "98B4375E1D753E5B91627516F6D70977", "Domain": "726970706C652E636F6D", + "Flags": 0, "LastLedgerSequence": 8820052, "Fee": "12", "Sequence": 23 diff --git a/test/fixtures/settings-specification.json b/test/fixtures/settings-specification.json index 1bc41adf..6bc5dbb4 100644 --- a/test/fixtures/settings-specification.json +++ b/test/fixtures/settings-specification.json @@ -1,6 +1,3 @@ { - "domain": "ripple.com", - "emailHash": "98B4375E1D753E5B91627516F6D70977", - "noFreeze": true, - "disallowIncomingXRP": true + "domain": "ripple.com" } diff --git a/test/fixtures/transaction-response.json b/test/fixtures/transaction-response.json new file mode 100644 index 00000000..293ed657 --- /dev/null +++ b/test/fixtures/transaction-response.json @@ -0,0 +1,89 @@ +{ + "type": "payment", + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "specification": { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "XRP", + "value": "1.112209" + } + }, + "destination": { + "address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "amount": { + "currency": "USD", + "value": "0.001", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM" + } + }, + "paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]", + "allowPartialPayment": false, + "noDirectRipple": false + }, + "outcome": { + "result": "tesSUCCESS", + "timestamp": "2013-03-12T23:56:50.000Z", + "fee": "0.00001", + "balanceChanges": { + "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [ + { + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "currency": "USD", + "value": "-0.001" + }, + { + "counterparty": "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr", + "currency": "USD", + "value": "0.001002" + } + ], + "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM": [ + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "0.001" + } + ], + "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59": [ + { + "counterparty": "", + "currency": "XRP", + "value": "-1.101208" + } + ], + "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr": [ + { + "counterparty": "", + "currency": "XRP", + "value": "1.101198" + }, + { + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currency": "USD", + "value": "-0.001002" + } + ] + }, + "orderbookChanges": { + "r9tGqzZgKxVFvzKFdUqXAqTzazWBUia8Qr": [ + { + "taker_pays": { + "currency": "XRP", + "counterparty": "", + "value": "-1.101198" + }, + "taker_gets": { + "currency": "USD", + "counterparty": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "value": "-0.001002" + }, + "sequence": 58, + "status": "open" + } + ] + }, + "ledgerVersion": 348860, + "sequence": 4 + } +}