mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-05 05:15:48 +00:00
Compare commits
45 Commits
0.13.0-rc1
...
0.13.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3df64091dd | ||
|
|
de5d9335d1 | ||
|
|
88a65f08d8 | ||
|
|
837f7e6e9b | ||
|
|
4faa857330 | ||
|
|
9c7b0cb889 | ||
|
|
a11abcc016 | ||
|
|
dd693fdc5f | ||
|
|
5ac2576fcf | ||
|
|
512817a2db | ||
|
|
1f8c8d88fa | ||
|
|
044ed53935 | ||
|
|
d47bb2749a | ||
|
|
0dc000839b | ||
|
|
462e440d5b | ||
|
|
1891fe0afd | ||
|
|
8cec60c4b0 | ||
|
|
7419244b39 | ||
|
|
eb9a48d2d6 | ||
|
|
e44d36b4af | ||
|
|
9a5d05f198 | ||
|
|
d7a20a5d53 | ||
|
|
b56680e24e | ||
|
|
886e80ff6d | ||
|
|
142187b024 | ||
|
|
72f3237aba | ||
|
|
a2406ac163 | ||
|
|
91a64137fe | ||
|
|
57ecbc58f8 | ||
|
|
ea4d1007b8 | ||
|
|
16bc7b986b | ||
|
|
115f95fa96 | ||
|
|
b77b76ebb5 | ||
|
|
f516298a84 | ||
|
|
edb31a0c9c | ||
|
|
e99010f363 | ||
|
|
fa865f8409 | ||
|
|
40b613b7a2 | ||
|
|
a79b010572 | ||
|
|
7404795dc6 | ||
|
|
47a9fb5803 | ||
|
|
701d4c5722 | ||
|
|
d8d6f945ec | ||
|
|
a6821bb8ab | ||
|
|
2f163c3b6e |
@@ -7,7 +7,9 @@
|
||||
- [Method documentation](https://rawgit.com/ripple/ripple-lib/develop/docs/api.html)
|
||||
|
||||
**Changes**
|
||||
+ [Add Remote.closeCurrentPathFind function, so current pathfind can be properly closed](https://github.com/ripple/ripple-lib/commit/e99010f363fc7cbe7fd547d3ca5b32ea083c44e6)
|
||||
+ [Implement Balance Sheet API](https://github.com/ripple/ripple-lib/pull/579)
|
||||
+ [Fix bugs in orderbook subscription](https://github.com/ripple/ripple-lib/commit/7404795dc64a85216148de7bc3ca7da7b33f4490)
|
||||
+ [Fix crash due to rippled slowDown error](https://github.com/ripple/ripple-lib/commit/84838b2e9f6969b593b8462a62a6b8f516ada937)
|
||||
+ [Fix: Emit error events and return error on pathfind](https://github.com/ripple/ripple-lib/commit/1ccbaf677631a1944eb05d90f7afc5f3690a03dd)
|
||||
+ [Deprecate core and remove snake case method copying](https://github.com/ripple/ripple-lib/commit/fb8dc44ec1d49bb05cd0cdbe6dd4ab211195868a)
|
||||
|
||||
@@ -14,6 +14,8 @@ __(More examples coming soon!)__
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
|
||||
+ [Transaction events](REFERENCE.md#transaction-events)
|
||||
4. [Subscriptions](REFERENCE.md#subscriptions)
|
||||
+ [Orderbook subscription](REFERENCE.md#orderbook-subscription)
|
||||
|
||||
###Also see:
|
||||
|
||||
@@ -240,11 +242,10 @@ var options = {
|
||||
limit: < limit >
|
||||
};
|
||||
|
||||
var request = remote.requestBookOffers(options);
|
||||
|
||||
request.request(function(err, offers) {
|
||||
//handle offers
|
||||
var request = remote.requestBookOffers(options, function(err, offers) {
|
||||
// handle offers
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
##Transaction requests
|
||||
@@ -352,3 +353,24 @@ transaction.submit(function(err, res) {
|
||||
#Amount objects
|
||||
|
||||
Coming Soon
|
||||
|
||||
#Subscriptions
|
||||
|
||||
##Orderbook subscription
|
||||
|
||||
Subscribes to an orderbook (including autobridged books). Send the orderbook on subscribe then notifies updates.
|
||||
Available events: ['transaction', 'model', 'trade', 'offer_added', 'offer_removed', 'offer_changed', 'offer_funds_changed']
|
||||
```js
|
||||
parameters = {
|
||||
currency_pays: <string>,
|
||||
issuer_pays: <string>,
|
||||
currency_gets: <string>,
|
||||
issuer_gets: <string>
|
||||
}
|
||||
```
|
||||
Basic subscription:
|
||||
```js
|
||||
var Orderbook = Remote.book(parameters);
|
||||
Orderbook.on('model', handler);
|
||||
|
||||
```
|
||||
|
||||
86
npm-shrinkwrap.json
generated
86
npm-shrinkwrap.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.13.0-rc12",
|
||||
"version": "0.13.0-rc15",
|
||||
"npm-shrinkwrap-version": "5.4.0",
|
||||
"node-version": "v0.12.7",
|
||||
"dependencies": {
|
||||
@@ -132,9 +132,73 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-binary-codec": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.6.tgz",
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz"
|
||||
},
|
||||
"create-hash": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
|
||||
"dependencies": {
|
||||
"cipher-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz"
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
|
||||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"decimal.js": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-hashes": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.0.1.tgz",
|
||||
"dependencies": {
|
||||
"create-hash": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
|
||||
"dependencies": {
|
||||
"cipher-base": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
|
||||
},
|
||||
"sha.js": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-keypairs": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.9.0.tgz",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
||||
"dependencies": {
|
||||
"brorand": {
|
||||
"version": "1.0.5",
|
||||
@@ -149,22 +213,6 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-address-codec": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-1.6.0.tgz",
|
||||
"dependencies": {
|
||||
"x-address-codec": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.6.0.tgz",
|
||||
"dependencies": {
|
||||
"base-x": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.13.0-rc12",
|
||||
"version": "0.13.0-rc15",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -27,7 +27,9 @@
|
||||
"lodash": "^3.1.0",
|
||||
"lru-cache": "~2.5.0",
|
||||
"ripple-address-codec": "^2.0.1",
|
||||
"ripple-keypairs": "^0.9.0",
|
||||
"ripple-binary-codec": "^0.0.6",
|
||||
"ripple-hashes": "^0.0.1",
|
||||
"ripple-keypairs": "^0.10.0",
|
||||
"ripple-lib-transactionparser": "^0.5.1",
|
||||
"ripple-lib-value": "0.1.0",
|
||||
"sjcl-codec": "0.1.0",
|
||||
|
||||
@@ -26,7 +26,7 @@ gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
@@ -40,4 +40,4 @@ exit_on_error
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
cd ../..
|
||||
|
||||
@@ -26,7 +26,7 @@ gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
exit_on_error
|
||||
@@ -40,4 +40,4 @@ exit_on_error
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
cd ../..
|
||||
|
||||
@@ -4,21 +4,11 @@
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const validator = require('is-my-json-valid');
|
||||
const core = require('./utils').core;
|
||||
const ValidationError = require('./errors').ValidationError;
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
|
||||
let SCHEMAS = {};
|
||||
|
||||
function isValidAddress(address: string): boolean {
|
||||
return typeof address === 'string' && address.length > 0 &&
|
||||
address[0] === 'r' &&
|
||||
core.UInt160.is_valid(address);
|
||||
}
|
||||
|
||||
function isValidLedgerHash(ledgerHash) {
|
||||
return core.UInt256.is_valid(ledgerHash);
|
||||
}
|
||||
|
||||
function loadSchemas() {
|
||||
// listed explicitly for webpack (instead of scanning schemas directory)
|
||||
const schemas = [
|
||||
@@ -102,8 +92,7 @@ function formatSchemaErrors(errors) {
|
||||
}
|
||||
|
||||
function schemaValidate(schemaName: string, object: any): void {
|
||||
const formats = {address: isValidAddress,
|
||||
ledgerHash: isValidLedgerHash};
|
||||
const formats = {address: isValidAddress};
|
||||
const options = {schemas: SCHEMAS, formats: formats,
|
||||
verbose: true, greedy: true};
|
||||
const schema = SCHEMAS[schemaName];
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"format": "uri",
|
||||
"pattern": "^wss?://"
|
||||
}
|
||||
},
|
||||
"proxy": {
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
55
src/api/common/types.js
Normal file
55
src/api/common/types.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export type RippledAmountIOU = {
|
||||
currency: string,
|
||||
value: string,
|
||||
issuer?: string
|
||||
}
|
||||
|
||||
export type RippledAmount = string | RippledAmountIOU
|
||||
|
||||
|
||||
export type Amount = {
|
||||
value: string,
|
||||
currency: string,
|
||||
counterparty?: string
|
||||
}
|
||||
|
||||
|
||||
// Amount where counterparty and value are optional
|
||||
export type LaxLaxAmount = {
|
||||
currency: string,
|
||||
value?: string,
|
||||
counterparty?: string
|
||||
}
|
||||
|
||||
// A currency-counterparty pair, or just currency if it's XRP
|
||||
export type Issue = {
|
||||
currency: string,
|
||||
counterparty?: string
|
||||
}
|
||||
|
||||
export type Adjustment = {
|
||||
address: string,
|
||||
amount: Amount,
|
||||
tag?: number
|
||||
}
|
||||
|
||||
export type MaxAdjustment = {
|
||||
address: string,
|
||||
maxAmount: Amount,
|
||||
tag?: number
|
||||
}
|
||||
|
||||
export type MinAdjustment = {
|
||||
address: string,
|
||||
minAmount: Amount,
|
||||
tag?: number
|
||||
}
|
||||
|
||||
export type Memo = {
|
||||
type?: string,
|
||||
format?: string,
|
||||
data?: string
|
||||
}
|
||||
@@ -7,7 +7,7 @@ const errors = require('./errors');
|
||||
const es6promisify = require('es6-promisify');
|
||||
const keypairs = require('ripple-keypairs');
|
||||
|
||||
type Amount = {currency: string, issuer: string, value: string}
|
||||
import type {Amount, RippledAmount} from './types.js';
|
||||
|
||||
function dropsToXrp(drops: string): string {
|
||||
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
|
||||
@@ -17,13 +17,14 @@ function xrpToDrops(xrp: string): string {
|
||||
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
|
||||
}
|
||||
|
||||
function toRippledAmount(amount: Amount): string|Amount {
|
||||
function toRippledAmount(amount: Amount): RippledAmount {
|
||||
if (amount.currency === 'XRP') {
|
||||
return xrpToDrops(amount.value);
|
||||
}
|
||||
return {
|
||||
currency: amount.currency,
|
||||
issuer: amount.counterparty ? amount.counterparty : amount.issuer,
|
||||
issuer: amount.counterparty ? amount.counterparty :
|
||||
(amount.issuer ? amount.issuer : undefined),
|
||||
value: amount.value
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
@@ -5,8 +6,23 @@ const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
import type {Amount} from '../common/types.js';
|
||||
|
||||
function formatBalanceSheet(balanceSheet) {
|
||||
type BalanceSheetOptions = {
|
||||
excludeAddresses?: Array<string>,
|
||||
ledgerVersion?: number
|
||||
}
|
||||
|
||||
type GetBalanceSheet = {
|
||||
balances?: Array<Amount>,
|
||||
assets?: Array<Amount>,
|
||||
obligations?: Array<{
|
||||
currency: string,
|
||||
value: string
|
||||
}>
|
||||
}
|
||||
|
||||
function formatBalanceSheet(balanceSheet): GetBalanceSheet {
|
||||
const result = {};
|
||||
|
||||
if (!_.isUndefined(balanceSheet.balances)) {
|
||||
@@ -33,7 +49,9 @@ function formatBalanceSheet(balanceSheet) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getBalanceSheetAsync(address, options, callback) {
|
||||
function getBalanceSheetAsync(address: string, options: BalanceSheetOptions,
|
||||
callback
|
||||
) {
|
||||
validate.address(address);
|
||||
validate.getBalanceSheetOptions(options);
|
||||
|
||||
@@ -61,7 +79,8 @@ function getBalanceSheetAsync(address, options, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function getBalanceSheet(address: string, options = {}) {
|
||||
function getBalanceSheet(address: string, options: BalanceSheetOptions = {}
|
||||
): Promise<GetBalanceSheet> {
|
||||
return utils.promisify(getBalanceSheetAsync).call(this, address, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,20 @@ const getTrustlines = require('./trustlines');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {GetLedgerSequenceCallback} from '../../core/remote';
|
||||
import type {Remote, GetLedgerSequenceCallback} from '../../core/remote';
|
||||
|
||||
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
|
||||
|
||||
|
||||
function getTrustlineBalanceAmount(trustline) {
|
||||
type Balance = {
|
||||
value: string,
|
||||
currency: string,
|
||||
counterparty?: string
|
||||
}
|
||||
|
||||
type GetBalances = Array<Balance>
|
||||
|
||||
function getTrustlineBalanceAmount(trustline: Trustline) {
|
||||
return {
|
||||
currency: trustline.specification.currency,
|
||||
counterparty: trustline.specification.counterparty,
|
||||
@@ -19,16 +28,27 @@ function getTrustlineBalanceAmount(trustline) {
|
||||
};
|
||||
}
|
||||
|
||||
function formatBalances(balances) {
|
||||
const xrpBalance = {
|
||||
currency: 'XRP',
|
||||
value: balances.xrp
|
||||
};
|
||||
return [xrpBalance].concat(
|
||||
balances.trustlines.map(getTrustlineBalanceAmount));
|
||||
function formatBalances(options, balances) {
|
||||
const result = balances.trustlines.map(getTrustlineBalanceAmount);
|
||||
if (!(options.counterparty ||
|
||||
(options.currency && options.currency !== 'XRP')
|
||||
)) {
|
||||
const xrpBalance = {
|
||||
currency: 'XRP',
|
||||
value: balances.xrp
|
||||
};
|
||||
result.unshift(xrpBalance);
|
||||
}
|
||||
if (options.limit && result.length > options.limit) {
|
||||
const toRemove = result.length - options.limit;
|
||||
result.splice(-toRemove, toRemove);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTrustlinesAsync(account, options, callback) {
|
||||
function getTrustlinesAsync(account: string, options: TrustlinesOptions,
|
||||
callback
|
||||
) {
|
||||
getTrustlines.call(this, account, options)
|
||||
.then(data => callback(null, data))
|
||||
.catch(callback);
|
||||
@@ -44,7 +64,9 @@ function getLedgerVersionHelper(remote: Remote, optionValue?: number,
|
||||
}
|
||||
}
|
||||
|
||||
function getBalancesAsync(account, options, callback) {
|
||||
function getBalancesAsync(account: string, options: TrustlinesOptions,
|
||||
callback
|
||||
) {
|
||||
validate.address(account);
|
||||
validate.getBalancesOptions(options);
|
||||
|
||||
@@ -54,10 +76,11 @@ function getBalancesAsync(account, options, callback) {
|
||||
_.partial(utils.getXRPBalance, this.remote, account)
|
||||
),
|
||||
trustlines: _.partial(getTrustlinesAsync.bind(this), account, options)
|
||||
}, composeAsync(formatBalances, convertErrors(callback)));
|
||||
}, composeAsync(_.partial(formatBalances, options), convertErrors(callback)));
|
||||
}
|
||||
|
||||
function getBalances(account: string, options = {}) {
|
||||
function getBalances(account: string, options: TrustlinesOptions = {}
|
||||
): Promise<GetBalances> {
|
||||
return utils.promisify(getBalancesAsync).call(this, account, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,17 @@ const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
const parseLedger = require('./parse/ledger');
|
||||
import type {GetLedger} from './types.js';
|
||||
|
||||
function getLedgerAsync(options, callback) {
|
||||
type LedgerOptions = {
|
||||
ledgerVersion?: number,
|
||||
includeAllData?: boolean,
|
||||
includeTransactions?: boolean,
|
||||
includeState?: boolean
|
||||
}
|
||||
|
||||
|
||||
function getLedgerAsync(options: LedgerOptions, callback) {
|
||||
validate.getLedgerOptions(options);
|
||||
|
||||
const request = {
|
||||
@@ -21,7 +30,7 @@ function getLedgerAsync(options, callback) {
|
||||
convertErrors(callback)));
|
||||
}
|
||||
|
||||
function getLedger(options = {}) {
|
||||
function getLedger(options: LedgerOptions = {}): Promise<GetLedger> {
|
||||
return utils.promisify(getLedgerAsync).call(this, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,40 @@ const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
const parseOrderbookOrder = require('./parse/orderbook-order');
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {OrdersOptions, OrderSpecification} from './types.js';
|
||||
import type {Amount, Issue} from '../common/types.js';
|
||||
|
||||
type Orderbook = {
|
||||
base: Issue,
|
||||
counter: Issue
|
||||
}
|
||||
|
||||
type OrderbookItem = {
|
||||
specification: OrderSpecification,
|
||||
properties: {
|
||||
maker: string,
|
||||
sequence: number,
|
||||
makerExchangeRate: string
|
||||
},
|
||||
state?: {
|
||||
fundedAmount: Amount,
|
||||
priceOfFundedAmount: Amount
|
||||
}
|
||||
}
|
||||
|
||||
type OrderbookOrders = Array<OrderbookItem>
|
||||
|
||||
type GetOrderbook = {
|
||||
bids: OrderbookOrders,
|
||||
asks: OrderbookOrders
|
||||
}
|
||||
|
||||
// account is to specify a "perspective", which affects which unfunded offers
|
||||
// are returned
|
||||
function getBookOffers(remote, account, ledgerVersion, limit,
|
||||
takerGets, takerPays, callback
|
||||
function getBookOffers(remote: Remote, account: string,
|
||||
ledgerVersion?: number, limit?: number, takerGets: Issue,
|
||||
takerPays: Issue, callback
|
||||
) {
|
||||
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
|
||||
taker_gets: takerGets,
|
||||
@@ -22,15 +51,15 @@ function getBookOffers(remote, account, ledgerVersion, limit,
|
||||
}), composeAsync(data => data.offers, convertErrors(callback)));
|
||||
}
|
||||
|
||||
function isSameIssue(a, b) {
|
||||
function isSameIssue(a: Amount, b: Amount) {
|
||||
return a.currency === b.currency && a.counterparty === b.counterparty;
|
||||
}
|
||||
|
||||
function directionFilter(direction, order) {
|
||||
function directionFilter(direction: string, order: OrderbookItem) {
|
||||
return order.specification.direction === direction;
|
||||
}
|
||||
|
||||
function flipOrder(order) {
|
||||
function flipOrder(order: OrderbookItem) {
|
||||
const specification = order.specification;
|
||||
const flippedSpecification = {
|
||||
quantity: specification.totalPrice,
|
||||
@@ -41,12 +70,12 @@ function flipOrder(order) {
|
||||
return _.merge({}, order, {specification: newSpecification});
|
||||
}
|
||||
|
||||
function alignOrder(base, order) {
|
||||
function alignOrder(base: Amount, order: OrderbookItem) {
|
||||
const quantity = order.specification.quantity;
|
||||
return isSameIssue(quantity, base) ? order : flipOrder(order);
|
||||
}
|
||||
|
||||
function formatBidsAndAsks(orderbook, offers) {
|
||||
function formatBidsAndAsks(orderbook: Orderbook, offers) {
|
||||
// the "base" currency is the currency that you are buying or selling
|
||||
// the "counter" is the currency that the "base" is priced in
|
||||
// a "bid"/"ask" is an order to buy/sell the base, respectively
|
||||
@@ -64,7 +93,9 @@ function formatBidsAndAsks(orderbook, offers) {
|
||||
return {bids, asks};
|
||||
}
|
||||
|
||||
function getOrderbookAsync(account, orderbook, options, callback) {
|
||||
function getOrderbookAsync(account: string, orderbook: Orderbook,
|
||||
options: OrdersOptions, callback
|
||||
) {
|
||||
validate.address(account);
|
||||
validate.orderbook(orderbook);
|
||||
validate.getOrderbookOptions(options);
|
||||
@@ -78,7 +109,9 @@ function getOrderbookAsync(account, orderbook, options, callback) {
|
||||
callback));
|
||||
}
|
||||
|
||||
function getOrderbook(account: string, orderbook: Object, options = {}) {
|
||||
function getOrderbook(account: string, orderbook: Orderbook,
|
||||
options: OrdersOptions = {}
|
||||
): Promise<GetOrderbook> {
|
||||
return utils.promisify(getOrderbookAsync).call(this,
|
||||
account, orderbook, options);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,13 @@ const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
const parseAccountOrder = require('./parse/account-order');
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {OrdersOptions, Order} from './types.js';
|
||||
|
||||
function requestAccountOffers(remote, address, ledgerVersion, marker, limit,
|
||||
callback
|
||||
type GetOrders = Array<Order>
|
||||
|
||||
function requestAccountOffers(remote: Remote, address: string,
|
||||
ledgerVersion: number, marker: string, limit: number, callback
|
||||
) {
|
||||
remote.requestAccountOffers({
|
||||
account: address,
|
||||
@@ -23,7 +27,7 @@ function requestAccountOffers(remote, address, ledgerVersion, marker, limit,
|
||||
}), convertErrors(callback)));
|
||||
}
|
||||
|
||||
function getOrdersAsync(account, options, callback) {
|
||||
function getOrdersAsync(account: string, options: OrdersOptions, callback) {
|
||||
validate.address(account);
|
||||
validate.getOrdersOptions(options);
|
||||
|
||||
@@ -34,7 +38,8 @@ function getOrdersAsync(account, options, callback) {
|
||||
(order) => order.properties.sequence), callback));
|
||||
}
|
||||
|
||||
function getOrders(account: string, options = {}) {
|
||||
function getOrders(account: string, options: OrdersOptions = {}
|
||||
): Promise<GetOrders> {
|
||||
return utils.promisify(async.seq(
|
||||
utils.getLedgerOptionsWithLedgerVersion,
|
||||
getOrdersAsync)).call(this, account, options);
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
import type {Amount, RippledAmount} from '../../common/types.js';
|
||||
|
||||
type Amount = string | {currency: string, issuer: string, value: string}
|
||||
type XRPAmount = {currency: string, value: string}
|
||||
type IOUAmount = {currency: string, value: string, counterparty: string}
|
||||
type Output = XRPAmount | IOUAmount
|
||||
|
||||
function parseAmount(amount: Amount): Output {
|
||||
function parseAmount(amount: RippledAmount): Amount {
|
||||
if (typeof amount === 'string') {
|
||||
return {
|
||||
currency: 'XRP',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const _ = require('lodash');
|
||||
const removeUndefined = require('./utils').removeUndefined;
|
||||
const parseTransaction = require('./transaction');
|
||||
import type {GetLedger} from '../types.js';
|
||||
|
||||
function parseTransactions(transactions) {
|
||||
if (_.isEmpty(transactions)) {
|
||||
@@ -27,7 +28,7 @@ function parseState(state) {
|
||||
return {rawState: JSON.stringify(state)};
|
||||
}
|
||||
|
||||
function parseLedger(ledger: Object): Object {
|
||||
function parseLedger(ledger: Object): GetLedger {
|
||||
return removeUndefined(_.assign({
|
||||
accepted: ledger.accepted,
|
||||
closed: ledger.closed,
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const parseAmount = require('./amount');
|
||||
import type {Amount, RippledAmount} from '../../common/types.js';
|
||||
import type {GetPaths, RippledPathsResponse} from '../pathfind-types.js';
|
||||
|
||||
function parsePaths(paths) {
|
||||
return paths.map(steps => steps.map(step =>
|
||||
_.omit(step, ['type', 'type_hex'])));
|
||||
}
|
||||
|
||||
function removeAnyCounterpartyEncoding(address: string, amount: Object) {
|
||||
function removeAnyCounterpartyEncoding(address: string, amount: Amount) {
|
||||
return amount.counterparty === address ?
|
||||
_.omit(amount, 'counterparty') : amount;
|
||||
}
|
||||
@@ -21,7 +23,7 @@ function createAdjustment(address: string, adjustmentWithoutAddress: Object) {
|
||||
}
|
||||
|
||||
function parseAlternative(sourceAddress: string, destinationAddress: string,
|
||||
destinationAmount: Object, alternative: Object
|
||||
destinationAmount: RippledAmount, alternative: Object
|
||||
) {
|
||||
// we use "maxAmount"/"minAmount" here so that the result can be passed
|
||||
// directly to preparePayment
|
||||
@@ -38,7 +40,7 @@ function parseAlternative(sourceAddress: string, destinationAddress: string,
|
||||
};
|
||||
}
|
||||
|
||||
function parsePathfind(pathfindResult: Object): Object {
|
||||
function parsePathfind(pathfindResult: RippledPathsResponse): GetPaths {
|
||||
const sourceAddress = pathfindResult.source_account;
|
||||
const destinationAddress = pathfindResult.destination_account;
|
||||
const destinationAmount = pathfindResult.destination_amount;
|
||||
|
||||
@@ -67,15 +67,19 @@ function parseOutcome(tx: Object): ?Object {
|
||||
};
|
||||
}
|
||||
|
||||
function hexToString(hex) {
|
||||
return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined;
|
||||
}
|
||||
|
||||
function parseMemos(tx: Object): ?Array<Object> {
|
||||
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return tx.Memos.map((m) => {
|
||||
return removeUndefined({
|
||||
type: m.Memo.parsed_memo_type,
|
||||
format: m.Memo.parsed_memo_format,
|
||||
data: m.Memo.parsed_memo_data
|
||||
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)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
54
src/api/ledger/pathfind-types.js
Normal file
54
src/api/ledger/pathfind-types.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {Amount, LaxLaxAmount, RippledAmount, Adjustment, MaxAdjustment,
|
||||
MinAdjustment} from '../common/types.js';
|
||||
|
||||
|
||||
type Path = {
|
||||
source: Adjustment | MaxAdjustment,
|
||||
destination: Adjustment | MinAdjustment,
|
||||
paths: string
|
||||
}
|
||||
|
||||
export type GetPaths = Array<Path>
|
||||
|
||||
export type PathFind = {
|
||||
source: {
|
||||
address: string,
|
||||
amount?: Amount,
|
||||
currencies?: Array<{currency: string, counterparty?:string}>
|
||||
},
|
||||
destination: {
|
||||
address: string,
|
||||
amount: LaxLaxAmount
|
||||
}
|
||||
}
|
||||
|
||||
export type PathFindParams = {
|
||||
src_account: string,
|
||||
dst_amount: RippledAmount,
|
||||
dst_account: string,
|
||||
src_amount?: RippledAmount,
|
||||
src_currencies?: Array<string>
|
||||
}
|
||||
|
||||
export type RippledPathsResponse = {
|
||||
alternatives: Array<{
|
||||
paths_computed: Array<Array<{
|
||||
type: number,
|
||||
type_hex: string,
|
||||
account?: string,
|
||||
issuer?: string,
|
||||
currency?: string
|
||||
}>>,
|
||||
source_amount: RippledAmount
|
||||
}>,
|
||||
type: string,
|
||||
destination_account: string,
|
||||
destination_amount: RippledAmount,
|
||||
destination_currencies?: Array<string>,
|
||||
source_account?: string,
|
||||
source_currencies?: Array<{currency: string}>,
|
||||
full_reply?: boolean
|
||||
}
|
||||
@@ -11,27 +11,20 @@ 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,
|
||||
RippledPathsResponse} from './pathfind-types.js';
|
||||
|
||||
type PathFindParams = {
|
||||
src_currencies?: Array<string>, src_account: string,
|
||||
dst_amount: string | Object, dst_account?: string,
|
||||
src_amount?: string | Object
|
||||
}
|
||||
|
||||
function addParams(params: PathFindParams, result: {}) {
|
||||
return _.assign({}, result, {
|
||||
function addParams(params: PathFindParams, result: RippledPathsResponse) {
|
||||
return _.defaults(_.assign({}, result, {
|
||||
source_account: params.src_account,
|
||||
source_currencies: params.src_currencies,
|
||||
destination_amount: params.dst_amount
|
||||
});
|
||||
source_currencies: params.src_currencies
|
||||
}), {destination_amount: params.dst_amount});
|
||||
}
|
||||
|
||||
type PathFind = {
|
||||
source: {address: string, currencies: Array<string>},
|
||||
destination: {address: string, amount: string}
|
||||
}
|
||||
|
||||
function requestPathFind(remote, pathfind: PathFind, callback) {
|
||||
function requestPathFind(remote: Remote, pathfind: PathFind, callback) {
|
||||
const destinationAmount = _.assign({value: -1}, pathfind.destination.amount);
|
||||
const params: PathFindParams = {
|
||||
src_account: pathfind.source.address,
|
||||
@@ -65,7 +58,8 @@ function requestPathFind(remote, pathfind: PathFind, callback) {
|
||||
composeAsync(_.partial(addParams, params), convertErrors(callback)));
|
||||
}
|
||||
|
||||
function addDirectXrpPath(paths, xrpBalance) {
|
||||
function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string
|
||||
): RippledPathsResponse {
|
||||
// Add XRP "path" only if the source acct has enough XRP to make the payment
|
||||
const destinationAmount = paths.destination_amount;
|
||||
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
|
||||
@@ -77,13 +71,15 @@ function addDirectXrpPath(paths, xrpBalance) {
|
||||
return paths;
|
||||
}
|
||||
|
||||
function isRippledIOUAmount(amount) {
|
||||
function isRippledIOUAmount(amount: RippledAmount) {
|
||||
// rippled XRP amounts are specified as decimal strings
|
||||
return (typeof amount === 'object') &&
|
||||
amount.currency && (amount.currency !== 'XRP');
|
||||
}
|
||||
|
||||
function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
|
||||
function conditionallyAddDirectXRPPath(remote: Remote, address: string,
|
||||
paths: RippledPathsResponse, callback
|
||||
) {
|
||||
if (isRippledIOUAmount(paths.destination_amount)
|
||||
|| !_.includes(paths.destination_currencies, 'XRP')) {
|
||||
callback(null, paths);
|
||||
@@ -93,7 +89,7 @@ function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatResponse(pathfind, paths) {
|
||||
function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
|
||||
if (paths.alternatives && paths.alternatives.length > 0) {
|
||||
return parsePathfind(paths);
|
||||
}
|
||||
@@ -118,7 +114,7 @@ function formatResponse(pathfind, paths) {
|
||||
}
|
||||
}
|
||||
|
||||
function getPathsAsync(pathfind, callback) {
|
||||
function getPathsAsync(pathfind: PathFind, callback) {
|
||||
validate.pathfind(pathfind);
|
||||
|
||||
const address = pathfind.source.address;
|
||||
@@ -128,7 +124,7 @@ function getPathsAsync(pathfind, callback) {
|
||||
], composeAsync(_.partial(formatResponse, pathfind), callback));
|
||||
}
|
||||
|
||||
function getPaths(pathfind: Object) {
|
||||
function getPaths(pathfind: PathFind): Promise<GetPaths> {
|
||||
return utils.promisify(getPathsAsync).call(this, pathfind);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,31 @@ const composeAsync = utils.common.composeAsync;
|
||||
const AccountFlags = utils.common.constants.AccountFlags;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
|
||||
type SettingsOptions = {
|
||||
ledgerVersion?: number
|
||||
}
|
||||
|
||||
type GetSettings = {
|
||||
passwordSpent?: boolean,
|
||||
requireDestinationTag?: boolean,
|
||||
requireAuthorization?: boolean,
|
||||
disallowIncomingXRP?: boolean,
|
||||
disableMasterKey?: boolean,
|
||||
enableTransactionIDTracking?: boolean,
|
||||
noFreeze?: boolean,
|
||||
globalFreeze?: boolean,
|
||||
defaultRipple?: boolean,
|
||||
emailHash?: ?string,
|
||||
walletLocator?: ?string,
|
||||
walletSize?: ?number,
|
||||
messageKey?: string,
|
||||
domain?: string,
|
||||
transferRate?: ?number,
|
||||
signers?: string,
|
||||
regularKey?: string
|
||||
}
|
||||
|
||||
|
||||
function parseFlags(value) {
|
||||
const settings = {};
|
||||
for (const flagName in AccountFlags) {
|
||||
@@ -25,7 +50,7 @@ function formatSettings(response) {
|
||||
return _.assign({}, parsedFlags, parsedFields);
|
||||
}
|
||||
|
||||
function getSettingsAsync(account, options, callback) {
|
||||
function getSettingsAsync(account: string, options: SettingsOptions, callback) {
|
||||
validate.address(account);
|
||||
validate.getSettingsOptions(options);
|
||||
|
||||
@@ -38,7 +63,8 @@ function getSettingsAsync(account, options, callback) {
|
||||
composeAsync(formatSettings, convertErrors(callback)));
|
||||
}
|
||||
|
||||
function getSettings(account: string, options = {}) {
|
||||
function getSettings(account: string, options: SettingsOptions = {}
|
||||
): Promise<GetSettings> {
|
||||
return utils.promisify(getSettingsAsync).call(this, account, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {Amount, Memo} from '../common/types.js';
|
||||
|
||||
type Outcome = {
|
||||
result: string,
|
||||
timestamp?: string,
|
||||
ledgerVersion: number,
|
||||
indexInLedger: number,
|
||||
fee: string,
|
||||
balanceChanges: Object,
|
||||
orderbookChanges: Object,
|
||||
ledgerVersion: number,
|
||||
indexInLedger: number
|
||||
timestamp?: string
|
||||
}
|
||||
|
||||
type Adjustment = {
|
||||
@@ -56,18 +58,6 @@ type OrderCancellation = {
|
||||
orderSequence: number
|
||||
}
|
||||
|
||||
type Memo = {
|
||||
type?: string,
|
||||
format?: string,
|
||||
data?: string
|
||||
}
|
||||
|
||||
type Amount = {
|
||||
value: string,
|
||||
currency: string,
|
||||
counterparty?: string
|
||||
}
|
||||
|
||||
type Payment = {
|
||||
source: Adjustment,
|
||||
destination: Adjustment,
|
||||
@@ -88,7 +78,7 @@ type PaymentTransaction = {
|
||||
sequence: number
|
||||
}
|
||||
|
||||
type Order = {
|
||||
export type Order = {
|
||||
direction: string,
|
||||
quantity: Amount,
|
||||
totalPrice: Amount,
|
||||
@@ -138,10 +128,10 @@ export type TransactionOptions = {
|
||||
maxLedgerVersion?: number
|
||||
}
|
||||
|
||||
export type GetTransactionResponse = PaymentTransaction | OrderTransaction |
|
||||
export type TransactionType = PaymentTransaction | OrderTransaction |
|
||||
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction
|
||||
|
||||
export type GetTransactionResponseCallback =
|
||||
(err?: ?Error, data?: GetTransactionResponse) => void
|
||||
(err?: ?Error, data?: TransactionType) => void
|
||||
|
||||
export type CallbackType = (err?: ?Error, data?: Object) => void
|
||||
|
||||
@@ -11,7 +11,7 @@ const RippleError = require('../../core/rippleerror').RippleError;
|
||||
|
||||
import type {Remote} from '../../core/remote';
|
||||
|
||||
import type {CallbackType, GetTransactionResponse,
|
||||
import type {CallbackType, TransactionType,
|
||||
GetTransactionResponseCallback, TransactionOptions}
|
||||
from './transaction-types';
|
||||
|
||||
@@ -106,7 +106,7 @@ function getTransactionAsync(identifier: string, options: TransactionOptions,
|
||||
|
||||
function getTransaction(identifier: string,
|
||||
options: TransactionOptions = {}
|
||||
): Promise<GetTransactionResponse> {
|
||||
): Promise<TransactionType> {
|
||||
return utils.promisify(getTransactionAsync).call(this, identifier, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,29 @@ const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
|
||||
import type {Remote} from '../../core/remote';
|
||||
|
||||
import type {TransactionType} from './transaction-types';
|
||||
|
||||
|
||||
type TransactionsOptions = {
|
||||
start?: string,
|
||||
limit?: number,
|
||||
minLedgerVersion?: number,
|
||||
maxLedgerVersion?: number,
|
||||
earliestFirst?: boolean,
|
||||
excludeFailures?: boolean,
|
||||
initiated?: boolean,
|
||||
counterparty?: string,
|
||||
types?: Array<string>,
|
||||
binary?: boolean,
|
||||
startTx?: TransactionType
|
||||
}
|
||||
|
||||
type GetTransactionsResponse = Array<TransactionType>
|
||||
|
||||
type CallbackType = (err?: ?Error, data?: GetTransactionsResponse) => void
|
||||
|
||||
function parseAccountTxTransaction(tx) {
|
||||
// rippled uses a different response format for 'account_tx' than 'tx'
|
||||
tx.tx.meta = tx.meta;
|
||||
@@ -16,7 +39,7 @@ function parseAccountTxTransaction(tx) {
|
||||
return parseTransaction(tx.tx);
|
||||
}
|
||||
|
||||
function counterpartyFilter(filters, tx) {
|
||||
function counterpartyFilter(filters, tx: TransactionType) {
|
||||
if (!filters.counterparty) {
|
||||
return true;
|
||||
}
|
||||
@@ -31,7 +54,9 @@ function counterpartyFilter(filters, tx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function transactionFilter(address, filters, tx) {
|
||||
function transactionFilter(address: string, filters: TransactionsOptions,
|
||||
tx: TransactionType
|
||||
) {
|
||||
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
|
||||
return false;
|
||||
}
|
||||
@@ -50,13 +75,15 @@ function transactionFilter(address, filters, tx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function orderFilter(options, tx) {
|
||||
function orderFilter(options: TransactionsOptions, tx: TransactionType) {
|
||||
return !options.startTx || (options.earliestFirst ?
|
||||
utils.compareTransactions(tx, options.startTx) > 0 :
|
||||
utils.compareTransactions(tx, options.startTx) < 0);
|
||||
}
|
||||
|
||||
function formatPartialResponse(address, options, data) {
|
||||
function formatPartialResponse(address: string,
|
||||
options: TransactionsOptions, data
|
||||
) {
|
||||
return {
|
||||
marker: data.marker,
|
||||
results: data.transactions
|
||||
@@ -67,7 +94,9 @@ function formatPartialResponse(address, options, data) {
|
||||
};
|
||||
}
|
||||
|
||||
function getAccountTx(remote, address, options, marker, limit, callback) {
|
||||
function getAccountTx(remote: Remote, address: string,
|
||||
options: TransactionsOptions, marker: string, limit: number, callback
|
||||
) {
|
||||
const params = {
|
||||
account: address,
|
||||
// -1 is equivalent to earliest available validated ledger
|
||||
@@ -85,7 +114,9 @@ function getAccountTx(remote, address, options, marker, limit, callback) {
|
||||
convertErrors(callback)));
|
||||
}
|
||||
|
||||
function checkForLedgerGaps(remote, options, transactions) {
|
||||
function checkForLedgerGaps(remote: Remote, options: TransactionsOptions,
|
||||
transactions: GetTransactionsResponse
|
||||
) {
|
||||
let {minLedgerVersion, maxLedgerVersion} = options;
|
||||
|
||||
// if we reached the limit on number of transactions, then we can shrink
|
||||
@@ -105,7 +136,9 @@ function checkForLedgerGaps(remote, options, transactions) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatResponse(remote, options, transactions) {
|
||||
function formatResponse(remote: Remote, options: TransactionsOptions,
|
||||
transactions: GetTransactionsResponse
|
||||
) {
|
||||
const compare = options.earliestFirst ? utils.compareTransactions :
|
||||
_.rearg(utils.compareTransactions, 1, 0);
|
||||
const sortedTransactions = transactions.sort(compare);
|
||||
@@ -113,13 +146,17 @@ function formatResponse(remote, options, transactions) {
|
||||
return sortedTransactions;
|
||||
}
|
||||
|
||||
function getTransactionsInternal(remote, address, options, callback) {
|
||||
function getTransactionsInternal(remote: Remote, address: string,
|
||||
options: TransactionsOptions, callback
|
||||
) {
|
||||
const getter = _.partial(getAccountTx, remote, address, options);
|
||||
const format = _.partial(formatResponse, remote, options);
|
||||
utils.getRecursive(getter, options.limit, composeAsync(format, callback));
|
||||
}
|
||||
|
||||
function getTransactionsAsync(account, options, callback) {
|
||||
function getTransactionsAsync(account: string,
|
||||
options: TransactionsOptions, callback: CallbackType
|
||||
) {
|
||||
validate.address(account);
|
||||
validate.getTransactionsOptions(options);
|
||||
|
||||
@@ -138,7 +175,8 @@ function getTransactionsAsync(account, options, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
function getTransactions(account: string, options = {}) {
|
||||
function getTransactions(account: string, options: TransactionsOptions = {}
|
||||
): Promise<GetTransactionsResponse> {
|
||||
return utils.promisify(getTransactionsAsync).call(this, account, options);
|
||||
}
|
||||
|
||||
|
||||
33
src/api/ledger/trustlines-types.js
Normal file
33
src/api/ledger/trustlines-types.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export type TrustLineSpecification = {
|
||||
currency: string,
|
||||
counterparty: string,
|
||||
limit: string,
|
||||
qualityIn?: number,
|
||||
qualityOut?: number,
|
||||
ripplingDisabled?: boolean,
|
||||
authorized?: boolean,
|
||||
frozen?: boolean
|
||||
}
|
||||
|
||||
export type Trustline = {
|
||||
specification: TrustLineSpecification,
|
||||
counterparty: {
|
||||
limit: string,
|
||||
ripplingDisabled?: boolean,
|
||||
frozen?: boolean,
|
||||
authorized?: boolean
|
||||
},
|
||||
state: {
|
||||
balance: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TrustlinesOptions = {
|
||||
counterparty?: string,
|
||||
currency?: string,
|
||||
limit?: number,
|
||||
ledgerVersion?: number
|
||||
}
|
||||
@@ -8,11 +8,17 @@ const composeAsync = utils.common.composeAsync;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
const parseAccountTrustline = require('./parse/account-trustline');
|
||||
|
||||
function currencyFilter(currency, trustline) {
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
|
||||
|
||||
|
||||
type GetTrustlinesResponse = Array<Trustline>
|
||||
|
||||
function currencyFilter(currency: string, trustline: Trustline) {
|
||||
return currency === null || trustline.specification.currency === currency;
|
||||
}
|
||||
|
||||
function formatResponse(options, data) {
|
||||
function formatResponse(options: TrustlinesOptions, data) {
|
||||
return {
|
||||
marker: data.marker,
|
||||
results: data.lines.map(parseAccountTrustline)
|
||||
@@ -20,8 +26,8 @@ function formatResponse(options, data) {
|
||||
};
|
||||
}
|
||||
|
||||
function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
|
||||
callback
|
||||
function getAccountLines(remote: Remote, address: string, ledgerVersion: number,
|
||||
options: TrustlinesOptions, marker: string, limit: number, callback
|
||||
) {
|
||||
const requestOptions = {
|
||||
account: address,
|
||||
@@ -36,8 +42,7 @@ function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
|
||||
convertErrors(callback)));
|
||||
}
|
||||
|
||||
function getTrustlinesAsync(account: string, options: {currency: string,
|
||||
counterparty: string, limit: number, ledgerVersion: number},
|
||||
function getTrustlinesAsync(account: string, options: TrustlinesOptions,
|
||||
callback: () => void
|
||||
): void {
|
||||
validate.address(account);
|
||||
@@ -48,7 +53,8 @@ function getTrustlinesAsync(account: string, options: {currency: string,
|
||||
utils.getRecursive(getter, options.limit, callback);
|
||||
}
|
||||
|
||||
function getTrustlines(account: string, options = {}) {
|
||||
function getTrustlines(account: string, options: TrustlinesOptions = {}
|
||||
): Promise<GetTrustlinesResponse> {
|
||||
return utils.promisify(async.seq(
|
||||
utils.getLedgerOptionsWithLedgerVersion,
|
||||
getTrustlinesAsync)).call(this, account, options);
|
||||
|
||||
50
src/api/ledger/types.js
Normal file
50
src/api/ledger/types.js
Normal file
@@ -0,0 +1,50 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import type {Amount} from '../common/types.js';
|
||||
|
||||
export type OrdersOptions = {
|
||||
limit?: number,
|
||||
ledgerVersion?: number
|
||||
}
|
||||
|
||||
export type OrderSpecification = {
|
||||
direction: string,
|
||||
quantity: Amount,
|
||||
totalPrice: Amount,
|
||||
immediateOrCancel?: boolean,
|
||||
fillOrKill?: boolean,
|
||||
// If enabled, the offer will not consume offers that exactly match it, and
|
||||
// instead becomes an Offer node in the ledger. It will still consume offers
|
||||
// that cross it.
|
||||
passive?: boolean
|
||||
}
|
||||
|
||||
export type Order = {
|
||||
specification: OrderSpecification,
|
||||
properties: {
|
||||
maker: string,
|
||||
sequence: number,
|
||||
makerExchangeRate: string
|
||||
}
|
||||
}
|
||||
|
||||
export type GetLedger = {
|
||||
accepted: boolean,
|
||||
closed: boolean,
|
||||
stateHash: string,
|
||||
closeTime: number,
|
||||
closeTimeResolution: number,
|
||||
closeFlags: number,
|
||||
ledgerHash: string,
|
||||
ledgerVersion: number,
|
||||
parentLedgerHash: string,
|
||||
parentCloseTime: number,
|
||||
totalDrops: string,
|
||||
transactionHash: string,
|
||||
transactions?: Array<Object>,
|
||||
rawTransactions?: string,
|
||||
transactionHashes?: Array<string>,
|
||||
rawState?: string,
|
||||
stateHashes?: Array<string>
|
||||
}
|
||||
@@ -6,9 +6,18 @@ const common = require('../common');
|
||||
const dropsToXrp = common.dropsToXrp;
|
||||
const composeAsync = common.composeAsync;
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {TransactionType} from './transaction-types';
|
||||
import type {Issue} from '../common/types.js';
|
||||
|
||||
type Callback = (err: any, data: any) => void
|
||||
|
||||
type RecursiveData = {
|
||||
marker: string,
|
||||
results: Array<any>
|
||||
}
|
||||
|
||||
type RecursiveCallback = (err: any, data: RecursiveData) => void
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
assert(min <= max, 'Illegal clamp bounds');
|
||||
return Math.min(Math.max(value, min), max);
|
||||
@@ -21,7 +30,8 @@ function getXRPBalance(remote: Remote, address: string, ledgerVersion?: number,
|
||||
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
|
||||
}
|
||||
|
||||
type Getter = (marker: ?string, limit: number, callback: Callback) => void
|
||||
type Getter = (marker: ?string, limit: number,
|
||||
callback: RecursiveCallback) => void
|
||||
|
||||
// If the marker is omitted from a response, you have reached the end
|
||||
// getter(marker, limit, callback), callback(error, {marker, results})
|
||||
@@ -48,21 +58,20 @@ function getRecursive(getter: Getter, limit?: number, callback: Callback) {
|
||||
getRecursiveRecur(getter, undefined, limit || Infinity, callback);
|
||||
}
|
||||
|
||||
type Amount = {counterparty?: string, issuer?: string, value: string}
|
||||
|
||||
function renameCounterpartyToIssuer(amount?: Amount): ?{issuer?: string} {
|
||||
function renameCounterpartyToIssuer(amount?: Issue): ?{issuer?: string} {
|
||||
if (amount === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const issuer = amount.counterparty === undefined ?
|
||||
amount.issuer : amount.counterparty;
|
||||
(amount.issuer !== undefined ? amount.issuer : undefined) :
|
||||
amount.counterparty;
|
||||
const withIssuer = _.assign({}, amount, {issuer: issuer});
|
||||
return _.omit(withIssuer, 'counterparty');
|
||||
}
|
||||
|
||||
type Order = {taker_gets: Amount, taker_pays: Amount}
|
||||
type RequestBookOffersArgs = {taker_gets: Issue, taker_pays: Issue}
|
||||
|
||||
function renameCounterpartyToIssuerInOrder(order: Order) {
|
||||
function renameCounterpartyToIssuerInOrder(order: RequestBookOffersArgs) {
|
||||
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
|
||||
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
|
||||
const changes = {taker_gets: taker_gets, taker_pays: taker_pays};
|
||||
@@ -84,9 +93,8 @@ function signum(num) {
|
||||
* @returns {Number} [-1, 0, 1]
|
||||
*/
|
||||
|
||||
type Outcome = {outcome: {ledgerVersion: number, indexInLedger: number}};
|
||||
|
||||
function compareTransactions(first: Outcome, second: Outcome): number {
|
||||
function compareTransactions(first: TransactionType, second: TransactionType
|
||||
): number {
|
||||
if (!first.outcome || !second.outcome) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const common = require('../common');
|
||||
const hashes = require('ripple-hashes');
|
||||
|
||||
function convertLedgerHeader(header) {
|
||||
return {
|
||||
@@ -25,7 +26,7 @@ function convertLedgerHeader(header) {
|
||||
|
||||
function hashLedgerHeader(ledgerHeader) {
|
||||
const header = convertLedgerHeader(ledgerHeader);
|
||||
return common.core.Ledger.calculateLedgerHash(header);
|
||||
return hashes.computeLedgerHash(header);
|
||||
}
|
||||
|
||||
function computeTransactionHash(ledger) {
|
||||
@@ -39,8 +40,7 @@ function computeTransactionHash(ledger) {
|
||||
tx.meta ? {metaData: tx.meta} : {});
|
||||
return renameMeta;
|
||||
});
|
||||
const ledgerObject = common.core.Ledger.from_json({transactions: txs});
|
||||
const transactionHash = ledgerObject.calc_tx_hash().to_hex();
|
||||
const transactionHash = hashes.computeTransactionTreeHash(txs);
|
||||
if (ledger.transactionHash !== undefined
|
||||
&& ledger.transactionHash !== transactionHash) {
|
||||
throw new common.errors.ValidationError('transactionHash in header'
|
||||
@@ -54,8 +54,7 @@ function computeStateHash(ledger) {
|
||||
return ledger.stateHash;
|
||||
}
|
||||
const state = JSON.parse(ledger.rawState);
|
||||
const ledgerObject = common.core.Ledger.from_json({accountState: state});
|
||||
const stateHash = ledgerObject.calc_account_hash().to_hex();
|
||||
const stateHash = hashes.computeStateTreeHash(state);
|
||||
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
|
||||
throw new common.errors.ValidationError('stateHash in header'
|
||||
+ ' does not match computed hash of state');
|
||||
@@ -64,11 +63,11 @@ function computeStateHash(ledger) {
|
||||
}
|
||||
|
||||
function computeLedgerHash(ledger: Object): string {
|
||||
const hashes = {
|
||||
const subhashes = {
|
||||
transactionHash: computeTransactionHash(ledger),
|
||||
stateHash: computeStateHash(ledger)
|
||||
};
|
||||
return hashLedgerHeader(_.assign({}, ledger, hashes));
|
||||
return hashLedgerHeader(_.assign({}, ledger, subhashes));
|
||||
}
|
||||
|
||||
module.exports = computeLedgerHash;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Order} from '../ledger/transaction-types.js';
|
||||
|
||||
const OfferCreateFlags = {
|
||||
passive: {set: 'Passive'},
|
||||
@@ -10,7 +12,7 @@ const OfferCreateFlags = {
|
||||
fillOrKill: {set: 'FillOrKill'}
|
||||
};
|
||||
|
||||
function createOrderTransaction(account, order) {
|
||||
function createOrderTransaction(account: string, order: Order): Transaction {
|
||||
validate.address(account);
|
||||
validate.order(order);
|
||||
|
||||
@@ -30,12 +32,16 @@ function createOrderTransaction(account, order) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareOrderAsync(account, order, instructions, callback) {
|
||||
function prepareOrderAsync(account: string, order: Order,
|
||||
instructions: Instructions, callback
|
||||
) {
|
||||
const transaction = createOrderTransaction(account, order);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareOrder(account: string, order: Object, instructions = {}) {
|
||||
function prepareOrder(account: string, order: Order,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareOrderAsync.bind(this))(
|
||||
account, order, instructions);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
|
||||
function createOrderCancellationTransaction(account, sequence) {
|
||||
function createOrderCancellationTransaction(account: string,
|
||||
sequence: number
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.sequence(sequence);
|
||||
|
||||
@@ -13,16 +16,16 @@ function createOrderCancellationTransaction(account, sequence) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareOrderCancellationAsync(account, sequence, instructions,
|
||||
callback
|
||||
function prepareOrderCancellationAsync(account: string, sequence: number,
|
||||
instructions: Instructions, callback
|
||||
) {
|
||||
const transaction = createOrderCancellationTransaction(account, sequence);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareOrderCancellation(account: string, sequence: number,
|
||||
instructions = {}
|
||||
) {
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareOrderCancellationAsync.bind(this))(
|
||||
account, sequence, instructions);
|
||||
}
|
||||
|
||||
@@ -6,19 +6,44 @@ const validate = utils.common.validate;
|
||||
const toRippledAmount = utils.common.toRippledAmount;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
const ValidationError = utils.common.errors.ValidationError;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Amount, Adjustment, MaxAdjustment,
|
||||
MinAdjustment, Memo} from '../common/types.js';
|
||||
|
||||
function isXRPToXRPPayment(payment) {
|
||||
|
||||
type Payment = {
|
||||
source: Adjustment | MaxAdjustment,
|
||||
destination: Adjustment | MinAdjustment,
|
||||
paths?: string,
|
||||
memos?: Array<Memo>,
|
||||
// A 256-bit hash that can be used to identify a particular payment
|
||||
invoiceID?: string,
|
||||
// A boolean that, if set to true, indicates that this payment should go
|
||||
// through even if the whole amount cannot be delivered because of a lack of
|
||||
// liquidity or funds in the source_account account
|
||||
allowPartialPayment?: boolean,
|
||||
// A boolean that can be set to true if paths are specified and the sender
|
||||
// would like the Ripple Network to disregard any direct paths from
|
||||
// the source_account to the destination_account. This may be used to take
|
||||
// advantage of an arbitrage opportunity or by gateways wishing to issue
|
||||
// balances from a hot wallet to a user who has mistakenly set a trustline
|
||||
// directly to the hot wallet
|
||||
noDirectRipple?: boolean,
|
||||
limitQuality?: boolean
|
||||
}
|
||||
|
||||
function isXRPToXRPPayment(payment: Payment): boolean {
|
||||
const sourceCurrency = _.get(payment, 'source.maxAmount.currency');
|
||||
const destinationCurrency = _.get(payment, 'destination.amount.currency');
|
||||
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP';
|
||||
}
|
||||
|
||||
function isIOUWithoutCounterparty(amount) {
|
||||
function isIOUWithoutCounterparty(amount: Amount): boolean {
|
||||
return amount && amount.currency !== 'XRP'
|
||||
&& amount.counterparty === undefined;
|
||||
}
|
||||
|
||||
function applyAnyCounterpartyEncoding(payment) {
|
||||
function applyAnyCounterpartyEncoding(payment: Payment): void {
|
||||
// Convert blank counterparty to sender or receiver's address
|
||||
// (Ripple convention for 'any counterparty')
|
||||
// https://ripple.com/build/transactions/
|
||||
@@ -33,14 +58,15 @@ function applyAnyCounterpartyEncoding(payment) {
|
||||
});
|
||||
}
|
||||
|
||||
function createMaximalAmount(amount) {
|
||||
function createMaximalAmount(amount: Amount): Amount {
|
||||
const maxXRPValue = '100000000000';
|
||||
const maxIOUValue = '9999999999999999e80';
|
||||
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue;
|
||||
return _.assign(amount, {value: maxValue});
|
||||
}
|
||||
|
||||
function createPaymentTransaction(account, paymentArgument) {
|
||||
function createPaymentTransaction(account: string, paymentArgument: Payment
|
||||
): Transaction {
|
||||
const payment = _.cloneDeep(paymentArgument);
|
||||
applyAnyCounterpartyEncoding(payment);
|
||||
validate.address(account);
|
||||
@@ -115,12 +141,16 @@ function createPaymentTransaction(account, paymentArgument) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function preparePaymentAsync(account, payment, instructions, callback) {
|
||||
function preparePaymentAsync(account: string, payment: Payment,
|
||||
instructions: Instructions, callback
|
||||
) {
|
||||
const transaction = createPaymentTransaction(account, payment);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function preparePayment(account: string, payment: Object, instructions = {}) {
|
||||
function preparePayment(account: string, payment: Payment,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(preparePaymentAsync.bind(this))(
|
||||
account, payment, instructions);
|
||||
}
|
||||
|
||||
62
src/api/transaction/settings-types.js
Normal file
62
src/api/transaction/settings-types.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
type SettingPasswordSpent = {
|
||||
passwordSpent?: boolean,
|
||||
}
|
||||
type SettingRequireDestinationTag = {
|
||||
requireDestinationTag?: boolean,
|
||||
}
|
||||
type SettingRequireAuthorization = {
|
||||
requireAuthorization?: boolean,
|
||||
}
|
||||
type SettingDisallowIncomingXRP = {
|
||||
disallowIncomingXRP?: boolean,
|
||||
}
|
||||
type SettingDisableMasterKey = {
|
||||
disableMasterKey?: boolean,
|
||||
}
|
||||
type SettingEnableTransactionIDTracking = {
|
||||
enableTransactionIDTracking?: boolean,
|
||||
}
|
||||
type SettingNoFreeze = {
|
||||
noFreeze?: boolean,
|
||||
}
|
||||
type SettingGlobalFreeze = {
|
||||
globalFreeze?: boolean,
|
||||
}
|
||||
type SettingDefaultRipple = {
|
||||
defaultRipple?: boolean,
|
||||
}
|
||||
type SettingEmailHash = {
|
||||
emailHash?: ?string,
|
||||
}
|
||||
type SettingWalletLocator = {
|
||||
walletLocator?: ?string,
|
||||
}
|
||||
type SettingWalletSize = {
|
||||
walletSize?: ?number,
|
||||
}
|
||||
type SettingMessageKey = {
|
||||
messageKey?: string,
|
||||
}
|
||||
type SettingDomain = {
|
||||
domain?: string,
|
||||
}
|
||||
type SettingTransferRate = {
|
||||
transferRate?: ?number,
|
||||
}
|
||||
type SettingSigners = {
|
||||
signers?: string,
|
||||
}
|
||||
type SettingRegularKey = {
|
||||
regularKey?: string
|
||||
}
|
||||
|
||||
export type Settings = SettingRegularKey | SettingSigners |
|
||||
SettingTransferRate | SettingDomain | SettingMessageKey | SettingWalletSize |
|
||||
SettingWalletLocator | SettingEmailHash | SettingDefaultRipple |
|
||||
SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking |
|
||||
SettingDisableMasterKey | SettingDisallowIncomingXRP |
|
||||
SettingRequireAuthorization | SettingRequireDestinationTag |
|
||||
SettingPasswordSpent
|
||||
@@ -7,11 +7,14 @@ const validate = utils.common.validate;
|
||||
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
|
||||
const AccountFields = utils.common.constants.AccountFields;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Settings} from './settings-types.js';
|
||||
|
||||
// Emptry string passed to setting will clear it
|
||||
const CLEAR_SETTING = null;
|
||||
|
||||
function setTransactionFlags(transaction, values) {
|
||||
|
||||
function setTransactionFlags(transaction: Transaction, values: Settings) {
|
||||
const keys = Object.keys(values);
|
||||
assert(keys.length === 1, 'ERROR: can only set one setting per transaction');
|
||||
const flagName = keys[0];
|
||||
@@ -26,7 +29,7 @@ function setTransactionFlags(transaction, values) {
|
||||
}
|
||||
}
|
||||
|
||||
function setTransactionFields(transaction, input) {
|
||||
function setTransactionFields(transaction: Transaction, input: Settings) {
|
||||
const fieldSchema = AccountFields;
|
||||
for (const fieldName in fieldSchema) {
|
||||
const field = fieldSchema[fieldName];
|
||||
@@ -63,11 +66,12 @@ function setTransactionFields(transaction, input) {
|
||||
* are returned
|
||||
*/
|
||||
|
||||
function convertTransferRate(transferRate) {
|
||||
function convertTransferRate(transferRate: number | string): number | string {
|
||||
return (new BigNumber(transferRate)).shift(9).toNumber();
|
||||
}
|
||||
|
||||
function createSettingsTransaction(account, settings) {
|
||||
function createSettingsTransaction(account: string, settings: Settings
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.settings(settings);
|
||||
|
||||
@@ -90,12 +94,16 @@ function createSettingsTransaction(account, settings) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareSettingsAsync(account, settings, instructions, callback) {
|
||||
function prepareSettingsAsync(account: string, settings: Settings,
|
||||
instructions: Instructions, callback
|
||||
) {
|
||||
const transaction = createSettingsTransaction(account, settings);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareSettings(account: string, settings: Object, instructions = {}) {
|
||||
function prepareSettings(account: string, settings: Object,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareSettingsAsync.bind(this))(
|
||||
account, settings, instructions);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const keypairs = require('ripple-keypairs');
|
||||
const core = utils.common.core;
|
||||
const binary = require('ripple-binary-codec');
|
||||
const sha512 = require('hash.js').sha512;
|
||||
const validate = utils.common.validate;
|
||||
|
||||
/**
|
||||
@@ -17,20 +18,22 @@ const validate = utils.common.validate;
|
||||
*/
|
||||
const HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
|
||||
function serialize(txJSON) {
|
||||
return core.SerializedObject.from_json(txJSON);
|
||||
// For a hash function, rippled uses SHA-512 and then truncates the result
|
||||
// to the first 256 bytes. This algorithm, informally called SHA-512Half,
|
||||
// provides an output that has comparable security to SHA-256, but runs
|
||||
// faster on 64-bit processors.
|
||||
function sha512half(buffer) {
|
||||
return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64);
|
||||
}
|
||||
|
||||
function hashSerialization(serialized, prefix) {
|
||||
return serialized.hash(prefix || HASH_TX_ID).to_hex();
|
||||
}
|
||||
|
||||
function signingData(txJSON) {
|
||||
return core.Transaction.from_json(txJSON).signingData().buffer;
|
||||
const hexPrefix = prefix.toString(16).toUpperCase();
|
||||
return sha512half(new Buffer(hexPrefix + serialized, 'hex'));
|
||||
}
|
||||
|
||||
function computeSignature(txJSON, privateKey) {
|
||||
return keypairs.sign(signingData(txJSON), privateKey);
|
||||
const signingData = binary.encodeForSigning(txJSON);
|
||||
return keypairs.sign(signingData, privateKey);
|
||||
}
|
||||
|
||||
function sign(txJSON: string, secret: string
|
||||
@@ -44,9 +47,9 @@ function sign(txJSON: string, secret: string
|
||||
tx.SigningPubKey = keypair.publicKey;
|
||||
}
|
||||
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
|
||||
const serialized = serialize(tx);
|
||||
const serialized = binary.encode(tx);
|
||||
return {
|
||||
signedTransaction: serialized.to_hex(),
|
||||
signedTransaction: serialized,
|
||||
id: hashSerialization(serialized, HASH_TX_ID)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,16 @@ const validate = utils.common.validate;
|
||||
const Request = utils.common.core.Request;
|
||||
const convertErrors = utils.common.convertErrors;
|
||||
|
||||
function isImmediateRejection(engineResult) {
|
||||
type Submit = {
|
||||
success: boolean,
|
||||
engineResult: string,
|
||||
engineResultCode: number,
|
||||
engineResultMessage?: string,
|
||||
txBlob?: string,
|
||||
txJson?: Object
|
||||
}
|
||||
|
||||
function isImmediateRejection(engineResult: string): boolean {
|
||||
// note: "tel" errors mean the local server refused to process the
|
||||
// transaction *at that time*, but it could potentially buffer the
|
||||
// transaction and then process it at a later time, for example
|
||||
@@ -37,7 +46,7 @@ function submitAsync(txBlob: string, callback: (err: any, data: any) => void
|
||||
convertSubmitErrors(convertErrors(callback))));
|
||||
}
|
||||
|
||||
function submit(txBlob: string) {
|
||||
function submit(txBlob: string): Promise<Submit> {
|
||||
return utils.promisify(submitAsync.bind(this))(txBlob);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,18 @@ const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Memo} from '../common/types.js';
|
||||
|
||||
function createSuspendedPaymentCancellationTransaction(account, payment) {
|
||||
type SuspendedPaymentCancellation = {
|
||||
owner: string,
|
||||
paymentSequence: number,
|
||||
memos?: Array<Memo>
|
||||
}
|
||||
|
||||
function createSuspendedPaymentCancellationTransaction(account: string,
|
||||
payment: SuspendedPaymentCancellation
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.suspendedPaymentCancellation(payment);
|
||||
|
||||
@@ -24,15 +34,17 @@ function createSuspendedPaymentCancellationTransaction(account, payment) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentCancellationAsync(account, payment,
|
||||
instructions, callback) {
|
||||
function prepareSuspendedPaymentCancellationAsync(account: string,
|
||||
payment: SuspendedPaymentCancellation, instructions: Instructions, callback
|
||||
) {
|
||||
const transaction =
|
||||
createSuspendedPaymentCancellationTransaction(account, payment);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentCancellation(account: string, payment: Object,
|
||||
instructions = {}) {
|
||||
function prepareSuspendedPaymentCancellation(account: string,
|
||||
payment: SuspendedPaymentCancellation, instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareSuspendedPaymentCancellationAsync)
|
||||
.call(this, account, payment, instructions);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,21 @@ const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const toRippledAmount = utils.common.toRippledAmount;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Adjustment, MaxAdjustment, Memo} from '../common/types.js';
|
||||
|
||||
function createSuspendedPaymentCreationTransaction(account, payment) {
|
||||
type SuspendedPaymentCreation = {
|
||||
source: MaxAdjustment,
|
||||
destination: Adjustment,
|
||||
memos?: Array<Memo>,
|
||||
digest?: string,
|
||||
allowCancelAfter?: number,
|
||||
allowExecuteAfter?: number
|
||||
}
|
||||
|
||||
function createSuspendedPaymentCreationTransaction(account: string,
|
||||
payment: SuspendedPaymentCreation
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.suspendedPaymentCreation(payment);
|
||||
|
||||
@@ -41,15 +54,17 @@ function createSuspendedPaymentCreationTransaction(account, payment) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentCreationAsync(account, payment, instructions,
|
||||
callback) {
|
||||
function prepareSuspendedPaymentCreationAsync(account: string,
|
||||
payment: SuspendedPaymentCreation, instructions: Instructions, callback
|
||||
) {
|
||||
const transaction =
|
||||
createSuspendedPaymentCreationTransaction(account, payment);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentCreation(account: string, payment: Object,
|
||||
instructions = {}) {
|
||||
function prepareSuspendedPaymentCreation(account: string,
|
||||
payment: SuspendedPaymentCreation, instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareSuspendedPaymentCreationAsync)
|
||||
.call(this, account, payment, instructions);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,21 @@ const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {Memo} from '../common/types.js';
|
||||
|
||||
function createSuspendedPaymentExecutionTransaction(account, payment) {
|
||||
type SuspendedPaymentExecution = {
|
||||
owner: string,
|
||||
paymentSequence: number,
|
||||
memos?: Array<Memo>,
|
||||
method?: number,
|
||||
digest?: string,
|
||||
proof?: string
|
||||
}
|
||||
|
||||
function createSuspendedPaymentExecutionTransaction(account: string,
|
||||
payment: SuspendedPaymentExecution
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.suspendedPaymentExecution(payment);
|
||||
|
||||
@@ -34,15 +47,17 @@ function createSuspendedPaymentExecutionTransaction(account, payment) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentExecutionAsync(account, payment, instructions,
|
||||
callback) {
|
||||
function prepareSuspendedPaymentExecutionAsync(account: string,
|
||||
payment: SuspendedPaymentExecution, instructions: Instructions, callback
|
||||
) {
|
||||
const transaction =
|
||||
createSuspendedPaymentExecutionTransaction(account, payment);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareSuspendedPaymentExecution(account: string, payment: Object,
|
||||
instructions = {}) {
|
||||
function prepareSuspendedPaymentExecution(account: string,
|
||||
payment: SuspendedPaymentExecution, instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareSuspendedPaymentExecutionAsync)
|
||||
.call(this, account, payment, instructions);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
const BigNumber = require('bignumber.js');
|
||||
import type {Instructions, Prepare} from './types.js';
|
||||
import type {TrustLineSpecification} from '../ledger/trustlines-types.js';
|
||||
|
||||
const TrustSetFlags = {
|
||||
authorized: {set: 'SetAuth'},
|
||||
@@ -16,7 +18,9 @@ function convertQuality(quality) {
|
||||
(new BigNumber(quality)).shift(9).truncated().toNumber();
|
||||
}
|
||||
|
||||
function createTrustlineTransaction(account, trustline) {
|
||||
function createTrustlineTransaction(account: string,
|
||||
trustline: TrustLineSpecification
|
||||
): Transaction {
|
||||
validate.address(account);
|
||||
validate.trustline(trustline);
|
||||
|
||||
@@ -33,13 +37,16 @@ function createTrustlineTransaction(account, trustline) {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareTrustlineAsync(account, trustline, instructions, callback) {
|
||||
function prepareTrustlineAsync(account: string,
|
||||
trustline: TrustLineSpecification, instructions: Instructions, callback
|
||||
) {
|
||||
const transaction = createTrustlineTransaction(account, trustline);
|
||||
utils.prepareTransaction(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
function prepareTrustline(account: string, trustline: Object, instructions = {}
|
||||
) {
|
||||
function prepareTrustline(account: string,
|
||||
trustline: TrustLineSpecification, instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
return utils.promisify(prepareTrustlineAsync.bind(this))(
|
||||
account, trustline, instructions);
|
||||
}
|
||||
|
||||
19
src/api/transaction/types.js
Normal file
19
src/api/transaction/types.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export type Instructions = {
|
||||
sequence?: number,
|
||||
fee?: string,
|
||||
maxFee?: string,
|
||||
maxLedgerVersion?: number,
|
||||
maxLedgerVersionOffset?: number
|
||||
}
|
||||
|
||||
export type Prepare = {
|
||||
txJSON: string,
|
||||
instructions: {
|
||||
fee: string,
|
||||
sequence: number,
|
||||
maxLedgerVersion?: number
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,12 @@ const async = require('async');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const common = require('../common');
|
||||
const composeAsync = common.composeAsync;
|
||||
import type {Remote} from '../../core/remote';
|
||||
import type {Transaction} from '../../core/transaction';
|
||||
import type {Instructions} from './types.js';
|
||||
|
||||
function setTransactionBitFlags(transaction: any, values: any, flags: any
|
||||
function setTransactionBitFlags(transaction: Transaction, values: any,
|
||||
flags: any
|
||||
): void {
|
||||
for (const flagName in flags) {
|
||||
const flagValue = values[flagName];
|
||||
@@ -21,14 +25,14 @@ function setTransactionBitFlags(transaction: any, values: any, flags: any
|
||||
}
|
||||
}
|
||||
|
||||
function getFeeDrops(remote, callback) {
|
||||
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) {
|
||||
function formatPrepareResponse(txJSON: Object): Object {
|
||||
const instructions = {
|
||||
fee: txJSON.Fee,
|
||||
sequence: txJSON.Sequence,
|
||||
@@ -41,9 +45,9 @@ function formatPrepareResponse(txJSON) {
|
||||
}
|
||||
|
||||
type Callback = (err: ?(typeof Error),
|
||||
data: {txJSON: string, instructions: any}) => void;
|
||||
function prepareTransaction(transaction: any, remote: any, instructions: any,
|
||||
callback: Callback
|
||||
data: {txJSON: string, instructions: Instructions}) => void;
|
||||
function prepareTransaction(transaction: Transaction, remote: Remote,
|
||||
instructions: Instructions, callback: Callback
|
||||
): void {
|
||||
common.validate.instructions(instructions);
|
||||
|
||||
@@ -54,11 +58,11 @@ function prepareTransaction(transaction: any, remote: any, instructions: any,
|
||||
|
||||
function prepareMaxLedgerVersion(callback_) {
|
||||
if (instructions.maxLedgerVersion !== undefined) {
|
||||
txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10);
|
||||
txJSON.LastLedgerSequence = instructions.maxLedgerVersion;
|
||||
callback_();
|
||||
} else {
|
||||
const offset = instructions.maxLedgerVersionOffset !== undefined ?
|
||||
parseInt(instructions.maxLedgerVersionOffset, 10) : 3;
|
||||
instructions.maxLedgerVersionOffset : 3;
|
||||
remote.getLedgerSequence((error, ledgerVersion) => {
|
||||
txJSON.LastLedgerSequence = ledgerVersion + offset;
|
||||
callback_(error);
|
||||
@@ -84,7 +88,7 @@ function prepareTransaction(transaction: any, remote: any, instructions: any,
|
||||
|
||||
function prepareSequence(callback_) {
|
||||
if (instructions.sequence !== undefined) {
|
||||
txJSON.Sequence = parseInt(instructions.sequence, 10);
|
||||
txJSON.Sequence = instructions.sequence;
|
||||
callback_(null, formatPrepareResponse(txJSON));
|
||||
} else {
|
||||
remote.findAccount(account).getNextSequence(function(error, sequence) {
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const Currency = require('./currency').Currency;
|
||||
const normalizeCurrency = require('./currency').normalizeCurrency;
|
||||
const {XRPValue, IOUValue} = require('ripple-lib-value');
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
const {ACCOUNT_ONE, ACCOUNT_ZERO} = require('./constants');
|
||||
const {ACCOUNT_ONE, ACCOUNT_ZERO, CURRENCY_ONE}
|
||||
= require('./constants');
|
||||
|
||||
type Value = XRPValue | IOUValue;
|
||||
|
||||
@@ -21,7 +22,7 @@ function Amount(value = new XRPValue(NaN)) {
|
||||
|
||||
this._value = value;
|
||||
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
|
||||
this._currency = new Currency();
|
||||
this._currency = null;
|
||||
this._issuer = 'NaN';
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ Amount.NaN = function() {
|
||||
return result; // but let's be careful
|
||||
};
|
||||
|
||||
Amount.from_components_unsafe = function(value: Value, currency: Currency,
|
||||
Amount.from_components_unsafe = function(value: Value, currency: string,
|
||||
issuer: string, isNative: boolean
|
||||
) {
|
||||
const result = new Amount(value);
|
||||
@@ -190,12 +191,9 @@ Amount.prototype.divide = function(divisor) {
|
||||
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
|
||||
*/
|
||||
|
||||
Amount.prototype.ratio_human = function(denom, opts) {
|
||||
const options = extend({ }, opts);
|
||||
|
||||
Amount.prototype.ratio_human = function(denom) {
|
||||
const numerator = this.clone();
|
||||
|
||||
let denominator = Amount.from_json(denom);
|
||||
const denominator = Amount.from_json(denom);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!numerator.is_valid() || !denominator.is_valid()) {
|
||||
@@ -206,14 +204,6 @@ Amount.prototype.ratio_human = function(denom, opts) {
|
||||
return new Amount(NaN);
|
||||
}
|
||||
|
||||
// Apply interest/demurrage
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (options.reference_date) {
|
||||
denominator = denominator.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
// Special case: The denominator is a native (XRP) amount.
|
||||
//
|
||||
// In that case, it's going to be expressed as base units (1 XRP =
|
||||
@@ -252,23 +242,14 @@ Amount.prototype.ratio_human = function(denom, opts) {
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The product. Unit will be the same as the first factor.
|
||||
*/
|
||||
Amount.prototype.product_human = function(factor, options = {}) {
|
||||
|
||||
let fac = Amount.from_json(factor);
|
||||
Amount.prototype.product_human = function(factor) {
|
||||
const fac = Amount.from_json(factor);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!this.is_valid() || !fac.is_valid()) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
// Apply interest/demurrage
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (options.reference_date) {
|
||||
fac = fac.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
const product = this.multiply(fac);
|
||||
|
||||
// Special case: The second factor is a native (XRP) amount expressed as base
|
||||
@@ -398,7 +379,7 @@ Amount.prototype.equals = function(d, ignore_issuer) {
|
||||
return this.is_valid() && d.is_valid()
|
||||
&& this._is_native === d._is_native
|
||||
&& this._value.equals(d._value)
|
||||
&& (this._is_native || (this._currency.equals(d._currency)
|
||||
&& (this._is_native || ((this._currency === d._currency)
|
||||
&& (ignore_issuer || this._issuer === d._issuer)));
|
||||
};
|
||||
|
||||
@@ -425,7 +406,7 @@ Amount.prototype.is_valid = function() {
|
||||
};
|
||||
|
||||
Amount.prototype.is_valid_full = function() {
|
||||
return this.is_valid() && this._currency.is_valid()
|
||||
return this.is_valid() && this._currency !== null
|
||||
&& isValidAddress(this._issuer) && this._issuer !== ACCOUNT_ZERO;
|
||||
};
|
||||
|
||||
@@ -464,9 +445,7 @@ Amount.prototype.negate = function() {
|
||||
* $
|
||||
*/
|
||||
|
||||
Amount.prototype.parse_human = function(j, options) {
|
||||
const opts = options || {};
|
||||
|
||||
Amount.prototype.parse_human = function(j) {
|
||||
const hex_RE = /^[a-fA-F0-9]{40}$/;
|
||||
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
|
||||
|
||||
@@ -516,14 +495,6 @@ Amount.prototype.parse_human = function(j, options) {
|
||||
(this._is_native ? new XRPValue(value) :
|
||||
new IOUValue(value));
|
||||
this._set_value(newValue);
|
||||
|
||||
// Apply interest/demurrage
|
||||
if (opts.reference_date && this._currency.has_interest()) {
|
||||
const interest = this._currency.get_interest_at(opts.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -570,7 +541,7 @@ Amount.prototype.parse_quality =
|
||||
function(quality, counterCurrency, counterIssuer, opts) {
|
||||
const options = opts || {};
|
||||
|
||||
const baseCurrency = Currency.from_json(options.base_currency);
|
||||
const baseCurrency = options.base_currency;
|
||||
|
||||
const mantissa_hex = quality.substring(quality.length - 14);
|
||||
const offset_hex = quality.substring(
|
||||
@@ -578,11 +549,11 @@ function(quality, counterCurrency, counterIssuer, opts) {
|
||||
const mantissa = new IOUValue(mantissa_hex, null, 16);
|
||||
const offset = parseInt(offset_hex, 16) - 100;
|
||||
|
||||
this._currency = Currency.from_json(counterCurrency);
|
||||
this._currency = normalizeCurrency(counterCurrency);
|
||||
this._issuer = counterIssuer;
|
||||
this._is_native = this._currency.is_native();
|
||||
this._is_native = (this._currency === 'XRP');
|
||||
|
||||
if (this._is_native && baseCurrency.is_native()) {
|
||||
if (this._is_native && baseCurrency === 'XRP') {
|
||||
throw new Error('XRP/XRP quality is not allowed');
|
||||
}
|
||||
|
||||
@@ -613,7 +584,7 @@ function(quality, counterCurrency, counterIssuer, opts) {
|
||||
// pay:$price drops get:1 X
|
||||
// pay:($price / 1,000,000) XRP get:1 X
|
||||
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
|
||||
} else if (baseCurrency.is_valid() && baseCurrency.is_native()) {
|
||||
} else if (baseCurrency === 'XRP') {
|
||||
// pay:$price X get:1 drop
|
||||
// pay:($price * 1,000,000) X get:1 XRP
|
||||
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
|
||||
@@ -627,18 +598,12 @@ function(quality, counterCurrency, counterIssuer, opts) {
|
||||
this._set_value(nativeAdjusted);
|
||||
}
|
||||
|
||||
if (options.reference_date && baseCurrency.is_valid()
|
||||
&& baseCurrency.has_interest()) {
|
||||
const interest = baseCurrency.get_interest_at(options.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.parse_number = function(n) {
|
||||
this._is_native = false;
|
||||
this._currency = Currency.from_json(1);
|
||||
this._currency = CURRENCY_ONE;
|
||||
this._issuer = ACCOUNT_ONE;
|
||||
this._set_value(new IOUValue(n));
|
||||
return this;
|
||||
@@ -653,7 +618,7 @@ Amount.prototype.parse_json = function(j) {
|
||||
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (m) {
|
||||
this._currency = Currency.from_json(m[2]);
|
||||
this._currency = normalizeCurrency(m[2]);
|
||||
if (m[3]) {
|
||||
this._issuer = m[3];
|
||||
} else {
|
||||
@@ -662,7 +627,7 @@ Amount.prototype.parse_json = function(j) {
|
||||
this.parse_value(m[1]);
|
||||
} else {
|
||||
this.parse_native(j);
|
||||
this._currency = Currency.from_json('0');
|
||||
this._currency = 'XRP';
|
||||
this._issuer = ACCOUNT_ZERO;
|
||||
}
|
||||
break;
|
||||
@@ -679,8 +644,7 @@ Amount.prototype.parse_json = function(j) {
|
||||
if (j instanceof Amount) {
|
||||
j.copyTo(this);
|
||||
} else if (j.hasOwnProperty('value')) {
|
||||
// Parse the passed value to sanitize and copy it.
|
||||
this._currency.parse_json(j.currency, true); // Never XRP.
|
||||
this._currency = normalizeCurrency(j.currency);
|
||||
|
||||
if (typeof j.issuer !== 'string') {
|
||||
throw new Error('issuer must be a string');
|
||||
@@ -727,8 +691,8 @@ Amount.prototype.parse_value = function(j) {
|
||||
};
|
||||
|
||||
Amount.prototype.set_currency = function(c) {
|
||||
this._currency = Currency.from_json(c);
|
||||
this._is_native = this._currency.is_native();
|
||||
this._currency = normalizeCurrency(c);
|
||||
this._is_native = (c === 'XRP');
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -782,27 +746,6 @@ Amount.prototype.to_text = function() {
|
||||
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate present value based on currency and a reference date.
|
||||
*
|
||||
* This only affects demurraging and interest-bearing currencies.
|
||||
*
|
||||
* User should not store amount objects after the interest is applied. This is
|
||||
* intended by display functions such as toHuman().
|
||||
*
|
||||
* @param {Date|Number} referenceDate Date based on which demurrage/interest
|
||||
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
|
||||
* @return {Amount} The amount with interest applied.
|
||||
*/
|
||||
Amount.prototype.applyInterest = function(referenceDate) {
|
||||
if (!this._currency.has_interest()) {
|
||||
return this;
|
||||
}
|
||||
const interest = this._currency.get_interest_at(referenceDate);
|
||||
return this._copy(
|
||||
this._value.multiply(new IOUValue(interest.toString())));
|
||||
};
|
||||
|
||||
/**
|
||||
* Format only value in a human-readable format.
|
||||
*
|
||||
@@ -836,13 +779,9 @@ Amount.prototype.to_human = function(options) {
|
||||
|
||||
/* eslint-disable consistent-this */
|
||||
// Apply demurrage/interest
|
||||
let ref = this;
|
||||
const ref = this;
|
||||
/* eslint-enable consistent-this */
|
||||
|
||||
if (opts.reference_date) {
|
||||
ref = this.applyInterest(opts.reference_date);
|
||||
}
|
||||
|
||||
const isNegative = ref._value.isNegative();
|
||||
const valueString = ref._value.abs().toFixed();
|
||||
const parts = valueString.split('.');
|
||||
@@ -930,7 +869,7 @@ Amount.prototype.to_human = function(options) {
|
||||
Amount.prototype.to_human_full = function(options) {
|
||||
const opts = options || {};
|
||||
const value = this.to_human(opts);
|
||||
const currency = this._currency.to_human();
|
||||
const currency = this._currency;
|
||||
const issuer = this._issuer;
|
||||
const base = value + '/' + currency;
|
||||
return this.is_native() ? base : (base + '/' + issuer);
|
||||
@@ -943,8 +882,7 @@ Amount.prototype.to_json = function() {
|
||||
|
||||
const amount_json = {
|
||||
value: this.to_text(),
|
||||
currency: this._currency.has_interest() ?
|
||||
this._currency.to_hex() : this._currency.to_json()
|
||||
currency: this._currency
|
||||
};
|
||||
|
||||
if (isValidAddress(this._issuer)) {
|
||||
@@ -960,8 +898,7 @@ Amount.prototype.to_text_full = function() {
|
||||
}
|
||||
return this._is_native
|
||||
? this.to_human() + '/XRP'
|
||||
: this.to_text() + '/' + this._currency.to_json()
|
||||
+ '/' + this._issuer;
|
||||
: this.to_text() + '/' + this._currency + '/' + this._issuer;
|
||||
};
|
||||
|
||||
// For debugging.
|
||||
@@ -987,7 +924,7 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) {
|
||||
return type + ' value differs.';
|
||||
}
|
||||
if (!this._is_native) {
|
||||
if (!this._currency.equals(d._currency)) {
|
||||
if (this._currency !== d._currency) {
|
||||
return 'Non-XRP currency differs.';
|
||||
}
|
||||
if (!ignore_issuer && this._issuer !== d._issuer) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const Amount = require('./amount').Amount;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Utils = require('./orderbookutils');
|
||||
const {toHexCurrency} = require('./currency');
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
@@ -22,12 +22,10 @@ function AutobridgeCalculator(currencyGets, currencyPays,
|
||||
) {
|
||||
this._currencyGets = currencyGets;
|
||||
this._currencyPays = currencyPays;
|
||||
this._currencyGetsHex = currencyGets.to_hex();
|
||||
this._currencyPaysHex = currencyPays.to_hex();
|
||||
this._currencyGetsHex = toHexCurrency(currencyGets);
|
||||
this._currencyPaysHex = toHexCurrency(currencyPays);
|
||||
this._issuerGets = issuerGets;
|
||||
this._issuerGetsObj = UInt160.from_json(issuerGets);
|
||||
this._issuerPays = issuerPays;
|
||||
this._issuerPaysObj = UInt160.from_json(issuerPays);
|
||||
this.legOneOffers = _.cloneDeep(legOneOffers);
|
||||
this.legTwoOffers = _.cloneDeep(legTwoOffers);
|
||||
|
||||
@@ -94,9 +92,9 @@ AutobridgeCalculator.prototype._calculateInternal = function(
|
||||
}
|
||||
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
this._currencyGets, this._issuerGets);
|
||||
|
||||
if (legOneTakerGetsFunded.is_zero()) {
|
||||
legOnePointer++;
|
||||
@@ -157,19 +155,19 @@ AutobridgeCalculator.prototype._calculateInternal = function(
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyGets, this._issuerGets);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
this._currencyGets, this._issuerGets);
|
||||
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
|
||||
@@ -202,19 +200,19 @@ function(legOneOffer, legTwoOffer) {
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
const legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
this._currencyGets, this._issuerGets);
|
||||
const legTwoQuality = Utils.getOfferQuality(legTwoOffer,
|
||||
this._currencyGets, this._issuerGets);
|
||||
|
||||
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
// Update funded amount since leg two offer was not completely consumed
|
||||
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj)
|
||||
this._currencyGets, this._issuerGets)
|
||||
.subtract(autobridgedTakerGets)
|
||||
.to_text();
|
||||
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
|
||||
@@ -239,9 +237,9 @@ function(legOneOffer, legTwoOffer) {
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer,
|
||||
this._currencyGets, this._issuerGetsObj);
|
||||
this._currencyGets, this._issuerGets);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
@@ -378,12 +376,12 @@ AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
this.setLegOneTakerGetsFunded(
|
||||
legOneOffer,
|
||||
Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
|
||||
this._issuerPaysObj)
|
||||
this._issuerPays)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -405,7 +403,7 @@ AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
const takerGets = Utils.getOfferTakerGets(legOneOffer, this._currencyPays,
|
||||
this._issuerPaysObj);
|
||||
this._issuerPays);
|
||||
|
||||
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
|
||||
// After clamping, TakerGets is still greater than initial funded amount
|
||||
@@ -431,15 +429,15 @@ function(legOneOffer) {
|
||||
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
|
||||
|
||||
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj)
|
||||
this._currencyPays, this._issuerPays)
|
||||
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
|
||||
|
||||
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj)) >= 0
|
||||
this._currencyPays, this._issuerPays)) >= 0
|
||||
) {
|
||||
// There are enough extra funds to fully fund the offer
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
this._currencyPays, this._issuerPays);
|
||||
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
|
||||
@@ -466,8 +464,8 @@ function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
|
||||
|
||||
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
|
||||
legOneOffer.taker_pays_funded = takerGetsFunded
|
||||
.multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets,
|
||||
this._currencyPays, this._issuerPaysObj))
|
||||
.multiply(Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays))
|
||||
.to_text();
|
||||
|
||||
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
|
||||
@@ -488,8 +486,8 @@ function(legOneOffer, takerGets) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets,
|
||||
this._currencyPays, this._issuerPaysObj);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer,
|
||||
this._currencyPays, this._issuerPays);
|
||||
|
||||
legOneOffer.TakerGets = takerGets.to_text();
|
||||
legOneOffer.TakerPays = takerGets.multiply(legOneQuality).to_json();
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const BN = require('bn.js');
|
||||
const extend = require('extend');
|
||||
const {encode, decode} = require('ripple-address-codec');
|
||||
|
||||
const Base = {};
|
||||
|
||||
extend(Base, {
|
||||
VER_NONE: 1,
|
||||
VER_NODE_PUBLIC: 28,
|
||||
VER_NODE_PRIVATE: 32,
|
||||
VER_ACCOUNT_ID: 0,
|
||||
VER_ACCOUNT_PUBLIC: 35,
|
||||
VER_ACCOUNT_PRIVATE: 34,
|
||||
VER_FAMILY_GENERATOR: 41,
|
||||
VER_FAMILY_SEED: 33,
|
||||
VER_ED25519_SEED: [0x01, 0xE1, 0x4B]
|
||||
});
|
||||
|
||||
// --> input: big-endian array of bytes.
|
||||
// <-- string at least as long as input.
|
||||
Base.encode = function(input, alphabet) {
|
||||
return encode(input, {alphabet});
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function(input, alphabet) {
|
||||
if (typeof input !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return decode(input, {alphabet});
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function(version, input, alphabet) {
|
||||
return encode(input, {version, alphabet});
|
||||
};
|
||||
|
||||
// --> input : String
|
||||
// <-- NaN || BN
|
||||
Base.decode_check = function(version, input, alphabet) {
|
||||
try {
|
||||
const decoded = decode(input, {version, alphabet});
|
||||
return new BN(decoded);
|
||||
} catch (e) {
|
||||
return NaN;
|
||||
}
|
||||
};
|
||||
|
||||
exports.Base = Base;
|
||||
@@ -1,45 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
function normalize(digitArray) {
|
||||
let i = 0;
|
||||
while (digitArray[i] === 0) {
|
||||
++i;
|
||||
}
|
||||
if (i > 0) {
|
||||
digitArray.splice(0, i);
|
||||
}
|
||||
return digitArray;
|
||||
}
|
||||
|
||||
function divmod(digitArray, base, divisor) {
|
||||
let remainder = 0;
|
||||
let temp;
|
||||
let divided;
|
||||
let j = -1;
|
||||
|
||||
const length = digitArray.length;
|
||||
const quotient = new Array(length);
|
||||
|
||||
while (++j < length) {
|
||||
temp = remainder * base + digitArray[j];
|
||||
divided = temp / divisor;
|
||||
quotient[j] = divided << 0;
|
||||
remainder = temp % divisor;
|
||||
}
|
||||
return {quotient: normalize(quotient), remainder: remainder};
|
||||
}
|
||||
|
||||
function convertBase(digitArray, fromBase, toBase) {
|
||||
const result = [];
|
||||
let dividend = digitArray;
|
||||
let qr;
|
||||
while (dividend.length > 0) {
|
||||
qr = divmod(dividend, fromBase, toBase);
|
||||
result.unshift(qr.remainder);
|
||||
dividend = qr.quotient;
|
||||
}
|
||||
return normalize(result);
|
||||
}
|
||||
|
||||
module.exports = convertBase;
|
||||
@@ -2,5 +2,7 @@
|
||||
|
||||
module.exports = {
|
||||
ACCOUNT_ZERO: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
|
||||
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
ACCOUNT_ONE: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
CURRENCY_ZERO: '0000000000000000000000000000000000000000',
|
||||
CURRENCY_ONE: '0000000000000000000000000000000000000001'
|
||||
};
|
||||
|
||||
@@ -1,429 +1,58 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
|
||||
const extend = require('extend');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const utils = require('./utils');
|
||||
const Float = require('./ieee754').Float;
|
||||
function isISOCode(currency) {
|
||||
return /^[A-Z0-9]{3}$/.test(currency);
|
||||
}
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
function isHexCurrency(currency) {
|
||||
return /[A-Fa-f0-9]{40}/.test(currency);
|
||||
}
|
||||
|
||||
const Currency = extend(function() {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
// Json form:
|
||||
// '', 'XRP', '0': 0
|
||||
// 3-letter code: ...
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
this._update();
|
||||
}, UInt160);
|
||||
|
||||
Currency.prototype = Object.create(extend({}, UInt160.prototype));
|
||||
Currency.prototype.constructor = Currency;
|
||||
|
||||
Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
|
||||
|
||||
/**
|
||||
* Tries to correctly interpret a Currency as entered by a user.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* USD => currency
|
||||
* USD - Dollar => currency with optional full currency
|
||||
* name
|
||||
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
|
||||
* per year
|
||||
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
|
||||
* USD (1%pa) => US dollars with 1% effective interest
|
||||
* per year
|
||||
* INR - Indian Rupees => Optional full currency name with spaces
|
||||
* TYX - 30-Year Treasuries => Optional full currency with numbers
|
||||
* and a dash
|
||||
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
|
||||
* dash and interest rate
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better
|
||||
* understanding:
|
||||
*
|
||||
* ^\s* // start with any amount of whitespace
|
||||
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
|
||||
* digit numeric currency-code. See ISO 4217
|
||||
* (\s*-\s*[- \w]+) // optional full currency name following the dash
|
||||
* after currency code, full currency code can
|
||||
* contain letters, numbers and dashes
|
||||
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
|
||||
* . notation (-0.5%pa)
|
||||
* \s*$ // end with any amount of whitespace
|
||||
*
|
||||
*/
|
||||
|
||||
/* eslint-disable max-len*/
|
||||
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
|
||||
/* eslint-enable max-len*/
|
||||
|
||||
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
|
||||
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
};
|
||||
|
||||
Currency.from_human = function(j, opts) {
|
||||
return (new Currency().parse_human(j, opts));
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
if (j instanceof Currency) {
|
||||
this._value = j._value;
|
||||
this._update();
|
||||
return this;
|
||||
}
|
||||
|
||||
switch (typeof j) {
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (!j || j === '0') {
|
||||
// Empty string or XRP
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
break;
|
||||
}
|
||||
|
||||
if (j === '1') {
|
||||
// 'no currency'
|
||||
this.parse_hex(Currency.HEX_ONE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^[A-F0-9]{40}$/.test(j)) {
|
||||
// Hex format
|
||||
this.parse_hex(j);
|
||||
break;
|
||||
}
|
||||
|
||||
// match the given string to see if it's in an allowed format
|
||||
const matches = j.match(this.human_RE);
|
||||
|
||||
if (matches) {
|
||||
let currencyCode = matches[1];
|
||||
|
||||
// for the currency 'XRP' case
|
||||
// we drop everything else that could have been provided
|
||||
// e.g. 'XRP - Ripple'
|
||||
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
|
||||
// early break, we can't have interest on XRP
|
||||
break;
|
||||
}
|
||||
|
||||
// the full currency is matched as it is part of the valid currency
|
||||
// format, but not stored
|
||||
// var full_currency = matches[2] || '';
|
||||
const interest = matches[3] || '';
|
||||
|
||||
// interest is defined as interest per year, per annum (pa)
|
||||
let percentage = interest.match(/(-?\d+\.?\d+)/);
|
||||
|
||||
currencyCode = currencyCode.toUpperCase();
|
||||
|
||||
const currencyData = utils.arraySet(20, 0);
|
||||
|
||||
if (percentage) {
|
||||
/*
|
||||
* 20 byte layout of a interest bearing currency
|
||||
*
|
||||
* 01 __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __
|
||||
* CURCODE- DATE------- RATE------------------- RESERVED---
|
||||
*/
|
||||
|
||||
// byte 1 for type, use '1' to denote demurrage currency
|
||||
currencyData[0] = 1;
|
||||
|
||||
// byte 2-4 for currency code
|
||||
currencyData[1] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
// byte 5-8 are for reference date, but should always be 0 so we
|
||||
// won't fill it
|
||||
|
||||
// byte 9-16 are for the interest
|
||||
percentage = parseFloat(percentage[0]);
|
||||
|
||||
// the interest or demurrage is expressed as a yearly (per annum)
|
||||
// value
|
||||
const secondsPerYear = 31536000; // 60 * 60 * 24 * 365
|
||||
|
||||
// Calculating the interest e-fold
|
||||
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
|
||||
// 0.5% interest is expressed as 1.005, 0.005 more than 1
|
||||
const interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
|
||||
const bytes = Float.toIEEE754Double(interestEfold);
|
||||
|
||||
for (let i = 0; i <= bytes.length; i++) {
|
||||
currencyData[8 + i] = bytes[i] & 0xff;
|
||||
}
|
||||
|
||||
// the last 4 bytes are reserved for future use, so we won't fill
|
||||
// those
|
||||
|
||||
} else {
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
}
|
||||
|
||||
this.parse_bytes(currencyData);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Currency.prototype.parse_human = function(j) {
|
||||
return this.parse_json(j);
|
||||
};
|
||||
|
||||
/**
|
||||
* Recalculate internal representation.
|
||||
*
|
||||
* You should never need to call this.
|
||||
*/
|
||||
|
||||
Currency.prototype._update = function() {
|
||||
const bytes = this.to_bytes();
|
||||
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
let isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._native = false;
|
||||
this._type = -1;
|
||||
this._interest_start = NaN;
|
||||
this._interest_period = NaN;
|
||||
this._iso_code = '';
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
|
||||
&& (i === 12 || i === 13 || i === 14 || bytes[i] === 0);
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
this._iso_code = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
|
||||
if (this._iso_code === '\u0000\u0000\u0000') {
|
||||
this._native = true;
|
||||
this._iso_code = 'XRP';
|
||||
}
|
||||
|
||||
this._type = 0;
|
||||
} else if (bytes[0] === 0x01) { // Demurrage currency
|
||||
this._iso_code = String.fromCharCode(bytes[1])
|
||||
+ String.fromCharCode(bytes[2])
|
||||
+ String.fromCharCode(bytes[3]);
|
||||
|
||||
this._type = 1;
|
||||
this._interest_start = (bytes[4] << 24) +
|
||||
(bytes[5] << 16) +
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7]);
|
||||
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns copy.
|
||||
*
|
||||
* This copies code from UInt.copyTo so we do not call _update,
|
||||
* bvecause to_bytes is very expensive.
|
||||
*/
|
||||
|
||||
Currency.prototype.copyTo = function(d) {
|
||||
d._value = this._value;
|
||||
|
||||
if (this._version_byte !== undefined) {
|
||||
d._version_byte = this._version_byte;
|
||||
}
|
||||
|
||||
if (!d.is_valid()) {
|
||||
return d;
|
||||
}
|
||||
|
||||
d._native = this._native;
|
||||
d._type = this._type;
|
||||
d._interest_start = this._interest_start;
|
||||
d._interest_period = this._interest_period;
|
||||
d._iso_code = this._iso_code;
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
// XXX Probably not needed anymore?
|
||||
/*
|
||||
Currency.prototype.parse_bytes = function(byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||
var result;
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
|
||||
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
var currencyCode = String.fromCharCode(byte_array[12])
|
||||
+ String.fromCharCode(byte_array[13])
|
||||
+ String.fromCharCode(byte_array[14]);
|
||||
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== 'XRP' ) {
|
||||
this._value = currencyCode;
|
||||
} else if (currencyCode === '\0\0\0') {
|
||||
this._value = 0;
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
} else {
|
||||
// XXX Should support non-standard currency codes
|
||||
this._value = NaN;
|
||||
}
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
*/
|
||||
|
||||
Currency.prototype.is_native = function() {
|
||||
return this._native;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Boolean} whether this currency is an interest-bearing currency
|
||||
*/
|
||||
|
||||
Currency.prototype.has_interest = function() {
|
||||
return this._type === 1
|
||||
&& !isNaN(this._interest_start)
|
||||
&& !isNaN(this._interest_period);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} referenceDate_ number of seconds since the Ripple Epoch
|
||||
* (0:00 on January 1, 2000 UTC) used to calculate the
|
||||
* interest over provided interval pass in one years
|
||||
* worth of seconds to ge the yearly interest
|
||||
* @returns {number} interest for provided interval, can be negative for
|
||||
* demurred currencies
|
||||
*/
|
||||
Currency.prototype.get_interest_at = function(referenceDate_) {
|
||||
if (!this.has_interest()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let referenceDate = referenceDate_;
|
||||
|
||||
// use one year as a default period
|
||||
if (!referenceDate) {
|
||||
referenceDate = this._interest_start + 3600 * 24 * 365;
|
||||
}
|
||||
|
||||
if (referenceDate instanceof Date) {
|
||||
referenceDate = utils.fromTimestamp(referenceDate.getTime());
|
||||
}
|
||||
|
||||
// calculate interest by e-fold number
|
||||
return Math.exp((referenceDate - this._interest_start)
|
||||
/ this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.get_interest_percentage_at = function(referenceDate,
|
||||
decimals
|
||||
) {
|
||||
let interest = this.get_interest_at(referenceDate, decimals);
|
||||
|
||||
// convert to percentage
|
||||
interest = (interest * 100) - 100;
|
||||
const decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
|
||||
|
||||
// round to two decimals behind the dot
|
||||
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
|
||||
};
|
||||
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
//
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
// Currency.prototype.is_valid = function() {
|
||||
// return UInt.prototype.is_valid() && ...;
|
||||
// };
|
||||
|
||||
Currency.prototype.to_json = function(opts = {}) {
|
||||
if (!this.is_valid()) {
|
||||
// XXX This is backwards compatible behavior, but probably not very good.
|
||||
function getISOCode(hexCurrency) {
|
||||
const bytes = new Buffer(hexCurrency, 'hex');
|
||||
if (_.every(bytes, octet => octet === 0)) {
|
||||
return 'XRP';
|
||||
}
|
||||
|
||||
let currency;
|
||||
const fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
|
||||
opts.show_interest = opts.show_interest !== undefined
|
||||
? opts.show_interest
|
||||
: this.has_interest();
|
||||
|
||||
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
|
||||
currency = this._iso_code + fullName;
|
||||
if (opts.show_interest) {
|
||||
const decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
|
||||
const interestPercentage = this.has_interest()
|
||||
? this.get_interest_percentage_at(
|
||||
this._interest_start + 3600 * 24 * 365, decimals
|
||||
)
|
||||
: 0;
|
||||
currency += ' (' + interestPercentage + '%pa)';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback to returning the raw currency hex
|
||||
currency = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very
|
||||
// odd behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currency === Currency.HEX_ONE) {
|
||||
currency = 1;
|
||||
}
|
||||
if (!_.every(bytes, (octet, i) => octet === 0 || (i >= 12 && i <= 14))) {
|
||||
return null;
|
||||
}
|
||||
const code = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
return isISOCode(code) ? code : null;
|
||||
}
|
||||
|
||||
return currency;
|
||||
};
|
||||
function normalizeCurrency(currency) {
|
||||
if (isISOCode(currency.toUpperCase())) {
|
||||
return currency.toUpperCase();
|
||||
} else if (isHexCurrency(currency)) {
|
||||
const code = getISOCode(currency);
|
||||
return code === null ? currency.toUpperCase() : code;
|
||||
}
|
||||
throw new Error('invalid currency');
|
||||
}
|
||||
|
||||
Currency.prototype.to_human = function(opts) {
|
||||
// to_human() will always print the human-readable currency code if available.
|
||||
return this.to_json(opts);
|
||||
};
|
||||
function toHexCurrency(currency) {
|
||||
if (isISOCode(currency)) {
|
||||
const bytes = new Buffer(20);
|
||||
bytes.fill(0);
|
||||
if (currency !== 'XRP') {
|
||||
bytes[12] = currency.charCodeAt(0);
|
||||
bytes[13] = currency.charCodeAt(1);
|
||||
bytes[14] = currency.charCodeAt(2);
|
||||
}
|
||||
return bytes.toString('hex').toUpperCase();
|
||||
} else if (isHexCurrency(currency)) {
|
||||
return currency.toUpperCase();
|
||||
}
|
||||
throw new Error('invalid currency');
|
||||
}
|
||||
|
||||
Currency.prototype.get_iso = function() {
|
||||
return this._iso_code;
|
||||
};
|
||||
function isValidCurrency(currency) {
|
||||
return isISOCode(currency.toUpperCase()) || isHexCurrency(currency);
|
||||
}
|
||||
|
||||
exports.Currency = Currency;
|
||||
exports.normalizeCurrency = normalizeCurrency;
|
||||
exports.isValidCurrency = isValidCurrency;
|
||||
exports.toHexCurrency = toHexCurrency;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: move in helpers from serializedtypes to utils
|
||||
function toBytes(n) {
|
||||
return [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// leaf node in tree
|
||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
// inner transaction to multisign
|
||||
exports.HASH_TX_MULTISIGN = 0x534D5400; // 'SMT'
|
||||
|
||||
Object.keys(exports).forEach(function(k) {
|
||||
exports[k + '_BYTES'] = toBytes(exports[k]);
|
||||
});
|
||||
@@ -1,107 +0,0 @@
|
||||
// Convert a JavaScript number to IEEE-754 Double Precision
|
||||
// value represented as an array of 8 bytes (octets)
|
||||
//
|
||||
// Based on:
|
||||
// http://cautionsingularityahead.blogspot.com/2010/04/javascript-and-ieee754-redux.html
|
||||
//
|
||||
// Found and modified from:
|
||||
// https://gist.github.com/bartaz/1119041
|
||||
|
||||
var Float = exports.Float = {};
|
||||
|
||||
Float.toIEEE754 = function(v, ebits, fbits) {
|
||||
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
|
||||
// Compute sign, exponent, fraction
|
||||
var s, e, f;
|
||||
if (isNaN(v)) {
|
||||
e = (1 << bias) - 1; f = 1; s = 0;
|
||||
}
|
||||
else if (v === Infinity || v === -Infinity) {
|
||||
e = (1 << bias) - 1; f = 0; s = (v < 0) ? 1 : 0;
|
||||
}
|
||||
else if (v === 0) {
|
||||
e = 0; f = 0; s = (1 / v === -Infinity) ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
s = v < 0;
|
||||
v = Math.abs(v);
|
||||
|
||||
if (v >= Math.pow(2, 1 - bias)) {
|
||||
var ln = Math.min(Math.floor(Math.log(v) / Math.LN2), bias);
|
||||
e = ln + bias;
|
||||
f = v * Math.pow(2, fbits - ln) - Math.pow(2, fbits);
|
||||
}
|
||||
else {
|
||||
e = 0;
|
||||
f = v / Math.pow(2, 1 - bias - fbits);
|
||||
}
|
||||
}
|
||||
|
||||
// Pack sign, exponent, fraction
|
||||
var i, bits = [];
|
||||
for (i = fbits; i; i -= 1) { bits.push(f % 2 ? 1 : 0); f = Math.floor(f / 2); }
|
||||
for (i = ebits; i; i -= 1) { bits.push(e % 2 ? 1 : 0); e = Math.floor(e / 2); }
|
||||
bits.push(s ? 1 : 0);
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Bits to bytes
|
||||
var bytes = [];
|
||||
while (str.length) {
|
||||
bytes.push(parseInt(str.substring(0, 8), 2));
|
||||
str = str.substring(8);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Float.fromIEEE754 = function(bytes, ebits, fbits) {
|
||||
|
||||
// Bytes to bits
|
||||
var bits = [];
|
||||
for (var i = bytes.length; i; i -= 1) {
|
||||
var byte = bytes[i - 1];
|
||||
for (var j = 8; j; j -= 1) {
|
||||
bits.push(byte % 2 ? 1 : 0); byte = byte >> 1;
|
||||
}
|
||||
}
|
||||
bits.reverse();
|
||||
var str = bits.join('');
|
||||
|
||||
// Unpack sign, exponent, fraction
|
||||
var bias = (1 << (ebits - 1)) - 1;
|
||||
var s = parseInt(str.substring(0, 1), 2) ? -1 : 1;
|
||||
var e = parseInt(str.substring(1, 1 + ebits), 2);
|
||||
var f = parseInt(str.substring(1 + ebits), 2);
|
||||
|
||||
// Produce number
|
||||
if (e === (1 << ebits) - 1) {
|
||||
return f !== 0 ? NaN : s * Infinity;
|
||||
}
|
||||
else if (e > 0) {
|
||||
return s * Math.pow(2, e - bias) * (1 + f / Math.pow(2, fbits));
|
||||
}
|
||||
else if (f !== 0) {
|
||||
return s * Math.pow(2, -(bias-1)) * (f / Math.pow(2, fbits));
|
||||
}
|
||||
else {
|
||||
return s * 0;
|
||||
}
|
||||
}
|
||||
|
||||
Float.fromIEEE754Double = function(b) { return Float.fromIEEE754(b, 11, 52); }
|
||||
Float.toIEEE754Double = function(v) { return Float.toIEEE754(v, 11, 52); }
|
||||
Float.fromIEEE754Single = function(b) { return Float.fromIEEE754(b, 8, 23); }
|
||||
Float.toIEEE754Single = function(v) { return Float.toIEEE754(v, 8, 23); }
|
||||
|
||||
|
||||
// Convert array of octets to string binary representation
|
||||
// by bartaz
|
||||
|
||||
Float.toIEEE754DoubleString = function(v) {
|
||||
return exports.toIEEE754Double(v)
|
||||
.map(function(n){ for(n = n.toString(2);n.length < 8;n="0"+n); return n })
|
||||
.join('')
|
||||
.replace(/(.)(.{11})(.{52})/, "$1 $2 $3")
|
||||
}
|
||||
@@ -5,26 +5,17 @@ exports.Amount = require('./amount').Amount;
|
||||
exports.Account = require('./account').Account;
|
||||
exports.Transaction = require('./transaction').Transaction;
|
||||
exports.Currency = require('./currency').Currency;
|
||||
exports.Base = require('./base').Base;
|
||||
exports.UInt128 = require('./uint128').UInt128;
|
||||
exports.UInt160 = require('./uint160').UInt160;
|
||||
exports.UInt256 = require('./uint256').UInt256;
|
||||
exports.Meta = require('./meta').Meta;
|
||||
exports.SerializedObject = require('./serializedobject').SerializedObject;
|
||||
exports.RippleError = require('./rippleerror').RippleError;
|
||||
exports.binformat = require('./binformat');
|
||||
exports.utils = require('./utils');
|
||||
exports.Server = require('./server').Server;
|
||||
exports.Ledger = require('./ledger').Ledger;
|
||||
exports.TransactionQueue = require('./transactionqueue').TransactionQueue;
|
||||
exports.convertBase = require('./baseconverter');
|
||||
|
||||
exports._test = {
|
||||
Log: require('./log'),
|
||||
PathFind: require('./pathfind').PathFind,
|
||||
TransactionManager: require('./transactionmanager').TransactionManager,
|
||||
TransactionQueue: require('./transactionqueue').TransactionQueue,
|
||||
RangeSet: require('./rangeset').RangeSet,
|
||||
HashPrefixes: require('./hashprefixes')
|
||||
OrderbookUtils: require('./orderbookutils'),
|
||||
constants: require('./constants')
|
||||
};
|
||||
|
||||
exports.types = require('./serializedtypes');
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
'use strict';
|
||||
const BigNumber = require('bignumber.js');
|
||||
const Transaction = require('./transaction').Transaction;
|
||||
const SHAMap = require('./shamap').SHAMap;
|
||||
const SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const stypes = require('./serializedtypes');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Currency = require('./currency').Currency;
|
||||
|
||||
function Ledger() {
|
||||
this.ledger_json = {};
|
||||
}
|
||||
|
||||
Ledger.from_json = function(v) {
|
||||
const ledger = new Ledger();
|
||||
ledger.parse_json(v);
|
||||
return ledger;
|
||||
};
|
||||
|
||||
Ledger.space = require('./ledgerspaces');
|
||||
|
||||
/**
|
||||
* Generate the key for an AccountRoot entry.
|
||||
*
|
||||
* @param {String|UInt160} accountArg - Ripple Account
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcAccountRootEntryHash =
|
||||
Ledger.prototype.calcAccountRootEntryHash = function(accountArg) {
|
||||
const account = UInt160.from_json(accountArg);
|
||||
const index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.account.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for an Offer entry.
|
||||
*
|
||||
* @param {String|UInt160} accountArg - Ripple Account
|
||||
* @param {Number} sequence - Sequence number of the OfferCreate transaction
|
||||
* that instantiated this offer.
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcOfferEntryHash =
|
||||
Ledger.prototype.calcOfferEntryHash = function(accountArg, sequence) {
|
||||
const account = UInt160.from_json(accountArg);
|
||||
const index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.offer.charCodeAt(0)]);
|
||||
index.append(account.to_bytes());
|
||||
stypes.Int32.serialize(index, sequence);
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate the key for a RippleState entry.
|
||||
*
|
||||
* The ordering of the two account parameters does not matter.
|
||||
*
|
||||
* @param {String|UInt160} _account1 - First Ripple Account
|
||||
* @param {String|UInt160} _account2 - Second Ripple Account
|
||||
* @param {String|Currency} _currency - The currency code
|
||||
* @return {UInt256}
|
||||
*/
|
||||
Ledger.calcRippleStateEntryHash =
|
||||
Ledger.prototype.calcRippleStateEntryHash = function(
|
||||
_account1, _account2, _currency) {
|
||||
const currency = Currency.from_json(_currency);
|
||||
const account1 = UInt160.from_json(_account1);
|
||||
const account2 = UInt160.from_json(_account2);
|
||||
|
||||
if (!account1.is_valid()) {
|
||||
throw new Error('Invalid first account');
|
||||
}
|
||||
if (!account2.is_valid()) {
|
||||
throw new Error('Invalid second account');
|
||||
}
|
||||
if (!currency.is_valid()) {
|
||||
throw new Error('Invalid currency');
|
||||
}
|
||||
|
||||
const swap = account1.greater_than(account2);
|
||||
const lowAccount = swap ? account2 : account1;
|
||||
const highAccount = swap ? account1 : account2;
|
||||
const index = new SerializedObject();
|
||||
|
||||
index.append([0, Ledger.space.rippleState.charCodeAt(0)]);
|
||||
index.append(lowAccount.to_bytes());
|
||||
index.append(highAccount.to_bytes());
|
||||
index.append(currency.to_bytes());
|
||||
|
||||
return index.hash();
|
||||
};
|
||||
|
||||
Ledger.prototype.parse_json = function(v) {
|
||||
this.ledger_json = v;
|
||||
};
|
||||
|
||||
Ledger.prototype.calc_tx_hash = function() {
|
||||
const tx_map = new SHAMap();
|
||||
|
||||
this.ledger_json.transactions.forEach(function(tx_json) {
|
||||
const tx = Transaction.from_json(tx_json);
|
||||
const meta = SerializedObject.from_json(tx_json.metaData);
|
||||
|
||||
const data = new SerializedObject();
|
||||
stypes.VariableLength.serialize(data, tx.serialize().to_hex());
|
||||
stypes.VariableLength.serialize(data, meta.to_hex());
|
||||
tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
|
||||
});
|
||||
|
||||
return tx_map.hash();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} options - object
|
||||
*
|
||||
* @param {Boolean} [options.sanity_test=false] - If `true`, will serialize each
|
||||
* accountState item to binary and then back to json before finally
|
||||
* serializing for hashing. This is mostly to expose any issues with
|
||||
* ripple-lib's binary <--> json codecs.
|
||||
*
|
||||
* @return {UInt256} - hash of shamap
|
||||
*/
|
||||
Ledger.prototype.calc_account_hash = function(options) {
|
||||
const account_map = new SHAMap();
|
||||
let erred;
|
||||
|
||||
this.ledger_json.accountState.forEach(function(le) {
|
||||
let data = SerializedObject.from_json(le);
|
||||
|
||||
let json;
|
||||
if (options && options.sanity_test) {
|
||||
try {
|
||||
json = data.to_json();
|
||||
data = SerializedObject.from_json(json);
|
||||
} catch (e) {
|
||||
console.log('account state item: ', le);
|
||||
console.log('to_json() ', json);
|
||||
console.log('exception: ', e);
|
||||
erred = true;
|
||||
}
|
||||
}
|
||||
|
||||
account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE);
|
||||
});
|
||||
|
||||
if (erred) {
|
||||
throw new Error('There were errors with sanity_test'); // all logged above
|
||||
}
|
||||
|
||||
return account_map.hash();
|
||||
};
|
||||
|
||||
// see rippled Ledger::updateHash()
|
||||
Ledger.calculateLedgerHash =
|
||||
Ledger.prototype.calculateLedgerHash = function(ledgerHeader) {
|
||||
const so = new SerializedObject();
|
||||
const prefix = 0x4C575200;
|
||||
const totalCoins = (new BigNumber(ledgerHeader.total_coins)).toString(16);
|
||||
|
||||
stypes.Int32.serialize(so, Number(ledgerHeader.ledger_index));
|
||||
stypes.Int64.serialize(so, totalCoins);
|
||||
stypes.Hash256.serialize(so, ledgerHeader.parent_hash);
|
||||
stypes.Hash256.serialize(so, ledgerHeader.transaction_hash);
|
||||
stypes.Hash256.serialize(so, ledgerHeader.account_hash);
|
||||
stypes.Int32.serialize(so, ledgerHeader.parent_close_time);
|
||||
stypes.Int32.serialize(so, ledgerHeader.close_time);
|
||||
stypes.Int8.serialize(so, ledgerHeader.close_time_resolution);
|
||||
stypes.Int8.serialize(so, ledgerHeader.close_flags);
|
||||
|
||||
return so.hash(prefix).to_hex();
|
||||
};
|
||||
|
||||
exports.Ledger = Ledger;
|
||||
@@ -1,22 +0,0 @@
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* names are partitioned into namespaces.
|
||||
*
|
||||
* Each namespace is just a single character prefix.
|
||||
*/
|
||||
module.exports = {
|
||||
account : 'a',
|
||||
dirNode : 'd',
|
||||
generatorMap : 'g',
|
||||
nickname : 'n',
|
||||
rippleState : 'r',
|
||||
offer : 'o', // Entry for an offer.
|
||||
ownerDir : 'O', // Directory of things owned by an account.
|
||||
bookDir : 'B', // Directory of order books.
|
||||
contract : 'c',
|
||||
skipList : 's',
|
||||
amendment : 'f',
|
||||
feeSettings : 'e'
|
||||
};
|
||||
@@ -55,7 +55,7 @@ Log.makeLevel = function(level) {
|
||||
return function() {
|
||||
const args = Array.prototype.slice.apply(arguments);
|
||||
args[0] = this._prefix + args[0];
|
||||
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(2)]));
|
||||
Log.engine.logObject.apply(Log, [level].concat(args[0], [args.slice(1)]));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Amount = require('./amount').Amount;
|
||||
const ACCOUNT_ZERO = require('./constants').ACCOUNT_ZERO;
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
@@ -151,7 +150,7 @@ Meta.prototype.getAffectedAccounts = function() {
|
||||
for (const fieldName in fields) {
|
||||
const field = fields[fieldName];
|
||||
|
||||
if (this.isAccountField(fieldName) && UInt160.is_valid(field)) {
|
||||
if (this.isAccountField(fieldName) && isValidAddress(field)) {
|
||||
accounts.push(field);
|
||||
} else if (
|
||||
Meta.AMOUNT_FIELDS_AFFECTING_ISSUER.indexOf(fieldName) !== -1) {
|
||||
@@ -185,8 +184,8 @@ Meta.prototype.getAffectedBooks = function() {
|
||||
|
||||
const gets = Amount.from_json(node.fields.TakerGets);
|
||||
const pays = Amount.from_json(node.fields.TakerPays);
|
||||
let getsKey = gets.currency().to_json();
|
||||
let paysKey = pays.currency().to_json();
|
||||
let getsKey = gets.currency();
|
||||
let paysKey = pays.currency();
|
||||
|
||||
if (getsKey !== 'XRP') {
|
||||
getsKey += '/' + gets.issuer();
|
||||
|
||||
@@ -16,20 +16,14 @@ const extend = require('extend');
|
||||
const assert = require('assert');
|
||||
const async = require('async');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
const Amount = require('./amount').Amount;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Currency = require('./currency').Currency;
|
||||
const AutobridgeCalculator = require('./autobridgecalculator');
|
||||
const OrderBookUtils = require('./orderbookutils');
|
||||
const log = require('./log').internal.sub('orderbook');
|
||||
const {IOUValue} = require('ripple-lib-value');
|
||||
|
||||
function _sortOffers(a, b) {
|
||||
const aQuality = OrderBookUtils.getOfferQuality(a, this._currencyGets);
|
||||
const bQuality = OrderBookUtils.getOfferQuality(b, this._currencyGets);
|
||||
|
||||
return aQuality._value.comparedTo(bQuality._value);
|
||||
}
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
const {normalizeCurrency, isValidCurrency} = require('./currency');
|
||||
|
||||
function _sortOffersQuick(a, b) {
|
||||
return a.qualityHex.localeCompare(b.qualityHex);
|
||||
@@ -55,9 +49,9 @@ function OrderBook(remote,
|
||||
const self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._currencyGets = Currency.from_json(currencyGets);
|
||||
this._currencyGets = normalizeCurrency(currencyGets);
|
||||
this._issuerGets = issuerGets;
|
||||
this._currencyPays = Currency.from_json(currencyPays);
|
||||
this._currencyPays = normalizeCurrency(currencyPays);
|
||||
this._issuerPays = issuerPays;
|
||||
this._key = key;
|
||||
this._subscribed = false;
|
||||
@@ -93,11 +87,10 @@ function OrderBook(remote,
|
||||
this._calculatorRunning = false;
|
||||
|
||||
|
||||
this.sortOffers = this._currencyGets.has_interest() ?
|
||||
_sortOffers.bind(this) : _sortOffersQuick;
|
||||
this.sortOffers = _sortOffersQuick;
|
||||
|
||||
this._isAutobridgeable = !this._currencyGets.is_native()
|
||||
&& !this._currencyPays.is_native();
|
||||
this._isAutobridgeable = this._currencyGets !== 'XRP'
|
||||
&& this._currencyPays !== 'XRP';
|
||||
|
||||
function computeAutobridgedOffersWrapperOne() {
|
||||
if (!self._gotOffersFromLegOne) {
|
||||
@@ -256,6 +249,9 @@ OrderBook.offerRewrite = function(offer) {
|
||||
/**
|
||||
* Initialize orderbook. Get orderbook offers and subscribe to transactions
|
||||
* @api private
|
||||
* NOTE: this method is not meant to be publicly used
|
||||
* and it does not work for autobridged books since
|
||||
* it does not add listeners for them
|
||||
*/
|
||||
|
||||
OrderBook.prototype.subscribe = function() {
|
||||
@@ -333,21 +329,23 @@ OrderBook.prototype.destroy = function() {
|
||||
* Request orderbook entries from server
|
||||
*
|
||||
* @param {Function} callback
|
||||
* @param {boolean} internal - internal request made on 'subscribe'
|
||||
*/
|
||||
|
||||
OrderBook.prototype.requestOffers = function(callback = function() {},
|
||||
internal = false) {
|
||||
const self = this;
|
||||
|
||||
if (!this._remote.isConnected()) {
|
||||
if (!this._remote.isConnected() && !internal) {
|
||||
// do not make request if not online.
|
||||
// that requests will be queued and
|
||||
// eventually all of them will fire back
|
||||
callback(new RippleError('remote is offline'));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!this._shouldSubscribe) {
|
||||
callback(new Error('Should not request offers'));
|
||||
callback(new RippleError('Should not request offers'));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -355,7 +353,7 @@ OrderBook.prototype.requestOffers = function(callback = function() {},
|
||||
log.info('requesting offers', this._key);
|
||||
}
|
||||
|
||||
this._synchronized = false;
|
||||
this._synced = false;
|
||||
|
||||
if (this._isAutobridgeable && !internal) {
|
||||
this._gotOffersFromLegOne = false;
|
||||
@@ -375,7 +373,7 @@ OrderBook.prototype.requestOffers = function(callback = function() {},
|
||||
|
||||
if (!Array.isArray(res.offers)) {
|
||||
// XXX What now?
|
||||
callback(new Error('Invalid response'));
|
||||
callback(new RippleError('Invalid response'));
|
||||
self.emit('model', []);
|
||||
return;
|
||||
}
|
||||
@@ -426,7 +424,7 @@ OrderBook.prototype.requestTransferRate = function(callback) {
|
||||
|
||||
const self = this;
|
||||
|
||||
if (this._currencyGets.is_native()) {
|
||||
if (this._currencyGets === 'XRP') {
|
||||
// Transfer rate is default for the native currency
|
||||
this._issuerTransferRate = OrderBook.DEFAULT_TRANSFER_RATE;
|
||||
|
||||
@@ -572,7 +570,7 @@ OrderBook.prototype.applyTransferRate = function(balance) {
|
||||
|
||||
OrderBook.prototype.getOwnerFunds = function(account) {
|
||||
if (this.hasOwnerFunds(account)) {
|
||||
if (this._currencyGets.is_native()) {
|
||||
if (this._currencyGets === 'XRP') {
|
||||
return Amount.from_json(this._ownerFunds[account]);
|
||||
}
|
||||
return OrderBookUtils.normalizeAmount(this._ownerFunds[account]);
|
||||
@@ -692,7 +690,7 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) {
|
||||
if (amount) {
|
||||
return amount;
|
||||
}
|
||||
if (this._currencyGets.is_native()) {
|
||||
if (this._currencyGets === 'XRP') {
|
||||
return OrderBook.ZERO_NATIVE_AMOUNT.clone();
|
||||
}
|
||||
return OrderBook.ZERO_NORMALIZED_AMOUNT.clone();
|
||||
@@ -706,7 +704,7 @@ OrderBook.prototype.getOwnerOfferTotal = function(account) {
|
||||
*/
|
||||
|
||||
OrderBook.prototype.resetOwnerOfferTotal = function(account) {
|
||||
if (this._currencyGets.is_native()) {
|
||||
if (this._currencyGets === 'XRP') {
|
||||
this._ownerOffersTotal[account] = OrderBook.ZERO_NATIVE_AMOUNT.clone();
|
||||
} else {
|
||||
this._ownerOffersTotal[account] = OrderBook.ZERO_NORMALIZED_AMOUNT.clone();
|
||||
@@ -743,12 +741,12 @@ OrderBook.prototype.setOfferFundedAmount = function(offer) {
|
||||
} else if (previousOfferSum.compareTo(fundedAmount) < 0) {
|
||||
offer.taker_gets_funded = fundedAmount.subtract(previousOfferSum).to_text();
|
||||
|
||||
const quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets);
|
||||
const quality = OrderBookUtils.getOfferQuality(offer);
|
||||
const takerPaysFunded = quality.multiply(
|
||||
OrderBookUtils.getOfferTakerGetsFunded(offer)
|
||||
);
|
||||
|
||||
offer.taker_pays_funded = this._currencyPays.is_native()
|
||||
offer.taker_pays_funded = (this._currencyPays === 'XRP')
|
||||
? String(Math.floor(takerPaysFunded.to_number()))
|
||||
: takerPaysFunded.to_json().value;
|
||||
} else {
|
||||
@@ -795,7 +793,7 @@ OrderBook.prototype.parseAccountBalanceFromNode = function(node) {
|
||||
|
||||
assert(!isNaN(result.balance), 'node has an invalid balance');
|
||||
if (this._validAccounts[result.Account] === undefined) {
|
||||
assert(UInt160.is_valid(result.account), 'node has an invalid account');
|
||||
assert(isValidAddress(result.account), 'node has an invalid account');
|
||||
this._validAccounts[result.Account] = true;
|
||||
this._validAccountsCount++;
|
||||
}
|
||||
@@ -819,12 +817,12 @@ OrderBook.prototype.isBalanceChangeNode = function(node) {
|
||||
}
|
||||
|
||||
// Check if taker gets currency is native and balance is not a number
|
||||
if (this._currencyGets.is_native()) {
|
||||
if (this._currencyGets === 'XRP') {
|
||||
return !isNaN(node.fields.Balance);
|
||||
}
|
||||
|
||||
// Check if balance change is not for taker gets currency
|
||||
if (node.fields.Balance.currency !== this._currencyGets.to_json()) {
|
||||
if (node.fields.Balance.currency !== this._currencyGets) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -875,7 +873,7 @@ OrderBook.prototype.onTransaction = function(transaction) {
|
||||
OrderBook.prototype.updateFundedAmounts = function(transaction) {
|
||||
const self = this;
|
||||
|
||||
if (!this._currencyGets.is_native() && !this._issuerTransferRate) {
|
||||
if (this._currencyGets !== 'XRP' && !this._issuerTransferRate) {
|
||||
if (this._remote.trace) {
|
||||
log.info('waiting for transfer rate');
|
||||
}
|
||||
@@ -895,7 +893,7 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) {
|
||||
|
||||
const affectedNodes = transaction.mmeta.getNodes({
|
||||
nodeType: 'ModifiedNode',
|
||||
entryType: this._currencyGets.is_native() ? 'AccountRoot' : 'RippleState'
|
||||
entryType: this._currencyGets === 'XRP' ? 'AccountRoot' : 'RippleState'
|
||||
});
|
||||
|
||||
_.each(affectedNodes, function(node) {
|
||||
@@ -920,8 +918,6 @@ OrderBook.prototype.updateFundedAmounts = function(transaction) {
|
||||
*/
|
||||
|
||||
OrderBook.prototype.updateOwnerOffersFundedAmount = function(account) {
|
||||
// assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
|
||||
const self = this;
|
||||
|
||||
if (!this.hasOwnerFunds(account)) {
|
||||
@@ -1008,15 +1004,14 @@ OrderBook.prototype.notify = function(transaction) {
|
||||
}
|
||||
|
||||
let takerGetsTotal = Amount.from_json(
|
||||
'0' + ((Currency.from_json(this._currencyGets).is_native())
|
||||
'0' + (this._currencyGets === 'XRP'
|
||||
? ''
|
||||
: ('/' + this._currencyGets.to_human() + '/' + this._issuerGets))
|
||||
: ('/' + this._currencyGets + '/' + this._issuerGets))
|
||||
);
|
||||
|
||||
let takerPaysTotal = Amount.from_json(
|
||||
'0' + ((Currency.from_json(this._currencyPays).is_native())
|
||||
? ''
|
||||
: ('/' + this._currencyPays.to_human() + '/' + this._issuerPays))
|
||||
'0' + (this._currencyPays === 'XRP' ? ''
|
||||
: ('/' + this._currencyPays + '/' + this._issuerPays))
|
||||
);
|
||||
|
||||
const isOfferCancel =
|
||||
@@ -1027,7 +1022,7 @@ OrderBook.prototype.notify = function(transaction) {
|
||||
switch (node.nodeType) {
|
||||
case 'DeletedNode':
|
||||
if (self._validAccounts[node.fields.Account] === undefined) {
|
||||
assert(UInt160.is_valid(node.fields.Account),
|
||||
assert(isValidAddress(node.fields.Account),
|
||||
'node has an invalid account');
|
||||
self._validAccounts[node.fields.Account] = true;
|
||||
self._validAccountsCount++;
|
||||
@@ -1043,7 +1038,7 @@ OrderBook.prototype.notify = function(transaction) {
|
||||
|
||||
case 'ModifiedNode':
|
||||
if (self._validAccounts[node.fields.Account] === undefined) {
|
||||
assert(UInt160.is_valid(node.fields.Account),
|
||||
assert(isValidAddress(node.fields.Account),
|
||||
'node has an invalid account');
|
||||
self._validAccounts[node.fields.Account] = true;
|
||||
self._validAccountsCount++;
|
||||
@@ -1061,7 +1056,7 @@ OrderBook.prototype.notify = function(transaction) {
|
||||
|
||||
case 'CreatedNode':
|
||||
if (self._validAccounts[node.fields.Account] === undefined) {
|
||||
assert(UInt160.is_valid(node.fields.Account),
|
||||
assert(isValidAddress(node.fields.Account),
|
||||
'node has an invalid account');
|
||||
self._validAccounts[node.fields.Account] = true;
|
||||
self._validAccountsCount++;
|
||||
@@ -1113,27 +1108,10 @@ OrderBook.prototype.insertOffer = function(node) {
|
||||
|
||||
const originalLength = this._offers.length;
|
||||
|
||||
if (!this._currencyGets.has_interest()) {
|
||||
// use fast path
|
||||
for (let i = 0; i < originalLength; i++) {
|
||||
if (offer.qualityHex <= this._offers[i].qualityHex) {
|
||||
this._offers.splice(i, 0, offer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < originalLength; i++) {
|
||||
const quality = OrderBookUtils.getOfferQuality(offer, this._currencyGets);
|
||||
const existingOfferQuality = OrderBookUtils.getOfferQuality(
|
||||
this._offers[i],
|
||||
this._currencyGets
|
||||
);
|
||||
|
||||
if (quality.compareTo(existingOfferQuality) <= 0) {
|
||||
this._offers.splice(i, 0, offer);
|
||||
|
||||
break;
|
||||
}
|
||||
for (let i = 0; i < originalLength; i++) {
|
||||
if (offer.qualityHex <= this._offers[i].qualityHex) {
|
||||
this._offers.splice(i, 0, offer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1159,10 +1137,7 @@ OrderBook.prototype.insertOffer = function(node) {
|
||||
*/
|
||||
|
||||
OrderBook.prototype.normalizeAmount = function(currency, amountObj) {
|
||||
const value = currency.is_native()
|
||||
? amountObj
|
||||
: amountObj.value;
|
||||
|
||||
const value = (currency === 'XRP') ? amountObj : amountObj.value;
|
||||
return OrderBookUtils.normalizeAmount(value);
|
||||
};
|
||||
|
||||
@@ -1249,7 +1224,7 @@ OrderBook.prototype.setOffers = function(offers) {
|
||||
offer = OrderBook.offerRewrite(offers[i]);
|
||||
|
||||
if (this._validAccounts[offer.Account] === undefined) {
|
||||
assert(UInt160.is_valid(offer.Account), 'Account is invalid');
|
||||
assert(isValidAddress(offer.Account), 'Account is invalid');
|
||||
this._validAccounts[offer.Account] = true;
|
||||
this._validAccountsCount++;
|
||||
}
|
||||
@@ -1321,18 +1296,18 @@ OrderBook.prototype.toJSON =
|
||||
OrderBook.prototype.to_json = function() {
|
||||
const json = {
|
||||
taker_gets: {
|
||||
currency: this._currencyGets.to_hex()
|
||||
currency: this._currencyGets
|
||||
},
|
||||
taker_pays: {
|
||||
currency: this._currencyPays.to_hex()
|
||||
currency: this._currencyPays
|
||||
}
|
||||
};
|
||||
|
||||
if (!this._currencyGets.is_native()) {
|
||||
if (this._currencyGets !== 'XRP') {
|
||||
json.taker_gets.issuer = this._issuerGets;
|
||||
}
|
||||
|
||||
if (!this._currencyPays.is_native()) {
|
||||
if (this._currencyPays !== 'XRP') {
|
||||
json.taker_pays.issuer = this._issuerPays;
|
||||
}
|
||||
|
||||
@@ -1352,11 +1327,11 @@ OrderBook.prototype.isValid =
|
||||
OrderBook.prototype.is_valid = function() {
|
||||
// XXX Should check for same currency (non-native) && same issuer
|
||||
return (
|
||||
this._currencyPays && this._currencyPays.is_valid() &&
|
||||
(this._currencyPays.is_native() || UInt160.is_valid(this._issuerPays)) &&
|
||||
this._currencyGets && this._currencyGets.is_valid() &&
|
||||
(this._currencyGets.is_native() || UInt160.is_valid(this._issuerGets)) &&
|
||||
!(this._currencyPays.is_native() && this._currencyGets.is_native())
|
||||
this._currencyPays && isValidCurrency(this._currencyPays) &&
|
||||
(this._currencyPays === 'XRP' || isValidAddress(this._issuerPays)) &&
|
||||
this._currencyGets && isValidCurrency(this._currencyGets) &&
|
||||
(this._currencyGets === 'XRP' || isValidAddress(this._issuerGets)) &&
|
||||
!(this._currencyPays === 'XRP' && this._currencyGets === 'XRP')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1367,7 +1342,7 @@ OrderBook.prototype.is_valid = function() {
|
||||
|
||||
OrderBook.prototype.computeAutobridgedOffers = function(callback = function() {}
|
||||
) {
|
||||
assert(!this._currencyGets.is_native() && !this._currencyPays.is_native(),
|
||||
assert(this._currencyGets !== 'XRP' && this._currencyPays !== 'XRP',
|
||||
'Autobridging is only for IOU:IOU orderbooks');
|
||||
|
||||
|
||||
@@ -1392,7 +1367,7 @@ OrderBook.prototype.computeAutobridgedOffers = function(callback = function() {}
|
||||
|
||||
OrderBook.prototype.computeAutobridgedOffersWrapper = function() {
|
||||
if (!this._gotOffersFromLegOne || !this._gotOffersFromLegTwo ||
|
||||
!this._synchronized || this._destroyed || this._calculatorRunning
|
||||
!this._synced || this._destroyed || this._calculatorRunning
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const Types = require('./serializedtypes');
|
||||
const constants = require('./constants');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const {IOUValue} = require('ripple-lib-value');
|
||||
const binary = require('ripple-binary-codec');
|
||||
const OrderBookUtils = {};
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
@@ -22,14 +20,9 @@ function assertValidNumber(number, message) {
|
||||
* @return JSON amount object
|
||||
*/
|
||||
|
||||
function createAmount(value, currency_, counterparty_) {
|
||||
|
||||
const currency = currency_ instanceof Currency ?
|
||||
currency_ :
|
||||
Currency.from_json(currency_);
|
||||
|
||||
const counterparty = counterparty_ instanceof UInt160 ?
|
||||
counterparty_.to_json() : counterparty_;
|
||||
function createAmount(value, currency, counterparty) {
|
||||
assert(_.isString(counterparty), 'counterparty must be a string');
|
||||
assert(_.isString(currency), 'currency must be a string');
|
||||
|
||||
return Amount.from_components_unsafe(new IOUValue(value),
|
||||
currency, counterparty, false);
|
||||
@@ -111,27 +104,10 @@ OrderBookUtils.getOfferTakerGets = function(offer, currency_, issuer_) {
|
||||
* @param {Currency} currencyGets
|
||||
*/
|
||||
|
||||
OrderBookUtils.getOfferQuality = function(offer, currencyGets, currency_,
|
||||
issuer_
|
||||
) {
|
||||
let amount;
|
||||
|
||||
if (currencyGets.has_interest()) {
|
||||
// XXX Should use Amount#from_quality
|
||||
amount = Amount.from_json(
|
||||
offer.TakerPays
|
||||
).ratio_human(offer.TakerGets, {
|
||||
reference_date: new Date()
|
||||
});
|
||||
} else {
|
||||
|
||||
const currency = currency_ || getCurrencyFromOffer(offer);
|
||||
const issuer = issuer_ || getIssuerFromOffer(offer);
|
||||
|
||||
amount = createAmount(offer.quality, currency, issuer);
|
||||
}
|
||||
|
||||
return amount;
|
||||
OrderBookUtils.getOfferQuality = function(offer, currency_, issuer_) {
|
||||
const currency = currency_ || getCurrencyFromOffer(offer);
|
||||
const issuer = issuer_ || getIssuerFromOffer(offer);
|
||||
return createAmount(offer.quality, currency, issuer);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -145,11 +121,7 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets, currency_,
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
assert(quality instanceof Amount, 'Quality is not an amount');
|
||||
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, quality.to_text());
|
||||
|
||||
return so.to_hex();
|
||||
return OrderBookUtils.convertOfferQualityToHex(quality.to_text());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -162,17 +134,12 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) {
|
||||
*/
|
||||
|
||||
OrderBookUtils.convertOfferQualityToHexFromText = function(quality) {
|
||||
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, quality);
|
||||
|
||||
return so.to_hex();
|
||||
return binary.encodeQuality(quality);
|
||||
};
|
||||
|
||||
OrderBookUtils.CURRENCY_ONE = constants.CURRENCY_ONE;
|
||||
|
||||
OrderBookUtils.CURRENCY_ONE = Currency.from_json(1);
|
||||
|
||||
OrderBookUtils.ISSUER_ONE = UInt160.from_json(1);
|
||||
OrderBookUtils.ISSUER_ONE = constants.ACCOUNT_ONE;
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -18,23 +18,22 @@ const assert = require('assert');
|
||||
const _ = require('lodash');
|
||||
const LRU = require('lru-cache');
|
||||
const async = require('async');
|
||||
const constants = require('./constants');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const Server = require('./server').Server;
|
||||
const Request = require('./request').Request;
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const UInt256 = require('./uint256').UInt256;
|
||||
const {normalizeCurrency} = require('./currency');
|
||||
const Transaction = require('./transaction').Transaction;
|
||||
const Account = require('./account').Account;
|
||||
const Meta = require('./meta').Meta;
|
||||
const OrderBook = require('./orderbook').OrderBook;
|
||||
const PathFind = require('./pathfind').PathFind;
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
const utils = require('./utils');
|
||||
const hashprefixes = require('./hashprefixes');
|
||||
const log = require('./log').internal.sub('remote');
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
const binary = require('ripple-binary-codec');
|
||||
|
||||
export type GetLedgerSequenceCallback = (err?: ?Error, index?: number) => void;
|
||||
|
||||
@@ -138,6 +137,9 @@ function Remote(options = {}) {
|
||||
if (typeof this.submission_timeout !== 'number') {
|
||||
throw new TypeError('submission_timeout must be a number');
|
||||
}
|
||||
if (typeof this.pathfind_timeout !== 'number') {
|
||||
throw new TypeError('pathfind_timeout must be a number');
|
||||
}
|
||||
if (typeof this.automatic_resubmission !== 'boolean') {
|
||||
throw new TypeError('automatic_resubmission must be a boolean');
|
||||
}
|
||||
@@ -197,6 +199,7 @@ Remote.DEFAULTS = {
|
||||
max_fee: 1000000, // 1 XRP
|
||||
max_attempts: 10,
|
||||
submission_timeout: 1000 * 20,
|
||||
pathfind_timeout: 1000 * 10,
|
||||
automatic_resubmission: true,
|
||||
last_ledger_offset: 3,
|
||||
servers: [ ],
|
||||
@@ -1188,9 +1191,13 @@ Remote.prototype.requestTransaction = function(options, callback) {
|
||||
* @throws {Error} if a marker is provided, but no ledger_index or ledger_hash
|
||||
*/
|
||||
|
||||
function isValidLedgerHash(hash) {
|
||||
return /^[A-F0-9]{64}$/.test(hash);
|
||||
}
|
||||
|
||||
Remote.prototype._accountRequest = function(command, options, callback) {
|
||||
if (options.marker) {
|
||||
if (!(Number(options.ledger) > 0) && !UInt256.is_valid(options.ledger)) {
|
||||
if (!(Number(options.ledger) > 0) && !isValidLedgerHash(options.ledger)) {
|
||||
throw new Error(
|
||||
'A ledger_index or ledger_hash must be provided when using a marker');
|
||||
}
|
||||
@@ -1198,11 +1205,11 @@ Remote.prototype._accountRequest = function(command, options, callback) {
|
||||
|
||||
const request = new Request(this, command);
|
||||
|
||||
request.message.account = UInt160.json_rewrite(options.account);
|
||||
request.message.account = options.account;
|
||||
request.selectLedger(options.ledger);
|
||||
|
||||
if (UInt160.is_valid(options.peer)) {
|
||||
request.message.peer = UInt160.json_rewrite(options.peer);
|
||||
if (isValidAddress(options.peer)) {
|
||||
request.message.peer = options.peer;
|
||||
}
|
||||
|
||||
if (!isNaN(options.limit)) {
|
||||
@@ -1404,27 +1411,26 @@ Remote.prototype.requestAccountTx = function(options, callback) {
|
||||
*/
|
||||
|
||||
Remote.parseBinaryAccountTransaction = function(transaction) {
|
||||
const tx_obj = new SerializedObject(transaction.tx_blob);
|
||||
const tx_obj_json = tx_obj.to_json();
|
||||
const meta = new SerializedObject(transaction.meta).to_json();
|
||||
const tx_json = binary.decode(transaction.tx_blob);
|
||||
const meta = binary.decode(transaction.meta);
|
||||
|
||||
const 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 = tx_json;
|
||||
tx_result.tx.hash = Transaction.from_json(tx_json).hash();
|
||||
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) {
|
||||
switch (typeof tx_json.Amount) {
|
||||
case 'string':
|
||||
case 'object':
|
||||
tx_result.meta.delivered_amount = tx_obj_json.Amount;
|
||||
tx_result.meta.delivered_amount = tx_json.Amount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1433,10 +1439,10 @@ Remote.parseBinaryAccountTransaction = function(transaction) {
|
||||
};
|
||||
|
||||
Remote.parseBinaryTransaction = function(transaction) {
|
||||
const tx_obj = new SerializedObject(transaction.tx).to_json();
|
||||
const meta = new SerializedObject(transaction.meta).to_json();
|
||||
const tx_json = binary.decode(transaction.tx);
|
||||
const meta = binary.decode(transaction.meta);
|
||||
|
||||
const tx_result = tx_obj;
|
||||
const tx_result = tx_json;
|
||||
|
||||
tx_result.date = transaction.date;
|
||||
tx_result.hash = transaction.hash;
|
||||
@@ -1451,10 +1457,10 @@ Remote.parseBinaryTransaction = function(transaction) {
|
||||
tx_result.meta.delivered_amount = meta.DeliveredAmount;
|
||||
break;
|
||||
default:
|
||||
switch (typeof tx_obj.Amount) {
|
||||
switch (typeof tx_json.Amount) {
|
||||
case 'string':
|
||||
case 'object':
|
||||
tx_result.meta.delivered_amount = tx_obj.Amount;
|
||||
tx_result.meta.delivered_amount = tx_json.Amount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1473,7 +1479,7 @@ Remote.parseBinaryTransaction = function(transaction) {
|
||||
*/
|
||||
|
||||
Remote.parseBinaryLedgerData = function(ledgerData) {
|
||||
const data = new SerializedObject(ledgerData.data).to_json();
|
||||
const data = binary.decode(ledgerData.data);
|
||||
data.index = ledgerData.index;
|
||||
return data;
|
||||
};
|
||||
@@ -1526,22 +1532,22 @@ Remote.prototype.requestBookOffers = function(options, callback) {
|
||||
const request = new Request(this, 'book_offers');
|
||||
|
||||
request.message.taker_gets = {
|
||||
currency: Currency.json_rewrite(taker_gets.currency, {force_hex: true})
|
||||
currency: taker_gets.currency
|
||||
};
|
||||
|
||||
if (!Currency.from_json(request.message.taker_gets.currency).is_native()) {
|
||||
request.message.taker_gets.issuer = UInt160.json_rewrite(taker_gets.issuer);
|
||||
if (normalizeCurrency(request.message.taker_gets.currency) !== 'XRP') {
|
||||
request.message.taker_gets.issuer = taker_gets.issuer;
|
||||
}
|
||||
|
||||
request.message.taker_pays = {
|
||||
currency: Currency.json_rewrite(taker_pays.currency, {force_hex: true})
|
||||
currency: taker_pays.currency
|
||||
};
|
||||
|
||||
if (!Currency.from_json(request.message.taker_pays.currency).is_native()) {
|
||||
request.message.taker_pays.issuer = UInt160.json_rewrite(taker_pays.issuer);
|
||||
if (normalizeCurrency(request.message.taker_pays.currency) !== 'XRP') {
|
||||
request.message.taker_pays.issuer = taker_pays.issuer;
|
||||
}
|
||||
|
||||
request.message.taker = taker ? taker : UInt160.ACCOUNT_ONE;
|
||||
request.message.taker = taker ? taker : constants.ACCOUNT_ONE;
|
||||
request.selectLedger(ledger);
|
||||
|
||||
if (!isNaN(limit)) {
|
||||
@@ -1775,7 +1781,7 @@ Remote.prototype.requestOwnerCount = function(options, callback) {
|
||||
*/
|
||||
|
||||
Remote.prototype.getAccount = function(accountID) {
|
||||
return this._accounts[UInt160.json_rewrite(accountID)];
|
||||
return this._accounts[accountID];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1809,6 +1815,19 @@ Remote.prototype.findAccount = function(accountID) {
|
||||
return account ? account : this.addAccount(accountID);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes current pathfind, if there is one.
|
||||
* After that new pathfind can be created, without adding to queue.
|
||||
*
|
||||
* @return {void} -
|
||||
*/
|
||||
Remote.prototype.closeCurrentPathFind = function() {
|
||||
if (this._cur_path_find !== null) {
|
||||
this._cur_path_find.close();
|
||||
this._cur_path_find = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a pathfind
|
||||
*
|
||||
@@ -1818,6 +1837,10 @@ Remote.prototype.findAccount = function(accountID) {
|
||||
*/
|
||||
Remote.prototype.createPathFind = function(options, callback) {
|
||||
if (this._cur_path_find !== null) {
|
||||
if (callback === undefined) {
|
||||
throw new Error('Only one streaming pathfind ' +
|
||||
'request at a time is supported');
|
||||
}
|
||||
this._queued_path_finds.push({options, callback});
|
||||
return null;
|
||||
}
|
||||
@@ -1831,8 +1854,15 @@ Remote.prototype.createPathFind = function(options, callback) {
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
const updateTimeout = setTimeout(() => {
|
||||
callback(new RippleError('tejTimeout'));
|
||||
pathFind.close();
|
||||
this._cur_path_find = null;
|
||||
}, this.pathfind_timeout);
|
||||
|
||||
pathFind.on('update', (data) => {
|
||||
if (data.full_reply && !data.closed) {
|
||||
clearTimeout(updateTimeout);
|
||||
this._cur_path_find = null;
|
||||
callback(null, data);
|
||||
// "A client can only have one pathfinding request open at a time.
|
||||
@@ -1848,7 +1878,10 @@ Remote.prototype.createPathFind = function(options, callback) {
|
||||
}
|
||||
}
|
||||
});
|
||||
pathFind.on('error', callback);
|
||||
pathFind.on('error', (error) => {
|
||||
this._cur_path_find = null;
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
|
||||
this._cur_path_find = pathFind;
|
||||
@@ -1857,7 +1890,7 @@ Remote.prototype.createPathFind = function(options, callback) {
|
||||
};
|
||||
|
||||
Remote.prepareTrade = function(currency, issuer) {
|
||||
const suffix = Currency.from_json(currency).is_native() ? '' : ('/' + issuer);
|
||||
const suffix = normalizeCurrency(currency) === 'XRP' ? '' : ('/' + issuer);
|
||||
return currency + suffix;
|
||||
};
|
||||
|
||||
@@ -1899,8 +1932,7 @@ Remote.prototype.book = Remote.prototype.createOrderBook = function(options) {
|
||||
*/
|
||||
|
||||
Remote.prototype.accountSeq =
|
||||
Remote.prototype.getAccountSequence = function(account_, advance) {
|
||||
const account = UInt160.json_rewrite(account_);
|
||||
Remote.prototype.getAccountSequence = function(account, advance) {
|
||||
const accountInfo = this.accounts[account];
|
||||
|
||||
if (!accountInfo) {
|
||||
@@ -1923,9 +1955,7 @@ Remote.prototype.getAccountSequence = function(account_, advance) {
|
||||
*/
|
||||
|
||||
Remote.prototype.setAccountSequence =
|
||||
Remote.prototype.setAccountSeq = function(account_, sequence) {
|
||||
const account = UInt160.json_rewrite(account_);
|
||||
|
||||
Remote.prototype.setAccountSeq = function(account, sequence) {
|
||||
if (!this.accounts.hasOwnProperty(account)) {
|
||||
this.accounts[account] = { };
|
||||
}
|
||||
@@ -1991,8 +2021,7 @@ Remote.prototype.accountSeqCache = function(options, callback) {
|
||||
* @param {String} account
|
||||
*/
|
||||
|
||||
Remote.prototype.dirtyAccountRoot = function(account_) {
|
||||
const account = UInt160.json_rewrite(account_);
|
||||
Remote.prototype.dirtyAccountRoot = function(account) {
|
||||
delete this.ledgers.current.account_root[account];
|
||||
};
|
||||
|
||||
@@ -2065,8 +2094,7 @@ Remote.prototype.requestRippleBalance = function(options, callback) {
|
||||
|
||||
// accountHigh implies for account: balance is negated. highLimit is the
|
||||
// limit set by account.
|
||||
const accountHigh = UInt160.from_json(options.account)
|
||||
.equals(highLimit.issuer());
|
||||
const accountHigh = (options.account === highLimit.issuer());
|
||||
|
||||
request.emit('ripple_state', {
|
||||
account_balance: (accountHigh
|
||||
@@ -2107,12 +2135,11 @@ Remote.prepareCurrencies = function(currency) {
|
||||
const newCurrency = { };
|
||||
|
||||
if (currency.hasOwnProperty('issuer')) {
|
||||
newCurrency.issuer = UInt160.json_rewrite(currency.issuer);
|
||||
newCurrency.issuer = currency.issuer;
|
||||
}
|
||||
|
||||
if (currency.hasOwnProperty('currency')) {
|
||||
newCurrency.currency =
|
||||
Currency.json_rewrite(currency.currency, {force_hex: true});
|
||||
newCurrency.currency = currency.currency;
|
||||
}
|
||||
|
||||
return newCurrency;
|
||||
@@ -2128,12 +2155,8 @@ Remote.prepareCurrencies = function(currency) {
|
||||
|
||||
Remote.prototype.requestRipplePathFind = function(options, callback) {
|
||||
const request = new Request(this, 'ripple_path_find');
|
||||
|
||||
request.message.source_account = UInt160.json_rewrite(options.source_account);
|
||||
|
||||
request.message.destination_account =
|
||||
UInt160.json_rewrite(options.destination_account);
|
||||
|
||||
request.message.source_account = options.source_account;
|
||||
request.message.destination_account = options.destination_account;
|
||||
request.message.destination_amount =
|
||||
Amount.json_rewrite(options.destination_amount);
|
||||
|
||||
@@ -2159,11 +2182,8 @@ Remote.prototype.requestPathFindCreate = function(options, callback) {
|
||||
const request = new Request(this, 'path_find');
|
||||
request.message.subcommand = 'create';
|
||||
|
||||
request.message.source_account = UInt160.json_rewrite(options.source_account);
|
||||
|
||||
request.message.destination_account =
|
||||
UInt160.json_rewrite(options.destination_account);
|
||||
|
||||
request.message.source_account = options.source_account;
|
||||
request.message.destination_account = options.destination_account;
|
||||
request.message.destination_amount =
|
||||
Amount.json_rewrite(options.destination_amount);
|
||||
|
||||
@@ -2286,7 +2306,7 @@ Remote.prototype.requestGatewayBalances = function(options, callback) {
|
||||
|
||||
const request = new Request(this, 'gateway_balances');
|
||||
|
||||
request.message.account = UInt160.json_rewrite(options.account);
|
||||
request.message.account = options.account;
|
||||
|
||||
if (!_.isUndefined(options.hotwallet)) {
|
||||
request.message.hotwallet = options.hotwallet;
|
||||
|
||||
@@ -4,8 +4,7 @@ const _ = require('lodash');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const util = require('util');
|
||||
const async = require('async');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Currency = require('./currency').Currency;
|
||||
const {normalizeCurrency} = require('./currency');
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
|
||||
// Request events emitted:
|
||||
@@ -80,17 +79,23 @@ Request.prototype.request = function(servers, callback_) {
|
||||
// just in case
|
||||
this.emit = _.noop;
|
||||
this.cancel();
|
||||
this.remote.removeListener('connected', doRequest);
|
||||
}, this._timeout);
|
||||
|
||||
function onResponse() {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
if (this.remote.isConnected()) {
|
||||
this.remote.on('connected', doRequest);
|
||||
}
|
||||
|
||||
this.once('response', onResponse);
|
||||
function onRemoteError(error) {
|
||||
self.emit('error', error);
|
||||
}
|
||||
this.remote.once('error', onRemoteError); // e.g. rate-limiting slowDown error
|
||||
|
||||
this.once('response', () => {
|
||||
clearTimeout(timeout);
|
||||
this.remote.removeListener('connected', doRequest);
|
||||
this.remote.removeListener('error', onRemoteError);
|
||||
});
|
||||
|
||||
doRequest();
|
||||
|
||||
@@ -127,6 +132,7 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.on('error', function() {});
|
||||
let lastResponse = new Error('No servers available');
|
||||
const connectTimeouts = { };
|
||||
const emit = this.emit;
|
||||
@@ -143,23 +149,44 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
|
||||
}
|
||||
};
|
||||
|
||||
let serversCallbacks = { };
|
||||
let serversTimeouts = { };
|
||||
let serversClearConnectHandlers = { };
|
||||
|
||||
function iterator(server, callback) {
|
||||
// Iterator is called in parallel
|
||||
|
||||
if (server.isConnected()) {
|
||||
// Listen for proxied success/error event and apply filter
|
||||
self.once('proposed', function(res) {
|
||||
lastResponse = res;
|
||||
callback(isResponseSuccess(res));
|
||||
});
|
||||
const serverID = server.getServerID();
|
||||
|
||||
serversCallbacks[serverID] = callback;
|
||||
|
||||
function doRequest() {
|
||||
return server._request(self);
|
||||
}
|
||||
|
||||
if (server.isConnected()) {
|
||||
const timeout = setTimeout(() => {
|
||||
lastResponse = new RippleError('tejTimeout',
|
||||
JSON.stringify(self.message));
|
||||
|
||||
server.removeListener('connect', doRequest);
|
||||
delete serversCallbacks[serverID];
|
||||
delete serversClearConnectHandlers[serverID];
|
||||
|
||||
callback(false);
|
||||
}, self._timeout);
|
||||
|
||||
serversTimeouts[serverID] = timeout;
|
||||
serversClearConnectHandlers[serverID] = function() {
|
||||
server.removeListener('connect', doRequest);
|
||||
};
|
||||
|
||||
server.on('connect', doRequest);
|
||||
return doRequest();
|
||||
}
|
||||
|
||||
// Server is disconnected but should reconnect. Wait for it to reconnect,
|
||||
// and abort after a timeout
|
||||
const serverID = server.getServerID();
|
||||
|
||||
function serverReconnected() {
|
||||
clearTimeout(connectTimeouts[serverID]);
|
||||
connectTimeouts[serverID] = null;
|
||||
@@ -174,13 +201,59 @@ Request.prototype.broadcast = function(isResponseSuccess = isResponseNotError) {
|
||||
server.once('connect', serverReconnected);
|
||||
}
|
||||
|
||||
// Listen for proxied success/error event and apply filter
|
||||
function onProposed(result, server) {
|
||||
const serverID = server.getServerID();
|
||||
lastResponse = result;
|
||||
|
||||
const callback = serversCallbacks[serverID];
|
||||
delete serversCallbacks[serverID];
|
||||
|
||||
clearTimeout(serversTimeouts[serverID]);
|
||||
delete serversTimeouts[serverID];
|
||||
|
||||
if (serversClearConnectHandlers[serverID] !== undefined) {
|
||||
serversClearConnectHandlers[serverID]();
|
||||
delete serversClearConnectHandlers[serverID];
|
||||
}
|
||||
|
||||
if (callback !== undefined) {
|
||||
callback(isResponseSuccess(result));
|
||||
}
|
||||
}
|
||||
|
||||
this.on('proposed', onProposed);
|
||||
|
||||
let complete_ = null;
|
||||
|
||||
// e.g. rate-limiting slowDown error
|
||||
function onRemoteError(error) {
|
||||
serversCallbacks = {};
|
||||
_.forEach(serversTimeouts, clearTimeout);
|
||||
serversTimeouts = {};
|
||||
_.forEach(serversClearConnectHandlers, (handler) => {
|
||||
handler();
|
||||
});
|
||||
serversClearConnectHandlers = {};
|
||||
|
||||
lastResponse = error instanceof RippleError ? error :
|
||||
new RippleError(error);
|
||||
complete_(false);
|
||||
}
|
||||
|
||||
function complete(success) {
|
||||
self.removeListener('proposed', onProposed);
|
||||
self.remote.removeListener('error', onRemoteError);
|
||||
// Emit success if the filter is satisfied by any server
|
||||
// Emit error if the filter is not satisfied by any server
|
||||
// Include the last response
|
||||
emit.call(self, success ? 'success' : 'error', lastResponse);
|
||||
}
|
||||
|
||||
complete_ = complete;
|
||||
|
||||
this.remote.once('error', onRemoteError);
|
||||
|
||||
const servers = this.remote._servers.filter(function(server) {
|
||||
// Pre-filter servers that are disconnected and should not reconnect
|
||||
return (server.isConnected() || server._shouldConnect)
|
||||
@@ -242,7 +315,6 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
let called = false;
|
||||
|
||||
function requestError(error) {
|
||||
self.remote.removeListener('error', requestError);
|
||||
if (!called) {
|
||||
called = true;
|
||||
|
||||
@@ -255,14 +327,12 @@ Request.prototype.callback = function(callback, successEvent, errorEvent) {
|
||||
}
|
||||
|
||||
function requestSuccess(message) {
|
||||
self.remote.removeListener('error', requestError);
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback.call(self, null, message);
|
||||
}
|
||||
}
|
||||
|
||||
this.remote.once('error', requestError); // e.g. rate-limiting slowDown error
|
||||
this.once(this.successEvent, requestSuccess);
|
||||
this.once(this.errorEvent, requestError);
|
||||
|
||||
@@ -370,7 +440,7 @@ Request.prototype.selectLedger = function(ledger, defaultValue) {
|
||||
};
|
||||
|
||||
Request.prototype.accountRoot = function(account) {
|
||||
this.message.account_root = UInt160.json_rewrite(account);
|
||||
this.message.account_root = account;
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -384,7 +454,7 @@ Request.prototype.index = function(index) {
|
||||
// --> seq : sequence number of transaction creating offer (integer)
|
||||
Request.prototype.offerId = function(account, sequence) {
|
||||
this.message.offer = {
|
||||
account: UInt160.json_rewrite(account),
|
||||
account: account,
|
||||
seq: sequence
|
||||
};
|
||||
return this;
|
||||
@@ -422,8 +492,8 @@ Request.prototype.rippleState = function(account, issuer, currency) {
|
||||
this.message.ripple_state = {
|
||||
currency: currency,
|
||||
accounts: [
|
||||
UInt160.json_rewrite(account),
|
||||
UInt160.json_rewrite(issuer)
|
||||
account,
|
||||
issuer
|
||||
]
|
||||
};
|
||||
return this;
|
||||
@@ -435,7 +505,7 @@ Request.prototype.accounts = function(accountsIn, proposed) {
|
||||
|
||||
// Process accounts parameters
|
||||
const processedAccounts = accounts.map(function(account) {
|
||||
return UInt160.json_rewrite(account);
|
||||
return account;
|
||||
});
|
||||
|
||||
if (proposed) {
|
||||
@@ -453,7 +523,7 @@ Request.prototype.addAccount = function(account, proposed) {
|
||||
return this;
|
||||
}
|
||||
|
||||
const processedAccount = UInt160.json_rewrite(account);
|
||||
const processedAccount = account;
|
||||
const prop = proposed === true ? 'accounts_proposed' : 'accounts';
|
||||
this.message[prop] = (this.message[prop] || []).concat(processedAccount);
|
||||
|
||||
@@ -502,13 +572,11 @@ Request.prototype.addBook = function(book, snapshot) {
|
||||
}
|
||||
|
||||
const obj = json[side] = {
|
||||
currency: Currency.json_rewrite(book[side].currency, {
|
||||
force_hex: true
|
||||
})
|
||||
currency: normalizeCurrency(book[side].currency)
|
||||
};
|
||||
|
||||
if (!Currency.from_json(obj.currency).is_native()) {
|
||||
obj.issuer = UInt160.json_rewrite(book[side].issuer);
|
||||
if (obj.currency !== 'XRP') {
|
||||
obj.issuer = book[side].issuer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,369 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const BN = require('bn.js');
|
||||
const hashjs = require('hash.js');
|
||||
const sjclcodec = require('sjcl-codec');
|
||||
const binformat = require('./binformat');
|
||||
const stypes = require('./serializedtypes');
|
||||
const utils = require('./utils');
|
||||
const UInt256 = require('./uint256').UInt256;
|
||||
|
||||
const TRANSACTION_TYPES = { };
|
||||
|
||||
Object.keys(binformat.tx).forEach(function(key) {
|
||||
TRANSACTION_TYPES[binformat.tx[key][0]] = key;
|
||||
});
|
||||
|
||||
const LEDGER_ENTRY_TYPES = {};
|
||||
|
||||
Object.keys(binformat.ledger).forEach(function(key) {
|
||||
LEDGER_ENTRY_TYPES[binformat.ledger[key][0]] = key;
|
||||
});
|
||||
|
||||
const TRANSACTION_RESULTS = {};
|
||||
|
||||
Object.keys(binformat.ter).forEach(function(key) {
|
||||
TRANSACTION_RESULTS[binformat.ter[key]] = key;
|
||||
});
|
||||
|
||||
function fieldType(fieldName) {
|
||||
const fieldDef = binformat.fieldsInverseMap[fieldName];
|
||||
return binformat.types[fieldDef[0]];
|
||||
}
|
||||
|
||||
function SerializedObject(buf) {
|
||||
if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf))) {
|
||||
this.buffer = buf;
|
||||
} else if (typeof buf === 'string') {
|
||||
this.buffer = sjclcodec.bytes.fromBits(sjclcodec.hex.toBits(buf));
|
||||
} else if (!buf) {
|
||||
this.buffer = [];
|
||||
} else {
|
||||
throw new Error('Invalid buffer passed.');
|
||||
}
|
||||
this.pointer = 0;
|
||||
}
|
||||
|
||||
SerializedObject.from_json = function(obj) {
|
||||
const so = new SerializedObject();
|
||||
so.parse_json(obj);
|
||||
return so;
|
||||
};
|
||||
|
||||
SerializedObject.check_fields = function(typedef, obj) {
|
||||
const missingFields = [];
|
||||
const unknownFields = [];
|
||||
const fieldsMap = {};
|
||||
|
||||
// Get missing required fields
|
||||
typedef.forEach(function(field) {
|
||||
const fieldName = field[0];
|
||||
const isRequired = field[1] === binformat.REQUIRED;
|
||||
|
||||
if (isRequired && obj[fieldName] === undefined) {
|
||||
missingFields.push(fieldName);
|
||||
} else {
|
||||
fieldsMap[fieldName] = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Get fields that are not specified in format
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
if (!fieldsMap[key] && /^[A-Z]/.test(key)) {
|
||||
unknownFields.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
if (!(missingFields.length || unknownFields.length)) {
|
||||
// No missing or unknown fields
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMessage;
|
||||
|
||||
if (obj.TransactionType !== undefined) {
|
||||
errorMessage = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
} else if (obj.LedgerEntryType !== undefined) {
|
||||
errorMessage = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
} else {
|
||||
errorMessage = 'TransactionMetaData';
|
||||
}
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
errorMessage += ' is missing fields: ' + JSON.stringify(missingFields);
|
||||
}
|
||||
if (unknownFields.length > 0) {
|
||||
errorMessage += (missingFields.length ? ' and' : '')
|
||||
+ ' has unknown fields: ' + JSON.stringify(unknownFields);
|
||||
}
|
||||
|
||||
throw new Error(errorMessage);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.parse_json = function(obj_) {
|
||||
// Create a copy of the object so we don't modify it
|
||||
const obj = extend(true, {}, obj_);
|
||||
let typedef;
|
||||
|
||||
if (typeof obj.TransactionType === 'number') {
|
||||
obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType);
|
||||
if (!obj.TransactionType) {
|
||||
throw new Error('Transaction type ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj.LedgerEntryType === 'number') {
|
||||
obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType);
|
||||
|
||||
if (!obj.LedgerEntryType) {
|
||||
throw new Error('LedgerEntryType ID is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof obj.TransactionType === 'string') {
|
||||
typedef = binformat.tx[obj.TransactionType];
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('Transaction type is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.TransactionType = typedef.shift();
|
||||
} else if (typeof obj.LedgerEntryType === 'string') {
|
||||
typedef = binformat.ledger[obj.LedgerEntryType];
|
||||
|
||||
if (!Array.isArray(typedef)) {
|
||||
throw new Error('LedgerEntryType is invalid');
|
||||
}
|
||||
|
||||
typedef = typedef.slice();
|
||||
obj.LedgerEntryType = typedef.shift();
|
||||
|
||||
} else if (typeof obj.AffectedNodes === 'object') {
|
||||
typedef = binformat.metadata;
|
||||
} else {
|
||||
throw new Error('Object to be serialized must contain either' +
|
||||
' TransactionType, LedgerEntryType or AffectedNodes.');
|
||||
}
|
||||
|
||||
SerializedObject.check_fields(typedef, obj);
|
||||
this.serialize(typedef, obj);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.append = function(bytes_) {
|
||||
const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_;
|
||||
|
||||
// Make sure both buffer and bytes are Array. Either could be a Buffer.
|
||||
if (Array.isArray(this.buffer) && Array.isArray(bytes)) {
|
||||
// `this.buffer = this.buffer.concat(bytes)` can be unbearably slow for
|
||||
// large bytes length and acceptable bytes length is limited for
|
||||
// `Array.prototype.push.apply(this.buffer, bytes)` as every element in the
|
||||
// bytes array is pushed onto the stack, potentially causing a RangeError
|
||||
// exception. Both of these solutions are known to be problematic for
|
||||
// ledger 7501326. KISS instead
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
this.buffer.push(bytes[i]);
|
||||
}
|
||||
} else {
|
||||
this.buffer = this.buffer.concat(bytes);
|
||||
}
|
||||
|
||||
this.pointer += bytes.length;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.resetPointer = function() {
|
||||
this.pointer = 0;
|
||||
};
|
||||
|
||||
function readOrPeek(advance) {
|
||||
return function(bytes) {
|
||||
const start = this.pointer;
|
||||
const end = start + bytes;
|
||||
|
||||
if (end > this.buffer.length) {
|
||||
throw new Error('Buffer length exceeded');
|
||||
}
|
||||
|
||||
const result = this.buffer.slice(start, end);
|
||||
|
||||
if (advance) {
|
||||
this.pointer = end;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
SerializedObject.prototype.read = readOrPeek(true);
|
||||
|
||||
SerializedObject.prototype.peek = readOrPeek(false);
|
||||
|
||||
SerializedObject.prototype.to_bits = function() {
|
||||
return sjclcodec.bytes.toBits(this.buffer);
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_hex = function() {
|
||||
return sjclcodec.hex.fromBits(this.to_bits()).toUpperCase();
|
||||
};
|
||||
|
||||
SerializedObject.prototype.to_json = function() {
|
||||
const old_pointer = this.pointer;
|
||||
|
||||
this.resetPointer();
|
||||
|
||||
const output = { };
|
||||
|
||||
while (this.pointer < this.buffer.length) {
|
||||
const key_and_value = stypes.parse(this);
|
||||
const key = key_and_value[0];
|
||||
const value = key_and_value[1];
|
||||
output[key] = SerializedObject.jsonify_structure(value, key);
|
||||
}
|
||||
|
||||
this.pointer = old_pointer;
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.jsonify_structure = function(structure, fieldName) {
|
||||
let output;
|
||||
|
||||
switch (typeof structure) {
|
||||
case 'number':
|
||||
switch (fieldName) {
|
||||
case 'LedgerEntryType':
|
||||
output = LEDGER_ENTRY_TYPES[structure];
|
||||
break;
|
||||
case 'TransactionResult':
|
||||
output = TRANSACTION_RESULTS[structure];
|
||||
break;
|
||||
case 'TransactionType':
|
||||
output = TRANSACTION_TYPES[structure];
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
break;
|
||||
case 'object':
|
||||
if (structure === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof structure.to_json === 'function') {
|
||||
output = structure.to_json();
|
||||
} else if (structure instanceof BN) {
|
||||
// We assume that any BN is a UInt64 field
|
||||
assert.equal(fieldType(fieldName), 'Int64');
|
||||
output = utils.arrayToHex(structure.toArray('bn', 8));
|
||||
} else {
|
||||
// new Array or Object
|
||||
output = new structure.constructor();
|
||||
|
||||
const keys = Object.keys(structure);
|
||||
|
||||
for (let i = 0, l = keys.length; i < l; i++) {
|
||||
const key = keys[i];
|
||||
output[key] = SerializedObject.jsonify_structure(structure[key], key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
output = structure;
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
SerializedObject.prototype.serialize = function(typedef, obj) {
|
||||
// Serialize object without end marker
|
||||
stypes.Object.serialize(this, obj, true);
|
||||
|
||||
// ST: Old serialization
|
||||
/*
|
||||
// Ensure canonical order
|
||||
typedef = SerializedObject.sort_typedef(typedef);
|
||||
|
||||
// Serialize fields
|
||||
for (let i=0, l=typedef.length; i<l; i++) {
|
||||
this.serialize_field(typedef[i], obj);
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
SerializedObject.prototype.hash = function(prefix) {
|
||||
const sign_buffer = new SerializedObject();
|
||||
|
||||
// Add hashing prefix
|
||||
if (typeof prefix !== 'undefined') {
|
||||
stypes.Int32.serialize(sign_buffer, prefix);
|
||||
}
|
||||
|
||||
// Copy buffer to temporary buffer
|
||||
sign_buffer.append(this.buffer);
|
||||
const bytes = hashjs.sha512().update(sign_buffer.buffer).digest();
|
||||
|
||||
return UInt256.from_bytes(bytes.slice(0, 32));
|
||||
};
|
||||
|
||||
// DEPRECATED
|
||||
SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash;
|
||||
|
||||
SerializedObject.prototype.serialize_field = function(spec, obj) {
|
||||
const name = spec[0];
|
||||
const presence = spec[1];
|
||||
|
||||
if (typeof obj[name] !== 'undefined') {
|
||||
try {
|
||||
stypes.serialize(this, name, obj[name]);
|
||||
} catch (e) {
|
||||
// Add field name to message and rethrow
|
||||
e.message = 'Error serializing "' + name + '": ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
} else if (presence === binformat.REQUIRED) {
|
||||
throw new Error('Missing required field ' + name);
|
||||
}
|
||||
};
|
||||
|
||||
SerializedObject.get_field_header = function(type_id, field_id) {
|
||||
const buffer = [0];
|
||||
|
||||
if (type_id > 0xF) {
|
||||
buffer.push(type_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += (type_id & 0xF) << 4;
|
||||
}
|
||||
|
||||
if (field_id > 0xF) {
|
||||
buffer.push(field_id & 0xFF);
|
||||
} else {
|
||||
buffer[0] += field_id & 0xF;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
SerializedObject.sort_typedef = function(typedef) {
|
||||
assert(Array.isArray(typedef));
|
||||
|
||||
function sort_field_compare(a, b) {
|
||||
// Sort by type id first, then by field id
|
||||
return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2];
|
||||
}
|
||||
|
||||
return typedef.sort(sort_field_compare);
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_tx = function(id) {
|
||||
assert.strictEqual(typeof id, 'number');
|
||||
return TRANSACTION_TYPES[id];
|
||||
};
|
||||
|
||||
SerializedObject.lookup_type_le = function(id) {
|
||||
assert(typeof id === 'number');
|
||||
return LEDGER_ENTRY_TYPES[id];
|
||||
};
|
||||
|
||||
exports.SerializedObject = SerializedObject;
|
||||
@@ -1,935 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Type definitions for binary format.
|
||||
*
|
||||
* This file should not be included directly. Instead, find the format you're
|
||||
* trying to parse or serialize in binformat.js and pass that to
|
||||
* SerializedObject.parse() or SerializedObject.serialize().
|
||||
*/
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const BN = require('bn.js');
|
||||
const GlobalBigNumber = require('bignumber.js');
|
||||
const sjclcodec = require('sjcl-codec');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const binformat = require('./binformat');
|
||||
const utils = require('./utils');
|
||||
|
||||
const UInt128 = require('./uint128').UInt128;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const UInt256 = require('./uint256').UInt256;
|
||||
const Base = require('./base').Base;
|
||||
|
||||
const BigNumber = GlobalBigNumber.another({
|
||||
ROUNDING_MODE: GlobalBigNumber.ROUND_HALF_UP,
|
||||
DECIMAL_PLACES: 40
|
||||
});
|
||||
|
||||
function SerializedType(methods) {
|
||||
extend(this, methods);
|
||||
}
|
||||
|
||||
function isNumber(val) {
|
||||
return typeof val === 'number' && isFinite(val);
|
||||
}
|
||||
|
||||
function isString(val) {
|
||||
return typeof val === 'string';
|
||||
}
|
||||
|
||||
function isHexInt64String(val) {
|
||||
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
|
||||
}
|
||||
|
||||
function serializeBytes(so, byteData, noLength) {
|
||||
if (!noLength) {
|
||||
SerializedType.serialize_varint(so, byteData.length);
|
||||
}
|
||||
so.append(byteData);
|
||||
}
|
||||
|
||||
function serializeHex(so, hexData, noLength) {
|
||||
serializeBytes(so, utils.hexToArray(hexData), noLength);
|
||||
}
|
||||
|
||||
function convertHexToString(hexString) {
|
||||
const bits = sjclcodec.hex.toBits(hexString);
|
||||
return sjclcodec.utf8String.fromBits(bits);
|
||||
}
|
||||
|
||||
function sort_fields(keys) {
|
||||
function sort_field_compare(a, b) {
|
||||
const a_field_coordinates = binformat.fieldsInverseMap[a];
|
||||
const a_type_bits = a_field_coordinates[0];
|
||||
const a_field_bits = a_field_coordinates[1];
|
||||
const b_field_coordinates = binformat.fieldsInverseMap[b];
|
||||
const b_type_bits = b_field_coordinates[0];
|
||||
const b_field_bits = b_field_coordinates[1];
|
||||
|
||||
// Sort by type id first, then by field id
|
||||
return a_type_bits !== b_type_bits
|
||||
? a_type_bits - b_type_bits
|
||||
: a_field_bits - b_field_bits;
|
||||
}
|
||||
|
||||
return keys.sort(sort_field_compare);
|
||||
}
|
||||
|
||||
SerializedType.serialize_varint = function(so, val) {
|
||||
let value = val;
|
||||
if (value < 0) {
|
||||
throw new Error('Variable integers are unsigned.');
|
||||
}
|
||||
|
||||
if (value <= 192) {
|
||||
so.append([value]);
|
||||
} else if (value <= 12480) {
|
||||
value -= 193;
|
||||
so.append([193 + (value >>> 8), value & 0xff]);
|
||||
} else if (value <= 918744) {
|
||||
value -= 12481;
|
||||
so.append([241 + (value >>> 16), value >>> 8 & 0xff, value & 0xff]);
|
||||
} else {
|
||||
throw new Error('Variable integer overflow.');
|
||||
}
|
||||
};
|
||||
|
||||
SerializedType.prototype.parse_varint = function(so) {
|
||||
const b1 = so.read(1)[0];
|
||||
let b2;
|
||||
let b3;
|
||||
let result;
|
||||
|
||||
if (b1 > 254) {
|
||||
throw new Error('Invalid varint length indicator');
|
||||
}
|
||||
|
||||
if (b1 <= 192) {
|
||||
result = b1;
|
||||
} else if (b1 <= 240) {
|
||||
b2 = so.read(1)[0];
|
||||
result = 193 + (b1 - 193) * 256 + b2;
|
||||
} else if (b1 <= 254) {
|
||||
b2 = so.read(1)[0];
|
||||
b3 = so.read(1)[0];
|
||||
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// In the following, we assume that the inputs are in the proper range. Is this
|
||||
// correct?
|
||||
// Helper functions for 1-, 2-, and 4-byte integers.
|
||||
|
||||
/**
|
||||
* Convert an integer value into an array of bytes.
|
||||
*
|
||||
* The result is appended to the serialized object ('so').
|
||||
*
|
||||
* @param {Number} val value
|
||||
* @param {Number} bytes byte size
|
||||
* @return {Array} byte array
|
||||
*/
|
||||
function convertIntegerToByteArray(val, bytes) {
|
||||
if (!isNumber(val)) {
|
||||
throw new Error('Value is not a number', bytes);
|
||||
}
|
||||
|
||||
if (val < 0 || val >= Math.pow(256, bytes)) {
|
||||
throw new Error('Value out of bounds ');
|
||||
}
|
||||
|
||||
const newBytes = [ ];
|
||||
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
newBytes.unshift(val >>> (i * 8) & 0xff);
|
||||
}
|
||||
|
||||
return newBytes;
|
||||
}
|
||||
|
||||
// Convert a certain number of bytes from the serialized object ('so') into an
|
||||
// integer.
|
||||
function readAndSum(so, bytes) {
|
||||
let sum = 0;
|
||||
|
||||
if (bytes > 4) {
|
||||
throw new Error('This function only supports up to four bytes.');
|
||||
}
|
||||
|
||||
for (let i = 0; i < bytes; i++) {
|
||||
const byte = so.read(1)[0];
|
||||
sum += (byte << (8 * (bytes - i - 1)));
|
||||
}
|
||||
|
||||
// Convert to unsigned integer
|
||||
return sum >>> 0;
|
||||
}
|
||||
|
||||
const STInt8 = exports.Int8 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 1));
|
||||
},
|
||||
parse: function(so) {
|
||||
return readAndSum(so, 1);
|
||||
}
|
||||
});
|
||||
|
||||
STInt8.id = 16;
|
||||
|
||||
function serialize(so, field_name, value) {
|
||||
// so: a byte-stream to serialize into.
|
||||
// field_name: a string for the field name ('LedgerEntryType' etc.)
|
||||
// value: the value of that field.
|
||||
const field_coordinates = binformat.fieldsInverseMap[field_name];
|
||||
const type_bits = field_coordinates[0];
|
||||
const field_bits = field_coordinates[1];
|
||||
const tag_byte = (type_bits < 16
|
||||
? type_bits << 4
|
||||
: 0) | (field_bits < 16
|
||||
? field_bits
|
||||
: 0);
|
||||
let val = value;
|
||||
|
||||
if (field_name === 'LedgerEntryType' && typeof val === 'string') {
|
||||
val = binformat.ledger[val][0];
|
||||
}
|
||||
|
||||
if (field_name === 'TransactionResult' && typeof val === 'string') {
|
||||
val = binformat.ter[val];
|
||||
}
|
||||
|
||||
STInt8.serialize(so, tag_byte);
|
||||
|
||||
if (type_bits >= 16) {
|
||||
STInt8.serialize(so, type_bits);
|
||||
}
|
||||
|
||||
if (field_bits >= 16) {
|
||||
STInt8.serialize(so, field_bits);
|
||||
}
|
||||
|
||||
// Get the serializer class (ST...)
|
||||
let serialized_object_type;
|
||||
|
||||
if (field_name === 'Memo' && typeof val === 'object') {
|
||||
// for Memo we override the default behavior with our STMemo serializer
|
||||
serialized_object_type = exports.STMemo;
|
||||
} else {
|
||||
// for a field based on the type bits.
|
||||
serialized_object_type = exports[binformat.types[type_bits]];
|
||||
}
|
||||
|
||||
try {
|
||||
serialized_object_type.serialize(so, val);
|
||||
} catch (e) {
|
||||
e.message += ' (' + field_name + ')';
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
exports.serialize = exports.serialize_whatever = serialize;
|
||||
|
||||
// Take the serialized object, figure out what type/field it is, and return the
|
||||
// parsing of that.
|
||||
|
||||
function parse(so) {
|
||||
const tag_byte = so.read(1)[0];
|
||||
let type_bits = tag_byte >> 4;
|
||||
|
||||
if (type_bits === 0) {
|
||||
type_bits = so.read(1)[0];
|
||||
}
|
||||
|
||||
const field_bits = tag_byte & 0x0f;
|
||||
const field_name = (field_bits === 0)
|
||||
? binformat.fields[type_bits][so.read(1)[0]]
|
||||
: binformat.fields[type_bits][field_bits];
|
||||
|
||||
assert(field_name, 'Unknown field - header byte is 0x'
|
||||
+ tag_byte.toString(16));
|
||||
|
||||
// Get the parser class (ST...) for a field based on the type bits.
|
||||
const type = (field_name === 'Memo')
|
||||
? exports.STMemo
|
||||
: exports[binformat.types[type_bits]];
|
||||
|
||||
assert(type, 'Unknown type - header byte is 0x' + tag_byte.toString(16));
|
||||
|
||||
return [field_name, type.parse(so)]; // key, value
|
||||
}
|
||||
|
||||
exports.parse = exports.parse_whatever = parse;
|
||||
|
||||
const STInt16 = exports.Int16 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 2));
|
||||
},
|
||||
parse: function(so) {
|
||||
return readAndSum(so, 2);
|
||||
}
|
||||
});
|
||||
|
||||
STInt16.id = 1;
|
||||
|
||||
const STInt32 = exports.Int32 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
so.append(convertIntegerToByteArray(val, 4));
|
||||
},
|
||||
parse: function(so) {
|
||||
return readAndSum(so, 4);
|
||||
}
|
||||
});
|
||||
|
||||
STInt32.id = 2;
|
||||
|
||||
const STInt64 = exports.Int64 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
let bigNumObject;
|
||||
let value = val;
|
||||
|
||||
if (isNumber(value)) {
|
||||
value = Math.floor(value);
|
||||
if (value < 0) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = new BN(value, 10);
|
||||
} else if (isString(value)) {
|
||||
if (!isHexInt64String(value)) {
|
||||
throw new Error('Not a valid hex Int64.');
|
||||
}
|
||||
bigNumObject = new BN(value, 16);
|
||||
} else if (value instanceof BN) {
|
||||
if (value.cmpn(0) < 0) {
|
||||
throw new Error('Negative value for unsigned Int64 is invalid.');
|
||||
}
|
||||
bigNumObject = value;
|
||||
} else {
|
||||
throw new Error('Invalid type for Int64: ' + (typeof value) + ' value');
|
||||
}
|
||||
// `'be'` means big endian, and the following arg is the byte length, which
|
||||
// it will pad with 0s to if not enough bytes, or throw if over
|
||||
serializeBytes(so, bigNumObject.toArray('be', 8), /* noLength= */true);
|
||||
},
|
||||
parse: function(so) {
|
||||
const bytes = so.read(8);
|
||||
return new BN(bytes);
|
||||
}
|
||||
});
|
||||
|
||||
STInt64.id = 3;
|
||||
|
||||
const STHash128 = exports.Hash128 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const hash = UInt128.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash128');
|
||||
}
|
||||
serializeBytes(so, hash.to_bytes(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt128.from_bytes(so.read(16));
|
||||
}
|
||||
});
|
||||
|
||||
STHash128.id = 4;
|
||||
|
||||
const STHash256 = exports.Hash256 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const hash = UInt256.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash256');
|
||||
}
|
||||
serializeBytes(so, hash.to_bytes(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt256.from_bytes(so.read(32));
|
||||
}
|
||||
});
|
||||
|
||||
STHash256.id = 5;
|
||||
|
||||
const STHash160 = exports.Hash160 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const hash = UInt160.from_json(val);
|
||||
if (!hash.is_valid()) {
|
||||
throw new Error('Invalid Hash160');
|
||||
}
|
||||
serializeBytes(so, hash.to_bytes(), true); // noLength = true
|
||||
},
|
||||
parse: function(so) {
|
||||
return UInt160.from_bytes(so.read(20));
|
||||
}
|
||||
});
|
||||
|
||||
STHash160.id = 17;
|
||||
|
||||
// Internal
|
||||
const STCurrency = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const currencyData = val.to_bytes();
|
||||
|
||||
if (!currencyData) {
|
||||
throw new Error(
|
||||
'Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
|
||||
so.append(currencyData);
|
||||
},
|
||||
parse: function(so) {
|
||||
const bytes = so.read(20);
|
||||
const currency = Currency.from_bytes(bytes);
|
||||
// XXX Disabled check. Theoretically, the Currency class should support any
|
||||
// UInt160 value and consider it valid. But it doesn't, so for the
|
||||
// deserialization to be usable, we need to allow invalid results for
|
||||
// now.
|
||||
// if (!currency.is_valid()) {
|
||||
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
|
||||
// }
|
||||
return currency;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Quality is encoded into 64 bits:
|
||||
* (8 bits offset) (56 bits mantissa)
|
||||
*
|
||||
* Quality differs from Amount because it does not need the first two bits
|
||||
* to represent non-native and non-negative
|
||||
*/
|
||||
exports.Quality = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
let value;
|
||||
// if in format: amount/currency/issuer
|
||||
if (_.includes(val, '/')) {
|
||||
const amount = Amount.from_json(val);
|
||||
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
value = new BigNumber(amount.to_text());
|
||||
} else {
|
||||
value = new BigNumber(val);
|
||||
}
|
||||
|
||||
let hi = 0;
|
||||
let lo = 0;
|
||||
|
||||
const offset = value.e - 15;
|
||||
if (val !== 0) {
|
||||
// First eight bits: offset/exponent
|
||||
hi |= ((100 + offset) & 0xff) << 24;
|
||||
|
||||
// Remaining 56 bits: mantissa
|
||||
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0xffffff;
|
||||
lo = parseInt(mantissaHex.slice(-8), 16);
|
||||
}
|
||||
|
||||
const valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
|
||||
|
||||
so.append(valueBytes);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Amount is encoded into 64 bits:
|
||||
* (1 bit non-native) (1 bit non-negative) (8 bits offset) (54 bits mantissa)
|
||||
*/
|
||||
const STAmount = exports.Amount = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const amount = Amount.from_json(val);
|
||||
|
||||
if (!amount.is_valid()) {
|
||||
throw new Error('Not a valid Amount object.');
|
||||
}
|
||||
|
||||
const value = new BigNumber(amount.to_text());
|
||||
const offset = value.e - 15;
|
||||
|
||||
// Amount (64-bit integer)
|
||||
let valueBytes = utils.arraySet(8, 0);
|
||||
|
||||
if (amount.is_native()) {
|
||||
let valueHex = value.abs().toString(16);
|
||||
|
||||
if (Amount.strict_mode && value.abs().greaterThan(Amount.bi_xns_max)) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
// Enforce correct length (64 bits)
|
||||
if (Amount.strict_mode && valueHex.length > 16) {
|
||||
throw new Error('Value out of bounds');
|
||||
}
|
||||
|
||||
while (valueHex.length < 16) {
|
||||
valueHex = '0' + valueHex;
|
||||
}
|
||||
|
||||
valueBytes = sjclcodec.bytes.fromBits(sjclcodec.hex.toBits(valueHex));
|
||||
// Clear most significant two bits - these bits should already be 0 if
|
||||
// Amount enforces the range correctly, but we'll clear them anyway just
|
||||
// so this code can make certain guarantees about the encoded value.
|
||||
valueBytes[0] &= 0x3f;
|
||||
|
||||
if (!amount.is_negative()) {
|
||||
valueBytes[0] |= 0x40;
|
||||
}
|
||||
} else {
|
||||
let hi = 0;
|
||||
let lo = 0;
|
||||
|
||||
// First bit: non-native
|
||||
hi |= 1 << 31;
|
||||
|
||||
if (!amount.is_zero()) {
|
||||
// Second bit: non-negative?
|
||||
if (!amount.is_negative()) {
|
||||
hi |= 1 << 30;
|
||||
}
|
||||
|
||||
// Next eight bits: offset/exponent
|
||||
hi |= ((97 + offset) & 0xff) << 22;
|
||||
|
||||
// Remaining 54 bits: mantissa
|
||||
const mantissaDecimal = utils.getMantissaDecimalString(value.abs());
|
||||
const mantissaHex = (new BigNumber(mantissaDecimal)).toString(16);
|
||||
assert(mantissaHex.length <= 16,
|
||||
'Mantissa hex representation ' + mantissaHex +
|
||||
' exceeds the maximum length of 16');
|
||||
hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0x3fffff;
|
||||
lo = parseInt(mantissaHex.slice(-8), 16);
|
||||
}
|
||||
|
||||
valueBytes = sjclcodec.bytes.fromBits([hi, lo]);
|
||||
}
|
||||
|
||||
so.append(valueBytes);
|
||||
|
||||
if (!amount.is_native()) {
|
||||
// Currency (160-bit hash)
|
||||
const currency = amount.currency();
|
||||
STCurrency.serialize(so, currency, true);
|
||||
|
||||
// Issuer (160-bit hash)
|
||||
so.append(UInt160.from_json(amount.issuer()).to_bytes());
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
const value_bytes = so.read(8);
|
||||
let is_zero = !(value_bytes[0] & 0x7f);
|
||||
|
||||
for (let i = 1; i < 8; i++) {
|
||||
is_zero = is_zero && !value_bytes[i];
|
||||
}
|
||||
|
||||
const is_negative = !is_zero && !(value_bytes[0] & 0x40);
|
||||
|
||||
if (value_bytes[0] & 0x80) {
|
||||
// non-native
|
||||
const currency = STCurrency.parse(so);
|
||||
const issuer_bytes = so.read(20);
|
||||
const issuer = UInt160.from_bytes(issuer_bytes);
|
||||
issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
const offset =
|
||||
((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
|
||||
const mantissa_bytes = value_bytes.slice(1);
|
||||
mantissa_bytes[0] &= 0x3f;
|
||||
const mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16);
|
||||
const sign = is_negative ? '-' : '';
|
||||
const valueString = sign + mantissa.toString() + 'e' + offset.toString();
|
||||
|
||||
return Amount.from_json({
|
||||
currency: currency,
|
||||
issuer: issuer.to_json(),
|
||||
value: valueString
|
||||
});
|
||||
}
|
||||
|
||||
// native
|
||||
const integer_bytes = value_bytes.slice();
|
||||
integer_bytes[0] &= 0x3f;
|
||||
const integer_hex = utils.arrayToHex(integer_bytes);
|
||||
const value = new BigNumber(integer_hex, 16);
|
||||
return Amount.from_json((is_negative ? '-' : '') + value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
STAmount.id = 6;
|
||||
|
||||
const STVL = exports.VariableLength = exports.VL = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
if (typeof val === 'string') {
|
||||
serializeHex(so, val);
|
||||
} else {
|
||||
throw new Error('Unknown datatype.');
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
const len = this.parse_varint(so);
|
||||
return utils.arrayToHex(so.read(len));
|
||||
}
|
||||
});
|
||||
|
||||
STVL.id = 7;
|
||||
|
||||
const STAccount = exports.Account = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
const account = UInt160.from_json(val);
|
||||
if (!account.is_valid()) {
|
||||
throw new Error('Invalid account!');
|
||||
}
|
||||
serializeBytes(so, account.to_bytes());
|
||||
},
|
||||
parse: function(so) {
|
||||
const len = this.parse_varint(so);
|
||||
|
||||
if (len !== 20) {
|
||||
throw new Error('Non-standard-length account ID');
|
||||
}
|
||||
|
||||
const result = UInt160.from_bytes(so.read(len));
|
||||
result.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
if (false && !result.is_valid()) {
|
||||
throw new Error('Invalid Account');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
STAccount.id = 8;
|
||||
|
||||
const STPathSet = exports.PathSet = new SerializedType({
|
||||
typeBoundary: 0xff,
|
||||
typeEnd: 0x00,
|
||||
typeAccount: 0x01,
|
||||
typeCurrency: 0x10,
|
||||
typeIssuer: 0x20,
|
||||
serialize: function(so, val) {
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
// Boundary
|
||||
if (i) {
|
||||
STInt8.serialize(so, this.typeBoundary);
|
||||
}
|
||||
|
||||
for (let j = 0, l2 = val[i].length; j < l2; j++) {
|
||||
const entry = val[i][j];
|
||||
// if (entry.hasOwnProperty('_value')) {entry = entry._value;}
|
||||
let type = 0;
|
||||
|
||||
if (entry.account) {
|
||||
type |= this.typeAccount;
|
||||
}
|
||||
if (entry.currency) {
|
||||
type |= this.typeCurrency;
|
||||
}
|
||||
if (entry.issuer) {
|
||||
type |= this.typeIssuer;
|
||||
}
|
||||
|
||||
STInt8.serialize(so, type);
|
||||
|
||||
if (entry.account) {
|
||||
STHash160.serialize(so, entry.account);
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
const currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
if (entry.issuer) {
|
||||
STHash160.serialize(so, entry.issuer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
STInt8.serialize(so, this.typeEnd);
|
||||
},
|
||||
parse: function(so) {
|
||||
// should return a list of lists:
|
||||
/*
|
||||
[
|
||||
[entry, entry],
|
||||
[entry, entry, entry],
|
||||
[entry],
|
||||
[]
|
||||
]
|
||||
|
||||
each entry has one or more of the following attributes:
|
||||
amount, currency, issuer.
|
||||
*/
|
||||
|
||||
const path_list = [];
|
||||
let current_path = [];
|
||||
let tag_byte;
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
|
||||
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
|
||||
// TODO: try/catch this loop, and catch when we run out of data without
|
||||
// reaching the end of the data structure.
|
||||
// Now determine: is this an end, boundary, or entry-begin-tag?
|
||||
// console.log('Tag byte:', tag_byte);
|
||||
if (tag_byte === this.typeBoundary) {
|
||||
if (current_path) { // close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
current_path = [ ]; // and start a new one.
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's an entry-begin tag.
|
||||
const entry = {};
|
||||
let type = 0;
|
||||
|
||||
if (tag_byte & this.typeAccount) {
|
||||
entry.account = STHash160.parse(so);
|
||||
entry.account.set_version(Base.VER_ACCOUNT_ID);
|
||||
type = type | this.typeAccount;
|
||||
}
|
||||
if (tag_byte & this.typeCurrency) {
|
||||
entry.currency = STCurrency.parse(so);
|
||||
if (entry.currency.to_json() === 'XRP' && !entry.currency.is_native()) {
|
||||
entry.non_native = true;
|
||||
}
|
||||
type = type | this.typeCurrency;
|
||||
}
|
||||
if (tag_byte & this.typeIssuer) {
|
||||
entry.issuer = STHash160.parse(so);
|
||||
// Enable and set correct type of base-58 encoding
|
||||
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
type = type | this.typeIssuer;
|
||||
}
|
||||
|
||||
if (entry.account || entry.currency || entry.issuer) {
|
||||
entry.type = type;
|
||||
entry.type_hex = ('000000000000000' + type.toString(16)).slice(-16);
|
||||
current_path.push(entry);
|
||||
} else {
|
||||
// It must have at least something in it.
|
||||
throw new Error('Invalid path entry');
|
||||
}
|
||||
}
|
||||
|
||||
if (current_path) {
|
||||
// close the current path, if there is one,
|
||||
path_list.push(current_path);
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
});
|
||||
|
||||
STPathSet.id = 18;
|
||||
|
||||
const STVector256 = exports.Vector256 = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
// Assume val is an array of STHash256 objects.
|
||||
SerializedType.serialize_varint(so, val.length * 32);
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
STHash256.serialize(so, val[i]);
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
const length = this.parse_varint(so);
|
||||
const output = [];
|
||||
// length is number of bytes not number of Hash256
|
||||
for (let i = 0; i < length / 32; i++) {
|
||||
output.push(STHash256.parse(so));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STVector256.id = 19;
|
||||
|
||||
// Internal
|
||||
exports.STMemo = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
let keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
keys.forEach(function(key) {
|
||||
serialize(so, key, val[key]);
|
||||
});
|
||||
|
||||
if (!no_marker) {
|
||||
// Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
},
|
||||
parse: function(so) {
|
||||
const output = {};
|
||||
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
const keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
|
||||
if (output.MemoType !== undefined) {
|
||||
try {
|
||||
const parsedType = convertHexToString(output.MemoType);
|
||||
|
||||
if (parsedType !== 'unformatted_memo') {
|
||||
output.parsed_memo_type = parsedType;
|
||||
}
|
||||
/* eslint-disable no-empty */
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_type field
|
||||
}
|
||||
/* eslint-enable no-empty */
|
||||
}
|
||||
|
||||
if (output.MemoFormat !== undefined) {
|
||||
try {
|
||||
output.parsed_memo_format = convertHexToString(output.MemoFormat);
|
||||
/* eslint-disable no-empty */
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we don't know what's in the binary, apparently it's not a UTF-8
|
||||
// string
|
||||
// this is fine, we won't add the parsed_memo_format field
|
||||
}
|
||||
/* eslint-enable no-empty */
|
||||
}
|
||||
|
||||
if (output.MemoData !== undefined) {
|
||||
|
||||
try {
|
||||
if (output.parsed_memo_format === 'json') {
|
||||
// see if we can parse JSON
|
||||
output.parsed_memo_data =
|
||||
JSON.parse(convertHexToString(output.MemoData));
|
||||
|
||||
} else if (output.parsed_memo_format === 'text') {
|
||||
// otherwise see if we can parse text
|
||||
output.parsed_memo_data = convertHexToString(output.MemoData);
|
||||
}
|
||||
/* eslint-disable no-empty */
|
||||
} catch (e) {
|
||||
// empty
|
||||
// we'll fail in case the content does not match what the MemoFormat
|
||||
// described
|
||||
// this is fine, we won't add the parsed_memo_data, the user has to
|
||||
// parse themselves
|
||||
}
|
||||
/* eslint-enable no-empty */
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const STObject = exports.Object = new SerializedType({
|
||||
serialize: function(so, val, no_marker) {
|
||||
let keys = [];
|
||||
|
||||
Object.keys(val).forEach(function(key) {
|
||||
// Ignore lowercase field names - they're non-serializable fields by
|
||||
// convention.
|
||||
if (key[0] === key[0].toLowerCase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof binformat.fieldsInverseMap[key] === 'undefined') {
|
||||
throw new Error('JSON contains unknown field: "' + key + '"');
|
||||
}
|
||||
|
||||
keys.push(key);
|
||||
});
|
||||
|
||||
// Sort fields
|
||||
keys = sort_fields(keys);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
serialize(so, keys[i], val[keys[i]]);
|
||||
}
|
||||
|
||||
if (!no_marker) {
|
||||
// Object ending marker
|
||||
STInt8.serialize(so, 0xe1);
|
||||
}
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
const output = {};
|
||||
while (so.peek(1)[0] !== 0xe1) {
|
||||
const keyval = parse(so);
|
||||
output[keyval[0]] = keyval[1];
|
||||
}
|
||||
so.read(1);
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STObject.id = 14;
|
||||
|
||||
const STArray = exports.Array = new SerializedType({
|
||||
serialize: function(so, val) {
|
||||
for (let i = 0, l = val.length; i < l; i++) {
|
||||
const keys = Object.keys(val[i]);
|
||||
|
||||
if (keys.length !== 1) {
|
||||
throw new Error(
|
||||
'Cannot serialize an array containing non-single-key objects');
|
||||
}
|
||||
|
||||
const field_name = keys[0];
|
||||
const value = val[i][field_name];
|
||||
serialize(so, field_name, value);
|
||||
}
|
||||
|
||||
// Array ending marker
|
||||
STInt8.serialize(so, 0xf1);
|
||||
},
|
||||
|
||||
parse: function(so) {
|
||||
const output = [ ];
|
||||
|
||||
while (so.peek(1)[0] !== 0xf1) {
|
||||
const keyval = parse(so);
|
||||
const obj = { };
|
||||
obj[keyval[0]] = keyval[1];
|
||||
output.push(obj);
|
||||
}
|
||||
|
||||
so.read(1);
|
||||
|
||||
return output;
|
||||
}
|
||||
});
|
||||
|
||||
STArray.id = 15;
|
||||
@@ -482,11 +482,21 @@ Server.prototype.connect = function() {
|
||||
self.emit('message', message);
|
||||
};
|
||||
|
||||
function onRemoteError() {}
|
||||
|
||||
ws.onopen = function onOpen() {
|
||||
if (ws === self._ws) {
|
||||
self.emit('socket_open');
|
||||
|
||||
// e.g. rate-limiting slowDown error
|
||||
self._remote.once('error', onRemoteError);
|
||||
|
||||
// Subscribe to events
|
||||
self._request(self._remote._serverPrepareSubscribe(self));
|
||||
const request = self._remote._serverPrepareSubscribe(self);
|
||||
request.once('response', () => {
|
||||
self._remote.removeListener('error', onRemoteError);
|
||||
});
|
||||
self._request(request);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -676,7 +686,7 @@ Server.prototype._handleResponse = function(message) {
|
||||
const result = message.result;
|
||||
const responseEvent = 'response_' + command;
|
||||
|
||||
request.emit('success', result);
|
||||
request.emit('success', result, this);
|
||||
|
||||
[this, this._remote].forEach(function(emitter) {
|
||||
emitter.emit(responseEvent, result, request, message);
|
||||
@@ -690,9 +700,9 @@ Server.prototype._handleResponse = function(message) {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: message
|
||||
});
|
||||
}, this);
|
||||
}
|
||||
request.emit('response', message);
|
||||
request.emit('response', message, this);
|
||||
};
|
||||
|
||||
Server.prototype._handlePathFind = function(message) {
|
||||
@@ -800,7 +810,16 @@ Server.prototype._sendMessage = function(message) {
|
||||
if (this._remote.trace) {
|
||||
log.info(this.getServerID(), 'request:', message);
|
||||
}
|
||||
this._ws.send(JSON.stringify(message));
|
||||
this._ws.send(JSON.stringify(message), (error) => {
|
||||
// sometimes gives 'not opened'
|
||||
// without callback it wil throw
|
||||
if (error) {
|
||||
// resend in case of error
|
||||
this.once('connect', () => {
|
||||
this._sendMessage(message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var util = require('util');
|
||||
var hashprefixes = require('./hashprefixes');
|
||||
|
||||
var UInt256 = require('./uint256').UInt256;
|
||||
var SerializedObject = require('./serializedobject').SerializedObject;
|
||||
|
||||
/**
|
||||
* Abstract class representing a node in a SHAMap tree.
|
||||
*
|
||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNode() { }
|
||||
|
||||
SHAMapTreeNode.TYPE_INNER = 1;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
||||
|
||||
/**
|
||||
* @param {String} tag (64 hexadecimal characters)
|
||||
* @param {SHAMapTreeNode} node
|
||||
* @return {void}
|
||||
* @virtual
|
||||
*/
|
||||
/*eslint-disable no-unused-vars*/
|
||||
SHAMapTreeNode.prototype.add_item = function(tag, node) {
|
||||
throw new Error(
|
||||
'Called unimplemented virtual method SHAMapTreeNode#add_item.');
|
||||
};
|
||||
/*eslint-enable no-unused-vars*/
|
||||
|
||||
SHAMapTreeNode.prototype.hash = function() {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner (non-leaf) node in a SHAMap tree.
|
||||
* @param {Number} depth (i.e. how many parent inner nodes)
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNodeInner(depth) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
this.leaves = {};
|
||||
|
||||
this.type = SHAMapTreeNode.INNER;
|
||||
this.depth = depth === undefined ? 0 : depth;
|
||||
|
||||
this.empty = true;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
||||
|
||||
/**
|
||||
* @param {String} tag (equates to a ledger entry `index`)
|
||||
* @param {SHAMapTreeNode} node (to add)
|
||||
* @return {void}
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.add_item = function(tag, node) {
|
||||
var depth = this.depth;
|
||||
var existing_node = this.get_node(tag[depth]);
|
||||
|
||||
if (existing_node) {
|
||||
// A node already exists in this slot
|
||||
if (existing_node instanceof SHAMapTreeNodeInner) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
existing_node.add_item(tag, node);
|
||||
} else if (existing_node.tag === tag) {
|
||||
// Collision
|
||||
throw new Error(
|
||||
'Tried to add a node to a SHAMap that was already in there.');
|
||||
} else {
|
||||
// Turn it into an inner node
|
||||
var new_inner_node = new SHAMapTreeNodeInner(depth + 1);
|
||||
|
||||
// Parent new and existing node
|
||||
new_inner_node.add_item(existing_node.tag, existing_node);
|
||||
new_inner_node.add_item(tag, node);
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.set_node(tag[depth], new_inner_node);
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
this.set_node(tag[depth], node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
* @param {String} slot (a character 0-F)
|
||||
* @param {SHAMapTreeNode} node (to place)
|
||||
* @return {void}
|
||||
*/
|
||||
SHAMapTreeNodeInner.prototype.set_node = function(slot, node) {
|
||||
this.leaves[slot] = node;
|
||||
this.empty = false;
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.get_node = function(slot) {
|
||||
return this.leaves[slot];
|
||||
};
|
||||
|
||||
SHAMapTreeNodeInner.prototype.hash = function() {
|
||||
if (this.empty) {
|
||||
return UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
}
|
||||
|
||||
var hash_buffer = new SerializedObject();
|
||||
|
||||
for (var i = 0; i < 16; i++) {
|
||||
var leafHash = UInt256.from_hex(UInt256.HEX_ZERO);
|
||||
var slot = i.toString(16).toUpperCase();
|
||||
|
||||
if (typeof this.leaves[slot] === 'object') {
|
||||
leafHash = this.leaves[slot].hash();
|
||||
}
|
||||
|
||||
hash_buffer.append(leafHash.to_bytes());
|
||||
}
|
||||
|
||||
var hash = hash_buffer.hash(hashprefixes.HASH_INNER_NODE);
|
||||
|
||||
return UInt256.from_bits(hash);
|
||||
};
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
* @param {String} tag (equates to a ledger entry `index`)
|
||||
* @param {SerializedObject} node (bytes of account state, transaction etc)
|
||||
* @param {Number} type (one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc)
|
||||
* @class
|
||||
*/
|
||||
function SHAMapTreeNodeLeaf(tag, node, type) {
|
||||
SHAMapTreeNode.call(this);
|
||||
|
||||
if (typeof tag !== 'string') {
|
||||
throw new Error('Tag is unexpected type.');
|
||||
}
|
||||
|
||||
this.tag = tag;
|
||||
this.tag_bytes = UInt256.from_hex(this.tag).to_bytes();
|
||||
this.type = type;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
||||
|
||||
SHAMapTreeNodeLeaf.prototype.hash = function() {
|
||||
var buffer = new SerializedObject();
|
||||
switch (this.type) {
|
||||
case SHAMapTreeNode.TYPE_ACCOUNT_STATE:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_LEAF_NODE);
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
||||
return this.tag_bytes;
|
||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
||||
buffer.append(this.node);
|
||||
buffer.append(this.tag_bytes);
|
||||
return buffer.hash(hashprefixes.HASH_TX_NODE);
|
||||
default:
|
||||
throw new Error('Tried to hash a SHAMap node of unknown type.');
|
||||
}
|
||||
};
|
||||
|
||||
function SHAMap() {
|
||||
this.root = new SHAMapTreeNodeInner(0);
|
||||
}
|
||||
|
||||
SHAMap.prototype.add_item = function(tag, node, type) {
|
||||
node = new SHAMapTreeNodeLeaf(tag, node, type);
|
||||
this.root.add_item(tag, node);
|
||||
};
|
||||
|
||||
SHAMap.prototype.hash = function() {
|
||||
return this.root.hash();
|
||||
};
|
||||
|
||||
exports.SHAMap = SHAMap;
|
||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
||||
@@ -8,13 +8,13 @@ const EventEmitter = require('events').EventEmitter;
|
||||
const utils = require('./utils');
|
||||
const sjclcodec = require('sjcl-codec');
|
||||
const Amount = require('./amount').Amount;
|
||||
const Currency = require('./currency').Currency;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const SerializedObject = require('./serializedobject').SerializedObject;
|
||||
const {normalizeCurrency, isValidCurrency} = require('./currency');
|
||||
const RippleError = require('./rippleerror').RippleError;
|
||||
const hashprefixes = require('./hashprefixes');
|
||||
const log = require('./log').internal.sub('transaction');
|
||||
const {isValidAddress} = require('ripple-address-codec');
|
||||
const {isValidAddress, decodeAddress} = require('ripple-address-codec');
|
||||
const binary = require('ripple-binary-codec');
|
||||
const {computeTransactionHash, computeTransactionSigningHash}
|
||||
= require('ripple-hashes');
|
||||
|
||||
/**
|
||||
* @constructor Transaction
|
||||
@@ -385,24 +385,32 @@ Transaction.prototype.err = function(error, errorMessage) {
|
||||
};
|
||||
|
||||
Transaction.prototype.complete = function() {
|
||||
// Auto-fill the secret
|
||||
this._secret = this._secret || this.getSecret();
|
||||
const hasMultiSigners = this.hasMultiSigners();
|
||||
|
||||
if (_.isUndefined(this._secret)) {
|
||||
return this.err('tejSecretUnknown', 'Missing secret');
|
||||
}
|
||||
if (!hasMultiSigners) {
|
||||
// Auto-fill the secret
|
||||
this._secret = this._secret || this.getSecret();
|
||||
|
||||
if (this.remote && !(this.remote.local_signing || this.remote.trusted)) {
|
||||
return this.err(
|
||||
'tejServerUntrusted',
|
||||
'Attempt to give secret to untrusted server');
|
||||
if (_.isUndefined(this._secret)) {
|
||||
return this.err('tejSecretUnknown', 'Missing secret');
|
||||
}
|
||||
|
||||
if (this.remote && !(this.remote.local_signing || this.remote.trusted)) {
|
||||
return this.err(
|
||||
'tejServerUntrusted',
|
||||
'Attempt to give secret to untrusted server');
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.tx_json.SigningPubKey)) {
|
||||
try {
|
||||
this.setSigningPubKey(this.getSigningPubKey());
|
||||
} catch (e) {
|
||||
return this.err('tejSecretInvalid', 'Invalid secret');
|
||||
if (hasMultiSigners) {
|
||||
this.setSigningPubKey('');
|
||||
} else {
|
||||
try {
|
||||
this.setSigningPubKey(this.getSigningPubKey());
|
||||
} catch (e) {
|
||||
return this.err('tejSecretInvalid', 'Invalid secret');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,42 +460,23 @@ Transaction.prototype.setCanonicalFlag = function() {
|
||||
};
|
||||
|
||||
Transaction.prototype.serialize = function() {
|
||||
return SerializedObject.from_json(this.tx_json);
|
||||
return binary.encode(this.tx_json);
|
||||
};
|
||||
|
||||
Transaction.prototype.signingHash = function(testnet) {
|
||||
return this.hash(testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN');
|
||||
Transaction.prototype.signingHash = function() {
|
||||
return computeTransactionSigningHash(this.tx_json);
|
||||
};
|
||||
|
||||
Transaction.prototype.signingData = function() {
|
||||
const so = new SerializedObject();
|
||||
so.append(hashprefixes.HASH_TX_SIGN_BYTES);
|
||||
so.parse_json(this.tx_json);
|
||||
return so;
|
||||
return binary.encodeForSigning(this.tx_json);
|
||||
};
|
||||
|
||||
Transaction.prototype.multiSigningData = function(account) {
|
||||
const so = new SerializedObject();
|
||||
so.append(hashprefixes.HASH_TX_MULTISIGN_BYTES);
|
||||
so.parse_json(this.tx_json);
|
||||
so.append(UInt160.from_json(account).to_bytes());
|
||||
return so;
|
||||
return binary.encodeForMultisigning(this.tx_json, account);
|
||||
};
|
||||
|
||||
Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
|
||||
let prefix;
|
||||
|
||||
if (typeof prefix_ !== 'string') {
|
||||
prefix = hashprefixes.HASH_TX_ID;
|
||||
} else if (!hashprefixes.hasOwnProperty(prefix_)) {
|
||||
throw new Error('Unknown hashing prefix requested: ' + prefix_);
|
||||
} else {
|
||||
prefix = hashprefixes[prefix_];
|
||||
}
|
||||
|
||||
const hash = (serialized || this.serialize()).hash(prefix);
|
||||
|
||||
return asUINT256 ? hash : hash.to_hex();
|
||||
Transaction.prototype.hash = function() {
|
||||
return computeTransactionHash(this.tx_json);
|
||||
};
|
||||
|
||||
Transaction.prototype.sign = function(secret) {
|
||||
@@ -506,8 +495,7 @@ Transaction.prototype.sign = function(secret) {
|
||||
}
|
||||
|
||||
const keypair = deriveKeypair(secret || this._secret);
|
||||
this.tx_json.TxnSignature = sign(this.signingData().buffer,
|
||||
keypair.privateKey);
|
||||
this.tx_json.TxnSignature = sign(this.signingData(), keypair.privateKey);
|
||||
this.previousSigningHash = hash;
|
||||
|
||||
return this;
|
||||
@@ -692,14 +680,10 @@ Transaction.prototype.sourceTag = function(tag) {
|
||||
};
|
||||
|
||||
Transaction.prototype._setAccount = function(name, value) {
|
||||
const uInt160 = UInt160.from_json(value);
|
||||
|
||||
if (!uInt160.is_valid()) {
|
||||
if (!isValidAddress(value)) {
|
||||
throw new Error(name + ' must be a valid account');
|
||||
}
|
||||
|
||||
this.tx_json[name] = uInt160.to_json();
|
||||
|
||||
this.tx_json[name] = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -715,12 +699,12 @@ Transaction.prototype._setAmount = function(name, amount, options_) {
|
||||
throw new Error(name + ' value must be non-negative');
|
||||
}
|
||||
|
||||
const isNative = parsedAmount.currency().is_native();
|
||||
const isNative = parsedAmount.is_native();
|
||||
|
||||
if (isNative && options.no_native) {
|
||||
throw new Error(name + ' must be a non-native amount');
|
||||
}
|
||||
if (!(isNative || parsedAmount.currency().is_valid())) {
|
||||
if (!(isNative || isValidCurrency(parsedAmount.currency()))) {
|
||||
throw new Error(name + ' must have a valid currency');
|
||||
}
|
||||
if (!(isNative || isValidAddress(parsedAmount.issuer()))) {
|
||||
@@ -1176,15 +1160,15 @@ Transaction._rewritePath = function(path) {
|
||||
const newNode = { };
|
||||
|
||||
if (node.hasOwnProperty('account')) {
|
||||
newNode.account = UInt160.json_rewrite(node.account);
|
||||
newNode.account = node.account;
|
||||
}
|
||||
|
||||
if (node.hasOwnProperty('issuer')) {
|
||||
newNode.issuer = UInt160.json_rewrite(node.issuer);
|
||||
newNode.issuer = node.issuer;
|
||||
}
|
||||
|
||||
if (node.hasOwnProperty('currency')) {
|
||||
newNode.currency = Currency.json_rewrite(node.currency);
|
||||
newNode.currency = normalizeCurrency(node.currency);
|
||||
}
|
||||
|
||||
if (node.hasOwnProperty('type_hex')) {
|
||||
@@ -1384,7 +1368,7 @@ Transaction.prototype.offerCancel = function(options_) {
|
||||
Transaction._prepareSignerEntry = function(signer) {
|
||||
const {account, weight} = signer;
|
||||
|
||||
assert(UInt160.is_valid(account), 'Signer account invalid');
|
||||
assert(isValidAddress(account), 'Signer account invalid');
|
||||
assert(_.isNumber(weight), 'Signer weight missing');
|
||||
assert(weight > 0 && weight <= 65535, 'Signer weight must be 1-65535');
|
||||
|
||||
@@ -1606,7 +1590,7 @@ Transaction.prototype.setSigners = function(signers) {
|
||||
};
|
||||
|
||||
Transaction.prototype.addMultiSigner = function(signer) {
|
||||
assert(UInt160.is_valid(signer.Account), 'Signer must have a valid Account');
|
||||
assert(isValidAddress(signer.Account), 'Signer must have a valid Account');
|
||||
|
||||
if (_.isUndefined(this.tx_json.Signers)) {
|
||||
this.tx_json.Signers = [];
|
||||
@@ -1615,8 +1599,8 @@ Transaction.prototype.addMultiSigner = function(signer) {
|
||||
this.tx_json.Signers.push({Signer: signer});
|
||||
|
||||
this.tx_json.Signers.sort((a, b) => {
|
||||
return UInt160.from_json(a.Signer.Account)
|
||||
.cmp(UInt160.from_json(b.Signer.Account));
|
||||
return (new Buffer(decodeAddress(a.Signer.Account))).compare(
|
||||
new Buffer(decodeAddress(b.Signer.Account)));
|
||||
});
|
||||
|
||||
return this;
|
||||
@@ -1659,7 +1643,7 @@ Transaction.prototype.multiSign = function(account, secret) {
|
||||
|
||||
const signer = {
|
||||
Account: account,
|
||||
TxnSignature: sign(signingData.buffer, keypair.privateKey),
|
||||
TxnSignature: sign(signingData, keypair.privateKey),
|
||||
SigningPubKey: keypair.publicKey
|
||||
};
|
||||
|
||||
|
||||
@@ -524,9 +524,9 @@ TransactionManager.prototype._prepareRequest = function(tx) {
|
||||
tx.sign();
|
||||
|
||||
const serialized = tx.serialize();
|
||||
submitRequest.txBlob(serialized.to_hex());
|
||||
submitRequest.txBlob(serialized);
|
||||
|
||||
const hash = tx.hash(null, null, serialized);
|
||||
const hash = tx.hash(null, serialized);
|
||||
tx.addId(hash);
|
||||
} else {
|
||||
if (tx.hasMultiSigners()) {
|
||||
@@ -724,6 +724,10 @@ TransactionManager.prototype.submit = function(tx) {
|
||||
return;
|
||||
}
|
||||
|
||||
tx.once('cleanup', function() {
|
||||
self.getPending().remove(tx);
|
||||
});
|
||||
|
||||
if (!_.isNumber(tx.tx_json.Sequence)) {
|
||||
// Honor manually-set sequences
|
||||
tx.setSequence(this._nextSequence++);
|
||||
@@ -735,13 +739,8 @@ TransactionManager.prototype.submit = function(tx) {
|
||||
|
||||
if (tx.hasMultiSigners()) {
|
||||
tx.setResubmittable(false);
|
||||
tx.setSigningPubKey('');
|
||||
}
|
||||
|
||||
tx.once('cleanup', function() {
|
||||
self.getPending().remove(tx);
|
||||
});
|
||||
|
||||
if (!tx.complete()) {
|
||||
this._nextSequence -= 1;
|
||||
return;
|
||||
|
||||
269
src/core/uint.js
269
src/core/uint.js
@@ -1,269 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint new-cap: 1 */
|
||||
|
||||
const assert = require('assert');
|
||||
const lodash = require('lodash');
|
||||
const sjclcodec = require('sjcl-codec');
|
||||
const utils = require('./utils');
|
||||
const BN = require('bn.js');
|
||||
|
||||
//
|
||||
// Abstract UInt class
|
||||
//
|
||||
// Base class for UInt classes
|
||||
//
|
||||
|
||||
function UInt() {
|
||||
// Internal form: NaN or BN
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
UInt.json_rewrite = function(j, opts) {
|
||||
return this.from_json(j).to_json(opts);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_generic = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_generic(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_hex = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_hex(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_json = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_json(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bits = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_bits(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_bytes = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_bytes(j);
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_number = function(j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
}
|
||||
|
||||
return (new this()).parse_number(j);
|
||||
};
|
||||
|
||||
UInt.is_valid = function(j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
UInt.prototype.clone = function() {
|
||||
return this.copyTo(new this.constructor());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
UInt.prototype.copyTo = function(d) {
|
||||
d._value = this._value;
|
||||
|
||||
if (this._version_byte !== undefined) {
|
||||
d._version_byte = this._version_byte;
|
||||
}
|
||||
|
||||
if (typeof d._update === 'function') {
|
||||
d._update();
|
||||
}
|
||||
|
||||
return d;
|
||||
};
|
||||
|
||||
UInt.prototype.equals = function(o) {
|
||||
return this.is_valid() &&
|
||||
o.is_valid() &&
|
||||
// This throws but the expression will short circuit
|
||||
this.cmp(o) === 0;
|
||||
};
|
||||
|
||||
UInt.prototype.cmp = function(o) {
|
||||
assert(this.is_valid() && o.is_valid());
|
||||
return this._value.cmp(o._value);
|
||||
};
|
||||
|
||||
UInt.prototype.greater_than = function(o) {
|
||||
return this.cmp(o) > 0;
|
||||
};
|
||||
|
||||
UInt.prototype.less_than = function(o) {
|
||||
return this.cmp(o) < 0;
|
||||
};
|
||||
|
||||
UInt.prototype.is_valid = function() {
|
||||
return this._value instanceof BN;
|
||||
};
|
||||
|
||||
UInt.prototype.is_zero = function() {
|
||||
// cmpn means cmp with N)umber
|
||||
return this.is_valid() && this._value.cmpn(0) === 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update any derivative values.
|
||||
*
|
||||
* This allows subclasses to maintain caches of any data that they derive from
|
||||
* the main _value. For example, the Currency class keeps the currency type, the
|
||||
* currency code and other information about the currency cached.
|
||||
*
|
||||
* The reason for keeping this mechanism in this class is so every subclass can
|
||||
* call it whenever it modifies the internal state.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
UInt.prototype._update = function() {
|
||||
// Nothing to do by default. Subclasses will override this.
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt.prototype.parse_generic = function(j) {
|
||||
const subclass = this.constructor;
|
||||
|
||||
assert(typeof subclass.width === 'number', 'UInt missing width');
|
||||
|
||||
this._value = NaN;
|
||||
|
||||
switch (j) {
|
||||
case undefined:
|
||||
case '0':
|
||||
case subclass.STR_ZERO:
|
||||
case subclass.ACCOUNT_ZERO:
|
||||
case subclass.HEX_ZERO:
|
||||
this._value = new BN(0);
|
||||
break;
|
||||
|
||||
case '1':
|
||||
case subclass.STR_ONE:
|
||||
case subclass.ACCOUNT_ONE:
|
||||
case subclass.HEX_ONE:
|
||||
this._value = new BN(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (lodash.isString(j)) {
|
||||
switch (j.length) {
|
||||
case subclass.width:
|
||||
const hex = utils.arrayToHex(utils.stringToArray(j));
|
||||
this._value = new BN(hex, 16);
|
||||
break;
|
||||
case subclass.width * 2:
|
||||
// Assume hex, check char set
|
||||
this.parse_hex(j);
|
||||
break;
|
||||
}
|
||||
} else if (lodash.isNumber(j)) {
|
||||
this.parse_number(j);
|
||||
} else if (lodash.isArray(j)) {
|
||||
// Assume bytes array
|
||||
this.parse_bytes(j);
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_hex = function(j) {
|
||||
if (new RegExp(`^[0-9A-Fa-f]{${this.constructor.width * 2}}$`).test(j)) {
|
||||
this._value = new BN(j, 16);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bits = function(j) {
|
||||
return this.parse_bytes(sjclcodec.bytes.fromBits(j));
|
||||
};
|
||||
|
||||
UInt.prototype.parse_bytes = function(j) {
|
||||
if (Array.isArray(j) && j.length === this.constructor.width) {
|
||||
this._value = new BN(j);
|
||||
} else {
|
||||
this._value = NaN;
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_json = UInt.prototype.parse_hex;
|
||||
|
||||
UInt.prototype.parse_number = function(j) {
|
||||
this._value = NaN;
|
||||
|
||||
if (typeof j === 'number' && isFinite(j) && j >= 0) {
|
||||
this._value = new BN(j);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bytes = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._value.toArray('be', this.constructor.width);
|
||||
};
|
||||
|
||||
UInt.prototype.to_hex = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return utils.arrayToHex(this.to_bytes());
|
||||
};
|
||||
|
||||
UInt.prototype.to_json = UInt.prototype.to_hex;
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bits = function() {
|
||||
if (!this.is_valid()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sjclcodec.bytes.toBits(this.to_bytes());
|
||||
};
|
||||
|
||||
exports.UInt = UInt;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,25 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const utils = require('./utils');
|
||||
const extend = require('extend');
|
||||
const UInt = require('./uint').UInt;
|
||||
|
||||
//
|
||||
// UInt128 support
|
||||
//
|
||||
|
||||
const UInt128 = extend(function() {
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
UInt128.width = 16;
|
||||
UInt128.prototype = Object.create(extend({}, UInt.prototype));
|
||||
UInt128.prototype.constructor = UInt128;
|
||||
|
||||
const HEX_ZERO = UInt128.HEX_ZERO = '00000000000000000000000000000000';
|
||||
const HEX_ONE = UInt128.HEX_ONE = '00000000000000000000000000000000';
|
||||
|
||||
UInt128.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
UInt128.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt128 = UInt128;
|
||||
@@ -1,97 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const utils = require('./utils');
|
||||
const extend = require('extend');
|
||||
|
||||
const UInt = require('./uint').UInt;
|
||||
const Base = require('./base').Base;
|
||||
|
||||
//
|
||||
// UInt160 support
|
||||
//
|
||||
|
||||
const UInt160 = extend(function() {
|
||||
this._value = NaN;
|
||||
this._version_byte = undefined;
|
||||
this._update();
|
||||
}, UInt);
|
||||
|
||||
UInt160.width = 20;
|
||||
UInt160.prototype = Object.create(extend({}, UInt.prototype));
|
||||
UInt160.prototype.constructor = UInt160;
|
||||
|
||||
const HEX_ZERO = UInt160.HEX_ZERO = '0000000000000000000000000000000000000000';
|
||||
const HEX_ONE = UInt160.HEX_ONE = '0000000000000000000000000000000000000001';
|
||||
|
||||
UInt160.ACCOUNT_ZERO = 'rrrrrrrrrrrrrrrrrrrrrhoLvTp';
|
||||
UInt160.ACCOUNT_ONE = 'rrrrrrrrrrrrrrrrrrrrBZbvji';
|
||||
UInt160.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
UInt160.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
UInt160.prototype.set_version = function(j) {
|
||||
this._version_byte = j;
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.get_version = function() {
|
||||
return this._version_byte;
|
||||
};
|
||||
|
||||
// value = NaN on error.
|
||||
UInt160.prototype.parse_json = function(j) {
|
||||
if (typeof j === 'number' && !isNaN(j)) {
|
||||
// Allow raw numbers - DEPRECATED
|
||||
// This is used mostly by the test suite and is supported
|
||||
// as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR.
|
||||
this.parse_number(j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else if (typeof j !== 'string') {
|
||||
this._value = NaN;
|
||||
} else if (j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
this._version_byte = Base.VER_ACCOUNT_ID;
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt160.prototype.parse_generic = function(j) {
|
||||
UInt.prototype.parse_generic.call(this, j);
|
||||
|
||||
if (isNaN(this._value)) {
|
||||
if ((typeof j === 'string') && j[0] === 'r') {
|
||||
this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j);
|
||||
}
|
||||
}
|
||||
|
||||
this._update();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
|
||||
UInt160.prototype.to_json = function(opts = {}) {
|
||||
|
||||
if (this.is_valid()) {
|
||||
// If this value has a type, return a Base58 encoded string.
|
||||
if (typeof this._version_byte === 'number') {
|
||||
let output = Base.encode_check(this._version_byte, this.to_bytes());
|
||||
|
||||
if (opts.gateways && output in opts.gateways) {
|
||||
output = opts.gateways[output];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
return this.to_hex();
|
||||
}
|
||||
return NaN;
|
||||
};
|
||||
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,28 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const utils = require('./utils');
|
||||
const extend = require('extend');
|
||||
const UInt = require('./uint').UInt;
|
||||
|
||||
//
|
||||
// UInt256 support
|
||||
//
|
||||
|
||||
const UInt256 = extend(function() {
|
||||
this._value = NaN;
|
||||
}, UInt);
|
||||
|
||||
UInt256.width = 32;
|
||||
UInt256.prototype = Object.create(extend({}, UInt.prototype));
|
||||
UInt256.prototype.constructor = UInt256;
|
||||
|
||||
const HEX_ZERO = UInt256.HEX_ZERO = '00000000000000000000000000000000' +
|
||||
'00000000000000000000000000000000';
|
||||
|
||||
const HEX_ONE = UInt256.HEX_ONE = '00000000000000000000000000000000' +
|
||||
'00000000000000000000000000000001';
|
||||
|
||||
UInt256.STR_ZERO = utils.hexToString(HEX_ZERO);
|
||||
UInt256.STR_ONE = utils.hexToString(HEX_ONE);
|
||||
|
||||
exports.UInt256 = UInt256;
|
||||
@@ -1,4 +1,13 @@
|
||||
'use strict';
|
||||
const sha512 = require('hash.js').sha512;
|
||||
|
||||
// For a hash function, rippled uses SHA-512 and then truncates the result
|
||||
// to the first 256 bytes. This algorithm, informally called SHA-512Half,
|
||||
// provides an output that has comparable security to SHA-256, but runs
|
||||
// faster on 64-bit processors.
|
||||
function sha512half(buffer) {
|
||||
return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64);
|
||||
}
|
||||
|
||||
// returns the mantissa from the passed in string,
|
||||
// adding zeros until it has 16 sd
|
||||
@@ -21,7 +30,7 @@ function getMantissaDecimalString(bignum) {
|
||||
|
||||
function trace(comment, func) {
|
||||
return function() {
|
||||
console.log('%s: %s', trace, arguments.toString);
|
||||
console.log('%s: %s', comment, arguments.toString);
|
||||
func(arguments);
|
||||
};
|
||||
}
|
||||
@@ -112,7 +121,8 @@ function assert(assertion, msg) {
|
||||
* @return {Array} unique values (for string representation of value) in `arr`
|
||||
*/
|
||||
function arrayUnique(arr) {
|
||||
const u = {}, a = [];
|
||||
const u = {};
|
||||
const a = [];
|
||||
|
||||
for (let i = 0, l = arr.length; i < l; i++) {
|
||||
const k = arr[i];
|
||||
@@ -151,6 +161,7 @@ exports.time = {
|
||||
toRipple: fromTimestamp
|
||||
};
|
||||
|
||||
exports.sha512half = sha512half;
|
||||
exports.trace = trace;
|
||||
exports.arraySet = arraySet;
|
||||
exports.hexToString = hexToString;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const Amount = require('ripple-lib').Amount;
|
||||
const UInt160 = require('ripple-lib').UInt160;
|
||||
|
||||
|
||||
describe('Amount', function() {
|
||||
describe('Negatives', function() {
|
||||
@@ -170,10 +168,10 @@ describe('Amount', function() {
|
||||
assert.strictEqual(Amount.from_human('12345.6789 XAU').to_human_full(), '12,345.6789/XAU/NaN');
|
||||
});
|
||||
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000', function() {
|
||||
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_text_full(), '12345.6789/XAU (-0.5%pa)/NaN');
|
||||
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_text_full(), '12345.6789/015841551A748AD2C1F76FF6ECB0CCCD00000000/NaN');
|
||||
});
|
||||
it('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000 human', function() {
|
||||
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_human_full(), '12,345.6789/XAU (-0.5%pa)/NaN');
|
||||
assert.strictEqual(Amount.from_human('12345.6789 015841551A748AD2C1F76FF6ECB0CCCD00000000').to_human_full(), '12,345.6789/015841551A748AD2C1F76FF6ECB0CCCD00000000/NaN');
|
||||
});
|
||||
it('12345.6789 0000000000000000000000005553440000000000', function() {
|
||||
assert.strictEqual(Amount.from_human('12345.6789 0000000000000000000000005553440000000000').to_text_full(), '12345.6789/USD/NaN');
|
||||
@@ -246,22 +244,22 @@ describe('Amount', function() {
|
||||
});
|
||||
describe('from_number', function() {
|
||||
it('Number 1', function() {
|
||||
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(1).to_text_full(), '1/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Number 1 human', function() {
|
||||
assert.strictEqual(Amount.from_number(1).to_human_full(), '1/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(1).to_human_full(), '1/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Number 2', function() {
|
||||
assert.strictEqual(Amount.from_number(2).to_text_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(2).to_text_full(), '2/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Number 2 human', function() {
|
||||
assert.strictEqual(Amount.from_number(2).to_human_full(), '2/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(2).to_human_full(), '2/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply 2 "1" with 3 "1", by product_human', function() {
|
||||
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_text_full(), '6/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply 2 "1" with 3 "1", by product_human human', function() {
|
||||
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_human_full(), '6/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(2).product_human(Amount.from_number(3)).to_human_full(), '6/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply 3 USD with 3 "1"', function() {
|
||||
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_text_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
@@ -270,16 +268,16 @@ describe('Amount', function() {
|
||||
assert.strictEqual(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').multiply(Amount.from_number(3)).to_human_full(), '9/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('Multiply -1 "1" with 3 USD', function() {
|
||||
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply -1 "1" with 3 USD human', function() {
|
||||
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(-1).multiply(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply -1 "1" with 3 USD, by product_human', function() {
|
||||
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_text_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
it('Multiply -1 "1" with 3 USD, by product_human human', function() {
|
||||
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/1/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert.strictEqual(Amount.from_number(-1).product_human(Amount.from_json('3/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh')).to_human_full(), '-3/0000000000000000000000000000000000000001/rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
});
|
||||
describe('text_full_rewrite', function() {
|
||||
@@ -292,26 +290,6 @@ describe('Amount', function() {
|
||||
assert.strictEqual('1', Amount.json_rewrite(1));
|
||||
});
|
||||
});
|
||||
describe('UInt160', function() {
|
||||
it('Parse 0 export', function() {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').set_version(0).to_json());
|
||||
});
|
||||
it('Parse 1', function() {
|
||||
assert.deepEqual(UInt160.ACCOUNT_ONE, UInt160.from_generic('1').set_version(0).to_json());
|
||||
});
|
||||
it('Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export', function() {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrrhoLvTp').to_json());
|
||||
});
|
||||
it('Parse rrrrrrrrrrrrrrrrrrrrBZbvji export', function() {
|
||||
assert.strictEqual(UInt160.ACCOUNT_ONE, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrBZbvji').to_json());
|
||||
});
|
||||
it('is_valid rrrrrrrrrrrrrrrrrrrrrhoLvTp', function() {
|
||||
assert(UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvTp'));
|
||||
});
|
||||
it('!is_valid rrrrrrrrrrrrrrrrrrrrrhoLvT', function() {
|
||||
assert(!UInt160.is_valid('rrrrrrrrrrrrrrrrrrrrrhoLvT'));
|
||||
});
|
||||
});
|
||||
describe('Amount validity', function() {
|
||||
it('is_valid 1', function() {
|
||||
assert(Amount.is_valid(1));
|
||||
@@ -342,16 +320,10 @@ describe('Amount', function() {
|
||||
assert(isNaN(Amount.from_json('x').to_text()));
|
||||
});
|
||||
it('parse dem', function() {
|
||||
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('parse dem human', function() {
|
||||
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('parse dem', function() {
|
||||
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('parse dem human', function() {
|
||||
assert.strictEqual(Amount.from_json('10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').to_human_full(), '10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('Parse native 0', function() {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').to_text_full());
|
||||
@@ -970,9 +942,6 @@ describe('Amount', function() {
|
||||
it('Multiply XRP with XRP', function() {
|
||||
assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_text_full(), '0.0001/XRP');
|
||||
});
|
||||
it('Multiply USD with XAU (dem)', function() {
|
||||
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('Multiply 0 XRP with 0 XRP human', function() {
|
||||
assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_human_full());
|
||||
});
|
||||
@@ -1042,9 +1011,6 @@ describe('Amount', function() {
|
||||
it('Multiply XRP with XRP human', function() {
|
||||
assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_human_full(), '0.0001/XRP');
|
||||
});
|
||||
it('Multiply USD with XAU (dem) human', function() {
|
||||
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '19,900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ratio_human', function() {
|
||||
@@ -1059,12 +1025,6 @@ describe('Amount', function() {
|
||||
assert.deepEqual(c.to_json(), {value: '0.005441114728882572',
|
||||
currency: 'USD', issuer: 'rLFPPebckMYZf3urdomLsaqRGmQ6zHVrrK'});
|
||||
});
|
||||
it('Divide USD by XAU (dem)', function() {
|
||||
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('Divide USD by XAU (dem) human', function() {
|
||||
assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').ratio_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '201.0049931765529/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_invert', function() {
|
||||
@@ -1112,18 +1072,6 @@ describe('Amount', function() {
|
||||
it('BTC/USD inverse', function() {
|
||||
assert.strictEqual(Amount.from_quality('20294C923E80A51B487EB9547B3835FD483748B170D2D0A455071AFD498D0000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'BTC'}).to_text_full(), '0.5/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
|
||||
});
|
||||
it('XAU(dem)/XRP', function() {
|
||||
assert.strictEqual(Amount.from_quality('587322CCBDE0ABD01704769A73A077C32FB39057D813D4165F1FF973CAF997EF', 'XRP', NaN, {base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_text_full(), '90,452.246928/XRP');
|
||||
});
|
||||
it('XAU(dem)/XRP inverse', function() {
|
||||
assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_text_full(), '90,442.196677/XRP');
|
||||
});
|
||||
it('USD/XAU(dem)', function() {
|
||||
assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
|
||||
});
|
||||
it('USD/XAU(dem) inverse', function() {
|
||||
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_text_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
|
||||
});
|
||||
it('BTC/XRP human', function() {
|
||||
assert.strictEqual(Amount.from_quality('7B73A610A009249B0CC0D4311E8BA7927B5A34D86634581C5F0FF9FF678E1000', 'XRP', NaN, {base_currency: 'BTC'}).to_human_full(), '44,970/XRP');
|
||||
});
|
||||
@@ -1142,57 +1090,6 @@ describe('Amount', function() {
|
||||
it('BTC/USD inverse human', function() {
|
||||
assert.strictEqual(Amount.from_quality('20294C923E80A51B487EB9547B3835FD483748B170D2D0A455071AFD498D0000', 'USD', 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B', {inverse: true, base_currency: 'BTC'}).to_human_full(), '0.5/USD/rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B');
|
||||
});
|
||||
it('XAU(dem)/XRP human', function() {
|
||||
assert.strictEqual(Amount.from_quality('587322CCBDE0ABD01704769A73A077C32FB39057D813D4165F1FF973CAF997EF', 'XRP', NaN, {base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,452.246928/XRP');
|
||||
});
|
||||
it('XAU(dem)/XRP inverse human', function() {
|
||||
assert.strictEqual(Amount.from_quality('F72C7A9EAE4A45ED1FB547AD037D07B9B965C6E662BEBAFA4A03F2A976804235', 'XRP', NaN, {inverse: true, base_currency: '015841551A748AD2C1F76FF6ECB0CCCD00000000', reference_date: 443845330 + 31535000}).to_human_full(), '90,442.196677/XRP');
|
||||
});
|
||||
it('USD/XAU(dem) human', function() {
|
||||
assert.strictEqual(Amount.from_quality('4743E58E44974B325D42FD2BB683A6E36950F350EE46DD3A521B644B99782F5F', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007710100231303007/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
|
||||
});
|
||||
it('USD/XAU(dem) inverse human', function() {
|
||||
assert.strictEqual(Amount.from_quality('CDFD3AFB2F8C5DBEF75B081F7C957FF5509563266F28F36C5704A0FB0BAD8800', '015841551A748AD2C1F76FF6ECB0CCCD00000000', 'rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN', {inverse: true, base_currency: 'USD', reference_date: 443845330 + 31535000}).to_human_full(), '0.007675186123263489/XAU (-0.5%pa)/rUyPiNcSFFj6uMR2gEaD8jUerQ59G1qvwN');
|
||||
});
|
||||
});
|
||||
|
||||
describe('apply interest', function() {
|
||||
it('from_json apply interest 10 XAU', function() {
|
||||
let demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_text_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
demAmount = demAmount.applyInterest(459990264);
|
||||
assert.strictEqual(demAmount.to_text_full(), '9.294949401870436/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
|
||||
});
|
||||
it('from_json apply interest XAU', function() {
|
||||
let demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_text_full(), '1235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
demAmount = demAmount.applyInterest(459990264);
|
||||
assert.strictEqual(demAmount.to_text_full(), '1148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('from_human with reference date', function() {
|
||||
const demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date: 459990264});
|
||||
demAmount.set_issuer('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_text_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('from_json apply interest 10 XAU human', function() {
|
||||
let demAmount = Amount.from_json('10/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_human_full(), '10/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
demAmount = demAmount.applyInterest(459990264);
|
||||
assert.strictEqual(demAmount.to_human_full(), '9.294949401870436/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
|
||||
});
|
||||
it('from_json apply interest XAU human', function() {
|
||||
let demAmount = Amount.from_json('1235.5/0158415500000000C1F76FF6ECB0BAC600000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_human_full(), '1,235.5/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
demAmount = demAmount.applyInterest(459990264);
|
||||
assert.strictEqual(demAmount.to_human_full(), '1,148.390998601092/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
it('from_human with reference date human', function() {
|
||||
const demAmount = Amount.from_human('10 0158415500000000C1F76FF6ECB0BAC600000000', {reference_date: 459990264});
|
||||
demAmount.set_issuer('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
assert.strictEqual(demAmount.to_human_full(), '10.75853086191915/XAU (-0.5%pa)/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
|
||||
describe('amount limits', function() {
|
||||
|
||||
@@ -196,9 +196,45 @@ describe('RippleAPI', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('getBalances', function() {
|
||||
return this.api.getBalances(address).then(
|
||||
_.partial(checkResult, responses.getBalances, 'getBalances'));
|
||||
describe('RippleAPI', function() {
|
||||
|
||||
it('getBalances', function() {
|
||||
return this.api.getBalances(address).then(
|
||||
_.partial(checkResult, responses.getBalances, 'getBalances'));
|
||||
});
|
||||
|
||||
it('getBalances - limit', function() {
|
||||
const options = {
|
||||
limit: 3
|
||||
};
|
||||
const expectedResponse = responses.getBalances.slice(0, 3);
|
||||
return this.api.getBalances(address, options).then(
|
||||
_.partial(checkResult, expectedResponse, 'getBalances'));
|
||||
});
|
||||
|
||||
it('getBalances - limit & currency', function() {
|
||||
const options = {
|
||||
currency: 'USD',
|
||||
limit: 3
|
||||
};
|
||||
const expectedResponse = _.filter(responses.getBalances,
|
||||
item => item.currency === 'USD').slice(0, 3);
|
||||
return this.api.getBalances(address, options).then(
|
||||
_.partial(checkResult, expectedResponse, 'getBalances'));
|
||||
});
|
||||
|
||||
it('getBalances - limit & currency & issuer', function() {
|
||||
const options = {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
limit: 3
|
||||
};
|
||||
const expectedResponse = _.filter(responses.getBalances,
|
||||
item => item.currency === 'USD' &&
|
||||
item.counterparty === 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B').slice(0, 3);
|
||||
return this.api.getBalances(address, options).then(
|
||||
_.partial(checkResult, expectedResponse, 'getBalances'));
|
||||
});
|
||||
});
|
||||
|
||||
it('getBalanceSheet', function() {
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const Base = require('ripple-lib').Base;
|
||||
const fixtures = require('./fixtures/base58.json');
|
||||
|
||||
function digitArray(str) {
|
||||
return str.split('').map(function(d) {
|
||||
return parseInt(d, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function hexToByteArray(hex) {
|
||||
const byteArray = [];
|
||||
for (let i = 0; i < hex.length / 2; i++) {
|
||||
byteArray.push(parseInt(hex.slice(2 * i, 2 * i + 2), 16));
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
describe('Base', function() {
|
||||
describe('encode_check', function() {
|
||||
it('0', function() {
|
||||
const encoded = Base.encode_check(0, digitArray('00000000000000000000'));
|
||||
assert.strictEqual(encoded, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp');
|
||||
});
|
||||
it('1', function() {
|
||||
const encoded = Base.encode_check(0, digitArray('00000000000000000001'));
|
||||
assert.strictEqual(encoded, 'rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
});
|
||||
});
|
||||
describe('decode_check', function() {
|
||||
it('rrrrrrrrrrrrrrrrrrrrrhoLvTp', function() {
|
||||
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrrhoLvTp');
|
||||
assert(decoded.cmpn(0) === 0);
|
||||
});
|
||||
it('rrrrrrrrrrrrrrrrrrrrBZbvji', function() {
|
||||
const decoded = Base.decode_check(0, 'rrrrrrrrrrrrrrrrrrrrBZbvji');
|
||||
assert(decoded.cmpn(1) === 0);
|
||||
});
|
||||
});
|
||||
describe('decode-encode identity', function() {
|
||||
it('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
|
||||
const decoded = Base.decode('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
const encoded = Base.encode(decoded);
|
||||
assert.strictEqual(encoded, 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh');
|
||||
});
|
||||
});
|
||||
describe('encode', function() {
|
||||
it('fixtures', function() {
|
||||
for (let i = 0; i < fixtures.ripple.length; i++) {
|
||||
const testCase = fixtures.ripple[i];
|
||||
const encoded = Base.encode(hexToByteArray(testCase.hex));
|
||||
assert.strictEqual(encoded, testCase.string);
|
||||
}
|
||||
});
|
||||
});
|
||||
describe('decode', function() {
|
||||
it('fixtures', function() {
|
||||
for (let i = 0; i < fixtures.ripple.length; i++) {
|
||||
const testCase = fixtures.ripple[i];
|
||||
const decoded = Base.decode(testCase.string);
|
||||
assert.deepEqual(decoded, hexToByteArray(testCase.hex));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
'use strict';
|
||||
var assert = require('assert');
|
||||
var convertBase = require('ripple-lib').convertBase;
|
||||
|
||||
// Test cases from RFC-1924 (a joke RFC)
|
||||
var BASE85 = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||
+ 'abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~');
|
||||
var BASE10 = BASE85.slice(0, 10);
|
||||
var BASE16 = BASE85.slice(0, 16);
|
||||
|
||||
var DATA16 = '108000000000000000080800200C417A';
|
||||
var DATA10 = '21932261930451111902915077091070067066';
|
||||
var DATA85 = '4)+k&C#VzJ4br>0wv%Yp';
|
||||
|
||||
function encode(digitArray, encoding) {
|
||||
return digitArray.map(function(i) {
|
||||
return encoding.charAt(i);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function decode(encoded, encoding) {
|
||||
return encoded.split('').map(function(c) {
|
||||
return encoding.indexOf(c);
|
||||
});
|
||||
}
|
||||
|
||||
function convertBaseEncoded(value, fromEncoding, toEncoding) {
|
||||
var digitArray = decode(value, fromEncoding);
|
||||
var converted = convertBase(digitArray, fromEncoding.length,
|
||||
toEncoding.length);
|
||||
return encode(converted, toEncoding);
|
||||
}
|
||||
|
||||
describe('convertBase', function() {
|
||||
it('DEC -> HEX', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE16), DATA16);
|
||||
});
|
||||
it('HEX -> DEC', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE10), DATA10);
|
||||
});
|
||||
it('DEC -> B85', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA10, BASE10, BASE85), DATA85);
|
||||
});
|
||||
it('HEX -> B85', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA16, BASE16, BASE85), DATA85);
|
||||
});
|
||||
it('B85 -> DEC', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE10), DATA10);
|
||||
});
|
||||
it('B85 -> HEX', function () {
|
||||
assert.strictEqual(convertBaseEncoded(DATA85, BASE85, BASE16), DATA16);
|
||||
});
|
||||
});
|
||||
@@ -1,302 +0,0 @@
|
||||
/*eslint-disable */
|
||||
|
||||
var assert = require('assert');
|
||||
var currency = require('ripple-lib').Currency;
|
||||
var timeUtil = require('ripple-lib').utils.time;
|
||||
|
||||
describe('Currency', function() {
|
||||
describe('json_rewrite', function() {
|
||||
it('json_rewrite("USD") == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.json_rewrite('USD'));
|
||||
});
|
||||
it('json_rewrite("NaN") == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.json_rewrite(NaN));
|
||||
});
|
||||
it('json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000") == "XAU (-0.5%pa)"', function() {
|
||||
assert.strictEqual(currency.json_rewrite("015841551A748AD2C1F76FF6ECB0CCCD00000000"),
|
||||
"XAU (-0.5%pa)");
|
||||
});
|
||||
});
|
||||
describe('from_json', function() {
|
||||
it('from_json().to_json() == "XRP"', function() {
|
||||
var r = currency.from_json();
|
||||
assert(!r.is_valid());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json(NaN).to_json() == "XRP"', function() {
|
||||
var r = currency.from_json(NaN);
|
||||
assert(!r.is_valid());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json().to_json("") == "XRP"', function() {
|
||||
var r = currency.from_json('');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("XRP").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('XRP');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("0000000000000000000000000000000000000000").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('0000000000000000000000000000000000000000');
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
it('from_json("111").to_human()', function() {
|
||||
var r = currency.from_json("111");
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual('111', r.to_json());
|
||||
});
|
||||
it('from_json("1D2").to_human()', function() {
|
||||
var r = currency.from_json("1D2");
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual('1D2', r.to_json());
|
||||
});
|
||||
it('from_json("1").to_human()', function() {
|
||||
var r = currency.from_json('1');
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual(1, r.to_json());
|
||||
});
|
||||
it('from_json("#$%").to_human()', function() {
|
||||
var r = currency.from_json('#$%');
|
||||
assert(r.is_valid());
|
||||
assert.strictEqual('0000000000000000000000002324250000000000', r.to_json());
|
||||
});
|
||||
it('from_json("XAU").to_json() hex', function() {
|
||||
var r = currency.from_json("XAU");
|
||||
assert.strictEqual('0000000000000000000000005841550000000000', r.to_json({force_hex: true}));
|
||||
});
|
||||
it('from_json("XAU (0.5%pa").to_json() hex', function() {
|
||||
var r = currency.from_json("XAU (0.5%pa)");
|
||||
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r.to_json({force_hex: true}));
|
||||
});
|
||||
it('json_rewrite("015841550000000041F78E0A28CBF19200000000").to_json() hex', function() {
|
||||
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000');
|
||||
assert.strictEqual('XAU (0.5%pa)', r);
|
||||
});
|
||||
it('json_rewrite("015841550000000041F78E0A28CBF19200000000") hex', function() {
|
||||
var r = currency.json_rewrite('015841550000000041F78E0A28CBF19200000000', {force_hex: true});
|
||||
assert.strictEqual('015841550000000041F78E0A28CBF19200000000', r);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from_human', function() {
|
||||
it('From human "USD - Gold (-25%pa)"', function() {
|
||||
var cur = currency.from_human('USD - Gold (-25%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'USD (-25%pa)');
|
||||
assert.strictEqual(cur.to_hex(), '0155534400000000C19A22BC51297F0B00000000');
|
||||
assert.strictEqual(cur.to_json(), cur.to_human());
|
||||
});
|
||||
it('From human "EUR (-0.5%pa)', function() {
|
||||
var cur = currency.from_human('EUR (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (-0.5%pa)');
|
||||
});
|
||||
it('From human "EUR (0.5361%pa)", test decimals', function() {
|
||||
var cur = currency.from_human('EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:4}), 'EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
|
||||
});
|
||||
it('From human "EUR - Euro (0.5361%pa)", test decimals and full_name', function() {
|
||||
var cur = currency.from_human('EUR (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'EUR (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:4, full_name:'Euro'}), 'EUR - Euro (0.5361%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:void(0), full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:undefined, full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.to_json({decimals:'henk', full_name:'Euro'}), 'EUR - Euro (0.54%pa)');
|
||||
assert.strictEqual(cur.get_interest_percentage_at(undefined, 4), 0.5361);
|
||||
});
|
||||
it('From human "TYX - 30-Year Treasuries (1.5%pa)"', function() {
|
||||
var cur = currency.from_human('TYX - 30-Year Treasuries (1.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'TYX (1.5%pa)');
|
||||
});
|
||||
it('From human "TYX - 30-Year Treasuries"', function() {
|
||||
var cur = currency.from_human('TYX - 30-Year Treasuries');
|
||||
assert.strictEqual(cur.to_json(), 'TYX');
|
||||
});
|
||||
it('From human "INR - Indian Rupees (-0.5%)"', function() {
|
||||
var cur = currency.from_human('INR - Indian Rupees (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_json(), 'INR (-0.5%pa)');
|
||||
});
|
||||
it('From human "INR - 30 Indian Rupees"', function() {
|
||||
var cur = currency.from_human('INR - 30 Indian Rupees');
|
||||
assert.strictEqual(cur.to_json(), 'INR');
|
||||
});
|
||||
it('From human "XRP"', function() {
|
||||
var cur = currency.from_human('XRP');
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert(cur.is_native(), true);
|
||||
});
|
||||
it('From human "XRP - Ripples"', function() {
|
||||
var cur = currency.from_human('XRP - Ripples');
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert(cur.is_native(), true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('to_human', function() {
|
||||
it('"USD".to_human() == "USD"', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD').to_human());
|
||||
});
|
||||
it('"NaN".to_human() == "XRP"', function() {
|
||||
assert.strictEqual('XRP', currency.from_json(NaN).to_human());
|
||||
});
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human(), 'XAU (-0.5%pa)');
|
||||
});
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000") == "015841551A748AD2C1F76FF6ECB0CCCD00000000"', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold'}), 'XAU - Gold (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU with full name, do not show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:false}), 'XAU - Gold');
|
||||
});
|
||||
it('to_human interest XAU with full name, show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({full_name:'Gold', show_interest:true}), 'XAU - Gold (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU, do show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:true}), 'XAU (-0.5%pa)');
|
||||
});
|
||||
it('to_human interest XAU, do not show interest', function() {
|
||||
assert.strictEqual(currency.from_json("015841551A748AD2C1F76FF6ECB0CCCD00000000").to_human({show_interest:false}), 'XAU');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar show interest"', function() {
|
||||
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:true}), 'USD - US Dollar (0%pa)');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar do not show interest"', function() {
|
||||
assert.strictEqual(currency.from_json('USD').to_human({full_name:'US Dollar', show_interest:false}), 'USD - US Dollar');
|
||||
});
|
||||
it('to_human with full_name "USD - US Dollar"', function() {
|
||||
assert.strictEqual('USD - US Dollar', currency.from_json('USD').to_human({full_name:'US Dollar'}));
|
||||
});
|
||||
it('to_human with full_name "XRP - Ripples"', function() {
|
||||
assert.strictEqual('XRP - Ripples', currency.from_json('XRP').to_human({full_name:'Ripples'}));
|
||||
});
|
||||
it('to_human human "TIM" without full_name', function() {
|
||||
var cur = currency.from_json("TIM");
|
||||
assert.strictEqual(cur.to_human(), "TIM");
|
||||
});
|
||||
it('to_human "TIM" with null full_name', function() {
|
||||
var cur = currency.from_json("TIM");
|
||||
assert.strictEqual(cur.to_human({full_name: null}), "TIM");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('from_hex', function() {
|
||||
it('"015841551A748AD2C1F76FF6ECB0CCCD00000000" === "XAU (-0.5%pa)"', function() {
|
||||
var cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
assert.strictEqual(cur.to_json(), 'XAU (-0.5%pa)');
|
||||
assert.strictEqual(cur.to_hex(), '015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
assert.strictEqual(cur.to_json(), cur.to_human());
|
||||
});
|
||||
});
|
||||
describe('parse_json', function() {
|
||||
it('should parse a currency object', function() {
|
||||
assert.strictEqual('USD', new currency().parse_json(currency.from_json('USD')).to_json());
|
||||
assert.strictEqual('USD (0.5%pa)', new currency().parse_json(currency.from_json('USD (0.5%pa)')).to_json());
|
||||
});
|
||||
it('should clone for parse_json on itself', function() {
|
||||
var cur = currency.from_json('USD');
|
||||
var cur2 = currency.from_json(cur);
|
||||
assert.strictEqual(cur.to_json(), cur2.to_json());
|
||||
|
||||
cur = currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
cur2 = currency.from_json(cur);
|
||||
assert.strictEqual(cur.to_json(), cur2.to_json());
|
||||
});
|
||||
it('should parse json 0', function() {
|
||||
var cur = currency.from_json(0);
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert.strictEqual(cur.get_iso(), 'XRP');
|
||||
});
|
||||
it('should parse json 0', function() {
|
||||
var cur = currency.from_json('0');
|
||||
assert.strictEqual(cur.to_json(), 'XRP');
|
||||
assert.strictEqual(cur.get_iso(), 'XRP');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is_valid', function() {
|
||||
it('Currency.is_valid("XRP")', function() {
|
||||
assert(currency.is_valid('XRP'));
|
||||
});
|
||||
it('!Currency.is_valid(NaN)', function() {
|
||||
assert(!currency.is_valid(NaN));
|
||||
});
|
||||
it('from_json("XRP").is_valid()', function() {
|
||||
assert(currency.from_json('XRP').is_valid());
|
||||
});
|
||||
it('!from_json(NaN).is_valid()', function() {
|
||||
assert(!currency.from_json(NaN).is_valid());
|
||||
});
|
||||
});
|
||||
describe('clone', function() {
|
||||
it('should clone currency object', function() {
|
||||
var c = currency.from_json('XRP');
|
||||
assert.strictEqual('XRP', c.clone().to_json());
|
||||
});
|
||||
});
|
||||
describe('to_human', function() {
|
||||
it('should generate human string', function() {
|
||||
assert.strictEqual('XRP', currency.from_json('XRP').to_human());
|
||||
});
|
||||
});
|
||||
describe('has_interest', function() {
|
||||
it('should be true for type 1 currency codes', function() {
|
||||
assert(currency.from_hex('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
assert(currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000').has_interest());
|
||||
});
|
||||
it('should be false for type 0 currency codes', function() {
|
||||
assert(!currency.from_hex('0000000000000000000000005553440000000000').has_interest());
|
||||
assert(!currency.from_json('USD').has_interest());
|
||||
});
|
||||
});
|
||||
function precision(num, precision) {
|
||||
return +(Math.round(num + "e+"+precision) + "e-"+precision);
|
||||
}
|
||||
describe('get_interest_at', function() {
|
||||
it('should return demurred value for demurrage currency', function() {
|
||||
var cur = currency.from_json('015841551A748AD2C1F76FF6ECB0CCCD00000000');
|
||||
|
||||
// At start, no demurrage should occur
|
||||
assert.equal(1, cur.get_interest_at(443845330));
|
||||
assert.equal(1, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330))), 14));
|
||||
|
||||
// After one year, 0.5% should have occurred
|
||||
assert.equal(0.995, precision(cur.get_interest_at(443845330 + 31536000), 14));
|
||||
assert.equal(0.995, precision(cur.get_interest_at(new Date(timeUtil.fromRipple(443845330 + 31536000))), 14));
|
||||
|
||||
// After one demurrage period, 1/e should have occurred
|
||||
var epsilon = 1e-14;
|
||||
assert(Math.abs(
|
||||
1/Math.E - cur.get_interest_at(443845330 + 6291418827.05)) < epsilon);
|
||||
|
||||
// One year before start, it should be (roughly) 0.5% higher.
|
||||
assert.equal(1.005, precision(cur.get_interest_at(443845330 - 31536000), 4));
|
||||
|
||||
// One demurrage period before start, rate should be e
|
||||
assert.equal(Math.E, cur.get_interest_at(443845330 - 6291418827.05));
|
||||
});
|
||||
it('should return 0 for currency without interest', function() {
|
||||
var cur = currency.from_json('USD - US Dollar');
|
||||
assert.equal(0, cur.get_interest_at(443845330));
|
||||
assert.equal(0, cur.get_interest_at(443845330 + 31536000));
|
||||
});
|
||||
});
|
||||
describe('get_iso', function() {
|
||||
it('should get "XRP" iso_code', function() {
|
||||
assert.strictEqual('XRP', currency.from_json('XRP').get_iso());
|
||||
});
|
||||
it('should get iso_code', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD - US Dollar').get_iso());
|
||||
});
|
||||
it('should get iso_code', function() {
|
||||
assert.strictEqual('USD', currency.from_json('USD (0.5%pa)').get_iso());
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"hash": "f8f337dee5d5b238a10af4a4d56926ba26c83ee7af5a5a6474340c56f9252df3",
|
||||
"hash": "F8F337DEE5D5B238A10AF4A4D56926BA26C83EE7AF5A5A6474340C56F9252DF3",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "f8d5de632b1d8b64e577c46912cce483d6df4fd4e2cf4a3d586a099de3b27021",
|
||||
"hash": "F8D5DE632B1D8B64E577C46912CCE483D6DF4FD4E2CF4A3D586A099DE3B27021",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -120,7 +120,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "e9004490a92413e92dacd621ac73fd434a8950c350f7572ffeaf4d6aaf8fc288",
|
||||
"hash": "E9004490A92413E92DACD621AC73FD434A8950C350F7572FFEAF4D6AAF8FC288",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -180,7 +180,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "d44bff924d23211b82b8f604af6d92f260f8dd13103a96f03e48825c4a978fd6",
|
||||
"hash": "D44BFF924D23211B82B8F604AF6D92F260F8DD13103A96F03E48825C4A978FD6",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -240,7 +240,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "c978d915bfb17687335cbfc4b207d9e7213bcee35b468c2eee016cdce4edb6e4",
|
||||
"hash": "C978D915BFB17687335CBFC4B207D9E7213BCEE35B468C2EEE016CDCE4EDB6E4",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -372,7 +372,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "31b34fd7c90cdc6cf680a814debc6f616c69275c0e99711f904de088a8ed4b28",
|
||||
"hash": "31B34FD7C90CDC6CF680A814DEBC6F616C69275C0E99711F904DE088A8ED4B28",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
@@ -412,7 +412,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"hash": "260bc2964ffe6d81cb25c152f8054ffb2ce6ed04ff89d8d0d0559bc14bef0e46",
|
||||
"hash": "260BC2964FFE6D81CB25C152F8054FFB2CE6ED04FF89D8D0D0559BC14BEF0E46",
|
||||
"date": "2015-08-12T01:01:10+00:00",
|
||||
"ledger_index": 15202439,
|
||||
"tx": {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"value": "0.001"
|
||||
}
|
||||
},
|
||||
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
|
||||
"paths": "[[{\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"}]]"
|
||||
},
|
||||
"outcome": {
|
||||
"result": "tesSUCCESS",
|
||||
@@ -117,7 +117,7 @@
|
||||
"value": "0.001"
|
||||
}
|
||||
},
|
||||
"paths": "[[{\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
|
||||
"paths": "[[{\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"},{\"account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"currency\":\"USD\"}]]"
|
||||
},
|
||||
"outcome": {
|
||||
"result": "tesSUCCESS",
|
||||
|
||||
12
test/fixtures/api/rippled/account-lines.js
vendored
12
test/fixtures/api/rippled/account-lines.js
vendored
@@ -2,7 +2,7 @@
|
||||
const _ = require('lodash');
|
||||
const BASE_LEDGER_INDEX = 8819951;
|
||||
|
||||
module.exports.normal = function(request, options={}) {
|
||||
module.exports.normal = function(request, options = {}) {
|
||||
_.defaults(options, {
|
||||
ledger: BASE_LEDGER_INDEX
|
||||
});
|
||||
@@ -16,8 +16,7 @@ module.exports.normal = function(request, options={}) {
|
||||
marker: options.marker,
|
||||
limit: request.limit,
|
||||
ledger_index: options.ledger,
|
||||
lines: [
|
||||
{
|
||||
lines: _.filter([{
|
||||
account: 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z',
|
||||
balance: '0',
|
||||
currency: 'ASP',
|
||||
@@ -252,12 +251,12 @@ module.exports.normal = function(request, options={}) {
|
||||
quality_out: 0,
|
||||
freeze: true
|
||||
}
|
||||
]
|
||||
], item => !request.peer || item.account === request.peer)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.counterparty = function(request, options={}) {
|
||||
module.exports.counterparty = function(request, options = {}) {
|
||||
_.defaults(options, {
|
||||
ledger: BASE_LEDGER_INDEX
|
||||
});
|
||||
@@ -271,8 +270,7 @@ module.exports.counterparty = function(request, options={}) {
|
||||
marker: options.marker,
|
||||
limit: request.limit,
|
||||
ledger_index: options.ledger,
|
||||
lines: [
|
||||
{
|
||||
lines: [{
|
||||
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
balance: '0.3488146605801446',
|
||||
currency: 'CHF',
|
||||
|
||||
6
test/fixtures/api/rippled/account-tx.js
vendored
6
test/fixtures/api/rippled/account-tx.js
vendored
@@ -3,9 +3,9 @@
|
||||
const _ = require('lodash');
|
||||
const hashes = require('../../hashes');
|
||||
const addresses = require('../../addresses');
|
||||
const SerializedObject = require('ripple-lib').SerializedObject;
|
||||
const AccountSet = require('./tx/account-set.json');
|
||||
const NotFound = require('./tx/not-found.json');
|
||||
const binary = require('ripple-binary-codec');
|
||||
|
||||
module.exports = function(request, options = {}) {
|
||||
_.defaults(options, {
|
||||
@@ -229,8 +229,8 @@ module.exports = function(request, options = {}) {
|
||||
transactions: [
|
||||
{
|
||||
ledger_index: 348860 - Number(marker || 100),
|
||||
tx_blob: SerializedObject.from_json(tx).to_hex(),
|
||||
meta: SerializedObject.from_json(meta).to_hex(),
|
||||
tx_blob: binary.encode(tx),
|
||||
meta: binary.encode(meta),
|
||||
validated: options.validated
|
||||
}
|
||||
]
|
||||
|
||||
114
test/fixtures/binary-account-transaction.json
vendored
114
test/fixtures/binary-account-transaction.json
vendored
@@ -368,84 +368,56 @@
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
]
|
||||
],
|
||||
@@ -697,84 +669,56 @@
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
]
|
||||
],
|
||||
@@ -794,4 +738,4 @@
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
138
test/fixtures/binary-transaction.json
vendored
138
test/fixtures/binary-transaction.json
vendored
@@ -104,61 +104,41 @@
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rP9Lt7SZUEErkXAXyggceibTCEYbfSyx6n",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rP9Lt7SZUEErkXAXyggceibTCEYbfSyx6n"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rwBWBFZrbLzHoe3PhwWYv89iHJdxAFrxcB",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rwBWBFZrbLzHoe3PhwWYv89iHJdxAFrxcB"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
]
|
||||
],
|
||||
@@ -405,84 +385,56 @@
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMAz5ZnK73nyNUL4foAvaxdreczCkG3vA6"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
}
|
||||
]
|
||||
],
|
||||
@@ -871,60 +823,40 @@
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
|
||||
},
|
||||
{
|
||||
"account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
||||
},
|
||||
{
|
||||
"account": "rP5ShE8dGBH6hHtNvRESdMceen36XFBQmh",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
"account": "rP5ShE8dGBH6hHtNvRESdMceen36XFBQmh"
|
||||
},
|
||||
{
|
||||
"currency": "XRP",
|
||||
"type": 16,
|
||||
"type_hex": "0000000000000010"
|
||||
"currency": "XRP"
|
||||
}
|
||||
]
|
||||
],
|
||||
@@ -1225,4 +1157,4 @@
|
||||
"validated": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
test/fixtures/orderbook.js
vendored
8
test/fixtures/orderbook.js
vendored
@@ -5,9 +5,8 @@
|
||||
const _ = require('lodash');
|
||||
const addresses = require('./addresses');
|
||||
const Meta = require('ripple-lib').Meta;
|
||||
const SerializedObject = require('ripple-lib').SerializedObject;
|
||||
const Types = require('ripple-lib').types;
|
||||
const IOUValue = require('ripple-lib-value').IOUValue;
|
||||
const binary = require('ripple-binary-codec');
|
||||
|
||||
module.exports.FIAT_BALANCE = '10';
|
||||
module.exports.NATIVE_BALANCE = '55';
|
||||
@@ -824,10 +823,7 @@ module.exports.transactionWithCreatedOffer = function(options) {
|
||||
const takerPays = new IOUValue(module.exports.TAKER_PAYS);
|
||||
const quality = takerPays.divide(takerGets);
|
||||
|
||||
const so = new SerializedObject();
|
||||
Types.Quality.serialize(so, quality);
|
||||
|
||||
const BookDirectory = so.to_hex();
|
||||
const BookDirectory = binary.encodeQuality(quality.toString());
|
||||
|
||||
const meta = new Meta({
|
||||
AffectedNodes: [
|
||||
|
||||
2
test/fixtures/schemas/ledgerhash.json
vendored
2
test/fixtures/schemas/ledgerhash.json
vendored
@@ -3,5 +3,5 @@
|
||||
"title": "ledgerhash",
|
||||
"description": "A ledger hash",
|
||||
"type": "string",
|
||||
"format": "ledgerHash"
|
||||
"pattern": "^[A-F0-9]{64}$"
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/* eslint-disable max-len, valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
|
||||
const Ledger = require('ripple-lib').Ledger;
|
||||
|
||||
/**
|
||||
* @param ledger_index {Number}
|
||||
* Expects a corresponding ledger dump in $repo/test/fixtures/ folder
|
||||
*/
|
||||
function create_ledger_test(ledger_index) {
|
||||
describe(String(ledger_index), function() {
|
||||
const path = __dirname + '/fixtures/ledger-full-' + ledger_index + '.json';
|
||||
|
||||
const ledger_raw = fs.readFileSync(path);
|
||||
const ledger_json = JSON.parse(ledger_raw);
|
||||
const ledger = Ledger.from_json(ledger_json);
|
||||
|
||||
const hasAccounts = Array.isArray(ledger_json.accountState)
|
||||
&& ledger_json.accountState.length > 0;
|
||||
|
||||
if (hasAccounts) {
|
||||
it('has account_hash of ' + ledger_json.account_hash, function() {
|
||||
assert.equal(ledger_json.account_hash,
|
||||
ledger.calc_account_hash({sanity_test: true}).to_hex());
|
||||
});
|
||||
}
|
||||
it('has transaction_hash of ' + ledger_json.transaction_hash, function() {
|
||||
assert.equal(ledger_json.transaction_hash,
|
||||
ledger.calc_tx_hash().to_hex());
|
||||
});
|
||||
});
|
||||
}
|
||||
describe('Ledger', function() {
|
||||
// This is the first recorded ledger with a non empty transaction set
|
||||
create_ledger_test(38129);
|
||||
// Because, why not.
|
||||
create_ledger_test(40000);
|
||||
// 1311 AffectedNodes, no accounts
|
||||
create_ledger_test(7501326);
|
||||
|
||||
describe('#calcAccountRootEntryHash', function() {
|
||||
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
|
||||
const account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
const expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
||||
const actualEntryHash = Ledger.calcAccountRootEntryHash(account);
|
||||
|
||||
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calcRippleStateEntryHash', function() {
|
||||
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
|
||||
const account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
const account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
||||
const currency = 'USD';
|
||||
|
||||
const expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
||||
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||
});
|
||||
|
||||
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
|
||||
const account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
||||
const account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
||||
const currency = 'UAM';
|
||||
|
||||
const expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
||||
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
||||
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1.to_hex(), expectedEntryHash);
|
||||
assert.equal(actualEntryHash2.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#calcOfferEntryHash', function() {
|
||||
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
|
||||
const account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
||||
const sequence = 137;
|
||||
const expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
||||
const actualEntryHash = Ledger.calcOfferEntryHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash.to_hex(), expectedEntryHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -5,7 +5,7 @@
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert-diff');
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const Currency = require('ripple-lib').Currency;
|
||||
const OrderbookUtils = require('ripple-lib')._test.OrderbookUtils;
|
||||
const addresses = require('./fixtures/addresses');
|
||||
const fixtures = require('./fixtures/orderbook');
|
||||
const IOUValue = require('ripple-lib-value').IOUValue;
|
||||
@@ -32,10 +32,10 @@ describe('OrderBook Autobridging', function() {
|
||||
issuer_pays: addresses.ISSUER
|
||||
});
|
||||
|
||||
assert.deepEqual(book._legOneBook._currencyGets.to_hex(), Currency.from_json('XRP').to_hex());
|
||||
assert.deepEqual(book._legOneBook._currencyPays.to_hex(), Currency.from_json('USD').to_hex());
|
||||
assert.deepEqual(book._legTwoBook._currencyGets.to_hex(), Currency.from_json('EUR').to_hex());
|
||||
assert.deepEqual(book._legTwoBook._currencyPays.to_hex(), Currency.from_json('XRP').to_hex());
|
||||
assert.deepEqual(book._legOneBook._currencyGets, 'XRP');
|
||||
assert.deepEqual(book._legOneBook._currencyPays, 'USD');
|
||||
assert.deepEqual(book._legTwoBook._currencyGets, 'EUR');
|
||||
assert.deepEqual(book._legTwoBook._currencyPays, 'XRP');
|
||||
});
|
||||
|
||||
it('Compute autobridged offers', function(done) {
|
||||
@@ -843,4 +843,14 @@ describe('OrderBook Autobridging', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('convertOfferQualityToHexFromText', function() {
|
||||
const bookDirectory =
|
||||
'4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D06F4C3362FE1D0';
|
||||
const quality = '195796912.5171664';
|
||||
assert.strictEqual(
|
||||
OrderbookUtils.convertOfferQualityToHexFromText(quality),
|
||||
bookDirectory.slice(-16)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
const assert = require('assert-diff');
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const Currency = require('ripple-lib').Currency;
|
||||
const Amount = require('ripple-lib').Amount;
|
||||
const Meta = require('ripple-lib').Meta;
|
||||
const addresses = require('./fixtures/addresses');
|
||||
@@ -12,7 +11,6 @@ const fixtures = require('./fixtures/orderbook');
|
||||
const IOUValue = require('ripple-lib-value').IOUValue;
|
||||
|
||||
describe('OrderBook', function() {
|
||||
this.timeout(0);
|
||||
|
||||
function createRemote() {
|
||||
const remote = new Remote();
|
||||
@@ -33,10 +31,10 @@ describe('OrderBook', function() {
|
||||
|
||||
assert.deepEqual(book.toJSON(), {
|
||||
taker_gets: {
|
||||
currency: Currency.from_json('XRP').to_hex()
|
||||
currency: 'XRP'
|
||||
},
|
||||
taker_pays: {
|
||||
currency: Currency.from_json('BTC').to_hex(),
|
||||
currency: 'BTC',
|
||||
issuer: addresses.ISSUER
|
||||
}
|
||||
});
|
||||
@@ -49,11 +47,11 @@ describe('OrderBook', function() {
|
||||
|
||||
assert.deepEqual(book.toJSON(), {
|
||||
taker_gets: {
|
||||
currency: Currency.from_json('BTC').to_hex(),
|
||||
currency: 'BTC',
|
||||
issuer: addresses.ISSUER
|
||||
},
|
||||
taker_pays: {
|
||||
currency: Currency.from_json('XRP').to_hex()
|
||||
currency: 'XRP'
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -68,40 +66,155 @@ describe('OrderBook', function() {
|
||||
assert(book.isValid());
|
||||
});
|
||||
|
||||
it('Automatic subscription (based on listeners)', function(done) {
|
||||
const book = createRemote().createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_pays: 'BTC'
|
||||
});
|
||||
|
||||
book.subscribe = function() {
|
||||
done();
|
||||
};
|
||||
|
||||
book.on('model', function() {});
|
||||
});
|
||||
|
||||
it('Subscribe', function(done) {
|
||||
const book = createRemote().createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_pays: 'BTC'
|
||||
const remote = createRemote();
|
||||
const book = remote.createOrderBook({
|
||||
currency_pays: 'XRP',
|
||||
issuer_gets: addresses.ISSUER,
|
||||
currency_gets: 'BTC'
|
||||
});
|
||||
|
||||
let requestedOffers = false;
|
||||
remote.request = function(request) {
|
||||
const response = {};
|
||||
|
||||
book.subscribeTransactions = function() {
|
||||
assert(requestedOffers);
|
||||
done();
|
||||
};
|
||||
if (request.message.command === 'account_info') {
|
||||
response.account_data = {
|
||||
TransferRate: 1002000000
|
||||
};
|
||||
|
||||
book.requestOffers = function(callback) {
|
||||
requestedOffers = true;
|
||||
callback();
|
||||
} else if (request.message.command === 'book_offers') {
|
||||
response.offers = [];
|
||||
}
|
||||
|
||||
request.emit('success', response);
|
||||
};
|
||||
|
||||
book.subscribe();
|
||||
assert.strictEqual(book._subscribed, true);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Subscribe - gets XRP', function(done) {
|
||||
const remote = createRemote();
|
||||
const book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_pays: 'BTC'
|
||||
});
|
||||
|
||||
remote.isConnected = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
remote.request = function(request) {
|
||||
const response = {};
|
||||
if (request.message.command === 'book_offers') {
|
||||
response.offers = [];
|
||||
}
|
||||
|
||||
request.emit('success', response);
|
||||
};
|
||||
|
||||
book.subscribe();
|
||||
assert.strictEqual(book._subscribed, true, '_subscribed');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Subscribe - autobridged', function(done) {
|
||||
|
||||
const remote = createRemote();
|
||||
const book = remote.createOrderBook({
|
||||
currency_pays: 'USD',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_gets: 'BTC',
|
||||
issuer_gets: addresses.ISSUER
|
||||
});
|
||||
|
||||
remote.request = function(request) {
|
||||
const response = {};
|
||||
|
||||
if (request.message.command === 'account_info') {
|
||||
response.account_data = {
|
||||
TransferRate: 1002000000
|
||||
};
|
||||
|
||||
} else if (request.message.command === 'book_offers') {
|
||||
response.offers = [];
|
||||
}
|
||||
|
||||
request.emit('success', response);
|
||||
};
|
||||
|
||||
book.subscribe();
|
||||
assert.strictEqual(book._subscribed, true, '_subscribed');
|
||||
done();
|
||||
});
|
||||
|
||||
it('Automatic subscription (based on listeners)', function(done) {
|
||||
this.timeout(100);
|
||||
|
||||
const remote = createRemote();
|
||||
const book = remote.createOrderBook({
|
||||
currency_gets: 'XRP',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_pays: 'BTC'
|
||||
});
|
||||
|
||||
remote.request = function(request) {
|
||||
const response = {};
|
||||
|
||||
if (request.message.command === 'account_info') {
|
||||
response.account_data = {
|
||||
TransferRate: 1002000000
|
||||
};
|
||||
|
||||
} else if (request.message.command === 'book_offers') {
|
||||
response.offers = [];
|
||||
}
|
||||
|
||||
setImmediate(function() {
|
||||
request.emit('success', response);
|
||||
});
|
||||
};
|
||||
|
||||
book.on('model', function(offers) {
|
||||
assert.strictEqual(offers.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Automatic subscription (based on listeners) - autobridged', function(done) {
|
||||
this.timeout(100);
|
||||
|
||||
const remote = createRemote();
|
||||
const book = remote.createOrderBook({
|
||||
currency_pays: 'USD',
|
||||
issuer_pays: addresses.ISSUER,
|
||||
currency_gets: 'BTC',
|
||||
issuer_gets: addresses.ISSUER
|
||||
});
|
||||
|
||||
remote.request = function(request) {
|
||||
const response = {};
|
||||
|
||||
if (request.message.command === 'account_info') {
|
||||
response.account_data = {
|
||||
TransferRate: 1002000000
|
||||
};
|
||||
|
||||
} else if (request.message.command === 'book_offers') {
|
||||
response.offers = [];
|
||||
}
|
||||
|
||||
setImmediate(function() {
|
||||
request.emit('success', response);
|
||||
});
|
||||
};
|
||||
|
||||
book.on('model', function(offers) {
|
||||
assert.strictEqual(offers.length, 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Unsubscribe', function(done) {
|
||||
@@ -2332,10 +2445,10 @@ describe('OrderBook', function() {
|
||||
command: 'book_offers',
|
||||
id: undefined,
|
||||
taker_gets: {
|
||||
currency: '0000000000000000000000000000000000000000'
|
||||
currency: 'XRP'
|
||||
},
|
||||
taker_pays: {
|
||||
currency: '0000000000000000000000005553440000000000',
|
||||
currency: 'USD',
|
||||
issuer: addresses.ISSUER
|
||||
},
|
||||
taker: 'rrrrrrrrrrrrrrrrrrrrBZbvji',
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
|
||||
const assert = require('assert-diff');
|
||||
const lodash = require('lodash');
|
||||
const ripple = require('ripple-lib');
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const Server = require('ripple-lib').Server;
|
||||
const Transaction = require('ripple-lib').Transaction;
|
||||
const UInt160 = require('ripple-lib').UInt160;
|
||||
const Currency = require('ripple-lib').Currency;
|
||||
const Amount = require('ripple-lib').Amount;
|
||||
const PathFind = require('ripple-lib')._test.PathFind;
|
||||
const Log = require('ripple-lib')._test.Log;
|
||||
const ACCOUNT_ONE = require('ripple-lib')._test.constants.ACCOUNT_ONE;
|
||||
const RippleError = require('ripple-lib').RippleError;
|
||||
|
||||
let options;
|
||||
let remote;
|
||||
@@ -30,7 +29,7 @@ const TX_JSON = {
|
||||
Flags: 0,
|
||||
TransactionType: 'Payment',
|
||||
Account: ADDRESS,
|
||||
Destination: ripple.UInt160.ACCOUNT_ONE,
|
||||
Destination: ACCOUNT_ONE,
|
||||
Amount: {
|
||||
value: '1',
|
||||
currency: 'USD',
|
||||
@@ -921,7 +920,7 @@ describe('Remote', function() {
|
||||
value: 1
|
||||
}), {
|
||||
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
currency: '0000000000000000000000005553440000000000'
|
||||
currency: 'USD'
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1492,7 +1491,7 @@ describe('Remote', function() {
|
||||
|
||||
it('Construct account_tx request', function() {
|
||||
let request = remote.requestAccountTransactions({
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
limit: 5,
|
||||
@@ -1503,7 +1502,7 @@ describe('Remote', function() {
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_tx',
|
||||
id: undefined,
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
binary: true,
|
||||
@@ -1513,14 +1512,14 @@ describe('Remote', function() {
|
||||
});
|
||||
|
||||
request = remote.requestAccountTransactions({
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
min_ledger: -1,
|
||||
max_ledger: -1
|
||||
});
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_tx',
|
||||
id: undefined,
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
binary: true,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1
|
||||
@@ -1528,7 +1527,7 @@ describe('Remote', function() {
|
||||
});
|
||||
it('Construct account_tx request -- no binary', function() {
|
||||
const request = remote.requestAccountTransactions({
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
limit: 5,
|
||||
@@ -1540,7 +1539,7 @@ describe('Remote', function() {
|
||||
assert.deepEqual(request.message, {
|
||||
command: 'account_tx',
|
||||
id: undefined,
|
||||
account: UInt160.ACCOUNT_ONE,
|
||||
account: ACCOUNT_ONE,
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
binary: false,
|
||||
@@ -1612,13 +1611,13 @@ describe('Remote', function() {
|
||||
command: 'book_offers',
|
||||
id: undefined,
|
||||
taker_gets: {
|
||||
currency: Currency.from_human('USD').to_hex(),
|
||||
currency: 'USD',
|
||||
issuer: ADDRESS
|
||||
},
|
||||
taker_pays: {
|
||||
currency: Currency.from_human('XRP').to_hex()
|
||||
currency: 'XRP'
|
||||
},
|
||||
taker: UInt160.ACCOUNT_ONE
|
||||
taker: ACCOUNT_ONE
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1639,13 +1638,13 @@ describe('Remote', function() {
|
||||
command: 'book_offers',
|
||||
id: undefined,
|
||||
taker_gets: {
|
||||
currency: Currency.from_human('USD').to_hex(),
|
||||
currency: 'USD',
|
||||
issuer: ADDRESS
|
||||
},
|
||||
taker_pays: {
|
||||
currency: Currency.from_human('XRP').to_hex()
|
||||
currency: 'XRP'
|
||||
},
|
||||
taker: UInt160.ACCOUNT_ONE,
|
||||
taker: ACCOUNT_ONE,
|
||||
ledger_hash: LEDGER_HASH,
|
||||
limit: 10
|
||||
});
|
||||
@@ -1902,30 +1901,107 @@ describe('Remote', function() {
|
||||
},
|
||||
source_currencies: [{
|
||||
issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
|
||||
currency: '0000000000000000000000004254430000000000'
|
||||
currency: 'BTC'
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
it('createPathFind', function() {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
remote._servers = servers;
|
||||
|
||||
const pathfind = remote.createPathFind({
|
||||
src_account: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
dst_account: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
|
||||
dst_amount: '1/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
describe('createPathFind', function() {
|
||||
const pathfindParams = {
|
||||
src_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b',
|
||||
dst_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
|
||||
dst_amount: '0.001/USD/rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
src_currencies: [{
|
||||
currency: 'BTC', issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6'
|
||||
}]
|
||||
};
|
||||
const response1 = {
|
||||
id: 1,
|
||||
result: {
|
||||
alternatives: [],
|
||||
destination_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
|
||||
destination_amount: {
|
||||
currency: 'USD',
|
||||
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
value: '0.001'
|
||||
},
|
||||
full_reply: false,
|
||||
id: 1,
|
||||
source_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b'
|
||||
},
|
||||
status: 'success',
|
||||
type: 'response'
|
||||
};
|
||||
const response2 = {
|
||||
alternatives: [],
|
||||
destination_account: 'rB31JZB8o5BvxyvPiRABatGsXwKYVaqGmN',
|
||||
destination_amount: {
|
||||
currency: 'USD',
|
||||
issuer: 'rGr9PjmVe7MqEXTSbd3njhgJc2s5vpHV54',
|
||||
value: '0.001'
|
||||
},
|
||||
full_reply: true,
|
||||
id: 1,
|
||||
source_account: 'r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b',
|
||||
type: 'path_find'
|
||||
};
|
||||
|
||||
it('createPathFind', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006')
|
||||
];
|
||||
|
||||
remote._servers = servers;
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
setTimeout(() => {
|
||||
req.emit('success', response1, this);
|
||||
setTimeout(() => {
|
||||
remote._handleMessage(response2, this);
|
||||
}, 5);
|
||||
}, 5);
|
||||
};
|
||||
|
||||
remote.createPathFind(pathfindParams, (err, result) => {
|
||||
(function() {})(err);
|
||||
assert.deepEqual(result, response2);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// TODO: setup a mock server to provide a response
|
||||
pathfind.on('update', message => console.log(message));
|
||||
it('createPathFind - timeout', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
remote.submission_timeout = 50;
|
||||
remote._servers = servers;
|
||||
const pathfind = remote.createPathFind(pathfindParams);
|
||||
|
||||
pathfind.on('error', (error) => {
|
||||
assert(error instanceof RippleError);
|
||||
assert.strictEqual(error.result, 'tejTimeout');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('createPathFind - throw error without callback if already running', function() {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
remote._servers = servers;
|
||||
|
||||
const pathfind = remote.createPathFind(pathfindParams);
|
||||
pathfind.on('error', function() { });
|
||||
assert.throws(
|
||||
function() {
|
||||
remote.createPathFind(pathfindParams);
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
it('Construct path_find create request', function() {
|
||||
@@ -1951,7 +2027,7 @@ describe('Remote', function() {
|
||||
},
|
||||
source_currencies: [{
|
||||
issuer: 'rwxBjBC9fPzyQ9GgPZw6YYLNeRTSx5c2W6',
|
||||
currency: '0000000000000000000000004254430000000000'
|
||||
currency: 'BTC'
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ const assert = require('assert');
|
||||
const Request = require('ripple-lib').Request;
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const Server = require('ripple-lib').Server;
|
||||
const Currency = require('ripple-lib').Currency;
|
||||
const RippleError = require('ripple-lib').RippleError;
|
||||
|
||||
function makeServer(url) {
|
||||
@@ -49,6 +48,10 @@ describe('Request', function() {
|
||||
},
|
||||
on: function() {
|
||||
},
|
||||
once: function() {
|
||||
},
|
||||
removeListener: function() {
|
||||
},
|
||||
isConnected: function() {
|
||||
return true;
|
||||
}
|
||||
@@ -97,13 +100,7 @@ describe('Request', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- filterRequest', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
describe('Broadcast', function() {
|
||||
|
||||
const successResponse = {
|
||||
account_data: {
|
||||
@@ -122,76 +119,6 @@ describe('Request', function() {
|
||||
ledger_current_index: 9022821,
|
||||
validated: false
|
||||
};
|
||||
const errorResponse = {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: {
|
||||
id: 3,
|
||||
status: 'error',
|
||||
type: 'response',
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
error: 'actNotFound',
|
||||
error_code: 15,
|
||||
error_message: 'Account not found.',
|
||||
ledger_current_index: 9022856,
|
||||
request: {
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
command: 'account_info',
|
||||
id: 3
|
||||
},
|
||||
validated: false
|
||||
}
|
||||
};
|
||||
|
||||
function checkRequest(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'account_info');
|
||||
}
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
req.emit('error', errorResponse);
|
||||
};
|
||||
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setImmediate(function() {
|
||||
req.emit('success', successResponse);
|
||||
});
|
||||
};
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(function(res) {
|
||||
return res
|
||||
&& typeof res === 'object'
|
||||
&& !res.hasOwnProperty('error');
|
||||
});
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.deepEqual(res, successResponse);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- filterRequest -- no success', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
const errorResponse = {
|
||||
error: 'remoteError',
|
||||
@@ -220,295 +147,283 @@ describe('Request', function() {
|
||||
assert.strictEqual(req.message.command, 'account_info');
|
||||
}
|
||||
|
||||
function sendError(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
req.emit('error', errorResponse);
|
||||
}
|
||||
|
||||
servers[0]._request = sendError;
|
||||
servers[1]._request = sendError;
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(function(res) {
|
||||
function isSuccessResponse(res) {
|
||||
return res
|
||||
&& typeof res === 'object'
|
||||
&& !res.hasOwnProperty('error');
|
||||
});
|
||||
|
||||
request.callback(function(err) {
|
||||
setImmediate(function() {
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.deepEqual(err, new RippleError(errorResponse));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- filterRequest -- ledger prefilter', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
const successResponse = {
|
||||
account_data: {
|
||||
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
Balance: '13188802787',
|
||||
Flags: 0,
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
OwnerCount: 17,
|
||||
PreviousTxnID:
|
||||
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
|
||||
PreviousTxnLgrSeq: 8828020,
|
||||
Sequence: 1406,
|
||||
index:
|
||||
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
|
||||
},
|
||||
ledger_current_index: 9022821,
|
||||
validated: false
|
||||
};
|
||||
|
||||
function checkRequest(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'account_info');
|
||||
}
|
||||
|
||||
servers[0]._request = function() {
|
||||
assert(false, 'Should not request; server does not have ledger');
|
||||
};
|
||||
it('Send request', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setImmediate(function() {
|
||||
req.emit('success', successResponse);
|
||||
});
|
||||
};
|
||||
let requests = 0;
|
||||
|
||||
servers[0]._ledgerRanges.addRange(5, 6);
|
||||
servers[1]._ledgerRanges.addRange(1, 4);
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('error', errorResponse, this);
|
||||
}, 2);
|
||||
};
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
request.selectLedger(4);
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('success', successResponse, this);
|
||||
}, 10);
|
||||
};
|
||||
|
||||
request.filter(function(res) {
|
||||
return res
|
||||
&& typeof res === 'object'
|
||||
&& !res.hasOwnProperty('error');
|
||||
});
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(res, successResponse);
|
||||
done();
|
||||
});
|
||||
});
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
it('Send request -- filterRequest -- server reconnects', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
let requests = 0;
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
const successResponse = {
|
||||
account_data: {
|
||||
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
Balance: '13188802787',
|
||||
Flags: 0,
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
OwnerCount: 17,
|
||||
PreviousTxnID:
|
||||
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
|
||||
PreviousTxnLgrSeq: 8828020,
|
||||
Sequence: 1406,
|
||||
index:
|
||||
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
|
||||
},
|
||||
ledger_current_index: 9022821,
|
||||
validated: false
|
||||
};
|
||||
const errorResponse = {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: {
|
||||
id: 3,
|
||||
status: 'error',
|
||||
type: 'response',
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
error: 'actNotFound',
|
||||
error_code: 15,
|
||||
error_message: 'Account not found.',
|
||||
ledger_current_index: 9022856,
|
||||
request: {
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
command: 'account_info',
|
||||
id: 3
|
||||
},
|
||||
validated: false
|
||||
}
|
||||
};
|
||||
|
||||
function checkRequest(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'account_info');
|
||||
}
|
||||
|
||||
servers[0]._connected = false;
|
||||
servers[0]._shouldConnect = true;
|
||||
servers[0].removeAllListeners('connect');
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
req.emit('success', successResponse);
|
||||
};
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
|
||||
req.emit('error', errorResponse);
|
||||
|
||||
servers[0]._connected = true;
|
||||
servers[0].emit('connect');
|
||||
};
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(function(res) {
|
||||
return res
|
||||
&& typeof res === 'object'
|
||||
&& !res.hasOwnProperty('error');
|
||||
});
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
setImmediate(function() {
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.deepEqual(res, successResponse);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- filterRequest -- server fails to reconnect',
|
||||
function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
it('Send request -- timeout', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
let requests = 0;
|
||||
|
||||
const successResponse = {
|
||||
account_data: {
|
||||
Account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
Balance: '13188802787',
|
||||
Flags: 0,
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
OwnerCount: 17,
|
||||
PreviousTxnID:
|
||||
'C6A2313CD9E34FFA3EB42F82B2B30F7FE12A045F1F4FDDAF006B25D7286536DD',
|
||||
PreviousTxnLgrSeq: 8828020,
|
||||
Sequence: 1406,
|
||||
index:
|
||||
'4F83A2CF7E70F77F79A307E6A472BFC2585B806A70833CCD1C26105BAE0D6E05'
|
||||
},
|
||||
ledger_current_index: 9022821,
|
||||
validated: false
|
||||
};
|
||||
const errorResponse = {
|
||||
error: 'remoteError',
|
||||
error_message: 'Remote reported an error.',
|
||||
remote: {
|
||||
id: 3,
|
||||
status: 'error',
|
||||
type: 'response',
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
error: 'actNotFound',
|
||||
error_code: 15,
|
||||
error_message: 'Account not found.',
|
||||
ledger_current_index: 9022856,
|
||||
request: {
|
||||
account: 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC',
|
||||
command: 'account_info',
|
||||
id: 3
|
||||
},
|
||||
validated: false
|
||||
}
|
||||
};
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('error', errorResponse, this);
|
||||
}, 15);
|
||||
};
|
||||
|
||||
function checkRequest(req) {
|
||||
assert(req instanceof Request);
|
||||
assert.strictEqual(typeof req.message, 'object');
|
||||
assert.strictEqual(req.message.command, 'account_info');
|
||||
}
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('success', successResponse, this);
|
||||
}, 15);
|
||||
};
|
||||
|
||||
servers[0]._connected = false;
|
||||
servers[0]._shouldConnect = true;
|
||||
servers[0].removeAllListeners('connect');
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
setTimeout(function() {
|
||||
servers[0]._connected = true;
|
||||
servers[0].emit('connect');
|
||||
}, 20);
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
req.emit('success', successResponse);
|
||||
};
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
req.emit('error', errorResponse);
|
||||
};
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
request.setTimeout(10);
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
request.setReconnectTimeout(10);
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(function(res) {
|
||||
return res
|
||||
&& typeof res === 'object'
|
||||
&& !res.hasOwnProperty('error');
|
||||
request.callback(function(err, res) {
|
||||
setTimeout(function() {
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.strictEqual(res, undefined);
|
||||
assert(err instanceof RippleError);
|
||||
assert.strictEqual(err.result, 'tejTimeout');
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
});
|
||||
|
||||
request.callback(function(err) {
|
||||
setTimeout(function() {
|
||||
// Wait for the request that would emit 'success' to time out
|
||||
assert.deepEqual(err, new RippleError(errorResponse));
|
||||
assert.deepEqual(servers[0].listeners('connect'), []);
|
||||
it('Send request -- no success', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
function sendError(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('error', errorResponse, this);
|
||||
}, 2);
|
||||
}
|
||||
|
||||
servers[0]._request = sendError;
|
||||
servers[1]._request = sendError;
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
request.callback(function(err) {
|
||||
setImmediate(function() {
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.deepEqual(err, new RippleError(errorResponse));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- ledger prefilter', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
servers[0]._request = function() {
|
||||
assert(false, 'Should not request; server does not have ledger');
|
||||
};
|
||||
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setImmediate(() => {
|
||||
req.emit('success', successResponse, this);
|
||||
});
|
||||
};
|
||||
|
||||
servers[0]._ledgerRanges.addRange(5, 6);
|
||||
servers[1]._ledgerRanges.addRange(1, 4);
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
request.selectLedger(4);
|
||||
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
assert.deepEqual(res, successResponse);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- server reconnects', function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5006'),
|
||||
makeServer('wss://localhost:5007')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
servers[0]._connected = false;
|
||||
servers[0]._shouldConnect = true;
|
||||
servers[0].removeAllListeners('connect');
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('success', successResponse, this);
|
||||
}, 4);
|
||||
};
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
|
||||
setTimeout(() => {
|
||||
req.emit('error', errorResponse, this);
|
||||
|
||||
setTimeout(function() {
|
||||
servers[0]._connected = true;
|
||||
servers[0].emit('connect');
|
||||
}, 4);
|
||||
}, 2);
|
||||
};
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
request.callback(function(err, res) {
|
||||
assert.ifError(err);
|
||||
setImmediate(function() {
|
||||
assert.strictEqual(requests, 2, 'Failed to broadcast');
|
||||
assert.deepEqual(res, successResponse);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Send request -- server fails to reconnect',
|
||||
function(done) {
|
||||
const servers = [
|
||||
makeServer('wss://localhost:5008'),
|
||||
makeServer('wss://localhost:5009')
|
||||
];
|
||||
|
||||
let requests = 0;
|
||||
|
||||
servers[0]._connected = false;
|
||||
servers[0]._shouldConnect = true;
|
||||
servers[0].removeAllListeners('connect');
|
||||
|
||||
setTimeout(function() {
|
||||
servers[0]._connected = true;
|
||||
servers[0].emit('connect');
|
||||
}, 20);
|
||||
|
||||
servers[0]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('success', successResponse, this);
|
||||
}, 1);
|
||||
};
|
||||
servers[1]._request = function(req) {
|
||||
++requests;
|
||||
checkRequest(req);
|
||||
setTimeout(() => {
|
||||
req.emit('error', errorResponse, this);
|
||||
}, 2);
|
||||
};
|
||||
|
||||
const remote = new Remote();
|
||||
remote._connected = true;
|
||||
remote._servers = servers;
|
||||
|
||||
const request = new Request(remote, 'account_info');
|
||||
request.setReconnectTimeout(10);
|
||||
request.message.account = 'rnoFoLJmqmXe7a7iswk19yfdMHQkbQNrKC';
|
||||
|
||||
request.filter(isSuccessResponse);
|
||||
|
||||
request.callback(function(err) {
|
||||
setTimeout(function() {
|
||||
// Wait for the request that would emit 'success' to time out
|
||||
assert.deepEqual(err, new RippleError(errorResponse));
|
||||
assert.deepEqual(servers[0].listeners('connect'), []);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1024,11 +939,11 @@ describe('Request', function() {
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
@@ -1056,11 +971,11 @@ describe('Request', function() {
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('CNY').to_hex(),
|
||||
'currency': 'CNY',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
@@ -1085,11 +1000,11 @@ describe('Request', function() {
|
||||
assert.deepEqual(request.message.books, [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': '0000000000000000000000004555520000000000', // EUR hex
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': '0000000000000000000000005553440000000000', // USD hex
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
@@ -1143,11 +1058,11 @@ describe('Request', function() {
|
||||
|
||||
assert.deepEqual(request.message.books, [{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true,
|
||||
@@ -1179,11 +1094,11 @@ describe('Request', function() {
|
||||
|
||||
assert.deepEqual(request.message.books, [{
|
||||
'taker_gets': {
|
||||
'currency': Currency.from_json('EUR').to_hex(),
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': Currency.from_json('USD').to_hex(),
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'both': true
|
||||
@@ -1234,11 +1149,11 @@ describe('Request', function() {
|
||||
'books': [
|
||||
{
|
||||
'taker_gets': {
|
||||
'currency': '0000000000000000000000004555520000000000',
|
||||
'currency': 'EUR',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'taker_pays': {
|
||||
'currency': '0000000000000000000000005553440000000000',
|
||||
'currency': 'USD',
|
||||
'issuer': 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
'snapshot': true
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable max-len*/
|
||||
|
||||
const assert = require('assert');
|
||||
const lodash = require('lodash');
|
||||
const SerializedObject = require('ripple-lib').SerializedObject;
|
||||
const Amount = require('ripple-lib').Amount;
|
||||
const sjclcodec = require('sjcl-codec');
|
||||
|
||||
// Shortcuts
|
||||
const hex = sjclcodec.hex;
|
||||
const utf8 = sjclcodec.utf8String;
|
||||
|
||||
describe('Serialized object', function() {
|
||||
|
||||
function convertStringToHex(string) {
|
||||
return hex.fromBits(utf8.toBits(string)).toUpperCase();
|
||||
}
|
||||
|
||||
describe('#from_json(v).to_json() == v', function() {
|
||||
it('outputs same as passed to from_json', function() {
|
||||
const input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Flags: 0,
|
||||
Paths: [[
|
||||
{
|
||||
account: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
currency: 'USD',
|
||||
issuer: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
type: 49,
|
||||
type_hex: '0000000000000031'
|
||||
},
|
||||
{
|
||||
currency: 'XRP',
|
||||
type: 16,
|
||||
type_hex: '0000000000000010'
|
||||
}
|
||||
]],
|
||||
SendMax: {
|
||||
currency: 'USD',
|
||||
issuer: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
value: '2.74579388'
|
||||
},
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
const output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.deepEqual(input_json, output_json);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#from_json(v).to_json() == v -- invalid amount', function() {
|
||||
it('outputs same as passed to from_json', function() {
|
||||
const input_json = {
|
||||
Account: 'rUR9gTCcrUY9fMkz9rwcM9urPREh3LKXoW',
|
||||
Fee: '10',
|
||||
Flags: 0,
|
||||
Sequence: 65,
|
||||
SigningPubKey: '033D0B1FB932E0408C119107483190B61561DCE8529E29CB5D1C69128DA54DA715',
|
||||
TakerGets: '2188313981504612096',
|
||||
TakerPays: {
|
||||
currency: 'USD',
|
||||
issuer: 'r9rp9MUFRJVCVLRm3MTmUvSPNBSL3BuEFx',
|
||||
value: '99999999999'
|
||||
},
|
||||
TransactionType: 'OfferCreate',
|
||||
TxnSignature: '304602210085C6AE945643150E6D450CF796E45D74FB24B4E03E964A29CC6AFFEB346C77C80221009BE1B6678CF6C2E61F8F2696144C75AFAF66DF4FC0733DF9118EDEFEEFE33243'
|
||||
};
|
||||
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(input_json).to_json();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#from_json(v).to_json() == v -- invalid amount, strict_mode = false', function() {
|
||||
it('outputs same as passed to from_json', function() {
|
||||
const input_json = {
|
||||
Account: 'rUR9gTCcrUY9fMkz9rwcM9urPREh3LKXoW',
|
||||
Fee: '10',
|
||||
Flags: 0,
|
||||
Sequence: 65,
|
||||
SigningPubKey: '033D0B1FB932E0408C119107483190B61561DCE8529E29CB5D1C69128DA54DA715',
|
||||
TakerGets: '2188313981504612096',
|
||||
TakerPays: {
|
||||
currency: 'USD',
|
||||
issuer: 'r9rp9MUFRJVCVLRm3MTmUvSPNBSL3BuEFx',
|
||||
value: '99999999999'
|
||||
},
|
||||
TransactionType: 'OfferCreate',
|
||||
TxnSignature: 'FFFFFF210085C6AE945643150E6D450CF796E45D74FB24B4E03E964A29CC6AFFEB346C77C80221009BE1B6678CF6C2E61F8F2696144C75AFAF66DF4FC0733DF9118EDEFEEFE33243'
|
||||
};
|
||||
|
||||
const strictMode = Amount.strict_mode;
|
||||
Amount.strict_mode = false;
|
||||
const output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.deepEqual(input_json, output_json);
|
||||
Amount.strict_mode = strictMode;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#from_json', function() {
|
||||
it('understands TransactionType as a Number', function() {
|
||||
const input_json = {
|
||||
// no non required fields
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02',// VL field ;)
|
||||
TransactionType: 0 //
|
||||
};
|
||||
const output_json = SerializedObject.from_json(input_json).to_json();
|
||||
|
||||
assert.equal(0, input_json.TransactionType);
|
||||
assert.equal('Payment', output_json.TransactionType);
|
||||
});
|
||||
|
||||
it('understands LedgerEntryType as a Number', function() {
|
||||
const input_json = {
|
||||
// no, non required fields
|
||||
LedgerEntryType: 100,
|
||||
Flags: 0,
|
||||
Indexes: [],
|
||||
RootIndex: '000360186E008422E06B72D5B275E29EE3BE9D87A370F424E0E7BF613C465909'
|
||||
};
|
||||
|
||||
const output_json = SerializedObject.from_json(input_json).to_json();
|
||||
assert.equal(100, input_json.LedgerEntryType);
|
||||
assert.equal('DirectoryNode', output_json.LedgerEntryType);
|
||||
});
|
||||
|
||||
it('checks for missing required fields', function() {
|
||||
const input_json = {
|
||||
TransactionType: 'Payment',
|
||||
// no non required fields
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02'
|
||||
};
|
||||
|
||||
Object.keys(input_json).slice(1).forEach(function(k) {
|
||||
const bad_json = lodash.merge({}, input_json);
|
||||
delete bad_json[k];
|
||||
|
||||
assert.strictEqual(bad_json[k], undefined);
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(bad_json);
|
||||
}, new RegExp('Payment is missing fields: \\["' + k + '"\\]'));
|
||||
|
||||
});
|
||||
});
|
||||
it('checks for unknown fields', function() {
|
||||
const input_json = {
|
||||
TransactionType: 'Payment',
|
||||
// no non required fields
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Fee: '15',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02'
|
||||
};
|
||||
|
||||
Object.keys(input_json).slice(1).forEach(function(k) {
|
||||
const bad_json = lodash.merge({}, input_json);
|
||||
bad_json[k + 'z'] = bad_json[k];
|
||||
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(bad_json);
|
||||
}, new RegExp('Payment has unknown fields: \\["' + k + 'z"\\]'));
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('Format validation', function() {
|
||||
// Peercover actually had a problem submitting transactions without a `Fee`
|
||||
// and rippled was only informing of 'transaction is invalid'
|
||||
it('should throw an Error when there is a missing field', function() {
|
||||
const input_json = {
|
||||
Account: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Amount: '274579388',
|
||||
Destination: 'r4qLSAzv4LZ9TLsR7diphGwKnSEAMQTSjS',
|
||||
Sequence: 351,
|
||||
SigningPubKey: '02854B06CE8F3E65323F89260E9E19B33DA3E01B30EA4CA172612DE77973FAC58A',
|
||||
TransactionType: 'Payment',
|
||||
TxnSignature: '30450221009DA3A42DD25E3B22EC45AD8BA8FC7A954264264A816D300B2DF69F814D7D4DD2022072C9627F97EEC6DA13DE841E06E2CD985EF06A0FBB15DDBF0800D0730C8986BF'
|
||||
};
|
||||
assert.throws(
|
||||
function() {
|
||||
SerializedObject.from_json(input_json);
|
||||
},
|
||||
/Payment is missing fields: \["Fee"\]/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Memos', function() {
|
||||
let input_json;
|
||||
|
||||
beforeEach(function() {
|
||||
input_json = {
|
||||
'Flags': 2147483648,
|
||||
'TransactionType': 'Payment',
|
||||
'Account': 'rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF',
|
||||
'Amount': '1',
|
||||
'Destination': 'radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung',
|
||||
'Sequence': 281,
|
||||
'SigningPubKey': '03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51',
|
||||
'Fee': '12000',
|
||||
'LastLedgerSequence': 10074214,
|
||||
'TxnSignature': '304402201180636F2CE215CE97A29CD302618FAE60D63EBFC8903DE17A356E857A449C430220290F4A54F9DE4AC79034C8BEA5F1F8757F7505F1A6FF04D2E19B6D62E867256B'
|
||||
};
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, all strings text/plain ', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '74657374',
|
||||
MemoFormat: '74657874',
|
||||
MemoData: '736F6D652064617461'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'text';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = 'some data';
|
||||
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
|
||||
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('text');
|
||||
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, all strings, invalid MemoFormat', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
'Memo':
|
||||
{
|
||||
MemoType: '74657374',
|
||||
MemoFormat: '6170706C69636174696F6E2F6A736F6E',
|
||||
MemoData: '736F6D652064617461'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const so = SerializedObject.from_json(input_json).to_json();
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'application/json';
|
||||
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
|
||||
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('application/json');
|
||||
input_json.Memos[0].Memo.MemoData = convertStringToHex('some data');
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
assert.strictEqual(input_json.Memos[0].Memo.parsed_memo_data, undefined);
|
||||
});
|
||||
|
||||
it('should serialize and parse - full memo, json data, valid MemoFormat, ignored field', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '74657374',
|
||||
MemoFormat: '6A736F6E',
|
||||
ignored: 'ignored',
|
||||
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const so = SerializedObject.from_json(input_json).to_json();
|
||||
delete input_json.Memos[0].Memo.ignored;
|
||||
input_json.Memos[0].Memo.parsed_memo_type = 'test';
|
||||
input_json.Memos[0].Memo.parsed_memo_format = 'json';
|
||||
input_json.Memos[0].Memo.parsed_memo_data = {
|
||||
'string': 'some_string',
|
||||
'boolean': true
|
||||
};
|
||||
input_json.Memos[0].Memo.MemoType = convertStringToHex('test');
|
||||
input_json.Memos[0].Memo.MemoFormat = convertStringToHex('json');
|
||||
input_json.Memos[0].Memo.MemoData = convertStringToHex(JSON.stringify(
|
||||
{
|
||||
'string': 'some_string',
|
||||
'boolean': true
|
||||
}
|
||||
));
|
||||
|
||||
assert.deepEqual(so, input_json);
|
||||
});
|
||||
|
||||
it('should throw an error - invalid Memo field', function() {
|
||||
input_json.Memos = [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '74657374',
|
||||
MemoField: '6A736F6E',
|
||||
MemoData: '7B22737472696E67223A22736F6D655F737472696E67222C22626F6F6C65616E223A747275657D'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
assert.throws(function() {
|
||||
SerializedObject.from_json(input_json);
|
||||
}, /^Error: JSON contains unknown field: "MemoField" \(Memo\) \(Memos\)/);
|
||||
});
|
||||
|
||||
|
||||
it('should serialize json with memo - match hex output', function() {
|
||||
input_json = {
|
||||
Flags: 2147483648,
|
||||
TransactionType: 'Payment',
|
||||
Account: 'rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF',
|
||||
Amount: '1',
|
||||
Destination: 'radqi6ppXFxVhJdjzaATRBxdrPcVTf1Ung',
|
||||
Memos: [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '696D616765'
|
||||
}
|
||||
}
|
||||
],
|
||||
Sequence: 294,
|
||||
SigningPubKey: '03D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C51',
|
||||
Fee: '12000',
|
||||
LastLedgerSequence: 10404607,
|
||||
TxnSignature: '304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9'
|
||||
};
|
||||
|
||||
const serializedHex = '12000022800000002400000126201B009EC2FF614000000000000001684000000000002EE0732103D642E6457B8AB4D140E2C66EB4C484FAFB1BF267CB578EC4815FE6CD06379C517446304402206B53EDFA6EFCF6FE5BA76C81BABB60A3B55E9DE8A1462DEDC5F387879575E498022015AE7B59AA49E735D7F2E252802C4406CD00689BCE5057C477FE979D38D2DAC9811426C4CFB3BD05A9AA23936F2E81634C66A9820C9483143DD06317D19C6110CAFF150AE528F58843BE2CA1F9EA7C05696D616765E1F1';
|
||||
assert.strictEqual(SerializedObject.from_json(input_json).to_hex(), serializedHex);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,8 @@ const ws = require('ws');
|
||||
const lodash = require('lodash');
|
||||
const assert = require('assert-diff');
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const SerializedObject = require('ripple-lib').SerializedObject;
|
||||
const binary = require('ripple-binary-codec');
|
||||
const utils = require('ripple-lib').utils;
|
||||
const Transaction = require('ripple-lib').Transaction;
|
||||
const TransactionManager = require('ripple-lib')._test.TransactionManager;
|
||||
|
||||
@@ -335,11 +336,12 @@ describe('TransactionManager', function() {
|
||||
|
||||
const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
|
||||
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
|
||||
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
|
||||
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
|
||||
tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx),
|
||||
meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta)
|
||||
});
|
||||
|
||||
const hash = new SerializedObject(binaryTx.tx_blob).hash(0x54584E00).to_hex();
|
||||
const prefix = (0x54584E00).toString(16);
|
||||
const hash = utils.sha512half(new Buffer(prefix + binaryTx.tx_blob, 'hex'));
|
||||
|
||||
transaction.addId(hash);
|
||||
|
||||
@@ -364,8 +366,8 @@ describe('TransactionManager', function() {
|
||||
|
||||
const binaryTx = lodash.extend({}, ACCOUNT_TX_TRANSACTION, {
|
||||
ledger_index: ACCOUNT_TX_TRANSACTION.tx.ledger_index,
|
||||
tx_blob: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.tx).to_hex(),
|
||||
meta: SerializedObject.from_json(ACCOUNT_TX_TRANSACTION.meta).to_hex()
|
||||
tx_blob: binary.encode(ACCOUNT_TX_TRANSACTION.tx),
|
||||
meta: binary.encode(ACCOUNT_TX_TRANSACTION.meta)
|
||||
});
|
||||
|
||||
transactionManager._request = function() {
|
||||
@@ -417,9 +419,8 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.once('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
|
||||
@@ -457,9 +458,8 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.once('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
req.sendResponse(SUBMIT_TEC_RESPONSE, {id: m.id});
|
||||
@@ -500,7 +500,7 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
const deserialized = new SerializedObject(m.tx_blob).to_json();
|
||||
const deserialized = binary.decode(m.tx_blob);
|
||||
|
||||
switch (deserialized.TransactionType) {
|
||||
case 'Payment':
|
||||
@@ -570,8 +570,7 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
req.sendResponse(SUBMIT_TEF_RESPONSE, {id: m.id});
|
||||
});
|
||||
@@ -630,9 +629,8 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
|
||||
@@ -737,9 +735,8 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
|
||||
@@ -797,9 +794,8 @@ describe('TransactionManager', function() {
|
||||
});
|
||||
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
|
||||
@@ -867,9 +863,8 @@ describe('TransactionManager', function() {
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
rippled.on('request_submit', function(m, req) {
|
||||
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||
transaction.tx_json).to_hex());
|
||||
assert.strictEqual(new SerializedObject(m.tx_blob).to_json().Sequence,
|
||||
assert.strictEqual(m.tx_blob, binary.encode(transaction.tx_json));
|
||||
assert.strictEqual(binary.decode(m.tx_blob).Sequence,
|
||||
ACCOUNT_INFO_RESPONSE.result.account_data.Sequence);
|
||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
var assert = require('assert');
|
||||
var Transaction = require('ripple-lib').Transaction;
|
||||
var TransactionQueue = require('ripple-lib').TransactionQueue;
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const Transaction = require('ripple-lib').Transaction;
|
||||
const TransactionQueue = require('ripple-lib')._test.TransactionQueue;
|
||||
|
||||
describe('Transaction queue', function() {
|
||||
it('Push transaction', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
const queue = new TransactionQueue();
|
||||
const tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
@@ -13,8 +14,8 @@ describe('Transaction queue', function() {
|
||||
});
|
||||
|
||||
it('Remove transaction', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
const queue = new TransactionQueue();
|
||||
const tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
queue.remove(tx);
|
||||
@@ -23,8 +24,8 @@ describe('Transaction queue', function() {
|
||||
});
|
||||
|
||||
it('Remove transaction by ID', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
const queue = new TransactionQueue();
|
||||
const tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
@@ -33,32 +34,38 @@ describe('Transaction queue', function() {
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
|
||||
];
|
||||
|
||||
queue.remove('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
queue.remove(
|
||||
'3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
|
||||
assert.strictEqual(queue.length(), 1);
|
||||
|
||||
queue.remove('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
queue.remove(
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B');
|
||||
|
||||
assert.strictEqual(queue.length(), 0);
|
||||
});
|
||||
|
||||
it('Add sequence', function() {
|
||||
var queue = new TransactionQueue();
|
||||
const queue = new TransactionQueue();
|
||||
queue.addReceivedSequence(1);
|
||||
assert(queue.hasSequence(1));
|
||||
});
|
||||
|
||||
it('Add ID', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
queue.addReceivedId('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', tx);
|
||||
assert.strictEqual(queue.getReceived('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
|
||||
assert.strictEqual(queue.getReceived('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
const queue = new TransactionQueue();
|
||||
const tx = new Transaction();
|
||||
queue.addReceivedId(
|
||||
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B', tx);
|
||||
assert.strictEqual(queue.getReceived(
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'),
|
||||
undefined);
|
||||
assert.strictEqual(queue.getReceived(
|
||||
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
});
|
||||
|
||||
it('Get submission', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var tx = new Transaction();
|
||||
const queue = new TransactionQueue();
|
||||
const tx = new Transaction();
|
||||
|
||||
queue.push(tx);
|
||||
|
||||
@@ -67,16 +74,20 @@ describe('Transaction queue', function() {
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'
|
||||
];
|
||||
|
||||
assert.strictEqual(queue.getSubmission('1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission('2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission('3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), void(0));
|
||||
assert.strictEqual(queue.getSubmission(
|
||||
'1A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission(
|
||||
'2A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'), tx);
|
||||
assert.strictEqual(queue.getSubmission(
|
||||
'3A4DEBF37496464145AA301F0AA77712E3A2BFE3480D24C3584663F800B85B5B'),
|
||||
undefined);
|
||||
});
|
||||
|
||||
it('Iterate over queue', function() {
|
||||
var queue = new TransactionQueue();
|
||||
var count = 10;
|
||||
const queue = new TransactionQueue();
|
||||
let count = 10;
|
||||
|
||||
for (var i=0; i<count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
queue.push(new Transaction());
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
const assert = require('assert-diff');
|
||||
const lodash = require('lodash');
|
||||
const addresses = require('./fixtures/addresses');
|
||||
const ripple = require('ripple-lib');
|
||||
const Transaction = require('ripple-lib').Transaction;
|
||||
const TransactionQueue = require('ripple-lib').TransactionQueue;
|
||||
const TransactionQueue = require('ripple-lib')._test.TransactionQueue;
|
||||
const Remote = require('ripple-lib').Remote;
|
||||
const Server = require('ripple-lib').Server;
|
||||
const {decodeAddress} = require('ripple-address-codec');
|
||||
const binary = require('ripple-binary-codec');
|
||||
|
||||
const transactionResult = {
|
||||
engine_result: 'tesSUCCESS',
|
||||
@@ -344,7 +345,7 @@ describe('Transaction', function() {
|
||||
remote.setSecret(src, addresses.SECRET);
|
||||
|
||||
assert(transaction.complete());
|
||||
const json = transaction.serialize().to_json();
|
||||
const json = transaction.serialize();
|
||||
assert.notStrictEqual(json.Fee, '66500000', 'Fee == 66500000, i.e. 66.5 XRP!');
|
||||
});
|
||||
|
||||
@@ -583,7 +584,7 @@ describe('Transaction', function() {
|
||||
SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED',
|
||||
Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ',
|
||||
Flags: 0,
|
||||
Fee: 10,
|
||||
Fee: '10',
|
||||
Sequence: 1,
|
||||
TransactionType: 'AccountSet'
|
||||
};
|
||||
@@ -601,10 +602,9 @@ describe('Transaction', function() {
|
||||
const tx = Transaction.from_json(tx_json);
|
||||
const data = tx.signingData();
|
||||
|
||||
assert.strictEqual(data.hash().to_json(),
|
||||
expectedSigningHash);
|
||||
assert.strictEqual(tx.signingHash(), expectedSigningHash);
|
||||
|
||||
assert.strictEqual(data.to_hex(),
|
||||
assert.strictEqual(data,
|
||||
('535458001200032200000000240000000168400000000000000' +
|
||||
'A7321021FED5FD081CE5C4356431267D04C6E2167E4112C897D' +
|
||||
'5E10335D4E22B4DA49ED8114E0E6E281CA324AEE034B2BB8AC9' +
|
||||
@@ -618,7 +618,7 @@ describe('Transaction', function() {
|
||||
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
|
||||
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
|
||||
transaction.tx_json.Flags = 0;
|
||||
transaction.tx_json.Fee = 10;
|
||||
transaction.tx_json.Fee = '10';
|
||||
transaction.tx_json.Sequence = 1;
|
||||
transaction.tx_json.TransactionType = 'AccountSet';
|
||||
|
||||
@@ -633,29 +633,12 @@ describe('Transaction', function() {
|
||||
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
|
||||
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
|
||||
transaction.tx_json.Flags = 0;
|
||||
transaction.tx_json.Fee = 10;
|
||||
transaction.tx_json.Fee = '10';
|
||||
transaction.tx_json.Sequence = 1;
|
||||
transaction.tx_json.TransactionType = 'AccountSet';
|
||||
|
||||
assert.strictEqual(transaction.hash('HASH_TX_SIGN'), 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
|
||||
assert.strictEqual(transaction.hash('HASH_TX_SIGN_TESTNET'), '9FE7D27FC5B9891076B66591F99A683E01E0912986A629235459A3BD1961F341');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('Get hash - invalid prefix', function(done) {
|
||||
const transaction = new Transaction();
|
||||
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij';
|
||||
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
|
||||
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
|
||||
transaction.tx_json.Flags = 0;
|
||||
transaction.tx_json.Fee = 10;
|
||||
transaction.tx_json.Sequence = 1;
|
||||
transaction.tx_json.TransactionType = 'AccountSet';
|
||||
|
||||
assert.throws(function() {
|
||||
transaction.hash('HASH_TX_SIGNZ');
|
||||
});
|
||||
assert.strictEqual(transaction.signingHash(),
|
||||
'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
|
||||
|
||||
done();
|
||||
});
|
||||
@@ -839,7 +822,7 @@ describe('Transaction', function() {
|
||||
|
||||
const transaction = Transaction.from_json(input_json);
|
||||
|
||||
assert.deepEqual(transaction.serialize().to_hex(), expected_hex);
|
||||
assert.deepEqual(transaction.serialize(), expected_hex);
|
||||
});
|
||||
|
||||
it('Sign transaction', function(done) {
|
||||
@@ -848,7 +831,7 @@ describe('Transaction', function() {
|
||||
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED';
|
||||
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ';
|
||||
transaction.tx_json.Flags = 0;
|
||||
transaction.tx_json.Fee = 10;
|
||||
transaction.tx_json.Fee = '10';
|
||||
transaction.tx_json.Sequence = 1;
|
||||
transaction.tx_json.TransactionType = 'AccountSet';
|
||||
|
||||
@@ -2255,12 +2238,15 @@ describe('Transaction', function() {
|
||||
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
|
||||
const d1 = transaction.multiSigningData(a1);
|
||||
|
||||
const tbytes = ripple.SerializedObject.from_json(
|
||||
lodash.merge(transaction.tx_json, {SigningPubKey: ''})).buffer;
|
||||
const abytes = ripple.UInt160.from_json(a1).to_bytes();
|
||||
const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES;
|
||||
const txHex = binary.encode(
|
||||
lodash.merge(transaction.tx_json, {SigningPubKey: ''}));
|
||||
const abytes = decodeAddress(a1);
|
||||
const prefix = 0x534D5400;
|
||||
const prefixHex = prefix.toString(16);
|
||||
|
||||
assert.deepEqual(d1.buffer, prefix.concat(tbytes, abytes));
|
||||
assert.deepEqual(new Buffer(d1, 'hex'),
|
||||
Buffer.concat([new Buffer(prefixHex, 'hex'), new Buffer(txHex, 'hex'),
|
||||
new Buffer(abytes)]));
|
||||
});
|
||||
|
||||
it('Multisign', function() {
|
||||
@@ -2301,6 +2287,10 @@ describe('Transaction', function() {
|
||||
{Signer: s2},
|
||||
{Signer: s1}
|
||||
]);
|
||||
|
||||
transaction.remote = new Remote();
|
||||
assert(transaction.complete());
|
||||
assert.strictEqual(transaction.tx_json.SigningPubKey, '');
|
||||
});
|
||||
|
||||
it('Multisign -- missing LastLedgerSequence', function() {
|
||||
@@ -2316,4 +2306,27 @@ describe('Transaction', function() {
|
||||
transaction.getMultiSigningJson();
|
||||
});
|
||||
});
|
||||
|
||||
it('Multisign -- LastLedgerSequence autofill', function() {
|
||||
const transaction = Transaction.from_json({
|
||||
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
|
||||
Sequence: 1,
|
||||
Fee: '100',
|
||||
TransactionType: 'AccountSet',
|
||||
Flags: 0
|
||||
});
|
||||
|
||||
const sequence = 1;
|
||||
transaction.remote = {
|
||||
getLedgerSequenceSync: () => {
|
||||
return sequence;
|
||||
}
|
||||
};
|
||||
|
||||
const mJson = transaction.getMultiSigningJson();
|
||||
assert.strictEqual(mJson.LastLedgerSequence,
|
||||
sequence + 1 + transaction._lastLedgerOffset);
|
||||
assert.strictEqual(transaction.tx_json.LastLedgerSequence,
|
||||
sequence + 1 + transaction._lastLedgerOffset);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert-diff');
|
||||
const lodash = require('lodash');
|
||||
const ripple = require('ripple-lib');
|
||||
const fixtures = require('./fixtures/uint');
|
||||
|
||||
function resultError(test, result) {
|
||||
function type(e) {
|
||||
return Object.prototype.toString.call(e);
|
||||
}
|
||||
return `Expected ${type(test.input)}: ${test.input} to yield ${type(test.expected)}: ${test.expected === 'null' ? NaN : test.expected}. Actual: ${type(result)}: ${result}`;
|
||||
}
|
||||
|
||||
function makeTests(uIntType) {
|
||||
describe(uIntType, function() {
|
||||
const rippleType = ripple[uIntType];
|
||||
const tests = fixtures[uIntType];
|
||||
|
||||
it('from_json().to_json()', function() {
|
||||
tests['from_json().to_json()'].forEach(function(test) {
|
||||
let result = rippleType.from_json(test.input);
|
||||
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
|
||||
result = result.to_json();
|
||||
|
||||
if (test.expected === 'null') {
|
||||
// XXX
|
||||
// UInt160.to_json() returns NaN rather than null if input is invalid
|
||||
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
|
||||
} else {
|
||||
assert.strictEqual(result, test.expected, resultError(test, result));
|
||||
}
|
||||
});
|
||||
});
|
||||
it('from_json().to_bytes()', function() {
|
||||
tests['from_json().to_bytes()'].forEach(function(test) {
|
||||
const result = rippleType.from_json(test.input);
|
||||
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
|
||||
assert.deepEqual(result.to_bytes(), test.expected, resultError(test, result));
|
||||
});
|
||||
});
|
||||
it('from_number().to_json()', function() {
|
||||
tests['from_number().to_json()'].forEach(function(test) {
|
||||
let result = rippleType.from_number(test.input);
|
||||
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
|
||||
result = result.to_json();
|
||||
|
||||
if (test.expected === 'null') {
|
||||
// XXX
|
||||
// UInt160.to_json() returns NaN rather than null if input is invalid
|
||||
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
|
||||
} else {
|
||||
assert.strictEqual(result, test.expected, resultError(test, result));
|
||||
}
|
||||
});
|
||||
});
|
||||
it('from_number().to_hex()', function() {
|
||||
tests['from_number().to_hex()'].forEach(function(test) {
|
||||
const result = rippleType.from_number(test.input);
|
||||
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null', `Validity check failed: ${test.input}`);
|
||||
assert.strictEqual(result.to_hex(), test.expected, resultError(test, result));
|
||||
});
|
||||
});
|
||||
it('from_generic().to_*()', function() {
|
||||
tests['from_generic().to_*()'].forEach(function(test) {
|
||||
let result = rippleType.from_generic(test.input);
|
||||
|
||||
switch (test.input) {
|
||||
// XXX
|
||||
// from_generic() accepts these as "zero"
|
||||
case 0:
|
||||
case '0':
|
||||
case undefined:
|
||||
switch (test.outputMethod) {
|
||||
case 'to_bytes':
|
||||
test.expected = _.fill(Array(rippleType.width), 0);
|
||||
break;
|
||||
case 'to_json':
|
||||
case 'to_hex':
|
||||
test.expected = _.fill(Array(rippleType.width * 2), 0).join('');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert.strictEqual(result.is_valid(), String(test.expected) !== 'null',
|
||||
`Validity check failed: ${test.input} > ${test.expected}`);
|
||||
|
||||
result = result[test.outputMethod]();
|
||||
|
||||
if (test.expected === 'null') {
|
||||
// XXX
|
||||
// UInt160.to_json() returns NaN rather than null if input is invalid
|
||||
assert.strictEqual(lodash.isNaN(result), true, resultError(test, result));
|
||||
} else {
|
||||
assert.deepEqual(result, test.expected, resultError(test, result));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
['UInt128', 'UInt160', 'UInt256'].forEach(makeTests);
|
||||
Reference in New Issue
Block a user