mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 20:25:48 +00:00
Delete core, move "api" directory up to "src", and remove unused dependencies
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
[ignore]
|
||||
.*/src/api/.*
|
||||
.*/src/core/.*
|
||||
.*/dist/.*
|
||||
.*/test/fixtures/.*
|
||||
.*/ripple-lib/src/.*
|
||||
.*/ripple-lib/dist/.*
|
||||
.*/ripple-lib/test/fixtures/.*
|
||||
.*/node_modules/flow-bin/.*
|
||||
.*/node_modules/webpack/.*
|
||||
|
||||
[include]
|
||||
./node_modules/
|
||||
|
||||
38
npm-shrinkwrap.json
generated
38
npm-shrinkwrap.json
generated
@@ -4,10 +4,6 @@
|
||||
"npm-shrinkwrap-version": "5.4.0",
|
||||
"node-version": "v0.12.7",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "5.8.29",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz",
|
||||
@@ -22,14 +18,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.0.tgz"
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
|
||||
},
|
||||
"hash.js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
|
||||
@@ -102,10 +90,6 @@
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
|
||||
},
|
||||
"ripple-address-codec": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
|
||||
@@ -126,6 +110,10 @@
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
|
||||
},
|
||||
"create-hash": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
|
||||
@@ -186,6 +174,10 @@
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
|
||||
},
|
||||
"brorand": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
|
||||
@@ -206,20 +198,6 @@
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.1.tgz"
|
||||
},
|
||||
"ripple-lib-value": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ripple-lib-value/-/ripple-lib-value-0.1.0.tgz",
|
||||
"dependencies": {
|
||||
"bignumber.js": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.8.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sjcl-codec": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sjcl-codec/-/sjcl-codec-0.1.0.tgz"
|
||||
},
|
||||
"ws": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
|
||||
|
||||
@@ -15,23 +15,17 @@
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.9.0",
|
||||
"babel-runtime": "^5.5.4",
|
||||
"bignumber.js": "^2.0.3",
|
||||
"bn.js": "^3.1.1",
|
||||
"extend": "~1.2.1",
|
||||
"hash.js": "^1.0.3",
|
||||
"https-proxy-agent": "^1.0.0",
|
||||
"is-my-json-valid": "^2.12.2",
|
||||
"lodash": "^3.1.0",
|
||||
"lru-cache": "~2.5.0",
|
||||
"ripple-address-codec": "^2.0.1",
|
||||
"ripple-binary-codec": "^0.0.6",
|
||||
"ripple-hashes": "^0.0.1",
|
||||
"ripple-keypairs": "^0.10.0",
|
||||
"ripple-lib-transactionparser": "^0.5.1",
|
||||
"ripple-lib-value": "0.1.0",
|
||||
"sjcl-codec": "0.1.0",
|
||||
"ws": "~0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -27,7 +27,6 @@ unittest() {
|
||||
babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
|
||||
echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts
|
||||
mkdir -p test-compiled/node_modules
|
||||
ln -nfs ../../dist/npm/core test-compiled/node_modules/ripple-lib
|
||||
ln -nfs ../../dist/npm test-compiled/node_modules/ripple-api
|
||||
mocha --opts test-compiled/mocha.opts test-compiled
|
||||
rm -rf test-compiled
|
||||
|
||||
111
src/api/index.js
111
src/api/index.js
@@ -1,111 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const common = require('./common');
|
||||
const server = require('./server/server');
|
||||
const connect = server.connect;
|
||||
const disconnect = server.disconnect;
|
||||
const getServerInfo = server.getServerInfo;
|
||||
const getFee = server.getFee;
|
||||
const isConnected = server.isConnected;
|
||||
const getLedgerVersion = server.getLedgerVersion;
|
||||
const getTransaction = require('./ledger/transaction');
|
||||
const getTransactions = require('./ledger/transactions');
|
||||
const getTrustlines = require('./ledger/trustlines');
|
||||
const getBalances = require('./ledger/balances');
|
||||
const getBalanceSheet = require('./ledger/balance-sheet');
|
||||
const getPaths = require('./ledger/pathfind');
|
||||
const getOrders = require('./ledger/orders');
|
||||
const getOrderbook = require('./ledger/orderbook');
|
||||
const getSettings = require('./ledger/settings');
|
||||
const getAccountInfo = require('./ledger/accountinfo');
|
||||
const preparePayment = require('./transaction/payment');
|
||||
const prepareTrustline = require('./transaction/trustline');
|
||||
const prepareOrder = require('./transaction/order');
|
||||
const prepareOrderCancellation = require('./transaction/ordercancellation');
|
||||
const prepareSuspendedPaymentCreation =
|
||||
require('./transaction/suspended-payment-creation');
|
||||
const prepareSuspendedPaymentExecution =
|
||||
require('./transaction/suspended-payment-execution');
|
||||
const prepareSuspendedPaymentCancellation =
|
||||
require('./transaction/suspended-payment-cancellation');
|
||||
const prepareSettings = require('./transaction/settings');
|
||||
const sign = require('./transaction/sign');
|
||||
const submit = require('./transaction/submit');
|
||||
const errors = require('./common').errors;
|
||||
const generateAddress = common.generateAddressAPI;
|
||||
const computeLedgerHash = require('./offline/ledgerhash');
|
||||
const getLedger = require('./ledger/ledger');
|
||||
|
||||
type APIOptions = {
|
||||
servers?: Array<string>,
|
||||
feeCushion?: number,
|
||||
trace?: boolean,
|
||||
proxy?: string
|
||||
}
|
||||
|
||||
class RippleAPI extends EventEmitter {
|
||||
constructor(options: APIOptions = {}) {
|
||||
common.validate.apiOptions(options);
|
||||
super();
|
||||
if (options.servers !== undefined) {
|
||||
const servers: Array<string> = options.servers;
|
||||
if (servers.length === 1) {
|
||||
this._feeCushion = options.feeCushion || 1.2;
|
||||
this.connection = new common.Connection(servers[0], options);
|
||||
this.connection.on('ledgerClosed', message => {
|
||||
this.emit('ledgerClosed', server.formatLedgerClose(message));
|
||||
});
|
||||
} else {
|
||||
throw new errors.RippleError('Multi-server not implemented');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_.assign(RippleAPI.prototype, {
|
||||
connect,
|
||||
disconnect,
|
||||
isConnected,
|
||||
getServerInfo,
|
||||
getFee,
|
||||
getLedgerVersion,
|
||||
|
||||
getTransaction,
|
||||
getTransactions,
|
||||
getTrustlines,
|
||||
getBalances,
|
||||
getBalanceSheet,
|
||||
getPaths,
|
||||
getOrders,
|
||||
getOrderbook,
|
||||
getSettings,
|
||||
getAccountInfo,
|
||||
getLedger,
|
||||
|
||||
preparePayment,
|
||||
prepareTrustline,
|
||||
prepareOrder,
|
||||
prepareOrderCancellation,
|
||||
prepareSuspendedPaymentCreation,
|
||||
prepareSuspendedPaymentExecution,
|
||||
prepareSuspendedPaymentCancellation,
|
||||
prepareSettings,
|
||||
sign,
|
||||
submit,
|
||||
|
||||
generateAddress,
|
||||
errors
|
||||
});
|
||||
|
||||
// these are exposed only for use by unit tests; they are not part of the API
|
||||
RippleAPI._PRIVATE = {
|
||||
common,
|
||||
computeLedgerHash,
|
||||
ledgerUtils: require('./ledger/utils'),
|
||||
schemaValidator: require('./common/schema-validator')
|
||||
};
|
||||
|
||||
module.exports = RippleAPI;
|
||||
@@ -1,388 +0,0 @@
|
||||
'use strict';
|
||||
// Routines for working with an account.
|
||||
//
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
//
|
||||
// Events:
|
||||
// wallet_clean : True, iff the wallet has been updated.
|
||||
// wallet_dirty : True, iff the wallet needs to be updated.
|
||||
// balance: The current stamp balance.
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const extend = require('extend');
|
||||
const util = require('util');
|
||||
const {deriveAddress} = require('ripple-keypairs');
|
||||
const {EventEmitter} = require('events');
|
||||
const {TransactionManager} = require('./transactionmanager');
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
|
||||
/**
|
||||
* @constructor Account
|
||||
* @param {Remote} remote
|
||||
* @param {String} account
|
||||
*/
|
||||
|
||||
function Account(remote, address) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
const self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._address = address;
|
||||
this._subs = 0;
|
||||
|
||||
// Ledger entry object
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = { };
|
||||
|
||||
function listenerAdded(type) {
|
||||
if (_.includes(Account.subscribeEvents, type)) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.requestSubscribe()
|
||||
.addAccount(self._address)
|
||||
.broadcast().request();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
function listenerRemoved(type) {
|
||||
if (_.includes(Account.subscribeEvents, type)) {
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.requestUnsubscribe()
|
||||
.addAccount(self._address)
|
||||
.broadcast().request();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.on('removeListener', listenerRemoved);
|
||||
|
||||
function attachAccount(request) {
|
||||
if (isValidAddress(self._address) && self._subs) {
|
||||
request.addAccount(self._address);
|
||||
}
|
||||
}
|
||||
|
||||
this._remote.on('prepare_subscribe', attachAccount);
|
||||
|
||||
function handleTransaction(transaction) {
|
||||
if (!transaction.mmeta) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
|
||||
transaction.mmeta.each(function(an) {
|
||||
const isAccount = an.fields.Account === self._address;
|
||||
const isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
|
||||
|
||||
if (isAccountRoot) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
}
|
||||
|
||||
this.on('transaction', handleTransaction);
|
||||
|
||||
this._transactionManager = new TransactionManager(this);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
util.inherits(Account, EventEmitter);
|
||||
|
||||
/**
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
|
||||
Account.subscribeEvents = ['transaction', 'entry'];
|
||||
|
||||
Account.prototype.toJson = function() {
|
||||
return this._address;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the AccountId is valid.
|
||||
*
|
||||
* Note: This does not tell you whether the account exists in the ledger.
|
||||
*/
|
||||
|
||||
Account.prototype.isValid = function() {
|
||||
return isValidAddress(this._address);
|
||||
};
|
||||
|
||||
/**
|
||||
* Request account info
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.getInfo = function(callback) {
|
||||
return this._remote.requestAccountInfo({account: this._address}, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the current AccountRoot entry.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* 'entry' event.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.entry = function(callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
extend(self._entry, info.account_data);
|
||||
self.emit('entry', self._entry);
|
||||
callback(null, info);
|
||||
}
|
||||
}
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype.getNextSequence = function(callback_) {
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function isNotFound(err) {
|
||||
return err && typeof err === 'object'
|
||||
&& typeof err.remote === 'object'
|
||||
&& err.remote.error === 'actNotFound';
|
||||
}
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (isNotFound(err)) {
|
||||
// New accounts will start out as sequence one
|
||||
callback(null, 1);
|
||||
} else if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, info.account_data.Sequence);
|
||||
}
|
||||
}
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve this account's Ripple trust lines.
|
||||
*
|
||||
* To keep up-to-date with changes to the AccountRoot entry, subscribe to the
|
||||
* 'lines' event. (Not yet implemented.)
|
||||
*
|
||||
* @param {function(err, lines)} callback Called with the result
|
||||
*/
|
||||
|
||||
Account.prototype.lines = function(callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function accountLines(err, res) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self._lines = res.lines;
|
||||
self.emit('lines', self._lines);
|
||||
callback(null, res);
|
||||
}
|
||||
}
|
||||
|
||||
this._remote.requestAccountLines({account: this._address}, accountLines);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve this account's single trust line.
|
||||
*
|
||||
* @param {string} currency Currency
|
||||
* @param {string} address Ripple address
|
||||
* @param {function(err, line)} callback Called with the result
|
||||
* @returns {Account}
|
||||
*/
|
||||
|
||||
Account.prototype.line = function(currency, address, callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
self.lines(function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let line;
|
||||
|
||||
for (let i = 0; i < data.lines.length; i++) {
|
||||
const l = data.lines[i];
|
||||
if (l.account === address && l.currency === currency) {
|
||||
line = l;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, line);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify object of a relevant transaction.
|
||||
*
|
||||
* This is only meant to be called by the Remote class. You should never have to
|
||||
* call this yourself.
|
||||
*
|
||||
* @param {Object} message
|
||||
*/
|
||||
|
||||
Account.prototype.notify =
|
||||
Account.prototype.notifyTx = function(transaction) {
|
||||
// Only trigger the event if the account object is actually
|
||||
// subscribed - this prevents some weird phantom events from
|
||||
// occurring.
|
||||
if (!this._subs) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit('transaction', transaction);
|
||||
|
||||
const account = transaction.transaction.Account;
|
||||
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isThisAccount = (account === this._address);
|
||||
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound',
|
||||
transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit a transaction to an account's
|
||||
* transaction manager
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
|
||||
Account.prototype.submit = function(transaction) {
|
||||
this._transactionManager.submit(transaction);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check whether the given public key is valid for this account
|
||||
*
|
||||
* @param {Hex-encoded_String|RippleAddress} public_key Public key
|
||||
* @param {Function} callback Is a callback
|
||||
* @returns {void}
|
||||
*
|
||||
* @callback
|
||||
* param {Error} err
|
||||
* param {Boolean} true if the public key is valid and active, false otherwise
|
||||
*/
|
||||
Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
const self = this;
|
||||
let public_key_as_uint160;
|
||||
|
||||
try {
|
||||
public_key_as_uint160 = Account._publicKeyToAddress(public_key);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
function getAccountInfo(async_callback) {
|
||||
self.getInfo(function(err, account_info_res) {
|
||||
|
||||
// If the remote responds with an Account Not Found error then the account
|
||||
// is unfunded and thus we can assume that the master key is active
|
||||
if (err && err.remote && err.remote.error === 'actNotFound') {
|
||||
async_callback(null, null);
|
||||
} else {
|
||||
async_callback(err, account_info_res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function publicKeyIsValid(account_info_res, async_callback) {
|
||||
// Catch the case of unfunded accounts
|
||||
if (!account_info_res) {
|
||||
|
||||
if (public_key_as_uint160 === self._address) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const account_info = account_info_res.account_data;
|
||||
|
||||
// Respond with true if the RegularKey is set and matches the given
|
||||
// public key or if the public key matches the account address and
|
||||
// the lsfDisableMaster is not set
|
||||
if (account_info.RegularKey &&
|
||||
account_info.RegularKey === public_key_as_uint160) {
|
||||
async_callback(null, true);
|
||||
} else if (account_info.Account === public_key_as_uint160 &&
|
||||
((account_info.Flags & 0x00100000) === 0)) {
|
||||
async_callback(null, true);
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
}
|
||||
|
||||
const steps = [
|
||||
getAccountInfo,
|
||||
publicKeyIsValid
|
||||
];
|
||||
|
||||
async.waterfall(steps, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a hex-encoded public key to a Ripple Address
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {Hex-encoded_string|RippleAddress} public_key Public key
|
||||
* @returns {RippleAddress} Ripple Address
|
||||
*/
|
||||
Account._publicKeyToAddress = function(public_key) {
|
||||
if (isValidAddress(public_key)) {
|
||||
return public_key;
|
||||
} else if (/^[0-9a-fA-F]+$/.test(public_key)) {
|
||||
return deriveAddress(public_key);
|
||||
} else { // eslint-disable-line no-else-return
|
||||
throw new Error('Public key is invalid. Must be a UInt160 or a hex string');
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Account
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,937 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Represent Ripple amounts and currencies.
|
||||
// - Numbers in hex are big-endian.
|
||||
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const normalizeCurrency = require('./currency').normalizeCurrency;
|
||||
const {XRPValue, IOUValue} = require('ripple-lib-value');
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
const {ACCOUNT_ONE, ACCOUNT_ZERO, CURRENCY_ONE}
|
||||
= require('./constants');
|
||||
|
||||
type Value = XRPValue | IOUValue;
|
||||
|
||||
function Amount(value = new XRPValue(NaN)) {
|
||||
// Json format:
|
||||
// integer : XRP
|
||||
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
|
||||
assert(value instanceof XRPValue || value instanceof IOUValue);
|
||||
|
||||
this._value = value;
|
||||
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
|
||||
this._currency = null;
|
||||
this._issuer = 'NaN';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set strict_mode = false to disable amount range checking
|
||||
*/
|
||||
|
||||
Amount.strict_mode = true;
|
||||
|
||||
const consts = {
|
||||
currency_xns: 0,
|
||||
currency_one: 1,
|
||||
xns_precision: 6,
|
||||
|
||||
// bi_ prefix refers to "big integer"
|
||||
// man refers to mantissa
|
||||
bi_man_max_value: '9999999999999999',
|
||||
bi_man_min_value: Number(1e15).toString(),
|
||||
bi_xns_max: Number(1e17).toString(),
|
||||
bi_xns_min: Number(-1e17).toString(),
|
||||
|
||||
cMinOffset: -96,
|
||||
cMaxOffset: 80,
|
||||
|
||||
// Maximum possible amount for non-XRP currencies using the maximum mantissa
|
||||
// with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF.
|
||||
max_value: '9999999999999999e80',
|
||||
// Minimum nonzero absolute value for non-XRP currencies.
|
||||
min_value: '1000000000000000e-96'
|
||||
};
|
||||
|
||||
const MAX_XRP_VALUE = new XRPValue(1e11);
|
||||
const MAX_IOU_VALUE = new IOUValue(consts.max_value);
|
||||
const MIN_IOU_VALUE = new IOUValue(consts.min_value);
|
||||
|
||||
const bi_xns_unit = new IOUValue(1e6);
|
||||
|
||||
// Add constants to Amount class
|
||||
extend(Amount, consts);
|
||||
|
||||
// DEPRECATED: Use Amount instead, e.g. Amount.currency_xns
|
||||
exports.consts = consts;
|
||||
|
||||
// Given '100/USD/ISSUER' return the a string with ISSUER remapped.
|
||||
Amount.text_full_rewrite = function(j) {
|
||||
return Amount.from_json(j).to_text_full();
|
||||
};
|
||||
|
||||
// Given '100/USD/ISSUER' return the json.
|
||||
Amount.json_rewrite = function(j) {
|
||||
return Amount.from_json(j).to_json();
|
||||
};
|
||||
|
||||
Amount.from_number = function(n) {
|
||||
return (new Amount()).parse_number(n);
|
||||
};
|
||||
|
||||
Amount.from_json = function(j) {
|
||||
return (new Amount()).parse_json(j);
|
||||
};
|
||||
|
||||
Amount.from_quality = function(quality, currency, issuer, opts) {
|
||||
return (new Amount()).parse_quality(quality, currency, issuer, opts);
|
||||
};
|
||||
|
||||
Amount.from_human = function(j, opts) {
|
||||
return (new Amount()).parse_human(j, opts);
|
||||
};
|
||||
|
||||
Amount.is_valid = function(j) {
|
||||
return Amount.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
Amount.is_valid_full = function(j) {
|
||||
return Amount.from_json(j).is_valid_full();
|
||||
};
|
||||
|
||||
Amount.NaN = function() {
|
||||
const result = new Amount();
|
||||
result._value = new IOUValue(NaN); // should have no effect
|
||||
return result; // but let's be careful
|
||||
};
|
||||
|
||||
Amount.from_components_unsafe = function(value: Value, currency: string,
|
||||
issuer: string, isNative: boolean
|
||||
) {
|
||||
const result = new Amount(value);
|
||||
result._is_native = isNative;
|
||||
result._currency = currency;
|
||||
result._issuer = issuer;
|
||||
|
||||
result._value = value.isZero() && value.isNegative() ?
|
||||
value.negate() : value;
|
||||
return result;
|
||||
};
|
||||
|
||||
// be sure that _is_native is set properly BEFORE calling _set_value
|
||||
Amount.prototype._set_value = function(value: Value) {
|
||||
this._value = value.isZero() && value.isNegative() ?
|
||||
value.negate() : value;
|
||||
this._check_limits();
|
||||
};
|
||||
|
||||
// Returns a new value which is the absolute value of this.
|
||||
Amount.prototype.abs = function() {
|
||||
return this._copy(this._value.abs());
|
||||
};
|
||||
|
||||
Amount.prototype.add = function(addend) {
|
||||
const addendAmount = addend instanceof Amount ?
|
||||
addend : Amount.from_json(addend);
|
||||
|
||||
if (!this.is_comparable(addendAmount)) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
return this._copy(this._value.add(addendAmount._value));
|
||||
};
|
||||
|
||||
Amount.prototype.subtract = function(subtrahend) {
|
||||
// Correctness over speed, less code has less bugs, reuse add code.
|
||||
const subsAmount = subtrahend instanceof Amount ?
|
||||
subtrahend : Amount.from_json(subtrahend);
|
||||
return this.add(subsAmount.negate());
|
||||
};
|
||||
|
||||
// XXX Diverges from cpp.
|
||||
Amount.prototype.multiply = function(multiplicand) {
|
||||
const multiplicandValue = multiplicand instanceof Amount ?
|
||||
multiplicand._value :
|
||||
Amount.from_json(multiplicand)._value;
|
||||
|
||||
return this._copy(this._value.multiply(multiplicandValue));
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.scale = function(scaleFactor) {
|
||||
return this.multiply(scaleFactor);
|
||||
};
|
||||
|
||||
Amount.prototype.divide = function(divisor) {
|
||||
const divisorValue = divisor instanceof Amount ?
|
||||
divisor._value :
|
||||
Amount.from_json(divisor)._value;
|
||||
|
||||
return this._copy(this._value.divide(divisorValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* This function calculates a ratio - such as a price - between two Amount
|
||||
* objects.
|
||||
*
|
||||
* The return value will have the same type (currency) as the numerator. This is
|
||||
* a simplification, which should be sane in most cases. For example, a USD/XRP
|
||||
* price would be rendered as USD.
|
||||
*
|
||||
* @example
|
||||
* const price = buy_amount.ratio_human(sell_amount);
|
||||
*
|
||||
* @this {Amount} The numerator (top half) of the fraction.
|
||||
* @param {Amount} denominator The denominator (bottom half) of the fraction.
|
||||
* @param opts Options for the calculation.
|
||||
* @param opts.reference_date {Date|Number} Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
|
||||
*/
|
||||
|
||||
Amount.prototype.ratio_human = function(denom) {
|
||||
const numerator = this.clone();
|
||||
const denominator = Amount.from_json(denom);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!numerator.is_valid() || !denominator.is_valid()) {
|
||||
return new Amount(NaN);
|
||||
}
|
||||
|
||||
if (denominator.is_zero()) {
|
||||
return new Amount(NaN);
|
||||
}
|
||||
|
||||
// Special case: The denominator is a native (XRP) amount.
|
||||
//
|
||||
// In that case, it's going to be expressed as base units (1 XRP =
|
||||
// 10^xns_precision base units).
|
||||
//
|
||||
// However, the unit of the denominator is lost, so when the resulting ratio
|
||||
// is printed, the ratio is going to be too small by a factor of
|
||||
// 10^xns_precision.
|
||||
//
|
||||
// To compensate, we multiply the numerator by 10^xns_precision.
|
||||
if (denominator._is_native) {
|
||||
numerator._set_value(numerator._value.multiply(bi_xns_unit));
|
||||
}
|
||||
|
||||
return numerator.divide(denominator);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a product of two amounts.
|
||||
*
|
||||
* This function allows you to calculate a product between two amounts which
|
||||
* retains XRPs human/external interpretation (i.e. 1 XRP = 1,000,000 base
|
||||
* units).
|
||||
*
|
||||
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
|
||||
*
|
||||
* @example
|
||||
* let sell_amount = buy_amount.product_human(price);
|
||||
*
|
||||
* @see Amount#ratio_human
|
||||
*
|
||||
* @param {Amount} factor The second factor of the product.
|
||||
* @param {Object} opts Options for the calculation.
|
||||
* @param {Date|Number} opts.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The product. Unit will be the same as the first factor.
|
||||
*/
|
||||
Amount.prototype.product_human = function(factor) {
|
||||
const fac = Amount.from_json(factor);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!this.is_valid() || !fac.is_valid()) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
const product = this.multiply(fac);
|
||||
|
||||
// Special case: The second factor is a native (XRP) amount expressed as base
|
||||
// units (1 XRP = 10^xns_precision base units).
|
||||
//
|
||||
// See also Amount#ratio_human.
|
||||
if (fac._is_native) {
|
||||
const quotient = product.divide(bi_xns_unit.toString());
|
||||
product._set_value(quotient._value);
|
||||
}
|
||||
|
||||
return product;
|
||||
};
|
||||
|
||||
/**
|
||||
* Turn this amount into its inverse.
|
||||
*
|
||||
* @return {Amount} self
|
||||
* @private
|
||||
*/
|
||||
Amount.prototype._invert = function() {
|
||||
this._set_value(this._value.invert());
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the inverse of this amount.
|
||||
*
|
||||
* @return {Amount} New Amount object with same currency and issuer, but the
|
||||
* inverse of the value.
|
||||
*/
|
||||
Amount.prototype.invert = function() {
|
||||
return this.clone()._invert();
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonicalize amount value is now taken care of in the Value classes
|
||||
*
|
||||
* Mirrors rippled's internal Amount representation
|
||||
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data
|
||||
* /protocol/STAmount.h#L31-L40
|
||||
*
|
||||
* Internal form:
|
||||
* 1: If amount is zero, then value is zero and offset is -100
|
||||
* 2: Otherwise:
|
||||
* legal offset range is -96 to +80 inclusive
|
||||
* value range is 10^15 to (10^16 - 1) inclusive
|
||||
* amount = value * [10 ^ offset]
|
||||
*
|
||||
* -------------------
|
||||
*
|
||||
* The amount can be epxresses as A x 10^B
|
||||
* Where:
|
||||
* - A must be an integer between 10^15 and (10^16)-1 inclusive
|
||||
* - B must be between -96 and 80 inclusive
|
||||
*
|
||||
* This results
|
||||
* - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81
|
||||
* - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80
|
||||
*
|
||||
* @returns {Amount}
|
||||
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is
|
||||
* bigger than supported
|
||||
*/
|
||||
|
||||
Amount.prototype._check_limits = function() {
|
||||
if (!Amount.strict_mode) {
|
||||
return this;
|
||||
}
|
||||
if (this._value.isNaN() || this._value.isZero()) {
|
||||
return this;
|
||||
}
|
||||
const absval = this._value.abs();
|
||||
if (this._is_native) {
|
||||
if (absval.greaterThan(MAX_XRP_VALUE)) {
|
||||
throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString());
|
||||
}
|
||||
} else {
|
||||
if (absval.lessThan(MIN_IOU_VALUE)) {
|
||||
throw new Error('Exceeding min value of ' + MIN_IOU_VALUE.toString());
|
||||
}
|
||||
if (absval.greaterThan(MAX_IOU_VALUE)) {
|
||||
throw new Error('Exceeding max value of ' + MAX_IOU_VALUE.toString());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.clone = function(negate) {
|
||||
return this.copyTo(new Amount(this._value), negate);
|
||||
};
|
||||
|
||||
Amount.prototype._copy = function(value) {
|
||||
const copy = this.clone();
|
||||
copy._set_value(value);
|
||||
return copy;
|
||||
};
|
||||
|
||||
Amount.prototype.compareTo = function(to) {
|
||||
const toAmount = to instanceof Amount ? to : Amount.from_json(to);
|
||||
|
||||
if (!this.is_comparable(toAmount)) {
|
||||
throw new Error('Not comparable');
|
||||
}
|
||||
return this._value.comparedTo(toAmount._value);
|
||||
};
|
||||
|
||||
// Make d a copy of this. Returns d.
|
||||
// Modification of objects internally refered to is not allowed.
|
||||
Amount.prototype.copyTo = function(d, negate) {
|
||||
d._value = negate ? this._value.negate() : this._value;
|
||||
d._is_native = this._is_native;
|
||||
d._currency = this._currency;
|
||||
d._issuer = this._issuer;
|
||||
return d;
|
||||
};
|
||||
|
||||
Amount.prototype.currency = function() {
|
||||
return this._currency;
|
||||
};
|
||||
|
||||
Amount.prototype.equals = function(d, ignore_issuer) {
|
||||
if (!(d instanceof Amount)) {
|
||||
return this.equals(Amount.from_json(d));
|
||||
}
|
||||
|
||||
return this.is_valid() && d.is_valid()
|
||||
&& this._is_native === d._is_native
|
||||
&& this._value.equals(d._value)
|
||||
&& (this._is_native || ((this._currency === d._currency)
|
||||
&& (ignore_issuer || this._issuer === d._issuer)));
|
||||
};
|
||||
|
||||
// True if Amounts are valid and both native or non-native.
|
||||
Amount.prototype.is_comparable = function(v) {
|
||||
return this.is_valid() && v.is_valid() && this._is_native === v._is_native;
|
||||
};
|
||||
|
||||
Amount.prototype.is_native = function() {
|
||||
return this._is_native;
|
||||
};
|
||||
|
||||
Amount.prototype.is_negative = function() {
|
||||
return this._value.isNegative();
|
||||
};
|
||||
|
||||
Amount.prototype.is_positive = function() {
|
||||
return !this.is_zero() && !this.is_negative();
|
||||
};
|
||||
|
||||
// Only checks the value. Not the currency and issuer.
|
||||
Amount.prototype.is_valid = function() {
|
||||
return !this._value.isNaN();
|
||||
};
|
||||
|
||||
Amount.prototype.is_valid_full = function() {
|
||||
return this.is_valid() && this._currency !== null
|
||||
&& isValidAddress(this._issuer) && this._issuer !== ACCOUNT_ZERO;
|
||||
};
|
||||
|
||||
Amount.prototype.is_zero = function() {
|
||||
return this._value.isZero();
|
||||
};
|
||||
|
||||
Amount.prototype.issuer = function() {
|
||||
return this._issuer;
|
||||
};
|
||||
|
||||
// Return a new value.
|
||||
Amount.prototype.negate = function() {
|
||||
return this.clone('NEGATE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to correctly interpret an amount as entered by a user.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* XRP 250 => 250000000/XRP
|
||||
* 25.2 XRP => 25200000/XRP
|
||||
* USD 100.40 => 100.4/USD/?
|
||||
* 100 => 100000000/XRP
|
||||
*
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better
|
||||
* understanding:
|
||||
*
|
||||
* // either 3 letter alphabetic currency-code or 3 digit numeric currency-code.
|
||||
* // See ISO 4217
|
||||
* ([A-z]{3}|[0-9]{3})
|
||||
*
|
||||
* // end of string
|
||||
* $
|
||||
*/
|
||||
|
||||
Amount.prototype.parse_human = function(j) {
|
||||
const hex_RE = /^[a-fA-F0-9]{40}$/;
|
||||
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
|
||||
|
||||
let value;
|
||||
let currency;
|
||||
|
||||
const words = j.split(' ').filter(function(word) {
|
||||
return word !== '';
|
||||
});
|
||||
|
||||
function isNumber(s) {
|
||||
return isFinite(s) && s !== '' && s !== null;
|
||||
}
|
||||
|
||||
if (words.length === 1) {
|
||||
if (isNumber(words[0])) {
|
||||
value = words[0];
|
||||
currency = 'XRP';
|
||||
} else {
|
||||
value = words[0].slice(0, -3);
|
||||
currency = words[0].slice(-3);
|
||||
if (!(isNumber(value) && currency.match(currency_RE))) {
|
||||
return new Amount();
|
||||
}
|
||||
}
|
||||
} else if (words.length === 2) {
|
||||
if (isNumber(words[0]) && words[1].match(hex_RE)) {
|
||||
value = words[0];
|
||||
currency = words[1];
|
||||
} else if (words[0].match(currency_RE) && isNumber(words[1])) {
|
||||
value = words[1];
|
||||
currency = words[0];
|
||||
} else if (isNumber(words[0]) && words[1].match(currency_RE)) {
|
||||
value = words[0];
|
||||
currency = words[1];
|
||||
} else {
|
||||
return new Amount();
|
||||
}
|
||||
} else {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
currency = currency.toUpperCase();
|
||||
this.set_currency(currency);
|
||||
this._is_native = (currency === 'XRP');
|
||||
const newValue =
|
||||
(this._is_native ? new XRPValue(value) :
|
||||
new IOUValue(value));
|
||||
this._set_value(newValue);
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.parse_issuer = function(issuer) {
|
||||
this._issuer = issuer;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a price from a BookDirectory index.
|
||||
*
|
||||
* BookDirectory ledger entries each encode the offer price in their index. This
|
||||
* method can decode that information and populate an Amount object with it.
|
||||
*
|
||||
* It is possible not to provide a currency or issuer, but be aware that Amount
|
||||
* objects behave differently based on the currency, so you may get incorrect
|
||||
* results.
|
||||
*
|
||||
* Prices involving demurraging currencies are tricky, since they depend on the
|
||||
* base and counter currencies.
|
||||
*
|
||||
* @param {String} quality 8 hex bytes quality or 32 hex bytes BookDirectory
|
||||
* index.
|
||||
* @param {Currency|String} counterCurrency currency of the resulting Amount
|
||||
* object.
|
||||
* @param {Issuer|String} counterIssuer Issuer of the resulting Amount object.
|
||||
* @param {Object} opts Additional options
|
||||
* @param {Boolean} opts.inverse If true, return the inverse of the price
|
||||
* encoded in the quality.
|
||||
* @param {Currency|String} opts.base_currency The other currency. This plays a
|
||||
* role with interest-bearing or demurrage currencies. In that case the
|
||||
* demurrage has to be applied when the quality is decoded, otherwise the
|
||||
* price will be false.
|
||||
* @param {Date|Number} opts.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @param {Boolean} opts.xrp_as_drops Whether XRP amount should be treated as
|
||||
* drops. When the base currency is XRP, the quality is calculated in drops.
|
||||
* For human use however, we want to think of 1000000 drops as 1 XRP and
|
||||
* prices as per-XRP instead of per-drop.
|
||||
* @return {Amount} self
|
||||
*/
|
||||
Amount.prototype.parse_quality =
|
||||
function(quality, counterCurrency, counterIssuer, opts) {
|
||||
const options = opts || {};
|
||||
|
||||
const baseCurrency = options.base_currency;
|
||||
|
||||
const mantissa_hex = quality.substring(quality.length - 14);
|
||||
const offset_hex = quality.substring(
|
||||
quality.length - 16, quality.length - 14);
|
||||
const mantissa = new IOUValue(mantissa_hex, null, 16);
|
||||
const offset = parseInt(offset_hex, 16) - 100;
|
||||
|
||||
this._currency = normalizeCurrency(counterCurrency);
|
||||
this._issuer = counterIssuer;
|
||||
this._is_native = (this._currency === 'XRP');
|
||||
|
||||
if (this._is_native && baseCurrency === 'XRP') {
|
||||
throw new Error('XRP/XRP quality is not allowed');
|
||||
}
|
||||
|
||||
/*
|
||||
The quality, as stored in the last 64 bits of a directory index, is stored as
|
||||
the quotient of TakerPays/TakerGets.
|
||||
|
||||
When `opts.inverse` is true we are looking at a quality used for determining a
|
||||
`bid` price and it must first be inverted, before our declared base/counter
|
||||
currencies are in line with the price.
|
||||
|
||||
For example:
|
||||
|
||||
quality as stored : 5 USD / 3000000 drops
|
||||
inverted : 3000000 drops / 5 USD
|
||||
*/
|
||||
const valueStr = mantissa.toString() + 'e' + offset.toString();
|
||||
let nativeAdjusted = new IOUValue(valueStr);
|
||||
nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted;
|
||||
|
||||
if (!options.xrp_as_drops) {
|
||||
// `In a currency exchange, the exchange rate is quoted as the units of the
|
||||
// counter currency in terms of a single unit of a base currency`. A
|
||||
// quality is how much taker must `pay` to get ONE `gets` unit thus:
|
||||
// pays ~= counterCurrency
|
||||
// gets ~= baseCurrency.
|
||||
if (this._is_native) {
|
||||
// pay:$price drops get:1 X
|
||||
// pay:($price / 1,000,000) XRP get:1 X
|
||||
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
|
||||
} else if (baseCurrency === 'XRP') {
|
||||
// pay:$price X get:1 drop
|
||||
// pay:($price * 1,000,000) X get:1 XRP
|
||||
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
|
||||
}
|
||||
}
|
||||
if (this._is_native) {
|
||||
this._set_value(
|
||||
new XRPValue(nativeAdjusted.round(6, XRPValue.getBNRoundDown())
|
||||
.toString()));
|
||||
} else {
|
||||
this._set_value(nativeAdjusted);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.parse_number = function(n) {
|
||||
this._is_native = false;
|
||||
this._currency = CURRENCY_ONE;
|
||||
this._issuer = ACCOUNT_ONE;
|
||||
this._set_value(new IOUValue(n));
|
||||
return this;
|
||||
};
|
||||
|
||||
// <-> j
|
||||
Amount.prototype.parse_json = function(j) {
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
// .../.../... notation is not a wire format. But allowed for easier
|
||||
// testing.
|
||||
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (m) {
|
||||
this._currency = normalizeCurrency(m[2]);
|
||||
if (m[3]) {
|
||||
this._issuer = m[3];
|
||||
} else {
|
||||
this._issuer = 'NaN';
|
||||
}
|
||||
this.parse_value(m[1]);
|
||||
} else {
|
||||
this.parse_native(j);
|
||||
this._currency = 'XRP';
|
||||
this._issuer = ACCOUNT_ZERO;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
this.parse_json(String(j));
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (j instanceof Amount) {
|
||||
j.copyTo(this);
|
||||
} else if (j.hasOwnProperty('value')) {
|
||||
this._currency = normalizeCurrency(j.currency);
|
||||
|
||||
if (typeof j.issuer !== 'string') {
|
||||
throw new Error('issuer must be a string');
|
||||
}
|
||||
this._issuer = j.issuer;
|
||||
|
||||
this.parse_value(j.value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Parse a XRP value from untrusted input.
|
||||
// - integer = raw units
|
||||
// - float = with precision 6
|
||||
// XXX Improvements: disallow leading zeros.
|
||||
Amount.prototype.parse_native = function(j) {
|
||||
if (j && typeof j === 'string' && !isNaN(j)) {
|
||||
if (j.indexOf('.') >= 0) {
|
||||
throw new Error('Native amounts must be specified in integer drops');
|
||||
}
|
||||
const value = new XRPValue(j);
|
||||
this._is_native = true;
|
||||
this._set_value(value.divide(bi_xns_unit));
|
||||
} else {
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Parse a non-native value for the json wire format.
|
||||
// Requires _currency to be set!
|
||||
Amount.prototype.parse_value = function(j) {
|
||||
this._is_native = false;
|
||||
const newValue = new IOUValue(j, IOUValue.getBNRoundDown());
|
||||
this._set_value(newValue);
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.set_currency = function(c) {
|
||||
this._currency = normalizeCurrency(c);
|
||||
this._is_native = (c === 'XRP');
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.set_issuer = function(issuer) {
|
||||
this._issuer = issuer;
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.to_number = function() {
|
||||
return Number(this.to_text());
|
||||
};
|
||||
|
||||
|
||||
// this one is needed because Value.abs creates new BigNumber,
|
||||
// and BigNumber constructor is very slow, so we want to
|
||||
// call it only if absolutely necessary
|
||||
function absValue(value: Value): Value {
|
||||
return value.isNegative() ? value.abs() : value;
|
||||
}
|
||||
|
||||
// Convert only value to JSON wire format.
|
||||
Amount.prototype.to_text = function() {
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
if (this._is_native) {
|
||||
return this._value.multiply(bi_xns_unit).toString();
|
||||
}
|
||||
|
||||
// not native
|
||||
const offset = this._value.getExponent() - 15;
|
||||
const sign = this._value.isNegative() ? '-' : '';
|
||||
const mantissa =
|
||||
utils.getMantissa16FromString(absValue(this._value).toString());
|
||||
if (offset !== 0 && (offset < -25 || offset > -4)) {
|
||||
// Use e notation.
|
||||
// XXX Clamp output.
|
||||
return sign + mantissa.toString() + 'e' + offset.toString();
|
||||
}
|
||||
|
||||
const val = '000000000000000000000000000'
|
||||
+ mantissa.toString()
|
||||
+ '00000000000000000000000';
|
||||
const pre = val.substring(0, offset + 43);
|
||||
const post = val.substring(offset + 43);
|
||||
const s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
|
||||
const s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
|
||||
|
||||
return sign + (s_pre ? s_pre[0] : '0')
|
||||
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Format only value in a human-readable format.
|
||||
*
|
||||
* @example
|
||||
* let pretty = amount.to_human({precision: 2});
|
||||
*
|
||||
* @param {Object} options Options for formatter.
|
||||
* @param {Number} options.precision Max. number of digits after decimal point.
|
||||
* @param {Number} options.min_precision Min. number of digits after dec. point.
|
||||
* @param {Boolean} options.skip_empty_fraction Don't show fraction if it
|
||||
* is zero, even if min_precision is set.
|
||||
* @param {Number} options.max_sig_digits Maximum number of significant digits.
|
||||
* Will cut fractional part, but never integer part.
|
||||
* @param {Boolean|String} options.group_sep Whether to show a separator every n
|
||||
* digits, if a string, that value will be used as the separator. Default: ','
|
||||
* @param {Number} options.group_width How many numbers will be grouped
|
||||
* together, default: 3.
|
||||
* @param {Boolean|String} options.signed Whether negative numbers will have a
|
||||
* prefix. If String, that string will be used as the prefix. Default: '-'
|
||||
* @param {Date|Number} options.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {String} amount string
|
||||
*/
|
||||
Amount.prototype.to_human = function(options) {
|
||||
const opts = options || {};
|
||||
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
/* eslint-disable consistent-this */
|
||||
// Apply demurrage/interest
|
||||
const ref = this;
|
||||
/* eslint-enable consistent-this */
|
||||
|
||||
const isNegative = ref._value.isNegative();
|
||||
const valueString = ref._value.abs().toFixed();
|
||||
const parts = valueString.split('.');
|
||||
let int_part = parts[0];
|
||||
let fraction_part = parts.length === 2 ? parts[1] : '';
|
||||
|
||||
int_part = int_part.replace(/^0*/, '');
|
||||
fraction_part = fraction_part.replace(/0*$/, '');
|
||||
|
||||
if (fraction_part.length || !opts.skip_empty_fraction) {
|
||||
// Enforce the maximum number of decimal digits (precision)
|
||||
if (typeof opts.precision === 'number') {
|
||||
let precision = Math.max(0, opts.precision);
|
||||
precision = Math.min(precision, fraction_part.length);
|
||||
const rounded = Number('0.' + fraction_part).toFixed(precision);
|
||||
|
||||
if (rounded < 1) {
|
||||
fraction_part = rounded.substring(2);
|
||||
} else {
|
||||
int_part = (Number(int_part) + 1).toString();
|
||||
fraction_part = '';
|
||||
}
|
||||
|
||||
while (fraction_part.length < precision) {
|
||||
fraction_part = '0' + fraction_part;
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of significant digits (max_sig_digits)
|
||||
if (typeof opts.max_sig_digits === 'number') {
|
||||
// First, we count the significant digits we have.
|
||||
// A zero in the integer part does not count.
|
||||
const int_is_zero = Number(int_part) === 0;
|
||||
let digits = int_is_zero ? 0 : int_part.length;
|
||||
|
||||
// Don't count leading zeros in the fractional part if the integer part is
|
||||
// zero.
|
||||
const sig_frac = int_is_zero
|
||||
? fraction_part.replace(/^0*/, '')
|
||||
: fraction_part;
|
||||
digits += sig_frac.length;
|
||||
|
||||
// Now we calculate where we are compared to the maximum
|
||||
let rounding = digits - opts.max_sig_digits;
|
||||
|
||||
// If we're under the maximum we want to cut no (=0) digits
|
||||
rounding = Math.max(rounding, 0);
|
||||
|
||||
// If we're over the maximum we still only want to cut digits from the
|
||||
// fractional part, not from the integer part.
|
||||
rounding = Math.min(rounding, fraction_part.length);
|
||||
|
||||
// Now we cut `rounding` digits off the right.
|
||||
if (rounding > 0) {
|
||||
fraction_part = fraction_part.slice(0, -rounding);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce the minimum number of decimal digits (min_precision)
|
||||
if (typeof opts.min_precision === 'number') {
|
||||
opts.min_precision = Math.max(0, opts.min_precision);
|
||||
while (fraction_part.length < opts.min_precision) {
|
||||
fraction_part += '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.group_sep !== false) {
|
||||
const sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
|
||||
const groups = utils.chunkString(int_part, opts.group_width || 3, true);
|
||||
int_part = groups.join(sep);
|
||||
}
|
||||
|
||||
let formatted = '';
|
||||
if (isNegative && opts.signed !== false) {
|
||||
formatted += '-';
|
||||
}
|
||||
|
||||
formatted += int_part.length ? int_part : '0';
|
||||
formatted += fraction_part.length ? '.' + fraction_part : '';
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
Amount.prototype.to_human_full = function(options) {
|
||||
const opts = options || {};
|
||||
const value = this.to_human(opts);
|
||||
const currency = this._currency;
|
||||
const issuer = this._issuer;
|
||||
const base = value + '/' + currency;
|
||||
return this.is_native() ? base : (base + '/' + issuer);
|
||||
};
|
||||
|
||||
Amount.prototype.to_json = function() {
|
||||
if (this._is_native) {
|
||||
return this.to_text();
|
||||
}
|
||||
|
||||
const amount_json = {
|
||||
value: this.to_text(),
|
||||
currency: this._currency
|
||||
};
|
||||
|
||||
if (isValidAddress(this._issuer)) {
|
||||
amount_json.issuer = this._issuer;
|
||||
}
|
||||
|
||||
return amount_json;
|
||||
};
|
||||
|
||||
Amount.prototype.to_text_full = function() {
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
return this._is_native
|
||||
? this.to_human() + '/XRP'
|
||||
: this.to_text() + '/' + this._currency + '/' + this._issuer;
|
||||
};
|
||||
|
||||
// For debugging.
|
||||
Amount.prototype.not_equals_why = function(d, ignore_issuer) {
|
||||
if (typeof d === 'string') {
|
||||
return this.not_equals_why(Amount.from_json(d));
|
||||
}
|
||||
if (!(d instanceof Amount)) {
|
||||
return 'Not an Amount';
|
||||
}
|
||||
if (!this.is_valid() || !d.is_valid()) {
|
||||
return 'Invalid amount.';
|
||||
}
|
||||
if (this._is_native !== d._is_native) {
|
||||
return 'Native mismatch.';
|
||||
}
|
||||
|
||||
const type = this._is_native ? 'XRP' : 'Non-XRP';
|
||||
if (!this._value.isZero() && this._value.negate().equals(d._value)) {
|
||||
return type + ' sign differs.';
|
||||
}
|
||||
if (!this._value.equals(d._value)) {
|
||||
return type + ' value differs.';
|
||||
}
|
||||
if (!this._is_native) {
|
||||
if (this._currency !== d._currency) {
|
||||
return 'Non-XRP currency differs.';
|
||||
}
|
||||
if (!ignore_issuer && this._issuer !== d._issuer) {
|
||||
return 'Non-XRP issuer differs: ' + d._issuer + '/' + this._issuer;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Amount = Amount;
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,496 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Utils = require('./orderbookutils');
|
||||
const {toHexCurrency} = require('./currency');
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
function assertValidLegOneOffer(legOneOffer, message) {
|
||||
assert(legOneOffer);
|
||||
assert.strictEqual(typeof legOneOffer, 'object', message);
|
||||
assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message);
|
||||
assertValidNumber(legOneOffer.TakerGets, message);
|
||||
}
|
||||
|
||||
function AutobridgeCalculator(currencyGets, currencyPays,
|
||||
legOneOffers, legTwoOffers, issuerGets, issuerPays
|
||||
) {
|
||||
this._currencyGets = currencyGets;
|
||||
this._currencyPays = currencyPays;
|
||||
this._currencyGetsHex = toHexCurrency(currencyGets);
|
||||
this._currencyPaysHex = toHexCurrency(currencyPays);
|
||||
this._issuerGets = issuerGets;
|
||||
this._issuerPays = issuerPays;
|
||||
this.legOneOffers = _.cloneDeep(legOneOffers);
|
||||
this.legTwoOffers = _.cloneDeep(legTwoOffers);
|
||||
|
||||
this._ownerFundsLeftover = {};
|
||||
}
|
||||
|
||||
const NULL_AMOUNT = Utils.normalizeAmount('0');
|
||||
|
||||
/**
|
||||
* Calculates an ordered array of autobridged offers by quality
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.calculate = function(callback) {
|
||||
|
||||
const legOnePointer = 0;
|
||||
const legTwoPointer = 0;
|
||||
|
||||
const offersAutobridged = [];
|
||||
|
||||
this.clearOwnerFundsLeftover();
|
||||
|
||||
this._calculateInternal(legOnePointer, legTwoPointer, offersAutobridged,
|
||||
callback);
|
||||
};
|
||||
|
||||
AutobridgeCalculator.prototype._calculateInternal = function(
|
||||
legOnePointer_, legTwoPointer_, offersAutobridged, callback
|
||||
) {
|
||||
|
||||
// Amount class is calling _check_limits after each operation in strict mode,
|
||||
// and _check_limits is very computationally expensive, so we turning it off
|
||||
// whle doing calculations
|
||||
this._oldMode = Amount.strict_mode;
|
||||
Amount.strict_mode = false;
|
||||
|
||||
let legOnePointer = legOnePointer_;
|
||||
let legTwoPointer = legTwoPointer_;
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) {
|
||||
// manually implement cooperative multitasking that yields after 30ms
|
||||
// of execution so user's browser stays responsive
|
||||
const lasted = (Date.now() - startTime);
|
||||
if (lasted > 30) {
|
||||
setTimeout(this._calculateInternal.bind(this, legOnePointer,
|
||||
legTwoPointer, offersAutobridged, callback), 0);
|
||||
|
||||
Amount.strict_mode = this._oldMode;
|
||||
return;
|
||||
}
|
||||
|
||||
const legOneOffer = this.legOneOffers[legOnePointer];
|
||||
const legTwoOffer = this.legTwoOffers[legTwoPointer];
|
||||
const leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account);
|
||||
let autobridgedOffer;
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
this.unclampLegOneOwnerFunds(legOneOffer);
|
||||
} else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) {
|
||||
this.adjustLegOneFundedAmount(legOneOffer);
|
||||
}
|
||||
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
|
||||
if (legOneTakerGetsFunded.is_zero()) {
|
||||
legOnePointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (legTwoTakerPaysFunded.is_zero()) {
|
||||
legTwoPointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// using private fields for speed
|
||||
if (legOneTakerGetsFunded._value.comparedTo(
|
||||
legTwoTakerPaysFunded._value) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legTwoPointer++;
|
||||
} else if (legTwoTakerPaysFunded._value.comparedTo(
|
||||
legOneTakerGetsFunded._value) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
} else {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithoutClamps(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
legTwoPointer++;
|
||||
}
|
||||
|
||||
offersAutobridged.push(autobridgedOffer);
|
||||
}
|
||||
|
||||
Amount.strict_mode = this._oldMode;
|
||||
callback(offersAutobridged);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one is greater than the input to leg two.
|
||||
* Therefore, we must effectively clamp leg one output to leg two input.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
|
||||
|
||||
this.clampLegOneOwnerFunds(legOneOffer);
|
||||
} else {
|
||||
// Update funded amount since leg one offer was not completely consumed
|
||||
const updatedTakerGetsFunded = legOneTakerGetsFunded
|
||||
.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded);
|
||||
}
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the input from leg two is greater than the output to leg one.
|
||||
* Therefore, we must effectively clamp leg two input to leg one output.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
const legTwoQuality = Utils.getOfferQuality(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
|
||||
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
// Update funded amount since leg two offer was not completely consumed
|
||||
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets)
|
||||
.subtract(autobridgedTakerGets)
|
||||
.to_text();
|
||||
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
|
||||
.subtract(legOneTakerGetsFunded)
|
||||
.to_text();
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one and the input to leg two are the same.
|
||||
* We do not need to clamp either.
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear owner funds leftovers
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() {
|
||||
this._ownerFundsLeftover = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset owner funds leftovers for an account to 0
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) {
|
||||
this._ownerFundsLeftover[account] = NULL_AMOUNT.clone();
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve leftover funds found after clamping leg one by account
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) {
|
||||
let amount = this._ownerFundsLeftover[account];
|
||||
|
||||
if (!amount) {
|
||||
amount = NULL_AMOUNT.clone();
|
||||
}
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add funds to account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.addLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account)
|
||||
.add(amount);
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an autobridged offer and compute synthetic values (e.g. quality)
|
||||
*
|
||||
* @param {Amount} takerGets
|
||||
* @param {Amount} takerPays
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.formatAutobridgedOffer =
|
||||
function(takerGets, takerPays) {
|
||||
assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid');
|
||||
assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid');
|
||||
|
||||
const autobridgedOffer = {};
|
||||
const quality = takerPays.divide(takerGets);
|
||||
|
||||
autobridgedOffer.TakerGets = {
|
||||
value: takerGets.to_text(),
|
||||
currency: this._currencyGetsHex,
|
||||
issuer: this._issuerGets
|
||||
};
|
||||
|
||||
autobridgedOffer.TakerPays = {
|
||||
value: takerPays.to_text(),
|
||||
currency: this._currencyPaysHex,
|
||||
issuer: this._issuerPays
|
||||
};
|
||||
|
||||
autobridgedOffer.quality = quality.to_text();
|
||||
|
||||
autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value;
|
||||
autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value;
|
||||
|
||||
autobridgedOffer.autobridged = true;
|
||||
|
||||
autobridgedOffer.BookDirectory =
|
||||
Utils.convertOfferQualityToHexFromText(autobridgedOffer.quality);
|
||||
autobridgedOffer.qualityHex = autobridgedOffer.BookDirectory;
|
||||
|
||||
return autobridgedOffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove funds clamp on leg one offer. This is necessary when the two offers
|
||||
* are owned by the same account. In this case, it doesn't matter if offer one
|
||||
* is not fully funded. Leg one out goes to leg two in and since its the same
|
||||
* account, an infinite amount can flow.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
this.setLegOneTakerGetsFunded(
|
||||
legOneOffer,
|
||||
Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
|
||||
this._issuerPays)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply clamp back on leg one offer after a round of autobridge calculation
|
||||
* completes. We must reapply clamps that have been removed because we cannot
|
||||
* guarantee that the next offer from leg two will also be from the same
|
||||
* account.
|
||||
*
|
||||
* When we reapply, it could happen that the amount of TakerGets left after
|
||||
* the autobridge calculation is less than the original funded amount. In this
|
||||
* case, we have extra funds we can use towards unfunded offers with worse
|
||||
* quality by the same owner.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
const takerGets = Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
|
||||
this._issuerPays);
|
||||
|
||||
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
|
||||
// After clamping, TakerGets is still greater than initial funded amount
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded);
|
||||
} else {
|
||||
const updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, takerGets);
|
||||
this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Increase leg one offer funded amount with extra funds found after applying
|
||||
* clamp.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.adjustLegOneFundedAmount =
|
||||
function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
|
||||
|
||||
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPays)
|
||||
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
|
||||
|
||||
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPays)) >= 0
|
||||
) {
|
||||
// There are enough extra funds to fully fund the offer
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
|
||||
this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
} else {
|
||||
// There are not enough extra funds to fully fund the offer
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, fundedSum);
|
||||
this.resetOwnerFundsLeftover(legOneOffer.Account);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets funded amount for a IOU:XRP offer. Also calculates taker
|
||||
* pays funded using offer quality and updates is_fully_funded flag
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGetsFunded
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGetsFunded =
|
||||
function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
|
||||
legOneOffer.taker_pays_funded = takerGetsFunded
|
||||
.multiply(Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays))
|
||||
.to_text();
|
||||
|
||||
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
|
||||
legOneOffer.is_fully_funded = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets amount for a IOU:XRP offer. Also calculates taker pays
|
||||
* using offer quality
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGets
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGets =
|
||||
function(legOneOffer, takerGets) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
legOneOffer.TakerGets = takerGets.to_text();
|
||||
legOneOffer.TakerPays = takerGets.multiply(legOneQuality).to_json();
|
||||
};
|
||||
|
||||
module.exports = AutobridgeCalculator;
|
||||
@@ -1,485 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint-disable max-len,spaced-comment,array-bracket-spacing,key-spacing*/
|
||||
/*eslint-disable no-multi-spaces,comma-spacing*/
|
||||
/*eslint-disable no-multi-spaces:0,space-in-brackets:0,key-spacing:0,comma-spacing:0*/
|
||||
|
||||
/**
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*
|
||||
* For reference, see rippled's definition:
|
||||
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol
|
||||
* /SField.cpp
|
||||
*/
|
||||
|
||||
exports.types = [
|
||||
undefined,
|
||||
|
||||
// Common
|
||||
'Int16', // 1
|
||||
'Int32', // 2
|
||||
'Int64', // 3
|
||||
'Hash128', // 4
|
||||
'Hash256', // 5
|
||||
'Amount', // 6
|
||||
'VL', // 7
|
||||
'Account', // 8
|
||||
|
||||
// 9-13 reserved
|
||||
undefined, // 9
|
||||
undefined, // 10
|
||||
undefined, // 11
|
||||
undefined, // 12
|
||||
undefined, // 13
|
||||
|
||||
'Object', // 14
|
||||
'Array', // 15
|
||||
|
||||
// Uncommon
|
||||
'Int8', // 16
|
||||
'Hash160', // 17
|
||||
'PathSet', // 18
|
||||
'Vector256' // 19
|
||||
];
|
||||
|
||||
/**
|
||||
* Field type map.
|
||||
*
|
||||
* Mapping of field type id to field type name.
|
||||
*/
|
||||
|
||||
const FIELDS_MAP = exports.fields = {
|
||||
// Common types
|
||||
1: { // Int16
|
||||
1: 'LedgerEntryType',
|
||||
2: 'TransactionType',
|
||||
3: 'SignerWeight'
|
||||
},
|
||||
2: { // Int32
|
||||
2: 'Flags',
|
||||
3: 'SourceTag',
|
||||
4: 'Sequence',
|
||||
5: 'PreviousTxnLgrSeq',
|
||||
6: 'LedgerSequence',
|
||||
7: 'CloseTime',
|
||||
8: 'ParentCloseTime',
|
||||
9: 'SigningTime',
|
||||
10: 'Expiration',
|
||||
11: 'TransferRate',
|
||||
12: 'WalletSize',
|
||||
13: 'OwnerCount',
|
||||
14: 'DestinationTag',
|
||||
// Skip 15
|
||||
16: 'HighQualityIn',
|
||||
17: 'HighQualityOut',
|
||||
18: 'LowQualityIn',
|
||||
19: 'LowQualityOut',
|
||||
20: 'QualityIn',
|
||||
21: 'QualityOut',
|
||||
22: 'StampEscrow',
|
||||
23: 'BondAmount',
|
||||
24: 'LoadFee',
|
||||
25: 'OfferSequence',
|
||||
26: 'FirstLedgerSequence',
|
||||
27: 'LastLedgerSequence',
|
||||
28: 'TransactionIndex',
|
||||
29: 'OperationLimit',
|
||||
30: 'ReferenceFeeUnits',
|
||||
31: 'ReserveBase',
|
||||
32: 'ReserveIncrement',
|
||||
33: 'SetFlag',
|
||||
34: 'ClearFlag',
|
||||
35: 'SignerQuorum',
|
||||
36: 'CancelAfter',
|
||||
37: 'FinishAfter',
|
||||
38: 'SignerListID'
|
||||
},
|
||||
3: { // Int64
|
||||
1: 'IndexNext',
|
||||
2: 'IndexPrevious',
|
||||
3: 'BookNode',
|
||||
4: 'OwnerNode',
|
||||
5: 'BaseFee',
|
||||
6: 'ExchangeRate',
|
||||
7: 'LowNode',
|
||||
8: 'HighNode'
|
||||
},
|
||||
4: { // Hash128
|
||||
1: 'EmailHash'
|
||||
},
|
||||
5: { // Hash256
|
||||
1: 'LedgerHash',
|
||||
2: 'ParentHash',
|
||||
3: 'TransactionHash',
|
||||
4: 'AccountHash',
|
||||
5: 'PreviousTxnID',
|
||||
6: 'LedgerIndex',
|
||||
7: 'WalletLocator',
|
||||
8: 'RootIndex',
|
||||
9: 'AccountTxnID',
|
||||
16: 'BookDirectory',
|
||||
17: 'InvoiceID',
|
||||
18: 'Nickname',
|
||||
19: 'Amendment',
|
||||
20: 'TicketID',
|
||||
21: 'Digest'
|
||||
},
|
||||
6: { // Amount
|
||||
1: 'Amount',
|
||||
2: 'Balance',
|
||||
3: 'LimitAmount',
|
||||
4: 'TakerPays',
|
||||
5: 'TakerGets',
|
||||
6: 'LowLimit',
|
||||
7: 'HighLimit',
|
||||
8: 'Fee',
|
||||
9: 'SendMax',
|
||||
16: 'MinimumOffer',
|
||||
17: 'RippleEscrow',
|
||||
18: 'DeliveredAmount'
|
||||
},
|
||||
7: { // VL
|
||||
1: 'PublicKey',
|
||||
2: 'MessageKey',
|
||||
3: 'SigningPubKey',
|
||||
4: 'TxnSignature',
|
||||
5: 'Generator',
|
||||
6: 'Signature',
|
||||
7: 'Domain',
|
||||
8: 'FundCode',
|
||||
9: 'RemoveCode',
|
||||
10: 'ExpireCode',
|
||||
11: 'CreateCode',
|
||||
12: 'MemoType',
|
||||
13: 'MemoData',
|
||||
14: 'MemoFormat',
|
||||
17: 'Proof'
|
||||
},
|
||||
8: { // Account
|
||||
1: 'Account',
|
||||
2: 'Owner',
|
||||
3: 'Destination',
|
||||
4: 'Issuer',
|
||||
7: 'Target',
|
||||
8: 'RegularKey'
|
||||
},
|
||||
14: { // Object
|
||||
1: undefined, // end of Object
|
||||
2: 'TransactionMetaData',
|
||||
3: 'CreatedNode',
|
||||
4: 'DeletedNode',
|
||||
5: 'ModifiedNode',
|
||||
6: 'PreviousFields',
|
||||
7: 'FinalFields',
|
||||
8: 'NewFields',
|
||||
9: 'TemplateEntry',
|
||||
10: 'Memo',
|
||||
11: 'SignerEntry',
|
||||
16: 'Signer'
|
||||
},
|
||||
15: { // Array
|
||||
1: undefined, // end of Array
|
||||
2: 'SigningAccounts',
|
||||
3: 'Signers',
|
||||
4: 'SignerEntries',
|
||||
5: 'Template',
|
||||
6: 'Necessary',
|
||||
7: 'Sufficient',
|
||||
8: 'AffectedNodes',
|
||||
9: 'Memos'
|
||||
},
|
||||
|
||||
// Uncommon types
|
||||
16: { // Int8
|
||||
1: 'CloseResolution',
|
||||
2: 'Method',
|
||||
3: 'TransactionResult'
|
||||
},
|
||||
17: { // Hash160
|
||||
1: 'TakerPaysCurrency',
|
||||
2: 'TakerPaysIssuer',
|
||||
3: 'TakerGetsCurrency',
|
||||
4: 'TakerGetsIssuer'
|
||||
},
|
||||
18: { // PathSet
|
||||
1: 'Paths'
|
||||
},
|
||||
19: { // Vector256
|
||||
1: 'Indexes',
|
||||
2: 'Hashes',
|
||||
3: 'Amendments'
|
||||
}
|
||||
};
|
||||
|
||||
const INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
|
||||
Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
|
||||
INVERSE_FIELDS_MAP[FIELDS_MAP[k1][k2]] = [Number(k1), Number(k2)];
|
||||
});
|
||||
});
|
||||
|
||||
const REQUIRED = exports.REQUIRED = 0;
|
||||
const OPTIONAL = exports.OPTIONAL = 1;
|
||||
const DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
const base = [
|
||||
[ 'TransactionType' , REQUIRED ],
|
||||
[ 'Flags' , OPTIONAL ],
|
||||
[ 'SourceTag' , OPTIONAL ],
|
||||
[ 'LastLedgerSequence' , OPTIONAL ],
|
||||
[ 'Account' , REQUIRED ],
|
||||
[ 'Sequence' , REQUIRED ],
|
||||
[ 'Fee' , REQUIRED ],
|
||||
[ 'OperationLimit' , OPTIONAL ],
|
||||
[ 'SigningPubKey' , REQUIRED ],
|
||||
[ 'TxnSignature' , OPTIONAL ],
|
||||
[ 'AccountTxnID' , OPTIONAL ],
|
||||
[ 'Memos' , OPTIONAL ],
|
||||
[ 'Signers' , OPTIONAL ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
AccountSet: [3].concat(base, [
|
||||
['EmailHash' , OPTIONAL],
|
||||
['WalletLocator' , OPTIONAL],
|
||||
['WalletSize' , OPTIONAL],
|
||||
['MessageKey' , OPTIONAL],
|
||||
['Domain' , OPTIONAL],
|
||||
['TransferRate' , OPTIONAL],
|
||||
['SetFlag' , OPTIONAL],
|
||||
['ClearFlag' , OPTIONAL]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
['LimitAmount' , OPTIONAL],
|
||||
['QualityIn' , OPTIONAL],
|
||||
['QualityOut' , OPTIONAL]
|
||||
]),
|
||||
OfferCreate: [7].concat(base, [
|
||||
['TakerPays' , REQUIRED],
|
||||
['TakerGets' , REQUIRED],
|
||||
['Expiration' , OPTIONAL],
|
||||
['OfferSequence' , OPTIONAL]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
['OfferSequence' , REQUIRED]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
['RegularKey' , OPTIONAL]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
['Destination' , REQUIRED],
|
||||
['Amount' , REQUIRED],
|
||||
['SendMax' , OPTIONAL],
|
||||
['Paths' , DEFAULT],
|
||||
['InvoiceID' , OPTIONAL],
|
||||
['DestinationTag' , OPTIONAL]
|
||||
]),
|
||||
Contract: [9].concat(base, [
|
||||
['Expiration' , REQUIRED],
|
||||
['BondAmount' , REQUIRED],
|
||||
['StampEscrow' , REQUIRED],
|
||||
['RippleEscrow' , REQUIRED],
|
||||
['CreateCode' , OPTIONAL],
|
||||
['FundCode' , OPTIONAL],
|
||||
['RemoveCode' , OPTIONAL],
|
||||
['ExpireCode' , OPTIONAL]
|
||||
]),
|
||||
RemoveContract: [10].concat(base, [
|
||||
['Target' , REQUIRED]
|
||||
]),
|
||||
EnableFeature: [100].concat(base, [
|
||||
['Feature' , REQUIRED]
|
||||
]),
|
||||
EnableAmendment: [100].concat(base, [
|
||||
['Amendment' , REQUIRED]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
['BaseFee' , REQUIRED],
|
||||
['ReferenceFeeUnits' , REQUIRED],
|
||||
['ReserveBase' , REQUIRED],
|
||||
['ReserveIncrement' , REQUIRED]
|
||||
]),
|
||||
TicketCreate: [10].concat(base, [
|
||||
['Target' , OPTIONAL],
|
||||
['Expiration' , OPTIONAL]
|
||||
]),
|
||||
TicketCancel: [11].concat(base, [
|
||||
['TicketID' , REQUIRED]
|
||||
]),
|
||||
SignerListSet: [12].concat(base, [
|
||||
['SignerQuorum', REQUIRED],
|
||||
['SignerEntries', OPTIONAL]
|
||||
]),
|
||||
SuspendedPaymentCreate: [1].concat(base, [
|
||||
['Destination' , REQUIRED],
|
||||
['Amount' , REQUIRED],
|
||||
['Digest' , OPTIONAL],
|
||||
['CancelAfter' , OPTIONAL],
|
||||
['FinishAfter' , OPTIONAL],
|
||||
['DestinationTag' , OPTIONAL]
|
||||
]),
|
||||
SuspendedPaymentFinish: [2].concat(base, [
|
||||
['Owner' , REQUIRED],
|
||||
['OfferSequence' , REQUIRED],
|
||||
['Method' , OPTIONAL],
|
||||
['Digest' , OPTIONAL],
|
||||
['Proof' , OPTIONAL]
|
||||
]),
|
||||
SuspendedPaymentCancel: [4].concat(base, [
|
||||
['Owner' , REQUIRED],
|
||||
['OfferSequence' , REQUIRED]
|
||||
])
|
||||
};
|
||||
|
||||
const sleBase = [
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED]
|
||||
];
|
||||
|
||||
exports.ledger = {
|
||||
AccountRoot: [97].concat(sleBase,[
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['TransferRate', OPTIONAL],
|
||||
['WalletSize', OPTIONAL],
|
||||
['OwnerCount', REQUIRED],
|
||||
['EmailHash', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['AccountTxnID', OPTIONAL],
|
||||
['WalletLocator', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['MessageKey', OPTIONAL],
|
||||
['Domain', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['RegularKey', OPTIONAL]]),
|
||||
Contract: [99].concat(sleBase,[
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', REQUIRED],
|
||||
['BondAmount', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['Balance', REQUIRED],
|
||||
['FundCode', OPTIONAL],
|
||||
['RemoveCode', OPTIONAL],
|
||||
['ExpireCode', OPTIONAL],
|
||||
['CreateCode', OPTIONAL],
|
||||
['Account', REQUIRED],
|
||||
['Owner', REQUIRED],
|
||||
['Issuer', REQUIRED]]),
|
||||
DirectoryNode: [100].concat(sleBase,[
|
||||
['IndexNext', OPTIONAL],
|
||||
['IndexPrevious', OPTIONAL],
|
||||
['ExchangeRate', OPTIONAL],
|
||||
['RootIndex', REQUIRED],
|
||||
['Owner', OPTIONAL],
|
||||
['TakerPaysCurrency', OPTIONAL],
|
||||
['TakerPaysIssuer', OPTIONAL],
|
||||
['TakerGetsCurrency', OPTIONAL],
|
||||
['TakerGetsIssuer', OPTIONAL],
|
||||
['Indexes', REQUIRED]]),
|
||||
EnabledFeatures: [102].concat(sleBase,[
|
||||
['Features', REQUIRED]]),
|
||||
FeeSettings: [115].concat(sleBase,[
|
||||
['ReferenceFeeUnits', REQUIRED],
|
||||
['ReserveBase', REQUIRED],
|
||||
['ReserveIncrement', REQUIRED],
|
||||
['BaseFee', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL]]),
|
||||
GeneratorMap: [103].concat(sleBase,[
|
||||
['Generator', REQUIRED]]),
|
||||
LedgerHashes: [104].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['FirstLedgerSequence', OPTIONAL],
|
||||
['LastLedgerSequence', OPTIONAL],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Hashes', REQUIRED]]),
|
||||
Nickname: [110].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['MinimumOffer', OPTIONAL],
|
||||
['Account', REQUIRED]]),
|
||||
Offer: [111].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['Sequence', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['Expiration', OPTIONAL],
|
||||
['BookNode', REQUIRED],
|
||||
['OwnerNode', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['BookDirectory', REQUIRED],
|
||||
['TakerPays', REQUIRED],
|
||||
['TakerGets', REQUIRED],
|
||||
['Account', REQUIRED]]),
|
||||
RippleState: [114].concat(sleBase,[
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED],
|
||||
['HighQualityIn', OPTIONAL],
|
||||
['HighQualityOut', OPTIONAL],
|
||||
['LowQualityIn', OPTIONAL],
|
||||
['LowQualityOut', OPTIONAL],
|
||||
['LowNode', OPTIONAL],
|
||||
['HighNode', OPTIONAL],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]]),
|
||||
SignerList: [83].concat(sleBase,[
|
||||
['OwnerNode', REQUIRED],
|
||||
['SignerQuorum', REQUIRED],
|
||||
['SignerEntries', REQUIRED],
|
||||
['SignerListID', REQUIRED],
|
||||
['PreviousTxnID', REQUIRED],
|
||||
['PreviousTxnLgrSeq', REQUIRED]
|
||||
])
|
||||
};
|
||||
|
||||
exports.metadata = [
|
||||
['DeliveredAmount' , OPTIONAL],
|
||||
['TransactionIndex' , REQUIRED],
|
||||
['TransactionResult' , REQUIRED],
|
||||
['AffectedNodes' , REQUIRED]
|
||||
];
|
||||
|
||||
exports.ter = {
|
||||
tesSUCCESS : 0,
|
||||
tecCLAIM : 100,
|
||||
tecPATH_PARTIAL : 101,
|
||||
tecUNFUNDED_ADD : 102,
|
||||
tecUNFUNDED_OFFER : 103,
|
||||
tecUNFUNDED_PAYMENT : 104,
|
||||
tecFAILED_PROCESSING : 105,
|
||||
tecDIR_FULL : 121,
|
||||
tecINSUF_RESERVE_LINE : 122,
|
||||
tecINSUF_RESERVE_OFFER : 123,
|
||||
tecNO_DST : 124,
|
||||
tecNO_DST_INSUF_XRP : 125,
|
||||
tecNO_LINE_INSUF_RESERVE : 126,
|
||||
tecNO_LINE_REDUNDANT : 127,
|
||||
tecPATH_DRY : 128,
|
||||
tecUNFUNDED : 129, // Deprecated, old ambiguous unfunded.
|
||||
tecNO_ALTERNATIVE_KEY : 130,
|
||||
tecNO_REGULAR_KEY : 131,
|
||||
tecOWNERS : 132,
|
||||
tecNO_ISSUER : 133,
|
||||
tecNO_AUTH : 134,
|
||||
tecNO_LINE : 135,
|
||||
tecINSUFF_FEE : 136,
|
||||
tecFROZEN : 137,
|
||||
tecNO_TARGET : 138,
|
||||
tecNO_PERMISSION : 139,
|
||||
tecNO_ENTRY : 140,
|
||||
tecINSUFFICIENT_RESERVE : 141,
|
||||
tecNEED_MASTER_KEY : 142,
|
||||
tecDST_TAG_NEEDED : 143,
|
||||
tecINTERNAL : 144,
|
||||
tecOVERSIZE : 145
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
|
||||
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
CURRENCY_ZERO: '0000000000000000000000000000000000000000',
|
||||
CURRENCY_ONE: '0000000000000000000000000000000000000001'
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
|
||||
function isISOCode(currency) {
|
||||
return /^[A-Z0-9]{3}$/.test(currency);
|
||||
}
|
||||
|
||||
function isHexCurrency(currency) {
|
||||
return /[A-Fa-f0-9]{40}/.test(currency);
|
||||
}
|
||||
|
||||
function getISOCode(hexCurrency) {
|
||||
const bytes = new Buffer(hexCurrency, 'hex');
|
||||
if (_.every(bytes, octet => octet === 0)) {
|
||||
return 'XRP';
|
||||
}
|
||||
if (!_.every(bytes, (octet, i) => octet === 0 || (i >= 12 && i <= 14))) {
|
||||
return null;
|
||||
}
|
||||
const code = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
return isISOCode(code) ? code : null;
|
||||
}
|
||||
|
||||
function normalizeCurrency(currency) {
|
||||
if (isISOCode(currency.toUpperCase())) {
|
||||
return currency.toUpperCase();
|
||||
} else if (isHexCurrency(currency)) {
|
||||
const code = getISOCode(currency);
|
||||
return code === null ? currency.toUpperCase() : code;
|
||||
}
|
||||
throw new Error('invalid currency');
|
||||
}
|
||||
|
||||
function toHexCurrency(currency) {
|
||||
if (isISOCode(currency)) {
|
||||
const bytes = new Buffer(20);
|
||||
bytes.fill(0);
|
||||
if (currency !== 'XRP') {
|
||||
bytes[12] = currency.charCodeAt(0);
|
||||
bytes[13] = currency.charCodeAt(1);
|
||||
bytes[14] = currency.charCodeAt(2);
|
||||
}
|
||||
return bytes.toString('hex').toUpperCase();
|
||||
} else if (isHexCurrency(currency)) {
|
||||
return currency.toUpperCase();
|
||||
}
|
||||
throw new Error('invalid currency');
|
||||
}
|
||||
|
||||
function isValidCurrency(currency) {
|
||||
return isISOCode(currency.toUpperCase()) || isHexCurrency(currency);
|
||||
}
|
||||
|
||||
exports.normalizeCurrency = normalizeCurrency;
|
||||
exports.isValidCurrency = isValidCurrency;
|
||||
exports.toHexCurrency = toHexCurrency;
|
||||
@@ -1,21 +0,0 @@
|
||||
'use strict';
|
||||
exports.Remote = require('./remote').Remote;
|
||||
exports.Request = require('./request').Request;
|
||||
exports.Amount = require('./amount').Amount;
|
||||
exports.Account = require('./account').Account;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.RippleError = require('./rippleerror').RippleError;
|
||||
exports.utils = require('./utils');
|
||||
exports.Server = require('./server').Server;
|
||||
|
||||
exports._test = {
|
||||
Log: require('./log'),
|
||||
PathFind: require('./pathfind').PathFind,
|
||||
TransactionManager: require('./transactionmanager').TransactionManager,
|
||||
TransactionQueue: require('./transactionqueue').TransactionQueue,
|
||||
RangeSet: require('./rangeset').RangeSet,
|
||||
OrderbookUtils: require('./orderbookutils'),
|
||||
constants: require('./constants')
|
||||
};
|
||||
193
src/core/log.js
193
src/core/log.js
@@ -1,193 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
/**
|
||||
* Logging functionality for ripple-lib and any applications built on it.
|
||||
*
|
||||
* @param {String} namespace logging prefix
|
||||
* @return {Void} this function does not return...
|
||||
*/
|
||||
function Log(namespace) {
|
||||
if (!namespace) {
|
||||
this._namespace = [];
|
||||
} else if (Array.isArray(namespace)) {
|
||||
this._namespace = namespace;
|
||||
} else {
|
||||
this._namespace = [String(namespace)];
|
||||
}
|
||||
|
||||
this._prefix = this._namespace.concat(['']).join(': ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sub-logger.
|
||||
*
|
||||
* You can have a hierarchy of loggers.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var log = require('ripple').log.sub('server');
|
||||
*
|
||||
* log.info('connection successful');
|
||||
* // prints: 'server: connection successful'
|
||||
*
|
||||
* @param {String} namespace logging prefix
|
||||
* @return {Log} sub logger
|
||||
*/
|
||||
Log.prototype.sub = function(namespace) {
|
||||
const subNamespace = this._namespace.slice();
|
||||
|
||||
if (namespace && typeof namespace === 'string') {
|
||||
subNamespace.push(namespace);
|
||||
}
|
||||
|
||||
const subLogger = new Log(subNamespace);
|
||||
subLogger._setParent(this);
|
||||
return subLogger;
|
||||
};
|
||||
|
||||
Log.prototype._setParent = function(parentLogger) {
|
||||
this._parent = parentLogger;
|
||||
};
|
||||
|
||||
Log.makeLevel = function(level) {
|
||||
return function() {
|
||||
const args = Array.prototype.slice.apply(arguments);
|
||||
args[0] = this._prefix + args[0];
|
||||
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(1)]));
|
||||
};
|
||||
};
|
||||
|
||||
Log.prototype.debug = Log.makeLevel(1);
|
||||
Log.prototype.info = Log.makeLevel(2);
|
||||
Log.prototype.warn = Log.makeLevel(3);
|
||||
Log.prototype.error = Log.makeLevel(4);
|
||||
|
||||
/**
|
||||
* @param {String} message
|
||||
* @param {Array} details
|
||||
* @return {Array} prepared log info
|
||||
*/
|
||||
|
||||
function getLogInfo(message, args) {
|
||||
const stack = new Error().stack;
|
||||
|
||||
return [
|
||||
// Timestamp
|
||||
'[' + new Date().toISOString() + ']',
|
||||
message,
|
||||
'--',
|
||||
// Location
|
||||
(typeof stack === 'string') ? stack.split('\n')[4].replace(/^\s+/, '') : '',
|
||||
'\n'
|
||||
].concat(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} log level
|
||||
* @param {Array} log info
|
||||
*/
|
||||
|
||||
function logMessage(logLevel, args) {
|
||||
switch (logLevel) {
|
||||
case 1:
|
||||
case 2:
|
||||
console.log.apply(console, args);
|
||||
break;
|
||||
case 3:
|
||||
console.warn.apply(console, args);
|
||||
break;
|
||||
case 4:
|
||||
console.error.apply(console, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const engines = {};
|
||||
|
||||
/**
|
||||
* Basic logging connector.
|
||||
*
|
||||
* This engine has no formatting and works with the most basic of 'console.log'
|
||||
* implementations. This is the logging engine used in Node.js.
|
||||
*/
|
||||
engines.basic = {
|
||||
logObject: function logObject(level, message, args_) {
|
||||
const args = args_.map(function(arg) {
|
||||
return JSON.stringify(arg, null, 2);
|
||||
});
|
||||
|
||||
logMessage(level, getLogInfo(message, args));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Log engine for browser consoles.
|
||||
*
|
||||
* Browsers tend to have better consoles that support nicely formatted
|
||||
* JavaScript objects. This connector passes objects through to the logging
|
||||
* function without any stringification.
|
||||
*/
|
||||
engines.interactive = {
|
||||
logObject: function(level, message, args_) {
|
||||
const args = args_.map(function(arg) {
|
||||
return /MSIE/.test(navigator.userAgent)
|
||||
? JSON.stringify(arg, null, 2)
|
||||
: arg;
|
||||
});
|
||||
|
||||
logMessage(level, getLogInfo(message, args));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Null logging connector.
|
||||
*
|
||||
* This engine simply swallows all messages. Used when console.log is not
|
||||
* available.
|
||||
*/
|
||||
engines.none = {
|
||||
logObject: function() {}
|
||||
};
|
||||
|
||||
Log.getEngine = Log.prototype.getEngine = function() {
|
||||
return Log.engine;
|
||||
};
|
||||
|
||||
Log.setEngine = Log.prototype.setEngine = function(engine) {
|
||||
assert.strictEqual(typeof engine, 'object');
|
||||
assert.strictEqual(typeof engine.logObject, 'function');
|
||||
Log.engine = engine;
|
||||
};
|
||||
|
||||
if (typeof window !== 'undefined' && typeof console !== 'undefined') {
|
||||
Log.setEngine(engines.interactive);
|
||||
} else if (typeof console !== 'undefined' && console.log) {
|
||||
Log.setEngine(engines.basic);
|
||||
} else {
|
||||
Log.setEngine(engines.none);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a root logger as our main export.
|
||||
*
|
||||
* This means you can use the logger easily on the fly:
|
||||
* ripple.log.debug('My object is', myObj);
|
||||
*/
|
||||
module.exports = new Log();
|
||||
|
||||
/**
|
||||
* This is the logger for ripple-lib internally.
|
||||
*/
|
||||
module.exports.internal = module.exports.sub();
|
||||
|
||||
/**
|
||||
* Expose the class as well.
|
||||
*/
|
||||
module.exports.Log = Log;
|
||||
|
||||
/**
|
||||
* Expose log engines
|
||||
*/
|
||||
module.exports.engines = engines;
|
||||
262
src/core/meta.js
262
src/core/meta.js
@@ -1,262 +0,0 @@
|
||||
'use strict';
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const Amount = require('./amount').Amount;
|
||||
const ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO;
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
|
||||
/**
|
||||
* Meta data processing facility
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} transaction metadata
|
||||
*/
|
||||
|
||||
function Meta(data) {
|
||||
this.nodes = [ ];
|
||||
|
||||
if (typeof data !== 'object') {
|
||||
throw new TypeError('Missing metadata');
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.AffectedNodes)) {
|
||||
throw new TypeError('Metadata missing AffectedNodes');
|
||||
}
|
||||
|
||||
data.AffectedNodes.forEach(this.addNode, this);
|
||||
}
|
||||
|
||||
Meta.NODE_TYPES = [
|
||||
'CreatedNode',
|
||||
'ModifiedNode',
|
||||
'DeletedNode'
|
||||
];
|
||||
|
||||
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER = [
|
||||
'LowLimit',
|
||||
'HighLimit',
|
||||
'TakerPays',
|
||||
'TakerGets'
|
||||
];
|
||||
|
||||
Meta.ACCOUNT_FIELDS = [
|
||||
'Account',
|
||||
'Owner',
|
||||
'Destination',
|
||||
'Issuer',
|
||||
'Target'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {Object} node
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodeType = function(node) {
|
||||
let result = null;
|
||||
|
||||
for (let i = 0; i < Meta.NODE_TYPES.length; i++) {
|
||||
const type = Meta.NODE_TYPES[i];
|
||||
if (node.hasOwnProperty(type)) {
|
||||
result = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} field
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.isAccountField = function(field) {
|
||||
return Meta.ACCOUNT_FIELDS.indexOf(field) !== -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add node to metadata
|
||||
*
|
||||
* @param {Object} node
|
||||
* @api private
|
||||
*/
|
||||
|
||||
Meta.prototype.addNode = function(node) {
|
||||
this._affectedAccounts = undefined;
|
||||
this._affectedBooks = undefined;
|
||||
|
||||
const result = { };
|
||||
|
||||
result.nodeType = this.getNodeType(node);
|
||||
if (result.nodeType) {
|
||||
const _node = node[result.nodeType];
|
||||
result.diffType = result.nodeType;
|
||||
result.entryType = _node.LedgerEntryType;
|
||||
result.ledgerIndex = _node.LedgerIndex;
|
||||
result.fields = extend({ }, _node.PreviousFields,
|
||||
_node.NewFields, _node.FinalFields);
|
||||
result.fieldsPrev = _node.PreviousFields || { };
|
||||
result.fieldsNew = _node.NewFields || { };
|
||||
result.fieldsFinal = _node.FinalFields || { };
|
||||
|
||||
// getAffectedBooks will set this
|
||||
// result.bookKey = undefined;
|
||||
|
||||
this.nodes.push(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get affected nodes array
|
||||
*
|
||||
* @param {Object} filter options
|
||||
* @return {Array} nodes
|
||||
*/
|
||||
|
||||
Meta.prototype.getNodes = function(options) {
|
||||
if (typeof options === 'object') {
|
||||
return this.nodes.filter(function(node) {
|
||||
if (options.nodeType && options.nodeType !== node.nodeType) {
|
||||
return false;
|
||||
}
|
||||
if (options.entryType && options.entryType !== node.entryType) {
|
||||
return false;
|
||||
}
|
||||
if (options.bookKey && options.bookKey !== node.bookKey) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return this.nodes;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedAccounts = function() {
|
||||
if (this._affectedAccounts) {
|
||||
return this._affectedAccounts;
|
||||
}
|
||||
|
||||
const accounts = [ ];
|
||||
|
||||
// This code should match the behavior of the C++ method:
|
||||
// TransactionMetaSet::getAffectedAccounts
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
const node = this.nodes[i];
|
||||
const fields = (node.nodeType === 'CreatedNode')
|
||||
? node.fieldsNew
|
||||
: node.fieldsFinal;
|
||||
|
||||
for (const fieldName in fields) {
|
||||
const field = fields[fieldName];
|
||||
|
||||
if (this.isAccountField(fieldName) && isValidAddress(field)) {
|
||||
accounts.push(field);
|
||||
} else if (
|
||||
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName) !== -1) {
|
||||
const amount = Amount.from_json(field);
|
||||
const issuer = amount.issuer();
|
||||
if (isValidAddress(issuer) && issuer !== ACCOUNT_ZERO) {
|
||||
accounts.push(issuer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._affectedAccounts = utils.arrayUnique(accounts);
|
||||
|
||||
return this._affectedAccounts;
|
||||
};
|
||||
|
||||
Meta.prototype.getAffectedBooks = function() {
|
||||
if (this._affectedBooks) {
|
||||
return this._affectedBooks;
|
||||
}
|
||||
|
||||
const books = [ ];
|
||||
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
const node = this.nodes[i];
|
||||
|
||||
if (node.entryType !== 'Offer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const gets = Amount.from_json(node.fields.TakerGets);
|
||||
const pays = Amount.from_json(node.fields.TakerPays);
|
||||
let getsKey = gets.currency();
|
||||
let paysKey = pays.currency();
|
||||
|
||||
if (getsKey !== 'XRP') {
|
||||
getsKey += '/' + gets.issuer();
|
||||
}
|
||||
|
||||
if (paysKey !== 'XRP') {
|
||||
paysKey += '/' + pays.issuer();
|
||||
}
|
||||
|
||||
const key = getsKey + ':' + paysKey;
|
||||
|
||||
// Hell of a lot of work, so we are going to cache this. We can use this
|
||||
// later to good effect in OrderBook.notify to make sure we only process
|
||||
// pertinent offers.
|
||||
node.bookKey = key;
|
||||
|
||||
books.push(key);
|
||||
}
|
||||
|
||||
this._affectedBooks = utils.arrayUnique(books);
|
||||
|
||||
return this._affectedBooks;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute a function on each affected node.
|
||||
*
|
||||
* The callback is passed two parameters. The first is a node object which looks
|
||||
* like this:
|
||||
*
|
||||
* {
|
||||
* // Type of diff, e.g. CreatedNode, ModifiedNode
|
||||
* nodeType: 'CreatedNode'
|
||||
*
|
||||
* // Type of node affected, e.g. RippleState, AccountRoot
|
||||
* entryType: 'RippleState',
|
||||
*
|
||||
* // Index of the ledger this change occurred in
|
||||
* ledgerIndex: '01AB01AB...',
|
||||
*
|
||||
* // Contains all fields with later versions taking precedence
|
||||
* //
|
||||
* // This is a shorthand for doing things like checking which account
|
||||
* // this affected without having to check the nodeType.
|
||||
* fields: {...},
|
||||
*
|
||||
* // Old fields (before the change)
|
||||
* fieldsPrev: {...},
|
||||
*
|
||||
* // New fields (that have been added)
|
||||
* fieldsNew: {...},
|
||||
*
|
||||
* // Changed fields
|
||||
* fieldsFinal: {...}
|
||||
* }
|
||||
*/
|
||||
|
||||
[
|
||||
'forEach',
|
||||
'map',
|
||||
'filter',
|
||||
'every',
|
||||
'some',
|
||||
'reduce'
|
||||
].forEach(function(fn) {
|
||||
Meta.prototype[fn] = function() {
|
||||
return Array.prototype[fn].apply(this.nodes, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
Meta.prototype.each = Meta.prototype.forEach;
|
||||
|
||||
exports.Meta = Meta;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,153 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const constants = require('./constants');
|
||||
const Amount = require('./amount').Amount;
|
||||
const {IOUValue} = require('ripple-lib-value');
|
||||
const binary = require('ripple-binary-codec');
|
||||
const OrderBookUtils = {};
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Amount from a JSON amount object using
|
||||
* passed parameters for value, currency and counterparty
|
||||
*
|
||||
* @param amount of value, currency, counterparty
|
||||
* @return JSON amount object
|
||||
*/
|
||||
|
||||
function createAmount(value, currency, counterparty) {
|
||||
assert(_.isString(counterparty), 'counterparty must be a string');
|
||||
assert(_.isString(currency), 'currency must be a string');
|
||||
|
||||
return Amount.from_components_unsafe(new IOUValue(value),
|
||||
currency, counterparty, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets currency for getOfferTaker(Gets/Pays)Funded
|
||||
* @param offer
|
||||
* @return currency
|
||||
*/
|
||||
|
||||
function getCurrencyFromOffer(offer) {
|
||||
return offer.TakerPays.currency || offer.TakerGets.currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets issuer for getOfferTaker(Gets/Pays)Funded
|
||||
* @param offer
|
||||
* @return issuer
|
||||
*/
|
||||
|
||||
function getIssuerFromOffer(offer) {
|
||||
return offer.TakerPays.issuer || offer.TakerGets.issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts and returns offer's taker gets funded amount as a default IOU amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerGetsFunded = function(offer, currency_, issuer_) {
|
||||
assertValidNumber(offer.taker_gets_funded, 'Taker gets funded is invalid');
|
||||
|
||||
const currency = currency_ || getCurrencyFromOffer(offer);
|
||||
const issuer = issuer_ || getIssuerFromOffer(offer);
|
||||
|
||||
return createAmount(offer.taker_gets_funded, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts and returns offer's taker pays funded amount as a default IOU amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerPaysFunded = function(offer, currency_, issuer_) {
|
||||
assertValidNumber(offer.taker_pays_funded, 'Taker gets funded is invalid');
|
||||
|
||||
const currency = currency_ || getCurrencyFromOffer(offer);
|
||||
const issuer = issuer_ || getIssuerFromOffer(offer);
|
||||
|
||||
return createAmount(offer.taker_pays_funded, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get offer taker gets amount
|
||||
*
|
||||
* @param {Object} offer
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferTakerGets = function(offer, currency_, issuer_) {
|
||||
assert(typeof offer, 'object', 'Offer is invalid');
|
||||
|
||||
const currency = currency_ || offer.TakerPays.currency;
|
||||
const issuer = issuer_ || offer.TakerPays.issuer;
|
||||
|
||||
return createAmount(offer.TakerGets, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve offer quality
|
||||
*
|
||||
* @param {Object} offer
|
||||
* @param {Currency} currencyGets
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferQuality = function(offer, currency_, issuer_) {
|
||||
const currency = currency_ || getCurrencyFromOffer(offer);
|
||||
const issuer = issuer_ || getIssuerFromOffer(offer);
|
||||
return createAmount(offer.quality, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an offer quality amount to a hex that can be parsed by
|
||||
* Amount.parse_quality
|
||||
*
|
||||
* @param {Amount} quality
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
assert(quality instanceof Amount, 'Quality is not an amount');
|
||||
return OrderBookUtils.convertOfferQualityToHex(quality.to_text());
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an offer quality amount to a hex that can be parsed by
|
||||
* Amount.parse_quality
|
||||
*
|
||||
* @param {String} quality
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHexFromText = function(quality) {
|
||||
return binary.encodeQuality(quality);
|
||||
};
|
||||
|
||||
OrderBookUtils.CURRENCY_ONE = constants.CURRENCY_ONE;
|
||||
|
||||
OrderBookUtils.ISSUER_ONE = constants.ACCOUNT_ONE;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
OrderBookUtils.normalizeAmount = function(value) {
|
||||
return Amount.from_components_unsafe(new IOUValue(value),
|
||||
OrderBookUtils.CURRENCY_ONE, OrderBookUtils.ISSUER_ONE, false);
|
||||
};
|
||||
|
||||
module.exports = OrderBookUtils;
|
||||
@@ -1,93 +0,0 @@
|
||||
'use strict';
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const util = require('util');
|
||||
const Amount = require('./amount').Amount;
|
||||
|
||||
/**
|
||||
* Represents a persistent path finding request.
|
||||
*
|
||||
* Only one path find request is allowed per connection, so when another path
|
||||
* find request is triggered it will supercede the existing one, making it emit
|
||||
* the 'end' and 'superceded' events.
|
||||
*/
|
||||
|
||||
function PathFind(remote, src_account, dst_account, dst_amount,
|
||||
src_currencies, src_amount
|
||||
) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
|
||||
this.src_account = src_account;
|
||||
this.dst_account = dst_account;
|
||||
this.dst_amount = dst_amount;
|
||||
this.src_currencies = src_currencies;
|
||||
this.src_amount = src_amount;
|
||||
}
|
||||
|
||||
util.inherits(PathFind, EventEmitter);
|
||||
|
||||
/**
|
||||
* Submits a path_find_create request to the network.
|
||||
*
|
||||
* This starts a path find request, superceding all previous path finds.
|
||||
*
|
||||
* This will be called automatically by Remote when this object is instantiated,
|
||||
* so you should only have to call it if the path find was closed or superceded
|
||||
* and you wish to restart it.
|
||||
*/
|
||||
|
||||
PathFind.prototype.create = function() {
|
||||
const self = this;
|
||||
|
||||
const req = this.remote.requestPathFindCreate({
|
||||
source_account: this.src_account,
|
||||
destination_account: this.dst_account,
|
||||
destination_amount: this.dst_amount,
|
||||
source_currencies: this.src_currencies,
|
||||
send_max: this.src_amount
|
||||
});
|
||||
|
||||
req.once('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
req.once('success', function(msg) {
|
||||
self.notify_update(msg);
|
||||
});
|
||||
|
||||
// XXX We should add ourselves to prepare_subscribe or a similar mechanism so
|
||||
// that we can resubscribe after a reconnection.
|
||||
|
||||
req.broadcast().request();
|
||||
};
|
||||
|
||||
PathFind.prototype.close = function() {
|
||||
this.removeAllListeners('update');
|
||||
this.remote.requestPathFindClose().broadcast().request();
|
||||
this.emit('end');
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_update = function(message) {
|
||||
const src_account = message.source_account;
|
||||
const dst_account = message.destination_account;
|
||||
const dst_amount = Amount.from_json(message.destination_amount);
|
||||
|
||||
// Only pass the event along if this path find response matches what we were
|
||||
// looking for.
|
||||
if (this.src_account === src_account &&
|
||||
this.dst_account === dst_account &&
|
||||
dst_amount.equals(this.dst_amount)) {
|
||||
this.emit('update', message);
|
||||
}
|
||||
};
|
||||
|
||||
PathFind.prototype.notify_superceded = function() {
|
||||
// XXX If we're set to re-subscribe whenever we connect to a new server, then
|
||||
// we should cancel that behavior here. See PathFind#create.
|
||||
|
||||
this.emit('end');
|
||||
this.emit('superceded');
|
||||
};
|
||||
|
||||
exports.PathFind = PathFind;
|
||||
@@ -1,61 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const ranges = Symbol();
|
||||
|
||||
function mergeIntervals(intervals: Array<[number, number]>) {
|
||||
const stack = [[-Infinity, -Infinity]];
|
||||
_.forEach(_.sortBy(intervals, x => x[0]), interval => {
|
||||
const lastInterval = stack.pop();
|
||||
if (interval[0] <= lastInterval[1] + 1) {
|
||||
stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])]);
|
||||
} else {
|
||||
stack.push(lastInterval);
|
||||
stack.push(interval);
|
||||
}
|
||||
});
|
||||
return stack.slice(1);
|
||||
}
|
||||
|
||||
class RangeSet {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this[ranges] = [];
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this[ranges].map(range =>
|
||||
range[0].toString() + '-' + range[1].toString()).join(',');
|
||||
}
|
||||
|
||||
addRange(start: number, end: number) {
|
||||
assert(start <= end, 'invalid range');
|
||||
this[ranges] = mergeIntervals(this[ranges].concat([[start, end]]));
|
||||
}
|
||||
|
||||
addValue(value: number) {
|
||||
this.addRange(value, value);
|
||||
}
|
||||
|
||||
parseAndAddRanges(rangesString: string) {
|
||||
const rangeStrings = rangesString.split(',');
|
||||
_.forEach(rangeStrings, rangeString => {
|
||||
const range = rangeString.split('-').map(Number);
|
||||
this.addRange(range[0], range.length === 1 ? range[0] : range[1]);
|
||||
});
|
||||
}
|
||||
|
||||
containsRange(start: number, end: number) {
|
||||
return _.some(this[ranges], range => range[0] <= start && range[1] >= end);
|
||||
}
|
||||
|
||||
containsValue(value: number) {
|
||||
return this.containsRange(value, value);
|
||||
}
|
||||
}
|
||||
|
||||
exports.RangeSet = RangeSet;
|
||||
2473
src/core/remote.js
2473
src/core/remote.js
File diff suppressed because it is too large
Load Diff
@@ -1,633 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const util = require('util');
|
||||
const async = require('async');
|
||||
const {normalizeCurrency} = require('./currency');
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
// Request events emitted:
|
||||
// 'success' : Request successful.
|
||||
// 'error' : Request failed.
|
||||
// 'remoteError'
|
||||
// 'remoteUnexpected'
|
||||
// 'remoteDisconnected'
|
||||
|
||||
/**
|
||||
* Request
|
||||
*
|
||||
* @param {Remote} remote
|
||||
* @param {String} command
|
||||
*/
|
||||
|
||||
function Request(remote, command) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
this.remote = remote;
|
||||
this.requested = false;
|
||||
this.reconnectTimeout = 1000 * 3;
|
||||
this.successEvent = 'success';
|
||||
this.errorEvent = 'error';
|
||||
this.message = {
|
||||
command: command,
|
||||
id: undefined
|
||||
};
|
||||
this._timeout = this.remote.submission_timeout;
|
||||
}
|
||||
|
||||
util.inherits(Request, EventEmitter);
|
||||
|
||||
// Send the request to a remote.
|
||||
Request.prototype.request = function(servers, callback_) {
|
||||
const callback = typeof servers === 'function' ? servers : callback_;
|
||||
const self = this;
|
||||
|
||||
if (this.requested) {
|
||||
throw new Error('Already requested');
|
||||
}
|
||||
|
||||
this.emit('before');
|
||||
// emit handler can set requested flag
|
||||
if (this.requested) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.requested = true;
|
||||
this.callback(callback);
|
||||
|
||||
this.on('error', function() {});
|
||||
this.emit('request', this.remote);
|
||||
|
||||
function doRequest() {
|
||||
if (Array.isArray(servers)) {
|
||||
servers.forEach(function(server) {
|
||||
self.setServer(server);
|
||||
self.remote.request(self);
|
||||
}, self);
|
||||
} else {
|
||||
self.remote.request(self);
|
||||
}
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (typeof callback === 'function') {
|
||||
callback(new RippleError('tejTimeout'));
|
||||
}
|
||||
|
||||
this.emit('timeout');
|
||||
// just in case
|
||||
this.emit = _.noop;
|
||||
this.cancel();
|
||||
this.remote.removeListener('connected', doRequest);
|
||||
}, this._timeout);
|
||||
|
||||
if (this.remote.isConnected()) {
|
||||
this.remote.on('connected', doRequest);
|
||||
}
|
||||
|
||||
function onRemoteError(error) {
|
||||
self.emit('error', error);
|
||||
}
|
||||
this.remote.once('error', onRemoteError); // e.g. rate-limiting slowDown error
|
||||
|
||||
this.once('response', () => {
|
||||
clearTimeout(timeout);
|
||||
this.remote.removeListener('connected', doRequest);
|
||||
this.remote.removeListener('error', onRemoteError);
|
||||
});
|
||||
|
||||
doRequest();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
function isResponseNotError(res) {
|
||||
return typeof res === 'object' && !res.hasOwnProperty('error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast request to all servers, filter responses if a function is
|
||||
* provided. Return first response that satisfies the filter. Pre-filter
|
||||
* requests by ledger_index (if a ledger_index is set on the request), and
|
||||
* automatically retry servers when they reconnect--if they are expected to
|
||||
*
|
||||
* Whew
|
||||
*
|
||||
* @param [Function] fn
|
||||
*/
|
||||
|
||||
|
||||
Request.prototype.filter =
|
||||
Request.prototype.addFilter =
|
||||
Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
|
||||
const self = this;
|
||||
|
||||
if (!this.requested) {
|
||||
// Defer until requested, and prevent the normal request() from executing
|
||||
this.once('before', function() {
|
||||
self.requested = true;
|
||||
self.broadcast(isResponseSuccess);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
this.on('error', function() {});
|
||||
let lastResponse = new Error('No servers available');
|
||||
const connectTimeouts = { };
|
||||
const emit = this.emit;
|
||||
|
||||
this.emit = function(event, a, b) {
|
||||
// Proxy success/error events
|
||||
switch (event) {
|
||||
case 'success':
|
||||
case 'error':
|
||||
emit.call(self, 'proposed', a, b);
|
||||
break;
|
||||
default:
|
||||
emit.apply(self, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
let serversCallbacks = { };
|
||||
let serversTimeouts = { };
|
||||
let serversClearConnectHandlers = { };
|
||||
|
||||
function iterator(server, callback) {
|
||||
// Iterator is called in parallel
|
||||
|
||||
const serverID = server.getServerID();
|
||||
|
||||
serversCallbacks[serverID] = callback;
|
||||
|
||||
function doRequest() {
|
||||
return server._request(self);
|
||||
}
|
||||
|
||||
if (server.isConnected()) {
|
||||
const timeout = setTimeout(() => {
|
||||
lastResponse = new RippleError('tejTimeout',
|
||||
JSON.stringify(self.message));
|
||||
|
||||
server.removeListener('connect', doRequest);
|
||||
delete serversCallbacks[serverID];
|
||||
delete serversClearConnectHandlers[serverID];
|
||||
|
||||
callback(false);
|
||||
}, self._timeout);
|
||||
|
||||
serversTimeouts[serverID] = timeout;
|
||||
serversClearConnectHandlers[serverID] = function() {
|
||||
server.removeListener('connect', doRequest);
|
||||
};
|
||||
|
||||
server.on('connect', doRequest);
|
||||
return doRequest();
|
||||
}
|
||||
|
||||
// Server is disconnected but should reconnect. Wait for it to reconnect,
|
||||
// and abort after a timeout
|
||||
function serverReconnected() {
|
||||
clearTimeout(connectTimeouts[serverID]);
|
||||
connectTimeouts[serverID] = null;
|
||||
iterator(server, callback);
|
||||
}
|
||||
|
||||
connectTimeouts[serverID] = setTimeout(function() {
|
||||
server.removeListener('connect', serverReconnected);
|
||||
callback(false);
|
||||
}, self.reconnectTimeout);
|
||||
|
||||
server.once('connect', serverReconnected);
|
||||
}
|
||||
|
||||
// Listen for proxied success/error event and apply filter
|
||||
function onProposed(result, server) {
|
||||
const serverID = server.getServerID();
|
||||
lastResponse = result;
|
||||
|
||||
const callback = serversCallbacks[serverID];
|
||||
delete serversCallbacks[serverID];
|
||||
|
||||
clearTimeout(serversTimeouts[serverID]);
|
||||
delete serversTimeouts[serverID];
|
||||
|
||||
if (serversClearConnectHandlers[serverID] !== undefined) {
|
||||
serversClearConnectHandlers[serverID]();
|
||||
delete serversClearConnectHandlers[serverID];
|
||||
}
|
||||
|
||||
if (callback !== undefined) {
|
||||
callback(isResponseSuccess(result));
|
||||
}
|
||||
}
|
||||
|
||||
this.on('proposed', onProposed);
|
||||
|
||||
let complete_ = null;
|
||||
|
||||
// e.g. rate-limiting slowDown error
|
||||
function onRemoteError(error) {
|
||||
serversCallbacks = {};
|
||||
_.forEach(serversTimeouts, clearTimeout);
|
||||
serversTimeouts = {};
|
||||
_.forEach(serversClearConnectHandlers, (handler) => {
|
||||
handler();
|
||||
});
|
||||
serversClearConnectHandlers = {};
|
||||
|
||||
lastResponse = error instanceof RippleError ? error :
|
||||
new RippleError(error);
|
||||
complete_(false);
|
||||
}
|
||||
|
||||
function complete(success) {
|
||||
self.removeListener('proposed', onProposed);
|
||||
self.remote.removeListener('error', onRemoteError);
|
||||
// Emit success if the filter is satisfied by any server
|
||||
// Emit error if the filter is not satisfied by any server
|
||||
// Include the last response
|
||||
emit.call(self, success ? 'success' : 'error', lastResponse);
|
||||
}
|
||||
|
||||
complete_ = complete;
|
||||
|
||||
this.remote.once('error', onRemoteError);
|
||||
|
||||
const servers = this.remote._servers.filter(function(server) {
|
||||
// Pre-filter servers that are disconnected and should not reconnect
|
||||
return (server.isConnected() || server._shouldConnect)
|
||||
// Pre-filter servers that do not contain the ledger in request
|
||||
&& (!self.message.hasOwnProperty('ledger_index')
|
||||
|| server.hasLedger(self.message.ledger_index))
|
||||
&& (!self.message.hasOwnProperty('ledger_index_min')
|
||||
|| self.message.ledger_index_min === -1
|
||||
|| server.hasLedger(self.message.ledger_index_min))
|
||||
&& (!self.message.hasOwnProperty('ledger_index_max')
|
||||
|| self.message.ledger_index_max === -1
|
||||
|| server.hasLedger(self.message.ledger_index_max));
|
||||
});
|
||||
|
||||
// Apply iterator in parallel to connected servers, complete when the
|
||||
// supplied filter function is satisfied once by a server's response
|
||||
async.some(servers, iterator, complete);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.cancel = function() {
|
||||
this.removeAllListeners();
|
||||
this.on('error', function() {});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setCallback = function(fn) {
|
||||
if (typeof fn === 'function') {
|
||||
this.callback(fn);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setReconnectTimeout = function(timeout) {
|
||||
if (typeof timeout === 'number' && !isNaN(timeout)) {
|
||||
this.reconnectTimeout = timeout;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
const self = this;
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (typeof successEvent === 'string') {
|
||||
this.successEvent = successEvent;
|
||||
}
|
||||
if (typeof errorEvent === 'string') {
|
||||
this.errorEvent = errorEvent;
|
||||
}
|
||||
|
||||
let called = false;
|
||||
|
||||
function requestError(error) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
|
||||
if (!(error instanceof RippleError)) {
|
||||
callback.call(self, new RippleError(error));
|
||||
} else {
|
||||
callback.call(self, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function requestSuccess(message) {
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback.call(self, null, message);
|
||||
}
|
||||
}
|
||||
|
||||
this.once(this.successEvent, requestSuccess);
|
||||
this.once(this.errorEvent, requestError);
|
||||
|
||||
if (!this.requested) {
|
||||
this.request();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setTimeout = function(delay) {
|
||||
if (!_.isFinite(delay)) {
|
||||
throw new Error('delay must be number');
|
||||
}
|
||||
this._timeout = delay;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setServer = function(server) {
|
||||
let selected = null;
|
||||
|
||||
if (_.isString(server)) {
|
||||
selected = _.find(this.remote._servers, s => s._url === server) || null;
|
||||
} else if (_.isObject(server)) {
|
||||
selected = server;
|
||||
}
|
||||
|
||||
this.server = selected;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.buildPath = function(build) {
|
||||
if (this.remote.local_signing) {
|
||||
throw new Error(
|
||||
'`build_path` is completely ignored when doing local signing as '
|
||||
+ '`Paths` is a component of the signed blob. The `tx_blob` is signed,'
|
||||
+ 'sealed and delivered, and the txn unmodified after');
|
||||
}
|
||||
|
||||
if (build) {
|
||||
this.message.build_path = true;
|
||||
} else {
|
||||
// ND: rippled currently intreprets the mere presence of `build_path` as the
|
||||
// value being `truthy`
|
||||
delete this.message.build_path;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.ledgerChoose = function(current) {
|
||||
if (current) {
|
||||
this.message.ledger_index = this.remote._ledger_current_index;
|
||||
} else {
|
||||
this.message.ledger_hash = this.remote._ledger_hash;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger for a request.
|
||||
// - ledger_entry
|
||||
// - transaction_entry
|
||||
Request.prototype.ledgerHash = function(hash) {
|
||||
this.message.ledger_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Set the ledger_index for a request.
|
||||
// - ledger_entry
|
||||
Request.prototype.ledgerIndex = function(ledger_index) {
|
||||
this.message.ledger_index = ledger_index;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set either ledger_index or ledger_hash based on heuristic
|
||||
*
|
||||
* @param {Number|String} ledger - identifier
|
||||
* @param {Object} options -
|
||||
* @param {Number|String} defaultValue - default if `ledger` unspecifed
|
||||
*/
|
||||
Request.prototype.ledgerSelect =
|
||||
Request.prototype.selectLedger = function(ledger, defaultValue) {
|
||||
const selected = ledger || defaultValue;
|
||||
|
||||
switch (selected) {
|
||||
case 'current':
|
||||
case 'closed':
|
||||
case 'validated':
|
||||
this.message.ledger_index = selected;
|
||||
break;
|
||||
default:
|
||||
if (Number(selected) && isFinite(Number(selected))) {
|
||||
this.message.ledger_index = Number(selected);
|
||||
} else if (/^[A-F0-9]{64}$/.test(selected)) {
|
||||
this.message.ledger_hash = selected;
|
||||
} else if (selected !== undefined) {
|
||||
throw new Error('unknown ledger format: ' + selected);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.accountRoot = function(account) {
|
||||
this.message.account_root = account;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.index = function(index) {
|
||||
this.message.index = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
// Provide the information id an offer.
|
||||
// --> account
|
||||
// --> seq : sequence number of transaction creating offer (integer)
|
||||
Request.prototype.offerId = function(account, sequence) {
|
||||
this.message.offer = {
|
||||
account: account,
|
||||
seq: sequence
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
// --> index : ledger entry index.
|
||||
Request.prototype.offerIndex = function(index) {
|
||||
this.message.offer = index;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.secret = function(secret) {
|
||||
if (secret) {
|
||||
this.message.secret = secret;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txHash = function(hash) {
|
||||
this.message.tx_hash = hash;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txJson = function(json) {
|
||||
this.message.tx_json = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.txBlob = function(json) {
|
||||
this.message.tx_blob = json;
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.rippleState = function(account, issuer, currency) {
|
||||
this.message.ripple_state = {
|
||||
currency: currency,
|
||||
accounts: [
|
||||
account,
|
||||
issuer
|
||||
]
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setAccounts =
|
||||
Request.prototype.accounts = function(accountsIn, proposed) {
|
||||
const accounts = Array.isArray(accountsIn) ? accountsIn : [accountsIn];
|
||||
|
||||
// Process accounts parameters
|
||||
const processedAccounts = accounts.map(function(account) {
|
||||
return account;
|
||||
});
|
||||
|
||||
if (proposed) {
|
||||
this.message.accounts_proposed = processedAccounts;
|
||||
} else {
|
||||
this.message.accounts = processedAccounts;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addAccount = function(account, proposed) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccount, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
const processedAccount = account;
|
||||
const prop = proposed === true ? 'accounts_proposed' : 'accounts';
|
||||
this.message[prop] = (this.message[prop] || []).concat(processedAccount);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.setAccountsProposed =
|
||||
Request.prototype.rtAccounts =
|
||||
Request.prototype.accountsProposed = function(accounts) {
|
||||
return this.accounts(accounts, true);
|
||||
};
|
||||
|
||||
Request.prototype.addAccountProposed = function(account) {
|
||||
if (Array.isArray(account)) {
|
||||
account.forEach(this.addAccountProposed, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
return this.addAccount(account, true);
|
||||
};
|
||||
|
||||
Request.prototype.setBooks =
|
||||
Request.prototype.books = function(books, snapshot) {
|
||||
// Reset list of books (this method overwrites the current list)
|
||||
this.message.books = [];
|
||||
|
||||
for (let i = 0, l = books.length; i < l; i++) {
|
||||
const book = books[i];
|
||||
this.addBook(book, snapshot);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addBook = function(book, snapshot) {
|
||||
if (Array.isArray(book)) {
|
||||
book.forEach(this.addBook, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
const json = { };
|
||||
|
||||
function processSide(side) {
|
||||
if (!book[side]) {
|
||||
throw new Error('Missing ' + side);
|
||||
}
|
||||
|
||||
const obj = json[side] = {
|
||||
currency: normalizeCurrency(book[side].currency)
|
||||
};
|
||||
|
||||
if (obj.currency !== 'XRP') {
|
||||
obj.issuer = book[side].issuer;
|
||||
}
|
||||
}
|
||||
|
||||
['taker_gets', 'taker_pays'].forEach(processSide);
|
||||
|
||||
if (typeof snapshot !== 'boolean') {
|
||||
json.snapshot = true;
|
||||
} else if (snapshot) {
|
||||
json.snapshot = true;
|
||||
} else {
|
||||
delete json.snapshot;
|
||||
}
|
||||
|
||||
if (book.both) {
|
||||
json.both = true;
|
||||
}
|
||||
|
||||
this.message.books = (this.message.books || []).concat(json);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Request.prototype.addStream = function(stream, values) {
|
||||
if (Array.isArray(values)) {
|
||||
switch (stream) {
|
||||
case 'accounts':
|
||||
this.addAccount(values);
|
||||
break;
|
||||
case 'accounts_proposed':
|
||||
this.addAccountProposed(values);
|
||||
break;
|
||||
case 'books':
|
||||
this.addBook(values);
|
||||
break;
|
||||
}
|
||||
} else if (arguments.length > 1) {
|
||||
for (const arg in arguments) {
|
||||
this.addStream(arguments[arg]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.message.streams)) {
|
||||
this.message.streams = [];
|
||||
}
|
||||
|
||||
if (this.message.streams.indexOf(stream) === -1) {
|
||||
this.message.streams.push(stream);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.Request = Request;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user