Delete core, move "api" directory up to "src", and remove unused dependencies

This commit is contained in:
Chris Clark
2015-10-27 12:03:12 -07:00
parent 88b1c7e6eb
commit 97747deed9
175 changed files with 130 additions and 24723 deletions

View File

@@ -1,9 +1,9 @@
[ignore]
.*/src/api/.*
.*/src/core/.*
.*/dist/.*
.*/test/fixtures/.*
.*/ripple-lib/src/.*
.*/ripple-lib/dist/.*
.*/ripple-lib/test/fixtures/.*
.*/node_modules/flow-bin/.*
.*/node_modules/webpack/.*
[include]
./node_modules/

38
npm-shrinkwrap.json generated
View File

@@ -4,10 +4,6 @@
"npm-shrinkwrap-version": "5.4.0",
"node-version": "v0.12.7",
"dependencies": {
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
},
"babel-runtime": {
"version": "5.8.29",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.29.tgz",
@@ -22,14 +18,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.0.tgz"
},
"bn.js": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz"
},
"extend": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
},
"hash.js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
@@ -102,10 +90,6 @@
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
},
"lru-cache": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
},
"ripple-address-codec": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
@@ -126,6 +110,10 @@
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
"dependencies": {
"bn.js": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
},
"create-hash": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
@@ -186,6 +174,10 @@
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
"dependencies": {
"bn.js": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
},
"brorand": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
@@ -206,20 +198,6 @@
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.5.1.tgz"
},
"ripple-lib-value": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/ripple-lib-value/-/ripple-lib-value-0.1.0.tgz",
"dependencies": {
"bignumber.js": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.8.tgz"
}
}
},
"sjcl-codec": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/sjcl-codec/-/sjcl-codec-0.1.0.tgz"
},
"ws": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",

View File

@@ -15,23 +15,17 @@
"test": "test"
},
"dependencies": {
"async": "~0.9.0",
"babel-runtime": "^5.5.4",
"bignumber.js": "^2.0.3",
"bn.js": "^3.1.1",
"extend": "~1.2.1",
"hash.js": "^1.0.3",
"https-proxy-agent": "^1.0.0",
"is-my-json-valid": "^2.12.2",
"lodash": "^3.1.0",
"lru-cache": "~2.5.0",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.0.6",
"ripple-hashes": "^0.0.1",
"ripple-keypairs": "^0.10.0",
"ripple-lib-transactionparser": "^0.5.1",
"ripple-lib-value": "0.1.0",
"sjcl-codec": "0.1.0",
"ws": "~0.7.1"
},
"devDependencies": {

View File

@@ -27,7 +27,6 @@ unittest() {
babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
echo "--reporter spec --timeout 5000 --slow 500" > test-compiled/mocha.opts
mkdir -p test-compiled/node_modules
ln -nfs ../../dist/npm/core test-compiled/node_modules/ripple-lib
ln -nfs ../../dist/npm test-compiled/node_modules/ripple-api
mocha --opts test-compiled/mocha.opts test-compiled
rm -rf test-compiled

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +0,0 @@
'use strict';
module.exports = {
ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
CURRENCY_ZERO: '0000000000000000000000000000000000000000',
CURRENCY_ONE: '0000000000000000000000000000000000000001'
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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