mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-27 15:45:48 +00:00
Rewrite transaction parser and add unit test for getTransaction
This commit is contained in:
18
src/api/ledger/parse/amount.js
Normal file
18
src/api/ledger/parse/amount.js
Normal 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;
|
||||
11
src/api/ledger/parse/cancellation.js
Normal file
11
src/api/ledger/parse/cancellation.js
Normal 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;
|
||||
23
src/api/ledger/parse/fields.js
Normal file
23
src/api/ledger/parse/fields.js
Normal 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;
|
||||
103
src/api/ledger/parse/notification.js
Normal file
103
src/api/ledger/parse/notification.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict';
|
||||
const ripple = require('../utils').common.core;
|
||||
|
||||
/**
|
||||
* Convert a Ripple transaction in the JSON format,
|
||||
* along with some additional pieces of information,
|
||||
* into a Notification object.
|
||||
*
|
||||
* @param {Ripple Transaction in JSON Format} notification_details.transaction
|
||||
* @param {RippleAddress} notification_details.account
|
||||
* @param {Hex-encoded String}
|
||||
* notification_details.previous_transaction_identifier
|
||||
* @param {Hex-encoded String}
|
||||
* notification_details.next_transaction_identifier
|
||||
*
|
||||
* @returns {Notification}
|
||||
*/
|
||||
function NotificationParser() {}
|
||||
|
||||
NotificationParser.prototype.parse = function(notification_details, urlBase) {
|
||||
const transaction = notification_details.transaction;
|
||||
const account = notification_details.account;
|
||||
const previous_transaction_identifier =
|
||||
notification_details.previous_transaction_identifier;
|
||||
const next_transaction_identifier =
|
||||
notification_details.next_transaction_identifier;
|
||||
|
||||
const metadata = transaction.meta || { };
|
||||
|
||||
const notification = {
|
||||
account: account,
|
||||
type: transaction.TransactionType.toLowerCase(),
|
||||
direction: '', // set below
|
||||
state: (metadata.TransactionResult === 'tesSUCCESS'
|
||||
? 'validated' : 'failed'),
|
||||
result: metadata.TransactionResult || '',
|
||||
ledger: '' + transaction.ledger_index,
|
||||
hash: transaction.hash,
|
||||
timestamp: '',// set below
|
||||
transaction_url: '', // set below
|
||||
previous_transaction_identifier:
|
||||
notification_details.previous_transaction_identifier || '',
|
||||
previous_notification_url: '', // set below
|
||||
next_transaction_identifier:
|
||||
notification_details.next_transaction_identifier || '',
|
||||
next_notification_url: '' // set below
|
||||
};
|
||||
|
||||
notification.timestamp = transaction.date ?
|
||||
new Date(ripple.utils.time.fromRipple(transaction.date)).toISOString() : '';
|
||||
|
||||
// Direction
|
||||
if (account === transaction.Account) {
|
||||
notification.direction = 'outgoing';
|
||||
} else if (transaction.TransactionType === 'Payment'
|
||||
&& transaction.Destination !== account) {
|
||||
notification.direction = 'passthrough';
|
||||
} else {
|
||||
notification.direction = 'incoming';
|
||||
}
|
||||
|
||||
// Notification URL
|
||||
|
||||
if (notification.type === 'payment') {
|
||||
notification.transaction_url = urlBase + '/v1/accounts/'
|
||||
+ notification.account
|
||||
+ '/payments/'
|
||||
+ notification.hash;
|
||||
} else if (notification.type === 'offercreate'
|
||||
|| notification.type === 'offercancel') {
|
||||
notification.type = 'order';
|
||||
notification.transaction_url = urlBase + '/v1/accounts/'
|
||||
+ notification.account
|
||||
+ '/orders/' + notification.hash;
|
||||
} else if (notification.type === 'trustset') {
|
||||
notification.type = 'trustline';
|
||||
notification.transaction_url = urlBase + '/v1/transactions/'
|
||||
+ notification.hash;
|
||||
} else if (notification.type === 'accountset') {
|
||||
notification.type = 'settings';
|
||||
notification.transaction_url = urlBase + '/v1/transactions/'
|
||||
+ notification.hash;
|
||||
}
|
||||
|
||||
// Next notification URL
|
||||
|
||||
if (next_transaction_identifier) {
|
||||
notification.next_notification_url = urlBase + '/v1/accounts/'
|
||||
+ notification.account + '/notifications/' + next_transaction_identifier;
|
||||
}
|
||||
|
||||
// Previous notification URL
|
||||
if (previous_transaction_identifier) {
|
||||
notification.previous_notification_url = urlBase + '/v1/accounts/'
|
||||
+ notification.account + '/notifications/'
|
||||
+ previous_transaction_identifier;
|
||||
}
|
||||
|
||||
return notification;
|
||||
};
|
||||
|
||||
module.exports = new NotificationParser();
|
||||
26
src/api/ledger/parse/order.js
Normal file
26
src/api/ledger/parse/order.js
Normal 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;
|
||||
22
src/api/ledger/parse/pathfind.js
Normal file
22
src/api/ledger/parse/pathfind.js
Normal 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;
|
||||
48
src/api/ledger/parse/payment.js
Normal file
48
src/api/ledger/parse/payment.js
Normal 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;
|
||||
27
src/api/ledger/parse/settings.js
Normal file
27
src/api/ledger/parse/settings.js
Normal 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;
|
||||
42
src/api/ledger/parse/transaction.js
Normal file
42
src/api/ledger/parse/transaction.js
Normal 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;
|
||||
21
src/api/ledger/parse/trustline.js
Normal file
21
src/api/ledger/parse/trustline.js
Normal 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;
|
||||
40
src/api/ledger/parse/utils.js
Normal file
40
src/api/ledger/parse/utils.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user