mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-27 15:45: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]
|
[ignore]
|
||||||
.*/src/api/.*
|
.*/ripple-lib/src/.*
|
||||||
.*/src/core/.*
|
.*/ripple-lib/dist/.*
|
||||||
.*/dist/.*
|
.*/ripple-lib/test/fixtures/.*
|
||||||
.*/test/fixtures/.*
|
|
||||||
.*/node_modules/flow-bin/.*
|
.*/node_modules/flow-bin/.*
|
||||||
|
.*/node_modules/webpack/.*
|
||||||
|
|
||||||
[include]
|
[include]
|
||||||
./node_modules/
|
./node_modules/
|
||||||
|
|||||||
38
npm-shrinkwrap.json
generated
38
npm-shrinkwrap.json
generated
@@ -4,10 +4,6 @@
|
|||||||
"npm-shrinkwrap-version": "5.4.0",
|
"npm-shrinkwrap-version": "5.4.0",
|
||||||
"node-version": "v0.12.7",
|
"node-version": "v0.12.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": {
|
|
||||||
"version": "0.9.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
|
|
||||||
},
|
|
||||||
"babel-runtime": {
|
"babel-runtime": {
|
||||||
"version": "5.8.29",
|
"version": "5.8.29",
|
||||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz",
|
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz",
|
||||||
@@ -22,14 +18,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.0.tgz"
|
"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": {
|
"hash.js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
|
||||||
@@ -102,10 +90,6 @@
|
|||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
|
"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": {
|
"ripple-address-codec": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
|
||||||
@@ -126,6 +110,10 @@
|
|||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
|
||||||
|
},
|
||||||
"create-hash": {
|
"create-hash": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
|
||||||
@@ -186,6 +174,10 @@
|
|||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bn.js": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
|
||||||
|
},
|
||||||
"brorand": {
|
"brorand": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
|
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
|
||||||
@@ -206,20 +198,6 @@
|
|||||||
"version": "0.5.1",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.1.tgz"
|
"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": {
|
"ws": {
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
|
||||||
|
|||||||
@@ -15,23 +15,17 @@
|
|||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "~0.9.0",
|
|
||||||
"babel-runtime": "^5.5.4",
|
"babel-runtime": "^5.5.4",
|
||||||
"bignumber.js": "^2.0.3",
|
"bignumber.js": "^2.0.3",
|
||||||
"bn.js": "^3.1.1",
|
|
||||||
"extend": "~1.2.1",
|
|
||||||
"hash.js": "^1.0.3",
|
"hash.js": "^1.0.3",
|
||||||
"https-proxy-agent": "^1.0.0",
|
"https-proxy-agent": "^1.0.0",
|
||||||
"is-my-json-valid": "^2.12.2",
|
"is-my-json-valid": "^2.12.2",
|
||||||
"lodash": "^3.1.0",
|
"lodash": "^3.1.0",
|
||||||
"lru-cache": "~2.5.0",
|
|
||||||
"ripple-address-codec": "^2.0.1",
|
"ripple-address-codec": "^2.0.1",
|
||||||
"ripple-binary-codec": "^0.0.6",
|
"ripple-binary-codec": "^0.0.6",
|
||||||
"ripple-hashes": "^0.0.1",
|
"ripple-hashes": "^0.0.1",
|
||||||
"ripple-keypairs": "^0.10.0",
|
"ripple-keypairs": "^0.10.0",
|
||||||
"ripple-lib-transactionparser": "^0.5.1",
|
"ripple-lib-transactionparser": "^0.5.1",
|
||||||
"ripple-lib-value": "0.1.0",
|
|
||||||
"sjcl-codec": "0.1.0",
|
|
||||||
"ws": "~0.7.1"
|
"ws": "~0.7.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ unittest() {
|
|||||||
babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
|
babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
|
||||||
echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts
|
echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts
|
||||||
mkdir -p test-compiled/node_modules
|
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
|
ln -nfs ../../dist/npm test-compiled/node_modules/ripple-api
|
||||||
mocha --opts test-compiled/mocha.opts test-compiled
|
mocha --opts test-compiled/mocha.opts test-compiled
|
||||||
rm -rf 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