ripplelib
Version:
A JavaScript API for interacting with Ripple in Node.js and the browser
1,845 lines (1,535 loc) • 1.47 MB
JavaScript
var ripple =
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
var _Object$keys = __webpack_require__(28)['default'];
exports.Remote = __webpack_require__(1).Remote;
exports.Request = __webpack_require__(2).Request;
exports.Amount = __webpack_require__(3).Amount;
exports.Account = __webpack_require__(4).Account;
exports.Transaction = __webpack_require__(5).Transaction;
exports.Currency = __webpack_require__(6).Currency;
exports.Base = __webpack_require__(7).Base;
exports.UInt128 = __webpack_require__(8).UInt128;
exports.UInt160 = __webpack_require__(9).UInt160;
exports.UInt256 = __webpack_require__(10).UInt256;
exports.Seed = __webpack_require__(11).Seed;
exports.KeyPair = __webpack_require__(12).KeyPair;
exports.Meta = __webpack_require__(13).Meta;
exports.SerializedObject = __webpack_require__(14).SerializedObject;
exports.RippleError = __webpack_require__(15).RippleError;
exports.Message = __webpack_require__(16).Message;
exports.binformat = __webpack_require__(17);
exports.utils = __webpack_require__(18);
exports.Server = __webpack_require__(19).Server;
exports.Ledger = __webpack_require__(20).Ledger;
exports.TransactionQueue = __webpack_require__(21).TransactionQueue;
exports.RangeSet = __webpack_require__(22).RangeSet;
exports.convertBase = __webpack_require__(23);
exports._test = {
Log: __webpack_require__(24),
PathFind: __webpack_require__(25).PathFind,
TransactionManager: __webpack_require__(26).TransactionManager
};
// Important: We do not guarantee any specific version of SJCL or for any
// specific features to be included. The version and configuration may change at
// any time without warning.
//
// However, for programs that are tied to a specific version of ripple.js like
// the official client, it makes sense to expose the SJCL instance so we don't
// have to include it twice.
exports.sjcl = __webpack_require__(18).sjcl;
exports.types = __webpack_require__(27);
// camelCase to under_scored API conversion
function attachUnderscored(name) {
var o = exports[name];
_Object$keys(o.prototype).forEach(function (key) {
var UPPERCASE = /([A-Z]{1})[a-z]+/g;
if (!UPPERCASE.test(key)) {
return;
}
var underscored = key.replace(UPPERCASE, function (c) {
return '_' + c.toLowerCase();
});
o.prototype[underscored] = o.prototype[key];
});
}
['Remote', 'Request', 'Transaction', 'Account', 'Server'].forEach(attachUnderscored);
// vim:sw=2:sts=2:ts=8:et
/***/ },
/* 1 */
/***/ function(module, exports, __webpack_require__) {
'use strict';
// Interface to manage connections to rippled servers
//
// - We never send binary data.
// - We use the W3C interface for node and browser compatibility:
// http://www.w3.org/TR/websockets/#the-websocket-interface
//
// This class is intended for both browser and Node.js use.
//
// This class is designed to work via peer protocol via either the public or
// private WebSocket interfaces. The JavaScript class for the peer protocol
// has not yet been implemented. However, this class has been designed for it
// to be a very simple drop option.
var _Object$keys = __webpack_require__(28)['default'];
var EventEmitter = __webpack_require__(40).EventEmitter;
var util = __webpack_require__(41);
var assert = __webpack_require__(43);
var LRU = __webpack_require__(48);
var async = __webpack_require__(47);
var lodash = __webpack_require__(44);
var Server = __webpack_require__(19).Server;
var Request = __webpack_require__(2).Request;
var Amount = __webpack_require__(3).Amount;
var Currency = __webpack_require__(6).Currency;
var UInt160 = __webpack_require__(9).UInt160;
var UInt256 = __webpack_require__(10).UInt256;
var Transaction = __webpack_require__(5).Transaction;
var Account = __webpack_require__(4).Account;
var Meta = __webpack_require__(13).Meta;
var OrderBook = __webpack_require__(30).OrderBook;
var PathFind = __webpack_require__(25).PathFind;
var SerializedObject = __webpack_require__(14).SerializedObject;
var RippleError = __webpack_require__(15).RippleError;
var utils = __webpack_require__(18);
var hashprefixes = __webpack_require__(31);
var log = __webpack_require__(24).internal.sub('remote');
var Seed = __webpack_require__(11).Seed;
var KeyPair = __webpack_require__(12).KeyPair;
/**
* Interface to manage connections to rippled servers
*
* @param {Object} Options
*/
function Remote() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
EventEmitter.call(this);
var self = this;
lodash.merge(this, lodash.defaults(options, Remote.DEFAULTS));
this.state = 'offline'; // 'online', 'offline'
this._server_fatal = false; // server exited
this._stand_alone = undefined;
this._testnet = undefined;
this._ledger_current_index = undefined;
this._ledger_hash = undefined;
this._ledger_time = undefined;
this._connection_count = 0;
this._connected = false;
this._should_connect = true;
this._transaction_listeners = 0;
this._received_tx = new LRU({ max: 100 });
this._cur_path_find = null;
if (this.local_signing) {
// Local signing implies local fees and sequences
this.local_sequence = true;
this.local_fee = true;
}
this._servers = [];
this._primary_server = undefined;
// Cache information for accounts.
// DEPRECATED, will be removed
// Consider sequence numbers stable if you know you're not generating bad
// transactions.
// Otherwise, clear it to have it automatically refreshed from the network.
// account : { seq : __ }
this.accounts = {};
// Account objects by AccountId.
this._accounts = {};
// OrderBook objects
this._books = {};
// Secrets that we know about.
// Secrets can be set by calling set_secret(account, secret).
// account : secret
this.secrets = {};
// cache keypairs.
this.keyPairs = {};
// Cache for various ledgers.
// XXX Clear when ledger advances.
this.ledgers = {
current: {
account_root: {}
}
};
if (typeof this.trusted !== 'boolean') {
throw new TypeError('trusted must be a boolean');
}
if (typeof this.trace !== 'boolean') {
throw new TypeError('trace must be a boolean');
}
if (typeof this.allow_partial_history !== 'boolean') {
throw new TypeError('allow_partial_history must be a boolean');
}
if (typeof this.max_fee !== 'number') {
throw new TypeError('max_fee must be a number');
}
if (typeof this.max_attempts !== 'number') {
throw new TypeError('max_attempts must be a number');
}
if (typeof this.fee_cushion !== 'number') {
throw new TypeError('fee_cushion must be a number');
}
if (typeof this.local_signing !== 'boolean') {
throw new TypeError('local_signing must be a boolean');
}
if (typeof this.local_fee !== 'boolean') {
throw new TypeError('local_fee must be a boolean');
}
if (typeof this.local_sequence !== 'boolean') {
throw new TypeError('local_sequence must be a boolean');
}
if (typeof this.canonical_signing !== 'boolean') {
throw new TypeError('canonical_signing must be a boolean');
}
if (typeof this.submission_timeout !== 'number') {
throw new TypeError('submission_timeout must be a number');
}
if (typeof this.last_ledger_offset !== 'number') {
throw new TypeError('last_ledger_offset must be a number');
}
if (typeof this.orderbook_limit !== 'number') {
throw new TypeError('orderbook_limit must be a number');
}
if (!Array.isArray(this.servers)) {
throw new TypeError('servers must be an array');
}
this.setMaxListeners(this.max_listeners);
this.servers.forEach(function (serverOptions) {
var server = self.addServer(serverOptions);
server.setMaxListeners(self.max_listeners);
});
function listenersModified(action, event) {
// Automatically subscribe and unsubscribe to orderbook
// on the basis of existing event listeners
if (lodash.contains(Remote.TRANSACTION_EVENTS, event)) {
switch (action) {
case 'add':
if (++self._transaction_listeners === 1) {
self.requestSubscribe('transactions').request();
}
break;
case 'remove':
if (--self._transaction_listeners === 0) {
self.requestUnsubscribe('transactions').request();
}
break;
}
}
}
this.on('newListener', function (event) {
listenersModified('add', event);
});
this.on('removeListener', function (event) {
listenersModified('remove', event);
});
}
util.inherits(Remote, EventEmitter);
Remote.DEFAULTS = {
trusted: false,
trace: false,
allow_partial_history: true,
local_sequence: true,
local_fee: true,
local_signing: true,
canonical_signing: true,
fee_cushion: 1.2,
max_fee: 1000000, // 1 XRP
max_attempts: 10,
submission_timeout: 1000 * 20,
last_ledger_offset: 3,
orderbook_limit: 100,
servers: [],
max_listeners: 0 // remove Node EventEmitter warnings
};
Remote.TRANSACTION_EVENTS = ['transaction', 'transaction_all'];
// Flags for ledger entries. In support of account_root().
Remote.flags = {
// AccountRoot
account_root: {
PasswordSpent: 0x00010000, // password set fee is spent
RequireDestTag: 0x00020000, // require a DestinationTag for payments
RequireAuth: 0x00040000, // require a authorization to hold IOUs
DisallowXRP: 0x00080000, // disallow sending XRP
DisableMaster: 0x00100000, // force regular key
DefaultRipple: 0x00800000,
NoFreeze: 0x00200000, // permanently disallowed freezing trustlines
GlobalFreeze: 0x00400000 // trustlines globally frozen
},
// Offer
offer: {
Passive: 0x00010000,
Sell: 0x00020000 // offer was placed as a sell
},
// Ripple state
state: {
LowReserve: 0x00010000, // entry counts toward reserve
HighReserve: 0x00020000,
LowAuth: 0x00040000,
HighAuth: 0x00080000,
LowNoRipple: 0x00100000,
HighNoRipple: 0x00200000,
LowFreeze: 0x00400000,
HighFreeze: 0x00800000
}
};
/**
* Check that server message is valid
*
* @param {Object} message
* @return Boolean
*/
Remote.isValidMessage = function (message) {
return typeof message === 'object' && typeof message.type === 'string';
};
/**
* Check that server message contains valid
* ledger data
*
* @param {Object} message
* @return {Boolean}
*/
Remote.isValidLedgerData = function (message) {
return typeof message === 'object' && typeof message.fee_base === 'number' && typeof message.fee_ref === 'number' && typeof message.ledger_hash === 'string' && typeof message.ledger_index === 'number' && typeof message.ledger_time === 'number' && typeof message.reserve_base === 'number' && typeof message.reserve_inc === 'number';
};
/**
* Check that server message contains valid
* load status data
*
* @param {Object} message
* @return {Boolean}
*/
Remote.isValidLoadStatus = function (message) {
return typeof message.load_base === 'number' && typeof message.load_factor === 'number';
};
/**
* Check that provided ledger is validated
*
* @param {Object} ledger
* @return {Boolean}
*/
Remote.isValidated = function (message) {
return message && typeof message === 'object' && message.validated === true;
};
/**
* Set the emitted state: 'online' or 'offline'
*
* @param {String} state
*/
Remote.prototype._setState = function (state) {
if (this.state !== state) {
if (this.trace) {
log.info('set_state:', state);
}
this.state = state;
this.emit('state', state);
switch (state) {
case 'online':
this._online_state = 'open';
this._connected = true;
this.emit('connect');
this.emit('connected');
break;
case 'offline':
this._online_state = 'closed';
this._connected = false;
this.emit('disconnect');
this.emit('disconnected');
break;
}
}
};
/**
* Inform remote that the remote server is not comming back.
*/
Remote.prototype.setServerFatal = function () {
this._server_fatal = true;
};
/**
* Enable debug output
*
* @param {Boolean} trace
*/
Remote.prototype.setTrace = function (trace) {
this.trace = trace === undefined || trace;
return this;
};
Remote.prototype._trace = function () {
if (this.trace) {
log.info.apply(log, arguments);
}
};
/**
* Store a secret - allows the Remote to automatically fill
* out auth information.
*
* @param {String} account
* @param {String} secret
*/
Remote.prototype.setSecret = function (account, secret, id) {
this.secrets[account] = secret;
// try to cache keypairs, so we don't need to re-generate it for each tx.
this.setKey(account, this.generateKey(secret, id));
};
// Generate KeyPair from given secret.
// id could be an address, or index of account family.
Remote.prototype.generateKey = Remote.prototype.generateKeyPair = function (secret, id) {
try {
return Seed.from_json(secret).get_key(id);
} catch (e) {
throw new Error('Invalid Secret!');
}
};
/**
* Store a keypair
*
* @param {String} account
* @param {String | KeyPair} privatekey string or KeyPair
* privatekey can be in Hex, RFC1751, base58
*/
Remote.prototype.setKey = Remote.prototype.setKeyPair = function (account, key) {
key = KeyPair.from_json(key);
if (!key.is_valid()) throw new Error('Invalid private/keyPair!');
this.keyPairs[account] = key;
};
// get keypairs from cache, or generate from secret;
Remote.prototype.getKey = Remote.prototype.getKeyPair = function (account) {
var key = this.keyPairs[account];
if (!key) key = this.keyPairs[account] = this.generateKey(this.secrets[account]);
return key;
};
Remote.prototype.addServer = function (options) {
var self = this;
var server = new Server(this, options);
function serverMessage(data) {
self._handleMessage(data, server);
}
server.on('message', serverMessage);
function serverConnect() {
self._connection_count += 1;
if (options.primary) {
self._setPrimaryServer(server);
}
if (self._connection_count === 1) {
self._setState('online');
}
if (self._connection_count === self._servers.length) {
self.emit('ready');
}
}
server.on('connect', serverConnect);
function serverDisconnect() {
self._connection_count--;
if (self._connection_count === 0) {
self._setState('offline');
}
}
server.on('disconnect', serverDisconnect);
this._servers.push(server);
return server;
};
/**
* Reconnect to Ripple network
*/
Remote.prototype.reconnect = function () {
if (!this._should_connect) {
return;
}
log.info('reconnecting');
this._servers.forEach(function (server) {
server.reconnect();
});
};
/**
* Connect to the Ripple network
*
* @param {Function} callback
* @api public
*/
Remote.prototype.connect = function (callback) {
if (!this._servers.length) {
throw new Error('No servers available.');
}
if (typeof callback !== 'function') callback = function () {};
if (this.isConnected()) {
callback();
return this;
}
this.once('connect', callback);
this._should_connect = true;
this._servers.forEach(function (server) {
server.connect();
});
return this;
};
/**
* Disconnect from the Ripple network.
*
* @param {Function} callback
* @api public
*/
Remote.prototype.disconnect = function (callback_) {
if (!this._servers.length) {
throw new Error('No servers available, not disconnecting');
}
var callback = lodash.isFunction(callback_) ? callback_ : function () {};
this._should_connect = false;
if (!this.isConnected()) {
callback();
return this;
}
this.once('disconnect', callback);
this._servers.forEach(function (server) {
server.disconnect();
});
this._set_state('offline');
return this;
};
/**
* Handle server message. Server messages are proxied to
* the Remote, such that global events can be handled
*
* It is possible for messages to be dispatched after the
* connection is closed.
*
* @param {JSON} message
* @param {Server} server
*/
Remote.prototype._handleMessage = function (message, server) {
if (!Remote.isValidMessage(message)) {
// Unexpected response from remote.
var error = new RippleError('remoteUnexpected', 'Unexpected response from remote: ' + JSON.stringify(message));
this.emit('error', error);
log.error(error);
return;
}
switch (message.type) {
case 'ledgerClosed':
this._handleLedgerClosed(message, server);
break;
case 'serverStatus':
this._handleServerStatus(message, server);
break;
case 'transaction':
this._handleTransaction(message, server);
break;
case 'path_find':
this._handlePathFind(message, server);
break;
default:
if (this.trace) {
log.info(message.type + ': ', message);
}
break;
}
};
Remote.prototype.getLedgerSequence = function () {
if (!this._ledger_current_index) {
throw new Error('Ledger sequence has not yet been initialized');
}
return this._ledger_current_index;
};
/**
* Handle server ledger_closed event
*
* @param {Object} message
*/
Remote.prototype._handleLedgerClosed = function (message, server) {
var self = this;
// XXX If not trusted, need to verify we consider ledger closed.
// XXX Also need to consider a slow server or out of order response.
// XXX Be more defensive fields could be missing or of wrong type.
// YYY Might want to do some cache management.
if (!Remote.isValidLedgerData(message)) {
return;
}
var ledgerAdvanced = message.ledger_index >= this._ledger_current_index;
if (isNaN(this._ledger_current_index) || ledgerAdvanced) {
this._ledger_time = message.ledger_time;
this._ledger_hash = message.ledger_hash;
this._ledger_current_index = message.ledger_index + 1;
if (this.isConnected()) {
this.emit('ledger_closed', message, server);
} else {
this.once('connect', function () {
// Delay until server is 'online'
self.emit('ledger_closed', message, server);
});
}
}
};
/**
* Handle server server_status event
*
* @param {Object} message
*/
Remote.prototype._handleServerStatus = function (message, server) {
this.emit('server_status', message, server);
};
/**
* Handle server transaction event
*
* @param {Object} message
*/
Remote.prototype._handleTransaction = function (message, server) {
// XXX If not trusted, need proof.
var transactionHash = message.transaction.hash;
if (this._received_tx.get(transactionHash)) {
// De-duplicate transactions
return;
}
if (message.validated) {
this._received_tx.set(transactionHash, true);
}
if (this.trace) {
log.info('tx:', message);
}
var metadata = message.meta || message.metadata;
if (metadata) {
// Process metadata
message.mmeta = new Meta(metadata);
// Pass the event on to any related Account objects
message.mmeta.getAffectedAccounts().forEach(function (account) {
if (this._accounts[account]) {
this._accounts[account].notify(message);
}
}, this);
// Pass the event on to any related OrderBooks
message.mmeta.getAffectedBooks().forEach(function (book) {
if (this._books[book]) {
this._books[book].notify(message);
}
}, this);
} else {
// Transaction could be from proposed transaction stream
// XX
['Account', 'Destination'].forEach(function (prop) {
if (this._accounts[message.transaction[prop]]) {
this._accounts[message.transaction[prop]].notify(message);
}
}, this);
}
this.emit('transaction', message, server);
this.emit('transaction_all', message, server);
};
/**
* Handle server path_find event
*
* @param {Object} message
*/
Remote.prototype._handlePathFind = function (message, server) {
// Pass the event to the currently open PathFind object
if (this._cur_path_find) {
this._cur_path_find.notify_update(message);
}
this.emit('path_find_all', message, server);
};
/**
* Returns the current ledger hash
*
* @return {String} ledger hash
*/
Remote.prototype.getLedgerHash = function () {
return this._ledger_hash;
};
/**
* Set primary server. Primary server will be selected
* to handle requested regardless of its internally-tracked
* priority score
*
* @param {Server} server
*/
Remote.prototype._setPrimaryServer = Remote.prototype.setPrimaryServer = function (server) {
if (this._primary_server) {
this._primary_server._primary = false;
}
this._primary_server = server;
this._primary_server._primary = true;
};
/**
* Get connected state
*
* @return {Boolean} connected
*/
Remote.prototype.isConnected = function () {
return this._connected;
};
/**
* Get array of connected servers
*/
Remote.prototype.getConnectedServers = function () {
return this._servers.filter(function (server) {
return server.isConnected();
});
};
/**
* Select a server to handle a request. Servers are
* automatically prioritized
*/
Remote.prototype._getServer = Remote.prototype.getServer = function () {
if (this._primary_server && this._primary_server.isConnected()) {
return this._primary_server;
}
if (!this._servers.length) {
return null;
}
var connectedServers = this.getConnectedServers();
if (connectedServers.length === 0 || !connectedServers[0]) {
return null;
}
var server = connectedServers[0];
var cScore = server._score + server._fee;
for (var i = 1; i < connectedServers.length; i++) {
var _server = connectedServers[i];
var bScore = _server._score + _server._fee;
if (bScore < cScore) {
server = _server;
cScore = bScore;
}
}
return server;
};
/**
* Send a request. This method is called internally by Request
* objects. Each Request contains a reference to Remote, and
* Request.request calls Request.remote.request
*
* @param {Request} request
*/
Remote.prototype.request = function (request_) {
var request = request_;
if (typeof request === 'string') {
if (!/^request_/.test(request)) {
request = 'request_' + request;
}
if (typeof this[request] === 'function') {
var args = Array.prototype.slice.call(arguments, 1);
return this[request].apply(this, args);
}
throw new Error('Command does not exist: ' + request);
}
if (!(request instanceof Request)) {
throw new Error('Argument is not a Request');
}
if (!this._servers.length) {
return request.emit('error', new Error('No servers available'));
}
if (!this.isConnected()) {
return this.once('connect', this.request.bind(this, request));
}
if (request.server === null) {
return request.emit('error', new Error('Server does not exist'));
}
var server = request.server || this.getServer();
if (server) {
server._request(request);
} else {
request.emit('error', new Error('No servers available'));
}
};
/**
* Request ping
*
* @param [String] server host
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.ping = Remote.prototype.requestPing = function (host, callback_) {
var request = new Request(this, 'ping');
var callback = callback_;
switch (typeof host) {
case 'function':
callback = host;
break;
case 'string':
request.setServer(host);
break;
}
var then = Date.now();
request.once('success', function () {
request.emit('pong', Date.now() - then);
});
request.callback(callback, 'pong');
return request;
};
/**
* Request server_info
*
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestServerInfo = function (callback) {
return new Request(this, 'server_info').callback(callback);
};
/**
* Request ledger
*
* @return {Request} request
*/
Remote.prototype.requestLedger = function (options, callback_) {
// XXX This is a bad command. Some variants don't scale.
// XXX Require the server to be trusted.
// utils.assert(this.trusted);
var request = new Request(this, 'ledger');
var callback = callback_;
switch (typeof options) {
case 'undefined':
break;
case 'function':
callback = options;
break;
case 'object':
if (!options) {
break;
}
_Object$keys(options).forEach(function (o) {
switch (o) {
case 'full':
case 'expand':
case 'transactions':
case 'accounts':
request.message[o] = options[o] ? true : false;
break;
case 'ledger':
request.selectLedger(options.ledger);
break;
case 'ledger_index':
case 'ledger_hash':
request.message[o] = options[o];
break;
case 'closed':
case 'current':
case 'validated':
request.message.ledger_index = o;
break;
}
}, options);
break;
default:
request.selectLedger(options);
break;
}
request.callback(callback);
return request;
};
/**
* Request ledger_closed
*
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestLedgerClosed = Remote.prototype.requestLedgerHash = function (callback) {
// utils.assert(this.trusted); // If not trusted, need to check proof.
return new Request(this, 'ledger_closed').callback(callback);
};
/**
* Request ledger_header
*
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestLedgerHeader = function (callback) {
return new Request(this, 'ledger_header').callback(callback);
};
/**
* Request ledger_current
*
* Get the current proposed ledger entry. May be closed (and revised)
* at any time (even before returning).
*
* Only for unit testing.
*
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestLedgerCurrent = function (callback) {
return new Request(this, 'ledger_current').callback(callback);
};
/**
* Request ledger_data
*
* Get the contents of a specified ledger
*
* @param {Object} options
* @property {Boolean} [options.binary]- Flag which determines if rippled
* returns binary or parsed JSON
* @property {String|Number} [options.ledger] - Hash or sequence of a ledger
* to get contents for
* @property {Number} [options.limit] - Number of contents to retrieve
* from the ledger
* @property {Function} callback
*
* @callback
* @param {Error} error
* @param {LedgerData} ledgerData
*
* @return {Request} request
*/
Remote.prototype.requestLedgerData = function (options, callback) {
var request = new Request(this, 'ledger_data');
request.message.binary = options.binary !== false;
request.selectLedger(options.ledger);
request.message.limit = options.limit;
request.once('success', function (res) {
if (options.binary === false) {
request.emit('state', res);
return;
}
function iterator(ledgerData, next) {
async.setImmediate(function () {
next(null, Remote.parseBinaryLedgerData(ledgerData));
});
}
function complete(err, state) {
if (err) {
request.emit('error', err);
} else {
res.state = state;
request.emit('state', res);
}
}
async.mapSeries(res.state, iterator, complete);
});
request.callback(callback, 'state');
return request;
};
/**
* Request ledger_entry
*
* @param [String] type
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestLedgerEntry = function (type, callback_) {
// utils.assert(this.trusted);
// If not trusted, need to check proof, maybe talk packet protocol.
var self = this;
var request = new Request(this, 'ledger_entry');
var callback = lodash.isFunction(type) ? type : callback_;
// Transparent caching. When .request() is invoked, look in the Remote object
// for the result. If not found, listen, cache result, and emit it.
//
// Transparent caching:
if (type === 'account_root') {
request.request_default = request.request;
request.request = function () {
// Intercept default request.
var bDefault = true;
if (!self._ledger_hash && type === 'account_root') {
var cache = self.ledgers.current.account_root;
if (!cache) {
cache = self.ledgers.current.account_root = {};
}
var node = self.ledgers.current.account_root[request.message.account_root];
if (node) {
// Emulate fetch of ledger entry.
// console.log('request_ledger_entry: emulating');
// YYY Missing lots of fields.
request.emit('success', { node: node });
bDefault = false;
} else {
// Was not cached.
// XXX Only allow with trusted mode. Must sync response with advance
switch (type) {
case 'account_root':
request.once('success', function (message) {
// Cache node.
// console.log('request_ledger_entry: caching');
self.ledgers.current.account_root[message.node.Account] = message.node;
});
break;
default:
// This type not cached.
// console.log('request_ledger_entry: non-cached type');
}
}
}
if (bDefault) {
// console.log('request_ledger_entry: invoking');
request.request_default();
}
};
}
request.callback(callback);
return request;
};
/**
* Request subscribe
*
* @param {Array} streams
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestSubscribe = function (streams, callback) {
var request = new Request(this, 'subscribe');
if (streams) {
request.message.streams = Array.isArray(streams) ? streams : [streams];
}
request.callback(callback);
return request;
};
/**
* Request usubscribe
*
* @param {Array} streams
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestUnsubscribe = function (streams, callback) {
var request = new Request(this, 'unsubscribe');
if (streams) {
request.message.streams = Array.isArray(streams) ? streams : [streams];
}
request.callback(callback);
return request;
};
/**
* Request transaction_entry
*
* @param {String} transaction hash
* @param {String|Number} ledger hash or sequence
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestTransactionEntry = function (options_, callback_) {
// If not trusted, need to check proof, maybe talk packet protocol.
// utils.assert(this.trusted);
var options = undefined,
callback = callback_;
if (lodash.isPlainObject(options_)) {
options = lodash.merge({
ledger: options_.ledger_index || options_.ledger_hash
}, options_);
} else {
log.warn('DEPRECATED: First argument to request constructor should be' + ' an object containing request properties');
var args = Array.prototype.slice.call(arguments);
if (lodash.isFunction(lodash.last(args))) {
callback = args.pop();
}
options = {
hash: args.shift(),
ledger: args.shift()
};
}
var request = new Request(this, 'transaction_entry');
request.txHash(options.hash);
switch (typeof options.ledger) {
case 'string':
case 'number':
request.selectLedger(options.ledger);
break;
case 'undefined':
request.ledgerIndex('validated');
break;
default:
throw new Error('ledger must be a ledger index or hash');
}
request.callback(callback);
return request;
};
/**
* Request tx
*
* @param {Object|String} hash
* @property {String} hash.hash - Transaction hash
* @property {Boolean} [hash.binary=true] - Flag which determines if rippled
* returns binary or parsed JSON
* @param [Function] callback
* @return {Request} request
*/
Remote.prototype.requestTransaction = Remote.prototype.requestTx = function (options_, callback_) {
var options = undefined,
callback = callback_;
if (lodash.isPlainObject(options_)) {
options = lodash.merge({}, options_);
} else {
log.warn('DEPRECATED: First argument to request constructor should be' + ' an object containing request properties');
var args = Array.prototype.slice.call(arguments);
if (lodash.isFunction(lodash.last(args))) {
callback = args.pop();
}
options = {
hash: args.shift(),
binary: args.shift()
};
}
var request = new Request(this, 'tx');
request.message.binary = options.binary !== false;
request.message.transaction = options.hash;
request.once('success', function (res) {
if (options.binary === false) {
request.emit('transaction', res);
return;
}
request.emit('transaction', Remote.parseBinaryTransaction(res));
});
request.callback(callback, 'transaction');
return request;
};
/**
* Account Request
*
* Optional paging with limit and marker options
* supported in rippled for 'account_lines' and 'account_offers'
*
* The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {String} type - request name, e.g. 'account_lines'
* @param {Object} options - all optional
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback
* @return {Request}
* @throws {Error} if a marker is provided, but no ledger_index or ledger_hash
*/
Remote.accountRequest = function (command, options_, callback_) {
var options = undefined,
callback = callback_;
if (lodash.isPlainObject(options_)) {
options = lodash.merge({}, options_);
} else {
log.warn('DEPRECATED: First argument to request constructor should be' + ' an object containing request properties');
var args = Array.prototype.slice.call(arguments);
if (lodash.isFunction(lodash.last(args))) {
callback = args.pop();
}
options = {
account: args.shift(),
peer: args.shift(),
ledger: args.shift(),
limit: args.shift(),
marker: args.shift()
};
}
var _options = options;
var account = _options.account;
var ledger = _options.ledger;
var peer = _options.peer;
var limit = _options.limit;
var marker = _options.marker;
// if a marker is given, we need a ledger
// check if a valid ledger_index or ledger_hash is provided
if (marker) {
if (!(Number(ledger) > 0) && !UInt256.is_valid(ledger)) {
throw new Error('A ledger_index or ledger_hash must be provided when using a marker');
}
}
var request = new Request(this, command);
if (account) {
account = UInt160.json_rewrite(account);
request.message.account = account;
}
request.selectLedger(ledger);
if (UInt160.is_valid(peer)) {
request.message.peer = UInt160.json_rewrite(peer);
}
if (!isNaN(limit)) {
limit = Number(limit);
// max for 32-bit unsigned int is 4294967295
// we'll clamp to 1e9
if (limit > 1e9) {
limit = 1e9;
}
// min for 32-bit unsigned int is 0
// we'll clamp to 0
if (limit < 0) {
limit = 0;
}
request.message.limit = limit;
}
if (marker) {
request.message.marker = marker;
}
['strict', 'queue', 'signer_lists'].forEach(function (key) {
if (_options.hasOwnProperty(key)) request.message[key] = _options[key];
});
request.callback(callback);
return request;
};
/**
* Request account_info
*
* @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestAccountInfo = function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var options = ['account_info'].concat(args);
return Remote.accountRequest.apply(this, options);
};
/**
* Request account_currencies
*
* @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestAccountCurrencies = function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var options = ['account_currencies'].concat(args);
return Remote.accountRequest.apply(this, options);
};
/**
* Request account_lines
*
* Requests for account_lines support paging, provide a limit and marker
* to page through responses.
*
* The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param {String} peer - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestAccountLines = function () {
for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
// XXX Does this require the server to be trusted?
// utils.assert(this.trusted);
var options = ['account_lines'].concat(args);
return Remote.accountRequest.apply(this, options);
};
/**
* Request account_offers
*
* Requests for account_offers support paging, provide a limit and marker
* to page through responses.
*
* The paged responses aren't guaranteed to be reliable between
* ledger closes. You have to supply a ledger_index or ledger_hash
* when paging to ensure a complete response
*
* @param {Object} options
* @param {String} account - ripple address
* @param [String|Number] ledger identifier
* @param [Number] limit - max results per response
* @param {String} marker - start position in response paging
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestAccountOffers = function () {
for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
var options = ['account_offers'].concat(args);
return Remote.accountRequest.apply(this, options);
};
/**
* Request account_tx
*
* @param {Object} options
*
* @param {String} account
* @param [Number] ledger_index_min defaults to -1
* @param [Number] ledger_index_max defaults to -1
* @param [Boolean] binary, defaults to true
* @param [Boolean] parseBinary, defaults to true
* @param [Boolean] count, defaults to false
* @param [Boolean] descending, defaults to false
* @param [Number] offset, defaults to 0
* @param [Number] limit
*
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestAccountTransactions = Remote.prototype.requestAccountTx = function (options, callback) {
// XXX Does this require the server to be trusted?
// utils.assert(this.trusted);
var request = new Request(this, 'account_tx');
options.binary = options.binary !== false;
if (options.min_ledger !== undefined) {
options.ledger_index_min = options.min_ledger;
}
if (options.max_ledger !== undefined) {
options.ledger_index_max = options.max_ledger;
}
if (options.binary && options.parseBinary === undefined) {
options.parseBinary = true;
}
_Object$keys(options).forEach(function (o) {
switch (o) {
case 'account':
case 'ledger_index_min': // earliest
case 'ledger_index_max': // latest
case 'binary': // false
case 'count': // false
case 'descending': // false
case 'offset': // 0
case 'limit':
// extended account_tx
case 'forward': // false
case 'marker':
request.message[o] = this[o];
break;
}
}, options);
request.once('success', function (res) {
if (!options.parseBinary) {
request.emit('transactions', res);
return;
}
function iterator(transaction, next) {
async.setImmediate(function () {
next(null, Remote.parseBinaryAccountTransaction(transaction));
});
}
function complete(err, transactions) {
if (err) {
request.emit('error', err);
} else {
res.transactions = transactions;
request.emit('transactions', res);
}
}
async.mapSeries(res.transactions, iterator, complete);
});
request.callback(callback, 'transactions');
return request;
};
/**
* @param {Object} transaction
* @return {Transaction}
*/
Remote.parseBinaryAccountTransaction = function (transaction) {
var tx_obj = new SerializedObject(transaction.tx_blob);
var tx_obj_json = tx_obj.to_json();
var meta = new SerializedObject(transaction.meta).to_json();
var tx_result = {
validated: transaction.validated
};
tx_result.meta = meta;
tx_result.tx = tx_obj_json;
tx_result.tx.hash = tx_obj.hash(hashprefixes.HASH_TX_ID).to_hex();
tx_result.tx.ledger_index = transaction.ledger_index;
tx_result.tx.inLedger = transaction.ledger_index;
if (typeof meta.DeliveredAmount === 'object') {
tx_result.meta.delivered_amount = meta.DeliveredAmount;
} else {
switch (typeof tx_obj_json.Amount) {
case 'string':
case 'object':
tx_result.meta.delivered_amount = tx_obj_json.Amount;
break;
}
}
return tx_result;
};
Remote.parseBinaryTransaction = function (transaction) {
var tx_obj = new SerializedObject(transaction.tx).to_json();
var meta = new SerializedObject(transaction.meta).to_json();
var tx_result = tx_obj;
tx_result.date = transaction.date;
tx_result.hash = transaction.hash;
tx_result.inLedger = transaction.inLedger;
tx_result.ledger_index = transaction.ledger_index;
tx_result.meta = meta;
tx_result.validated = transaction.validated;
switch (typeof meta.DeliveredAmount) {
case 'string':
case 'object':
tx_result.meta.delivered_amount = meta.DeliveredAmount;
break;
default:
switch (typeof tx_obj.Amount) {
case 'string':
case 'object':
tx_result.meta.delivered_amount = tx_obj.Amount;
break;
}
}
return tx_result;
};
/**
* Parse binary ledger state data
*
* @param {Object} ledgerData
* @property {String} ledgerData.data
* @property {String} ledgerData.index
*
* @return {State}
*/
Remote.parseBinaryLedgerData = function (ledgerData) {
var data = new SerializedObject(ledgerData.data).to_json();
data.index = ledgerData.index;
return data;
};
/**
* Request the overall transaction history.
*
* Returns a list of transactions that happened recently on the network. The
* default number of transactions to be returned is 20.
*
* @param [Number] start
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestTransactionHistory = Remote.prototype.requestTxHistory = function (start_, callback_) {
// XXX Does this require the server to be trusted?
// utils.assert(this.trusted);
var request = new Request(this, 'tx_history');
var start = start_;
var callback = callback_;
if (lodash.isPlainObject(start)) {
start = start.start;
} else {
log.warn('DEPRECATED: First argument to request constructor should be' + ' an object containing request properties');
}
request.message.start = start;
request.callback(callback);
return request;
};
/**
* Request book_offers
*
* @param {Object} options
* @param {Object} options.gets - taker_gets with issuer and currency
* @param {Object} options.pays - taker_pays with issuer and currency
* @param {String} [options.taker]
* @param {String} [options.ledger]
* @param {String|Number} [options.limit]
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestBookOffers = function (options_, callback_) {
var options = undefined,
callback = callback_;
if (options_.gets || options_.taker_gets) {
options = lodash.merge({
pays: options_.taker_pays,
gets: options_.taker_gets
}, options_);
} else {
log.warn('DEPRECATED: First argument to request constructor should be' + ' an object containing request properties');
var args = Array.prototype.slice.call(arguments);
if (lodash.isFunction(lodash.last(args))) {
callback = args.pop();
}
options = {
gets: args.shift(),
pays: args.shift(),
taker: args.shift(),
ledger: args.shift(),
limit: args.shift()
};
}
var _options2 = options;
var gets = _options2.gets;
var pays = _options2.pays;
var taker = _options2.taker;
var ledger = _options2.ledger;
var limit = _options2.limit;
var request = new Request(this, 'book_offers');
request.message.taker_gets = {
currency: Currency.json_rewrite(gets.currency, { force_hex: true })
};
if (!Currency.from_json(request.message.taker_gets.currency).is_native()) {
request.message.taker_gets.issuer = UInt160.json_rewrite(gets.issuer);
}
request.message.taker_pays = {
currency: Currency.json_rewrite(pays.currency, { force_hex: true })
};
if (!Currency.from_json(request.message.taker_pays.currency).is_native()) {
request.message.taker_pays.issuer = UInt160.json_rewrite(pays.issuer);
}
request.message.taker = taker ? taker : UInt160.ACCOUNT_ONE;
request.selectLedger(ledger);
if (!isNaN(limit)) {
limit = Number(limit);
// max for 32-bit unsigned int is 4294967295
// we'll clamp to 1e9
if (limit > 1e9) {
limit = 1e9;
}
// min for 32-bit unsigned int is 0
// we'll clamp to 0
if (limit < 0) {
limit = 0;
}
request.message.limit = limit;
} else {
request.message.limit = this.orderbook_limit;
}
request.callback(callback);
return request;
};
/**
* Request wallet_accounts
*
* @param {String} seed
* @param [Function] callback
* @return {Request}
*/
Remote.prototype.requestWalletAccounts = function (options_, callback_) {
utils.assert(this.trusted); // Don't send secrets.
var options = undefined,
callback = callback_;
if (lodash.isPlainObject(options_)) {
options = lodash.merge({}, options_);
} else {
log.warn('DEPRECATED: First argument to reques