Merge pull request #369 from clark800/parse-tx

Rewrite transaction parser and add unit test for getTransaction
This commit is contained in:
Chris Clark
2015-06-16 16:55:15 -07:00
34 changed files with 570 additions and 707 deletions

View File

@@ -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
};

View File

@@ -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

View File

@@ -9,5 +9,6 @@ module.exports = {
server: require('./server'),
dropsToXrp: utils.dropsToXrp,
xrpToDrops: utils.xrpToDrops,
convertAmount: utils.convertAmount
toRippledAmount: utils.toRippledAmount,
wrapCatch: utils.wrapCatch
};

View File

@@ -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
}

View File

@@ -9,6 +9,7 @@
"qualityIn": {"$ref": "quality"},
"qualityOut": {"$ref": "quality"},
"allowRippling": {"type": "boolean"},
"authorized": {"type": "boolean"},
"frozen": {"type": "boolean"}
},
"required": ["currency", "counterparty", "limit"],

View File

@@ -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
};

View File

@@ -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');
}
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -0,0 +1,11 @@
'use strict';
const assert = require('assert');
function parseOrderCancellation(tx) {
assert(tx.TransactionType === 'OfferCancel');
return {
orderSequence: tx.OfferSequence
};
}
module.exports = parseOrderCancellation;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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) {

View File

@@ -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);
});
}

View File

@@ -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
};

View File

@@ -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.<Object,Error>} - 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
};

View File

@@ -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
};

View File

@@ -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');
}

View File

@@ -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(

View File

@@ -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
};

View File

@@ -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_);

View File

@@ -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));
});
});

View File

@@ -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) {

View File

@@ -2,9 +2,8 @@
"Flags": 1048576,
"TransactionType": "AccountSet",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"SetFlag": 6,
"EmailHash": "98B4375E1D753E5B91627516F6D70977",
"Domain": "726970706C652E636F6D",
"Flags": 0,
"LastLedgerSequence": 8820052,
"Fee": "12",
"Sequence": 23

View File

@@ -1,6 +1,3 @@
{
"domain": "ripple.com",
"emailHash": "98B4375E1D753E5B91627516F6D70977",
"noFreeze": true,
"disallowIncomingXRP": true
"domain": "ripple.com"
}

89
test/fixtures/transaction-response.json vendored Normal file
View File

@@ -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
}
}