BREAKING CHANGE: getFee returns promise, express fee in XRP in instructions response, use rawRequest to start decoupling Remote from API

This commit is contained in:
Chris Clark
2015-10-21 16:04:35 -07:00
parent 5aa212471c
commit cd17d6940f
52 changed files with 277 additions and 184 deletions

View File

@@ -26,6 +26,12 @@ class ConnectionError extends Error {
}
}
class NotConnectedError extends ConnectionError {
constructor(message) {
super(message);
}
}
class DisconnectedError extends ConnectionError {
constructor(message) {
super(message);
@@ -50,6 +56,7 @@ class Connection extends EventEmitter {
this._url = url;
this._timeout = options.timeout || (20 * 1000);
this._ws = null;
this._ledgerVersion = null;
this._nextRequestID = 1;
}
@@ -62,6 +69,9 @@ class Connection extends EventEmitter {
}
this.emit(data.id.toString(), data);
} else if (isStreamMessageType(data.type)) {
if (data.type === 'ledgerClosed') {
this._ledgerVersion = Number(data.ledger_index);
}
this.emit(data.type, data);
} else if (data.type === undefined && data.error) {
this.emit('error', data.error, data.error_message); // e.g. slowDown
@@ -77,12 +87,34 @@ class Connection extends EventEmitter {
return this._ws ? this._ws.readyState : WebSocket.CLOSED;
}
get _shouldBeConnected() {
return this._ws !== null;
}
_onUnexpectedClose() {
this._ledgerVersion = null;
this.connect().then();
}
_onOpen() {
const subscribeRequest = {
command: 'subscribe',
streams: ['ledger']
};
return this.request(subscribeRequest).then(() => {
const ledgerRequest = {
command: 'ledger',
ledger_index: 'validated'
};
return this.request(ledgerRequest).then(info => {
this._ledgerVersion = Number(info.ledger.ledger_index);
this.emit('connected');
});
});
}
connect() {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
if (this.state === WebSocket.OPEN) {
resolve();
} else if (this.state === WebSocket.CONNECTING) {
@@ -91,7 +123,7 @@ class Connection extends EventEmitter {
this._ws = new WebSocket(this._url);
this._ws.on('message', this._onMessage.bind(this));
this._ws.once('close', () => this._onUnexpectedClose);
this._ws.once('open', resolve);
this._ws.once('open', () => this._onOpen().then(resolve, reject));
}
});
}
@@ -104,7 +136,11 @@ class Connection extends EventEmitter {
this._ws.once('close', resolve);
} else {
this._ws.removeListener('close', this._onUnexpectedClose);
this._ws.once('close', resolve);
this._ws.once('close', () => {
this._ws = null;
this._ledgerVersion = null;
resolve();
});
this._ws.close();
}
});
@@ -114,6 +150,19 @@ class Connection extends EventEmitter {
return this.disconnect().then(() => this.connect());
}
getLedgerVersion() {
return new Promise((resolve, reject) => {
const ledgerVersion = this._ledgerVersion;
if (!this._shouldBeConnected) {
reject(new NotConnectedError());
} else if (this.state === WebSocket.OPEN && ledgerVersion !== null) {
resolve(ledgerVersion);
} else {
this.once('connected', () => resolve(this._ledgerVersion));
}
});
}
_send(message) {
return new Promise((resolve, reject) => {
this._ws.send(message, undefined, (error, result) => {
@@ -131,13 +180,18 @@ class Connection extends EventEmitter {
if (this.state === WebSocket.OPEN) {
this._send(message).then(resolve, reject);
} else {
this._ws.once('open', () => this._send(message).then(resolve, reject));
this._ws.once('connected', () =>
this._send(message).then(resolve, reject));
}
});
}
request(request, timeout) {
return new Promise((resolve, reject) => {
if (!this._shouldBeConnected) {
reject(new NotConnectedError());
}
let timer = null;
const self = this;
const id = this._nextRequestID;
@@ -179,6 +233,7 @@ class Connection extends EventEmitter {
this._ws.once('close', onDisconnect);
// JSON.stringify automatically removes keys with value of 'undefined'
const message = JSON.stringify(Object.assign({}, request, {id}));
this._sendWhenReady(message).then(() => {

View File

@@ -8,12 +8,14 @@ module.exports = {
errors: require('./errors'),
validate: require('./validate'),
txFlags: require('./txflags').txFlags,
serverInfo: require('./serverinfo'),
dropsToXrp: utils.dropsToXrp,
xrpToDrops: utils.xrpToDrops,
toRippledAmount: utils.toRippledAmount,
generateAddress: utils.generateAddress,
composeAsync: utils.composeAsync,
wrapCatch: utils.wrapCatch,
removeUndefined: utils.removeUndefined,
convertErrors: utils.convertErrors,
convertExceptions: utils.convertExceptions,
convertKeysFromSnakeCaseToCamelCase:

View File

@@ -4,6 +4,7 @@
"type": "object",
"properties": {
"trace": {"type": "boolean"},
"feeCushion": {"$ref": "value"},
"servers": {
"type": "array",
"items": {

View File

@@ -0,0 +1,67 @@
'use strict';
const _ = require('lodash');
const {RippledNetworkError} = require('./errors');
const {promisify, convertKeysFromSnakeCaseToCamelCase} = require('./utils');
export type GetServerInfoResponse = {
buildVersion: string,
completeLedgers: string,
hostid: string,
ioLatencyMs: number,
load?: {
jobTypes: Array<Object>,
threads: number
},
lastClose: {
convergeTimeS: number,
proposers: number
},
loadFactor: number,
peers: number,
pubkeyNode: string,
pubkeyValidator?: string,
serverState: string,
validatedLedger: {
age: number,
baseFeeXrp: number,
hash: string,
reserveBaseXrp: number,
reserveIncXrp: number,
seq: number
},
validationQuorum: number
}
function getServerInfoAsync(remote,
callback: (err: any, data?: GetServerInfoResponse) => void
): void {
remote.rawRequest({command: 'server_info'}, (error, response) => {
if (error) {
const message = _.get(error, ['remote', 'error_message'], error.message);
callback(new RippledNetworkError(message));
} else {
callback(null, convertKeysFromSnakeCaseToCamelCase(response.info));
}
});
}
function getServerInfo(remote: Object): Promise<GetServerInfoResponse> {
return promisify(getServerInfoAsync)(remote);
}
function computeFeeFromServerInfo(cushion: number,
serverInfo: GetServerInfoResponse
): number {
return (Number(serverInfo.validatedLedger.baseFeeXrp)
* Number(serverInfo.loadFactor) * cushion).toString();
}
function getFee(remote: Object, cushion: number) {
return getServerInfo(remote).then(
_.partial(computeFeeFromServerInfo, cushion));
}
module.exports = {
getServerInfo,
getFee
};

View File

@@ -115,6 +115,10 @@ function promisify(asyncFunction: AsyncFunction): Function {
return es6promisify(wrapCatch(asyncFunction));
}
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
}
module.exports = {
core,
dropsToXrp,
@@ -126,5 +130,6 @@ module.exports = {
convertExceptions,
convertErrors,
convertKeysFromSnakeCaseToCamelCase,
promisify
promisify,
removeUndefined
};

View File

@@ -47,6 +47,7 @@ function RippleAPI(options: {}) {
EventEmitter.call(this);
}
const _options = _.assign({}, options, {automatic_resubmission: false});
this._feeCushion = _options.feeCushion || 1.2;
this.remote = new common.core.Remote(_options);
this.remote.on('ledger_closed', message => {
this.emit('ledgerClosed', server.formatLedgerClose(message));

View File

@@ -2,10 +2,7 @@
'use strict';
const utils = require('./utils');
const removeUndefined = require('./parse/utils').removeUndefined;
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors, removeUndefined} = utils.common;
type AccountData = {
Sequence: number,
@@ -62,11 +59,12 @@ function getAccountInfoAsync(account: string, options: AccountInfoOptions,
validate.getAccountInfoOptions(options);
const request = {
command: 'account_info',
account: account,
ledger: options.ledgerVersion || 'validated'
ledger_index: options.ledgerVersion || 'validated'
};
this.remote.requestAccountInfo(request,
this.remote.rawRequest(request,
composeAsync(formatAccountInfo, convertErrors(callback)));
}

View File

@@ -3,9 +3,7 @@
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
import type {Amount} from '../common/types.js';
type BalanceSheetOptions = {
@@ -55,12 +53,13 @@ function getBalanceSheetAsync(address: string, options: BalanceSheetOptions,
validate.address(address);
validate.getBalanceSheetOptions(options);
const requestOptions = Object.assign({}, {
const request = {
command: 'gateway_balances',
account: address,
strict: true,
hotwallet: options.excludeAddresses,
ledger: options.ledgerVersion
});
ledger_index: options.ledgerVersion
};
const requestCallback = composeAsync(
formatBalanceSheet, convertErrors(callback));
@@ -71,11 +70,11 @@ function getBalanceSheetAsync(address: string, options: BalanceSheetOptions,
return;
}
if (_.isUndefined(requestOptions.ledger)) {
requestOptions.ledger = ledgerVersion;
if (_.isUndefined(request.ledger_index)) {
request.ledger_index = ledgerVersion;
}
this.remote.requestGatewayBalances(requestOptions, requestCallback);
this.remote.rawRequest(request, requestCallback);
});
}

View File

@@ -4,11 +4,8 @@ const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const getTrustlines = require('./trustlines');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
import type {Remote, GetLedgerSequenceCallback} from '../../core/remote';
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';

View File

@@ -1,9 +1,7 @@
/* @flow */
'use strict';
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
const parseLedger = require('./parse/ledger');
import type {GetLedger} from './types.js';
@@ -19,13 +17,14 @@ function getLedgerAsync(options: LedgerOptions, callback) {
validate.getLedgerOptions(options);
const request = {
ledger: options.ledgerVersion || 'validated',
command: 'ledger',
ledger_index: options.ledgerVersion || 'validated',
expand: options.includeAllData,
transactions: options.includeTransactions,
accounts: options.includeState
};
this.remote.requestLedger(request,
this.remote.rawRequest(request,
composeAsync(response => parseLedger(response.ledger),
convertErrors(callback)));
}

View File

@@ -3,9 +3,7 @@
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
const parseOrderbookOrder = require('./parse/orderbook-order');
import type {Remote} from '../../core/remote';
import type {OrdersOptions, OrderSpecification} from './types.js';
@@ -42,10 +40,11 @@ function getBookOffers(remote: Remote, account: string,
ledgerVersion?: number, limit?: number, takerGets: Issue,
takerPays: Issue, callback
) {
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
remote.rawRequest(utils.renameCounterpartyToIssuerInOrder({
command: 'book_offers',
taker_gets: takerGets,
taker_pays: takerPays,
ledger: ledgerVersion || 'validated',
ledger_index: ledgerVersion || 'validated',
limit: limit,
taker: account
}), composeAsync(data => data.offers, convertErrors(callback)));

View File

@@ -3,9 +3,7 @@
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
const parseAccountOrder = require('./parse/account-order');
import type {Remote} from '../../core/remote';
import type {OrdersOptions, Order} from './types.js';
@@ -15,11 +13,12 @@ type GetOrders = Array<Order>
function requestAccountOffers(remote: Remote, address: string,
ledgerVersion: number, marker: string, limit: number, callback
) {
remote.requestAccountOffers({
remote.rawRequest({
command: 'account_offers',
account: address,
marker: marker,
limit: utils.clamp(limit, 10, 400),
ledger: ledgerVersion
ledger_index: ledgerVersion
},
composeAsync((data) => ({
marker: data.marker,

View File

@@ -22,10 +22,6 @@ function parseTimestamp(tx: {date: string}): string | void {
return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
}
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
}
function removeEmptyCounterparty(amount) {
if (amount.counterparty === '') {
delete amount.counterparty;
@@ -76,7 +72,7 @@ function parseMemos(tx: Object): ?Array<Object> {
return undefined;
}
return tx.Memos.map((m) => {
return removeUndefined({
return utils.common.removeUndefined({
type: m.Memo.parsed_memo_type || hexToString(m.Memo.MemoType),
format: m.Memo.parsed_memo_format || hexToString(m.Memo.MemoFormat),
data: m.Memo.parsed_memo_data || hexToString(m.Memo.MemoData)
@@ -87,10 +83,10 @@ function parseMemos(tx: Object): ?Array<Object> {
module.exports = {
parseOutcome,
parseMemos,
removeUndefined,
adjustQualityForXRP,
dropsToXrp: utils.common.dropsToXrp,
constants: utils.common.constants,
txFlags: utils.common.txFlags,
core: utils.common.core
core: utils.common.core,
removeUndefined: utils.common.removeUndefined
};

View File

@@ -5,12 +5,9 @@ const async = require('async');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const parsePathfind = require('./parse/pathfind');
const validate = utils.common.validate;
const {validate, composeAsync, convertErrors, toRippledAmount} = utils.common;
const NotFoundError = utils.common.errors.NotFoundError;
const ValidationError = utils.common.errors.ValidationError;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const toRippledAmount = utils.common.toRippledAmount;
import type {Remote} from '../../core/remote';
import type {RippledAmount} from '../common/types.js';
import type {GetPaths, PathFind, PathFindParams,

View File

@@ -2,11 +2,9 @@
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const parseFields = require('./parse/fields');
const composeAsync = utils.common.composeAsync;
const {validate, composeAsync, convertErrors} = utils.common;
const AccountFlags = utils.common.constants.AccountFlags;
const convertErrors = utils.common.convertErrors;
type SettingsOptions = {
ledgerVersion?: number
@@ -55,11 +53,12 @@ function getSettingsAsync(account: string, options: SettingsOptions, callback) {
validate.getSettingsOptions(options);
const request = {
command: 'account_info',
account: account,
ledger: options.ledgerVersion || 'validated'
ledger_index: options.ledgerVersion || 'validated'
};
this.remote.requestAccountInfo(request,
this.remote.rawRequest(request,
composeAsync(formatSettings, convertErrors(callback)));
}

View File

@@ -4,9 +4,7 @@ const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const validate = utils.common.validate;
const errors = utils.common.errors;
const convertErrors = utils.common.convertErrors;
const {validate, convertErrors, errors} = utils.common;
const RippleError = require('../../core/rippleerror').RippleError;
import type {Remote} from '../../core/remote';
@@ -27,7 +25,12 @@ function attachTransactionDate(remote: Remote, tx: Object,
return;
}
remote.requestLedger(tx.ledger_index, (error, data) => {
const request = {
command: 'ledger',
ledger_index: tx.ledger_index
};
remote.rawRequest(request, (error, data) => {
if (error) {
callback(new errors.NotFoundError('Transaction ledger not found'));
} else if (typeof data.ledger.close_time === 'number') {
@@ -98,8 +101,8 @@ function getTransactionAsync(identifier: string, options: TransactionOptions,
}
async.waterfall([
_.partial(remote.requestTx.bind(remote),
{hash: identifier, binary: false}),
_.partial(remote.rawRequest.bind(remote),
{command: 'tx', transaction: identifier, binary: false}),
_.partial(attachTransactionDate, remote)
], maxLedgerGetter.bind(this));
}

View File

@@ -2,15 +2,13 @@
/* eslint-disable max-params */
'use strict';
const _ = require('lodash');
const binary = require('ripple-binary-codec');
const {computeTransactionHash} = require('ripple-hashes');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const getTransaction = require('./transaction');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
import type {Remote} from '../../core/remote';
import type {TransactionType} from './transaction-types';
@@ -32,11 +30,22 @@ type GetTransactionsResponse = Array<TransactionType>
type CallbackType = (err?: ?Error, data?: GetTransactionsResponse) => void
function parseBinaryTransaction(transaction) {
const tx = binary.decode(transaction.tx_blob);
tx.hash = computeTransactionHash(tx);
tx.ledger_index = transaction.ledger_index;
return {
tx: tx,
meta: binary.decode(transaction.meta),
validated: transaction.validated
};
}
function parseAccountTxTransaction(tx) {
const _tx = tx.tx_blob ? parseBinaryTransaction(tx) : tx;
// rippled uses a different response format for 'account_tx' than 'tx'
tx.tx.meta = tx.meta;
tx.tx.validated = tx.validated;
return parseTransaction(tx.tx);
return parseTransaction(_.assign({}, _tx.tx,
{meta: _tx.meta, validated: _tx.validated}));
}
function counterpartyFilter(filters, tx: TransactionType) {
@@ -97,7 +106,8 @@ function formatPartialResponse(address: string,
function getAccountTx(remote: Remote, address: string,
options: TransactionsOptions, marker: string, limit: number, callback
) {
const params = {
const request = {
command: 'account_tx',
account: address,
// -1 is equivalent to earliest available validated ledger
ledger_index_min: options.minLedgerVersion || -1,
@@ -109,7 +119,7 @@ function getAccountTx(remote: Remote, address: string,
marker: marker
};
remote.requestAccountTx(params,
remote.rawRequest(request,
composeAsync(_.partial(formatPartialResponse, address, options),
convertErrors(callback)));
}

View File

@@ -3,9 +3,7 @@
const _ = require('lodash');
const async = require('async');
const utils = require('./utils');
const validate = utils.common.validate;
const composeAsync = utils.common.composeAsync;
const convertErrors = utils.common.convertErrors;
const {validate, composeAsync, convertErrors} = utils.common;
const parseAccountTrustline = require('./parse/account-trustline');
import type {Remote} from '../../core/remote';
@@ -29,15 +27,16 @@ function formatResponse(options: TrustlinesOptions, data) {
function getAccountLines(remote: Remote, address: string, ledgerVersion: number,
options: TrustlinesOptions, marker: string, limit: number, callback
) {
const requestOptions = {
const request = {
command: 'account_lines',
account: address,
ledger: ledgerVersion,
ledger_index: ledgerVersion,
marker: marker,
limit: utils.clamp(limit, 10, 400),
peer: options.counterparty
};
remote.requestAccountLines(requestOptions,
remote.rawRequest(request,
composeAsync(_.partial(formatResponse, options),
convertErrors(callback)));
}

View File

@@ -26,7 +26,12 @@ function clamp(value: number, min: number, max: number): number {
function getXRPBalance(remote: Remote, address: string, ledgerVersion?: number,
callback: Callback
): void {
remote.requestAccountInfo({account: address, ledger: ledgerVersion},
const request = {
command: 'account_info',
account: address,
ledger_index: ledgerVersion
};
remote.rawRequest(request,
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
}

View File

@@ -1,66 +1,13 @@
/* @flow */
'use strict';
const _ = require('lodash');
const common = require('../common');
type GetServerInfoResponse = {
buildVersion: string,
completeLedgers: string,
hostid: string,
ioLatencyMs: number,
load?: {
jobTypes: Array<Object>,
threads: number
},
lastClose: {
convergeTimeS: number,
proposers: number
},
loadFactor: number,
peers: number,
pubkeyNode: string,
pubkeyValidator?: string,
serverState: string,
validatedLedger: {
age: number,
baseFeeXrp: number,
hash: string,
reserveBaseXrp: number,
reserveIncXrp: number,
seq: number
},
validationQuorum: number
}
import type {GetServerInfoResponse} from '../common/serverinfo';
function isConnected(): boolean {
const server = this.remote.getServer();
return Boolean(server && server.isConnected());
}
function getServerInfoAsync(
callback: (err: any, data?: GetServerInfoResponse) => void
): void {
this.remote.requestServerInfo((error, response) => {
if (error) {
const message = _.get(error, ['remote', 'error_message'], error.message);
callback(new common.errors.RippledNetworkError(message));
} else {
callback(null,
common.convertKeysFromSnakeCaseToCamelCase(response.info));
}
});
}
function getFee(): ?number {
if (!this.remote.getConnectedServers().length) {
throw new common.errors.RippledNetworkError('No servers available.');
}
const fee = this.remote.createTransaction()._computeFee();
return fee === undefined ? undefined : common.dropsToXrp(fee);
}
function getLedgerVersion(): Promise<number> {
return common.promisify(this.remote.getLedgerSequence).call(this.remote);
}
@@ -86,7 +33,12 @@ function disconnect(): Promise<void> {
}
function getServerInfo(): Promise<GetServerInfoResponse> {
return common.promisify(getServerInfoAsync).call(this);
return common.serverInfo.getServerInfo(this.remote);
}
function getFee(): Promise<number> {
const cushion = this._feeCushion || 1.2;
return common.serverInfo.getFee(this.remote, cushion);
}
function rippleTimeToISO8601(rippleTime: string): string {

View File

@@ -41,7 +41,7 @@ function prepareOrderAsync(account: string, order: Order,
instructions: Instructions, callback
) {
const txJSON = createOrderTransaction(account, order);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareOrder(account: string, order: Order,

View File

@@ -21,7 +21,7 @@ function prepareOrderCancellationAsync(account: string, sequence: number,
instructions: Instructions, callback
) {
const txJSON = createOrderCancellationTransaction(account, sequence);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareOrderCancellation(account: string, sequence: number,

View File

@@ -145,7 +145,7 @@ function preparePaymentAsync(account: string, payment: Payment,
instructions: Instructions, callback
) {
const txJSON = createPaymentTransaction(account, payment);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function preparePayment(account: string, payment: Payment,

View File

@@ -99,7 +99,7 @@ function prepareSettingsAsync(account: string, settings: Settings,
instructions: Instructions, callback
) {
const txJSON = createSettingsTransaction(account, settings);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareSettings(account: string, settings: Object,

View File

@@ -35,7 +35,7 @@ function prepareSuspendedPaymentCancellationAsync(account: string,
) {
const txJSON =
createSuspendedPaymentCancellationTransaction(account, payment);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareSuspendedPaymentCancellation(account: string,

View File

@@ -54,7 +54,7 @@ function prepareSuspendedPaymentCreationAsync(account: string,
payment: SuspendedPaymentCreation, instructions: Instructions, callback
) {
const txJSON = createSuspendedPaymentCreationTransaction(account, payment);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareSuspendedPaymentCreation(account: string,

View File

@@ -47,7 +47,7 @@ function prepareSuspendedPaymentExecutionAsync(account: string,
payment: SuspendedPaymentExecution, instructions: Instructions, callback
) {
const txJSON = createSuspendedPaymentExecutionTransaction(account, payment);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareSuspendedPaymentExecution(account: string,

View File

@@ -54,7 +54,7 @@ function prepareTrustlineAsync(account: string,
trustline: TrustLineSpecification, instructions: Instructions, callback
) {
const txJSON = createTrustlineTransaction(account, trustline);
utils.prepareTransaction(txJSON, this.remote, instructions, callback);
utils.prepareTransaction(txJSON, this, instructions, callback);
}
function prepareTrustline(account: string,

View File

@@ -4,25 +4,16 @@ const _ = require('lodash');
const async = require('async');
const BigNumber = require('bignumber.js');
const common = require('../common');
const composeAsync = common.composeAsync;
const txFlags = common.txFlags;
import type {Remote} from '../../core/remote';
import type {Instructions} from './types.js';
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
}
function getFeeDrops(remote: Remote, callback) {
const feeUnits = 10; // all transactions currently have a fee of 10 fee units
remote.feeTxAsync(feeUnits, (err, data) => {
callback(err, data ? data.to_text() : undefined);
});
}
function formatPrepareResponse(txJSON: Object): Object {
const instructions = {
fee: txJSON.Fee,
fee: common.dropsToXrp(txJSON.Fee),
sequence: txJSON.Sequence,
maxLedgerVersion: txJSON.LastLedgerSequence
};
@@ -42,7 +33,7 @@ function setCanonicalFlag(txJSON) {
type Callback = (err: ?(typeof Error),
data: {txJSON: string, instructions: Instructions}) => void;
function prepareTransaction(txJSON: Object, remote: Remote,
function prepareTransaction(txJSON: Object, api: Object,
instructions: Instructions, callback: Callback
): void {
common.validate.instructions(instructions);
@@ -57,7 +48,7 @@ function prepareTransaction(txJSON: Object, remote: Remote,
} else {
const offset = instructions.maxLedgerVersionOffset !== undefined ?
instructions.maxLedgerVersionOffset : 3;
remote.getLedgerSequence((error, ledgerVersion) => {
api.remote.getLedgerSequence((error, ledgerVersion) => {
txJSON.LastLedgerSequence = ledgerVersion + offset;
callback_(error);
});
@@ -69,14 +60,16 @@ function prepareTransaction(txJSON: Object, remote: Remote,
txJSON.Fee = common.xrpToDrops(instructions.fee);
callback_();
} else {
getFeeDrops(remote, composeAsync((serverFeeDrops) => {
common.serverInfo.getFee(api.remote, api._feeCushion).then(fee => {
const feeDrops = common.xrpToDrops(fee);
if (instructions.maxFee !== undefined) {
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
txJSON.Fee = BigNumber.min(serverFeeDrops, maxFeeDrops).toString();
txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString();
} else {
txJSON.Fee = serverFeeDrops;
txJSON.Fee = feeDrops;
}
}, callback_));
callback_();
});
}
}
@@ -85,8 +78,12 @@ function prepareTransaction(txJSON: Object, remote: Remote,
txJSON.Sequence = instructions.sequence;
callback_(null, formatPrepareResponse(txJSON));
} else {
remote.findAccount(account).getNextSequence(function(error, sequence) {
txJSON.Sequence = sequence;
const request = {
command: 'account_info',
account: account
};
api.remote.rawRequest(request, function(error, response) {
txJSON.Sequence = response.account_data.Sequence;
callback_(error, formatPrepareResponse(txJSON));
});
}

View File

@@ -810,6 +810,12 @@ Remote.prototype.request = function(request) {
}
};
Remote.prototype.rawRequest = function(message, callback) {
const request = new Request(this, message.command);
_.assign(request.message, _.omit(message, _.isUndefined));
request.request(callback);
};
/**
* Request ping
*

View File

@@ -607,7 +607,9 @@ describe('RippleAPI', function() {
});
it('getFee', function() {
assert.strictEqual(this.api.getFee(), '0.000012');
return this.api.getFee().then(fee => {
assert.strictEqual(fee, '0.000012');
});
});
it('disconnect & isConnected', function() {

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"OfferCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":23,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2148139008,\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":{\"value\":\"10.1\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"TakerPays\":\"2000000\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":\"2000000\",\"TakerPays\":{\"value\":\"10.1\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147811328,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"10000\",\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147942400,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"LTC\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\"},\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"Paths\":[[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"},{\"account\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"WalletLocator\":\"0\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"ClearFlag\":1,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SetFlag\":1,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"SetRegularKey\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"RegularKey\":\"rAR8rR8sUkBoCZFawhkWzY4Y5YoyuznwD\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TransferRate\":1000000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"SuspendedPaymentCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"SuspendedPaymentCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"CancelAfter\":494009529,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"SuspendedPaymentFinish\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"Method\":1,\"Digest\":\"8F434346648F6B96DF89DDA901C5176B10A6D83961DD3C1AC88B59B2DC327AA4\",\"Proof\":\"7768617465766572\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"value\":\"0.1\",\"currency\":\"BTC\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -1,7 +1,7 @@
{
"txJSON": "{\"Flags\":2149711872,\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"value\":\"10000\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"QualityIn\":910000000,\"QualityOut\":870000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "12",
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}

View File

@@ -40,7 +40,11 @@ function main() {
makeRequest(connection, request4)
]).then(() => {
console.log('Done');
process.exit();
});
connection.getLedgerVersion().then(console.log);
connection.on('ledgerClosed', ledger => {
console.log(ledger);
connection.getLedgerVersion().then(console.log);
});
});
}

View File

@@ -176,11 +176,12 @@ describe('integration tests', function() {
it('getFee', function() {
const fee = this.api.getFee();
return this.api.getFee().then(fee => {
assert.strictEqual(typeof fee, 'string');
assert(!isNaN(Number(fee)));
assert(parseFloat(fee) === Number(fee));
});
});
it('getLedgerVersion', function() {