UNPKG

ripplelib

Version:

A JavaScript API for interacting with Ripple in Node.js and the browser

1,845 lines (1,535 loc) 1.47 MB
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