mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-05 05:15:48 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6757aced2 | ||
|
|
5c84eed292 | ||
|
|
b648387a57 | ||
|
|
5232f95c3f | ||
|
|
0742960ec4 | ||
|
|
4c41b7f8df | ||
|
|
c5d0c24237 | ||
|
|
8b5c51ceaa | ||
|
|
c09bceb66a | ||
|
|
e7afd3ec76 | ||
|
|
3d30be3472 | ||
|
|
e08367365f | ||
|
|
6692fbeed4 | ||
|
|
36a9e7a7cf | ||
|
|
49b5ff5fd9 | ||
|
|
1bde56a11a | ||
|
|
f47d7b6935 | ||
|
|
2dbad40a34 | ||
|
|
39f6a51794 | ||
|
|
7ec128c2e4 | ||
|
|
b55f0e849e | ||
|
|
f158390ba1 | ||
|
|
e4b245104a | ||
|
|
789497b07e | ||
|
|
5cf01ba099 | ||
|
|
e8669891f8 | ||
|
|
ac0f265a5b | ||
|
|
fcd6b430e1 | ||
|
|
f3ad8a9b80 | ||
|
|
43ff824da1 | ||
|
|
b8022610ca | ||
|
|
03defe203a | ||
|
|
aedcbe56b3 | ||
|
|
c365db460a | ||
|
|
cfdc4752d0 | ||
|
|
e1964ac5ed | ||
|
|
e17ab9cd8f | ||
|
|
edc15b8727 | ||
|
|
034f8d41fc | ||
|
|
0fa70db1e1 | ||
|
|
fa7ba9b72b | ||
|
|
3a20123e0f | ||
|
|
03510d1bc4 | ||
|
|
8b116f637a | ||
|
|
9c49de6552 | ||
|
|
842347bcab | ||
|
|
628b9c4853 | ||
|
|
d60b6ee33f | ||
|
|
4f4fcbbc70 | ||
|
|
cc896670dc | ||
|
|
eb2a497dee | ||
|
|
0cf5ce1416 | ||
|
|
988381d584 | ||
|
|
0b163eae23 | ||
|
|
3a3ff8a65e | ||
|
|
9f183a6dfc | ||
|
|
fadfd4e06c | ||
|
|
eb521faa8d | ||
|
|
6b572ca862 | ||
|
|
d075ec6716 | ||
|
|
14e6bf5ef9 | ||
|
|
b6bddd3b0e | ||
|
|
d5ed9b6cf5 | ||
|
|
7a9912d4e0 | ||
|
|
4d2ddceb4e | ||
|
|
0c98082b25 |
25
.eslintrc.json
Normal file
25
.eslintrc.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"no-useless-constructor": 0,
|
||||
"no-unused-vars": 0,
|
||||
"no-prototype-builtins": 0,
|
||||
"require-atomic-updates": 0
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
lib-cov
|
||||
coverage.html
|
||||
src
|
||||
dist/bower
|
||||
@@ -1,11 +1,9 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
- 8
|
||||
- 10
|
||||
- 11
|
||||
script:
|
||||
- yarn compile
|
||||
- yarn test
|
||||
- yarn build
|
||||
- yarn lint
|
||||
|
||||
@@ -12,7 +12,7 @@ Warning: Use at your own risk.
|
||||
|
||||
## Data and visualizations
|
||||
|
||||
- **[Wipple - XRP Intelligence](https://wipple.devnull.network/)**
|
||||
- **[xrp1ntel - XRP Intelligence](https://xrp1ntel.com/)**
|
||||
|
||||
Monitor the XRP Network in real time and explore historical statistics.
|
||||
|
||||
|
||||
151
Gulpfile.js
151
Gulpfile.js
@@ -1,151 +0,0 @@
|
||||
/* eslint-disable no-var, no-param-reassign */
|
||||
/* these eslint rules are disabled because gulp does not support babel yet */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const gulp = require('gulp');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const pkg = require('./package.json');
|
||||
|
||||
const uglifyOptions = {
|
||||
mangle: {
|
||||
reserved: ['_', 'RippleError', 'RippledError', 'UnexpectedError',
|
||||
'LedgerVersionError', 'ConnectionError', 'NotConnectedError',
|
||||
'DisconnectedError', 'TimeoutError', 'ResponseFormatError',
|
||||
'ValidationError', 'NotFoundError', 'MissingLedgerHistoryError',
|
||||
'PendingLedgerVersionError'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function getWebpackConfig(extension, overrides) {
|
||||
overrides = overrides || {};
|
||||
let defaults = {
|
||||
cache: true,
|
||||
externals: [{
|
||||
'lodash': '_'
|
||||
}],
|
||||
entry: './src/index.ts',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: path.join(__dirname, 'build/'),
|
||||
filename: `ripple-${pkg.version}${extension}`
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet$/, './wallet-web'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^.*setup-api$/,
|
||||
'./setup-api-web')
|
||||
],
|
||||
module: {
|
||||
rules: [{
|
||||
test: /jayson/,
|
||||
use: 'null',
|
||||
}, {
|
||||
test: /\.ts$/,
|
||||
use: [{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
composite: false,
|
||||
declaration: false,
|
||||
declarationMap: false
|
||||
}
|
||||
},
|
||||
}],
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.ts', '.js' ]
|
||||
},
|
||||
};
|
||||
return _.assign({}, defaults, overrides);
|
||||
}
|
||||
|
||||
function webpackConfigForWebTest(testFileName) {
|
||||
var match = testFileName.match(/\/?([^\/]*)-test.js$/);
|
||||
if (!match) {
|
||||
assert(false, 'wrong filename:' + testFileName);
|
||||
}
|
||||
var configOverrides = {
|
||||
externals: [{
|
||||
'lodash': '_',
|
||||
'ripple-api': 'ripple',
|
||||
'net': 'null'
|
||||
}],
|
||||
entry: testFileName,
|
||||
output: {
|
||||
library: match[1].replace(/-/g, '_'),
|
||||
path: path.join(__dirname, 'test-compiled-for-web/'),
|
||||
filename: match[1] + '-test.js'
|
||||
}
|
||||
};
|
||||
return getWebpackConfig('.js', configOverrides);
|
||||
}
|
||||
|
||||
function createLink(from, to) {
|
||||
if (fs.existsSync(to)) {
|
||||
fs.unlinkSync(to);
|
||||
}
|
||||
fs.linkSync(from, to);
|
||||
}
|
||||
|
||||
function createBuildLink(callback) {
|
||||
return function(err, res) {
|
||||
createLink('./build/ripple-' + pkg.version + '.js',
|
||||
'./build/ripple-latest.js');
|
||||
callback(err, res);
|
||||
};
|
||||
}
|
||||
|
||||
function watch(callback) {
|
||||
gulp.watch('src/*', gulp.series(buildDebug));
|
||||
callback();
|
||||
}
|
||||
|
||||
function build(callback) {
|
||||
webpack(getWebpackConfig('.js'), createBuildLink(callback));
|
||||
}
|
||||
|
||||
function buildDebug(callback) {
|
||||
const webpackConfig = getWebpackConfig('-debug.js', {devtool: 'eval'});
|
||||
webpackConfig.plugins.unshift(new webpack.LoaderOptionsPlugin({debug: true}));
|
||||
webpack(webpackConfig, callback);
|
||||
}
|
||||
|
||||
function buildMin(callback) {
|
||||
const webpackConfig = getWebpackConfig('-min.js');
|
||||
webpackConfig.plugins.push(new UglifyJsPlugin({uglifyOptions}));
|
||||
webpack(webpackConfig, function() {
|
||||
createLink('./build/ripple-' + pkg.version + '-min.js',
|
||||
'./build/ripple-latest-min.js');
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function buildTests(callback) {
|
||||
var times = 0;
|
||||
function done() {
|
||||
if (++times >= 5) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
webpack(webpackConfigForWebTest('./test/rangeset-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/connection-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/api-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/broadcast-api-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/integration/integration-test.js',
|
||||
'integration/'), done);
|
||||
}
|
||||
|
||||
exports.watch = watch;
|
||||
exports.build = build;
|
||||
exports.buildDebug = buildDebug;
|
||||
exports.buildMin = buildMin;
|
||||
exports.buildTests = buildTests;
|
||||
|
||||
exports.default = gulp.parallel(build, buildDebug, buildMin);
|
||||
40
HISTORY.md
40
HISTORY.md
@@ -1,5 +1,45 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
## 1.4.1 (2019-11-06)
|
||||
|
||||
* Compatibility: Change TypeScript compile target back to `es6` (#1071)
|
||||
* WARNING: This allows for the use of Node v6, which is no longer supported by Node.js, as it was end-of-life'd in April 2019
|
||||
* We recommend updating to Node v8/v10 ASAP in order to get security updates and fixes from the Node.js team
|
||||
* We are not actively running tests against Node v6 (ref #1076)
|
||||
* Docs: `getAccountObjects` doc fix
|
||||
* Dependencies:
|
||||
* Update `bignumber.js`
|
||||
* Update `ripple-keypairs`
|
||||
* Update `ws`
|
||||
* Build process: Update `webpack` flow
|
||||
|
||||
## 1.4.0 (2019-10-28)
|
||||
|
||||
* Unref timer so it does not hang the Node.js process
|
||||
* Add a 2-second timeout for connect()
|
||||
* Improve getTransaction() error when tx has not been validated yet
|
||||
* Add support for the new X-address format
|
||||
* Fix error in Safari, Chrome 78, Firefox 70
|
||||
* Some error messages have changed slightly. For example:
|
||||
* `-instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format`
|
||||
* `+instance.Account is not of a type(s) string,instance.Account is not exactly one from <xAddress>,<classicAddress>`
|
||||
|
||||
### Internal improvements
|
||||
|
||||
* Reduce dependency size
|
||||
* Move tests to TypeScript
|
||||
* Replace tslint with eslint
|
||||
* Update https-proxy-agent
|
||||
* Add tests
|
||||
|
||||
## 1.3.4 (2019-10-18)
|
||||
|
||||
* Update ripple-lib-transactionparser
|
||||
* Improve error message when signing fails (e.g. due to trailing zeros)
|
||||
* Integrate ripple-hashes (in TypeScript with improved naming and docs)
|
||||
* Add multi-signing example to sign() method docs
|
||||
* Update TypeScript
|
||||
|
||||
## 1.3.3 (2019-09-10)
|
||||
|
||||
* Expand node version compatibility to support Node.js 12 ([ripple-binary-codec#32](https://github.com/ripple/ripple-binary-codec/issues/32))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[](https://travis-ci.org/ripple/ripple-lib)
|
||||
# ripple-lib
|
||||
|
||||
A JavaScript API for interacting with the XRP Ledger
|
||||
|
||||
189
docs/index.md
189
docs/index.md
@@ -80,6 +80,7 @@
|
||||
- [sign](#sign)
|
||||
- [combine](#combine)
|
||||
- [submit](#submit)
|
||||
- [generateXAddress](#generatexaddress)
|
||||
- [generateAddress](#generateaddress)
|
||||
- [isValidAddress](#isvalidaddress)
|
||||
- [isValidSecret](#isvalidsecret)
|
||||
@@ -225,7 +226,19 @@ Methods that depend on the state of the XRP Ledger are unavailable in offline mo
|
||||
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
|
||||
```
|
||||
|
||||
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
|
||||
```json
|
||||
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
|
||||
```
|
||||
|
||||
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
|
||||
|
||||
### Classic Address
|
||||
|
||||
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
|
||||
|
||||
### X-address
|
||||
|
||||
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
|
||||
|
||||
## Account Sequence Number
|
||||
|
||||
@@ -377,12 +390,12 @@ Name | Type | Description
|
||||
source | object | The source of the funds to be sent.
|
||||
*source.* address | [address](#address) | The address to send from.
|
||||
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
|
||||
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
|
||||
destination | object | The destination of the funds to be sent.
|
||||
*destination.* address | [address](#address) | An address representing the destination of the transaction.
|
||||
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
|
||||
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
|
||||
allowPartialPayment | boolean | *Optional* If true, this payment should proceed even if the whole amount cannot be delivered due to a lack of liquidity or a lack of funds in the source account.
|
||||
invoiceID | string | *Optional* A 256-bit hash that can be used to identify a particular payment.
|
||||
@@ -538,7 +551,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
|
||||
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#address) | A Ripple account address
|
||||
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
|
||||
@@ -851,7 +864,7 @@ Returns the response from invoking the specified command, with the specified opt
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -2289,12 +2302,12 @@ Name | Type | Description
|
||||
source | object | Properties of the source of the payment.
|
||||
*source.* address | [address](#address) | The address to send from.
|
||||
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
|
||||
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
|
||||
destination | object | Properties of the destination of the payment.
|
||||
*destination.* address | [address](#address) | An address representing the destination of the transaction.
|
||||
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
|
||||
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
|
||||
paths | string | The paths of trustlines and orders to use in executing the payment.
|
||||
|
||||
@@ -3906,7 +3919,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
|
||||
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#address) | A Ripple account address
|
||||
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
|
||||
@@ -4029,12 +4042,13 @@ limit | integer | *Optional* (May be omitted) The limit that was used in this re
|
||||
validated | boolean | *Optional* If included and set to true, the information in this request comes from a validated ledger version. Otherwise, the information is subject to change.
|
||||
|
||||
The types of objects that may be returned include:
|
||||
* Offer objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* RippleState objects for trust lines where this account's side is not in the default state.
|
||||
* A SignerList object if the account has multi-signing enabled.
|
||||
* Escrow objects for held payments that have not yet been executed or canceled.
|
||||
* PayChannel objects for open payment channels.
|
||||
* Check objects for pending checks.
|
||||
|
||||
* `Offer` objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* `RippleState` objects for trust lines where this account's side is not in the default state.
|
||||
* A `SignerList` object if the account has multi-signing enabled.
|
||||
* `Escrow` objects for held payments that have not yet been executed or canceled.
|
||||
* `PayChannel` objects for open payment channels.
|
||||
* `Check` objects for pending checks.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -5403,6 +5417,8 @@ options | object | *Optional* Options that control the type of signature that wi
|
||||
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
|
||||
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field cannot be used with keypair).
|
||||
|
||||
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
@@ -5430,6 +5446,94 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||
```
|
||||
|
||||
|
||||
### Example (multisigning)
|
||||
|
||||
```javascript
|
||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
||||
|
||||
// jon's address will have a multi-signing setup with a quorum of 2
|
||||
const jon = {
|
||||
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
|
||||
};
|
||||
const aya = {
|
||||
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
|
||||
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
|
||||
};
|
||||
const bran = {
|
||||
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
|
||||
};
|
||||
|
||||
// Setup the signers list with a quorum of 2
|
||||
const multiSignSetupTransaction = {
|
||||
"Flags": 0,
|
||||
"TransactionType": "SignerListSet",
|
||||
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
|
||||
"Fee": "120",
|
||||
"SignerQuorum": 2,
|
||||
"SignerEntries": [
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
|
||||
"SignerWeight": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
|
||||
"SignerWeight": 1
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// a transaction which requires multi signing
|
||||
const multiSignPaymentTransaction = {
|
||||
TransactionType: 'Payment',
|
||||
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
Amount: '88000000'
|
||||
};
|
||||
|
||||
const api = new RippleAPI({
|
||||
server: 'wss://s.altnet.rippletest.net:51233'
|
||||
});
|
||||
|
||||
api.connect().then(() => {
|
||||
// adding the multi signing feature to jon's account
|
||||
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
|
||||
console.log(prepared);
|
||||
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
|
||||
api.submit(jonSign).then( response => {
|
||||
console.log(response.resultCode, response.resultMessage);
|
||||
|
||||
// multi sign a transaction
|
||||
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
|
||||
console.log(prepared);
|
||||
|
||||
// Aya and Bran sign it too but with 'signAs' set to their own account
|
||||
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
|
||||
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
|
||||
|
||||
// signatures are combined and submitted
|
||||
let combinedTx = api.combine([ayaSign, branSign]);
|
||||
api.submit(combinedTx.signedTransaction).then(response => {
|
||||
console.log(response.tx_json.hash);
|
||||
return api.disconnect();
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
}).catch(console.error)
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
|
||||
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
|
||||
```
|
||||
[ValidationError(txJSON is not the same for all signedTransactions)]
|
||||
```
|
||||
|
||||
## combine
|
||||
|
||||
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
|
||||
@@ -5531,9 +5635,9 @@ return api.submit(signedTransaction)
|
||||
```
|
||||
|
||||
|
||||
## generateAddress
|
||||
## generateXAddress
|
||||
|
||||
`generateAddress(): {address: string, secret: string}`
|
||||
`generateXAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
@@ -5544,6 +5648,7 @@ Name | Type | Description
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -5551,8 +5656,8 @@ This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
address | [address](#address) | A randomly generated Ripple account address.
|
||||
secret | secret string | The secret corresponding to the `address`.
|
||||
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
|
||||
secret | secret string | The secret corresponding to the address.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -5563,6 +5668,52 @@ return api.generateAddress();
|
||||
|
||||
```json
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## generateAddress
|
||||
|
||||
`generateAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* includeClassicAddress | boolean | *Optional* If `true`, return the classic address, in addition to the X-address.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
|
||||
classicAddress | [classicAddress](#classic-address) | A randomly generated XRP Ledger Account ID (classic address).
|
||||
address | [classicAddress](#classic-address) | Deprecated: Use `classicAddress` instead.
|
||||
secret | secret string | The secret corresponding to the address.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
```
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
@@ -5573,7 +5724,7 @@ return api.generateAddress();
|
||||
|
||||
`isValidAddress(address: string): boolean`
|
||||
|
||||
Checks if the specified string contains a valid address.
|
||||
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,19 @@
|
||||
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
|
||||
```
|
||||
|
||||
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
|
||||
```json
|
||||
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
|
||||
```
|
||||
|
||||
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
|
||||
|
||||
### Classic Address
|
||||
|
||||
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
|
||||
|
||||
### X-address
|
||||
|
||||
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
|
||||
|
||||
## Account Sequence Number
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## generateAddress
|
||||
|
||||
`generateAddress(): {address: string, secret: string}`
|
||||
`generateAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
|
||||
23
docs/src/generateXAddress.md.ejs
Normal file
23
docs/src/generateXAddress.md.ejs
Normal file
@@ -0,0 +1,23 @@
|
||||
## generateXAddress
|
||||
|
||||
`generateXAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
### Parameters
|
||||
|
||||
<%- renderSchema('input/generate-x-address.json') %>
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
<%- renderSchema('output/generate-x-address.json') %>
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
```
|
||||
|
||||
<%- renderFixture('responses/generate-x-address.json') %>
|
||||
@@ -15,12 +15,13 @@ This method returns a promise that resolves with an object with the following st
|
||||
<%- renderSchema('output/get-account-objects.json') %>
|
||||
|
||||
The types of objects that may be returned include:
|
||||
* Offer objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* RippleState objects for trust lines where this account's side is not in the default state.
|
||||
* A SignerList object if the account has multi-signing enabled.
|
||||
* Escrow objects for held payments that have not yet been executed or canceled.
|
||||
* PayChannel objects for open payment channels.
|
||||
* Check objects for pending checks.
|
||||
|
||||
* `Offer` objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* `RippleState` objects for trust lines where this account's side is not in the default state.
|
||||
* A `SignerList` object if the account has multi-signing enabled.
|
||||
* `Escrow` objects for held payments that have not yet been executed or canceled.
|
||||
* `PayChannel` objects for open payment channels.
|
||||
* `Check` objects for pending checks.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<% include sign.md.ejs %>
|
||||
<% include combine.md.ejs %>
|
||||
<% include submit.md.ejs %>
|
||||
<% include generateXAddress.md.ejs %>
|
||||
<% include generateAddress.md.ejs %>
|
||||
<% include isValidAddress.md.ejs %>
|
||||
<% include isValidSecret.md.ejs %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`isValidAddress(address: string): boolean`
|
||||
|
||||
Checks if the specified string contains a valid address.
|
||||
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Returns the response from invoking the specified command, with the specified opt
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ This method can sign any of [the transaction types supported by ripple-binary-co
|
||||
|
||||
<%- renderSchema("input/sign.json") %>
|
||||
|
||||
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
@@ -31,3 +33,91 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||
```
|
||||
|
||||
<%- renderFixture("responses/sign.json") %>
|
||||
|
||||
### Example (multisigning)
|
||||
|
||||
```javascript
|
||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
||||
|
||||
// jon's address will have a multi-signing setup with a quorum of 2
|
||||
const jon = {
|
||||
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
|
||||
};
|
||||
const aya = {
|
||||
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
|
||||
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
|
||||
};
|
||||
const bran = {
|
||||
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
|
||||
};
|
||||
|
||||
// Setup the signers list with a quorum of 2
|
||||
const multiSignSetupTransaction = {
|
||||
"Flags": 0,
|
||||
"TransactionType": "SignerListSet",
|
||||
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
|
||||
"Fee": "120",
|
||||
"SignerQuorum": 2,
|
||||
"SignerEntries": [
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
|
||||
"SignerWeight": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
|
||||
"SignerWeight": 1
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// a transaction which requires multi signing
|
||||
const multiSignPaymentTransaction = {
|
||||
TransactionType: 'Payment',
|
||||
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
Amount: '88000000'
|
||||
};
|
||||
|
||||
const api = new RippleAPI({
|
||||
server: 'wss://s.altnet.rippletest.net:51233'
|
||||
});
|
||||
|
||||
api.connect().then(() => {
|
||||
// adding the multi signing feature to jon's account
|
||||
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
|
||||
console.log(prepared);
|
||||
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
|
||||
api.submit(jonSign).then( response => {
|
||||
console.log(response.resultCode, response.resultMessage);
|
||||
|
||||
// multi sign a transaction
|
||||
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
|
||||
console.log(prepared);
|
||||
|
||||
// Aya and Bran sign it too but with 'signAs' set to their own account
|
||||
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
|
||||
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
|
||||
|
||||
// signatures are combined and submitted
|
||||
let combinedTx = api.combine([ayaSign, branSign]);
|
||||
api.submit(combinedTx.signedTransaction).then(response => {
|
||||
console.log(response.tx_json.hash);
|
||||
return api.disconnect();
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
}).catch(console.error)
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
|
||||
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
|
||||
```
|
||||
[ValidationError(txJSON is not the same for all signedTransactions)]
|
||||
```
|
||||
|
||||
62
package.json
62
package.json
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.3.3",
|
||||
"version": "1.4.1",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||
"files": [
|
||||
"dist/npm/*",
|
||||
"build/*"
|
||||
"build/ripple-latest-min.js"
|
||||
],
|
||||
"main": "dist/npm/",
|
||||
"types": "dist/npm/index.d.ts",
|
||||
@@ -18,50 +18,52 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^3.2.0",
|
||||
"bignumber.js": "^4.1.0",
|
||||
"https-proxy-agent": "2.2.1",
|
||||
"@types/ws": "^6.0.3",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^3.0.4",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ripple-address-codec": "^4.0.0",
|
||||
"ripple-binary-codec": "^0.2.4",
|
||||
"ripple-hashes": "^0.3.4",
|
||||
"ripple-keypairs": "^0.10.1",
|
||||
"ripple-lib-transactionparser": "0.7.1",
|
||||
"ws": "^3.3.1"
|
||||
"ripple-keypairs": "^0.11.0",
|
||||
"ripple-lib-transactionparser": "0.8.0",
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "11.13.0",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.12.5",
|
||||
"@typescript-eslint/eslint-plugin": "^2.3.3",
|
||||
"@typescript-eslint/parser": "^2.3.3",
|
||||
"assert-diff": "^1.0.1",
|
||||
"doctoc": "^0.15.0",
|
||||
"ejs": "^2.3.4",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"gulp": "^4.0.2",
|
||||
"json-loader": "^0.5.2",
|
||||
"eslint": "^6.5.1",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"json-schema-to-markdown-table": "^0.4.0",
|
||||
"mocha": "6.1.3",
|
||||
"mocha": "6.2.0",
|
||||
"mocha-junit-reporter": "^1.9.1",
|
||||
"null-loader": "^0.1.1",
|
||||
"nyc": "^14.1.1",
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-loader": "^3.2.0",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-eslint-rules": "^4.1.1",
|
||||
"typescript": "3.4.2",
|
||||
"uglifyjs-webpack-plugin": "^1.1.4",
|
||||
"webpack": "3.12.0"
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "^3.6.4",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"build:schemas": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/",
|
||||
"build:lib": "tsc --build",
|
||||
"build:web": "webpack",
|
||||
"build": "yarn build:schemas && yarn build:lib && yarn build:web",
|
||||
"analyze": "yarn build:web --analyze",
|
||||
"watch": "yarn build:lib --watch",
|
||||
"clean": "rm -rf dist/npm",
|
||||
"doctoc": "doctoc docs/index.md --title '# RippleAPI Reference' --github --maxlevel 2",
|
||||
"docgen": "node --harmony scripts/build_docs.js",
|
||||
"clean": "rm -rf dist/npm",
|
||||
"compile": "mkdir -p dist/npm/common/js && cp -r src/common/js/ dist/npm/common/js/ && mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc --build",
|
||||
"watch": "tsc -w",
|
||||
"prepublish": "npm run clean && npm run compile && npm run build",
|
||||
"prepublish": "yarn clean && yarn build",
|
||||
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
|
||||
"lint": "tslint -p ./",
|
||||
"lint": "eslint src/**/*.ts 'test/*-test.{ts,js}'",
|
||||
"perf": "./scripts/perf_test.sh",
|
||||
"start": "node scripts/http.js"
|
||||
},
|
||||
|
||||
38
src/api.ts
38
src/api.ts
@@ -7,7 +7,8 @@ import {
|
||||
dropsToXrp,
|
||||
rippleTimeToISO8601,
|
||||
iso8601ToRippleTime,
|
||||
txFlags
|
||||
txFlags,
|
||||
ensureClassicAddress
|
||||
} from './common'
|
||||
import {
|
||||
connect,
|
||||
@@ -46,8 +47,8 @@ import prepareSettings from './transaction/settings'
|
||||
import sign from './transaction/sign'
|
||||
import combine from './transaction/combine'
|
||||
import submit from './transaction/submit'
|
||||
import {generateAddressAPI} from './offline/generate-address'
|
||||
import {deriveKeypair, deriveAddress} from './offline/derive'
|
||||
import {generateAddressAPI, GenerateAddressOptions, GeneratedAddress} from './offline/generate-address'
|
||||
import {deriveKeypair, deriveAddress, deriveXAddress} from './offline/derive'
|
||||
import computeLedgerHash from './offline/ledgerhash'
|
||||
import signPaymentChannelClaim from './offline/sign-payment-channel-claim'
|
||||
import verifyPaymentChannelClaim from './offline/verify-payment-channel-claim'
|
||||
@@ -74,6 +75,7 @@ import {getServerInfo, getFee} from './common/serverinfo'
|
||||
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
import {ConnectionOptions} from './common/connection'
|
||||
import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec'
|
||||
|
||||
export interface APIOptions extends ConnectionOptions {
|
||||
server?: string,
|
||||
@@ -138,7 +140,14 @@ class RippleAPI extends EventEmitter {
|
||||
this.emit('connected')
|
||||
})
|
||||
this.connection.on('disconnected', code => {
|
||||
this.emit('disconnected', code)
|
||||
let finalCode = code;
|
||||
// This is a backwards-compatible fix for this change in the ws library:
|
||||
// https://github.com/websockets/ws/issues/1257
|
||||
// TODO: Remove in next major, breaking version
|
||||
if (finalCode === 1005) {
|
||||
finalCode = 1000;
|
||||
}
|
||||
this.emit('disconnected', finalCode)
|
||||
})
|
||||
} else {
|
||||
// use null object pattern to provide better error message if user
|
||||
@@ -175,7 +184,8 @@ class RippleAPI extends EventEmitter {
|
||||
async request(command: string, params: any = {}): Promise<any> {
|
||||
return this.connection.request({
|
||||
...params,
|
||||
command
|
||||
command,
|
||||
account: params.account ? ensureClassicAddress(params.account) : undefined
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,6 +298,15 @@ class RippleAPI extends EventEmitter {
|
||||
return results
|
||||
}
|
||||
|
||||
// @deprecated Use X-addresses instead
|
||||
generateAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
|
||||
return generateAddressAPI({...options, includeClassicAddress: true})
|
||||
}
|
||||
|
||||
generateXAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
|
||||
return generateAddressAPI(options)
|
||||
}
|
||||
|
||||
connect = connect
|
||||
disconnect = disconnect
|
||||
isConnected = isConnected
|
||||
@@ -328,7 +347,6 @@ class RippleAPI extends EventEmitter {
|
||||
combine = combine
|
||||
submit = submit
|
||||
|
||||
generateAddress = generateAddressAPI
|
||||
deriveKeypair = deriveKeypair
|
||||
deriveAddress = deriveAddress
|
||||
computeLedgerHash = computeLedgerHash
|
||||
@@ -336,6 +354,14 @@ class RippleAPI extends EventEmitter {
|
||||
verifyPaymentChannelClaim = verifyPaymentChannelClaim
|
||||
errors = errors
|
||||
|
||||
static deriveXAddress = deriveXAddress
|
||||
|
||||
// RippleAPI.deriveClassicAddress (static) is a new name for api.deriveAddress
|
||||
static deriveClassicAddress = deriveAddress
|
||||
|
||||
static isValidXAddress = isValidXAddress
|
||||
static isValidClassicAddress = isValidClassicAddress
|
||||
|
||||
xrpToDrops = xrpToDrops
|
||||
dropsToXrp = dropsToXrp
|
||||
rippleTimeToISO8601 = rippleTimeToISO8601
|
||||
|
||||
@@ -7,11 +7,14 @@ function setPrototypeOf(object, prototype) {
|
||||
}
|
||||
|
||||
function getConstructorName(object: object): string {
|
||||
// hack for internet explorer
|
||||
if (!object.constructor.name) {
|
||||
return object.constructor.toString().match(/^function\s+([^(]*)/)![1]
|
||||
if (object.constructor.name) {
|
||||
return object.constructor.name
|
||||
}
|
||||
return object.constructor.name
|
||||
// try to guess it on legacy browsers (ie)
|
||||
const constructorString = object.constructor.toString()
|
||||
const functionConstructor = constructorString.match(/^function\s+([^(]*)/)
|
||||
const classConstructor = constructorString.match(/^class\s([^\s]*)/)
|
||||
return functionConstructor ? functionConstructor[1] : classConstructor[1]
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as _ from 'lodash'
|
||||
import {EventEmitter} from 'events'
|
||||
import {parse as parseUrl} from 'url'
|
||||
import * as WebSocket from 'ws'
|
||||
import WebSocket from 'ws'
|
||||
import RangeSet from './rangeset'
|
||||
import {RippledError, DisconnectedError, NotConnectedError,
|
||||
TimeoutError, ResponseFormatError, ConnectionError,
|
||||
RippledNotInitializedError} from './errors'
|
||||
|
||||
export interface ConnectionOptions {
|
||||
trace?: boolean,
|
||||
trace?: boolean
|
||||
proxy?: string
|
||||
proxyAuthorization?: string
|
||||
authorization?: string
|
||||
@@ -16,7 +16,8 @@ export interface ConnectionOptions {
|
||||
key?: string
|
||||
passphrase?: string
|
||||
certificate?: string
|
||||
timeout?: number
|
||||
timeout?: number,
|
||||
connectionTimeout?: number
|
||||
}
|
||||
|
||||
class Connection extends EventEmitter {
|
||||
@@ -38,11 +39,13 @@ class Connection extends EventEmitter {
|
||||
private _availableLedgerVersions = new RangeSet()
|
||||
private _nextRequestID: number = 1
|
||||
private _retry: number = 0
|
||||
private _connectTimer: null|NodeJS.Timer = null
|
||||
private _retryTimer: null|NodeJS.Timer = null
|
||||
private _onOpenErrorBound: null| null|((...args: any[]) => void) = null
|
||||
private _onUnexpectedCloseBound: null|((...args: any[]) => void) = null
|
||||
private _fee_base: null|number = null
|
||||
private _fee_ref: null|number = null
|
||||
private _connectionTimeout: number
|
||||
|
||||
constructor(url, options: ConnectionOptions = {}) {
|
||||
super()
|
||||
@@ -61,6 +64,7 @@ class Connection extends EventEmitter {
|
||||
this._passphrase = options.passphrase
|
||||
this._certificate = options.certificate
|
||||
this._timeout = options.timeout || (20 * 1000)
|
||||
this._connectionTimeout = options.connectionTimeout || 2000
|
||||
}
|
||||
|
||||
_updateLedgerVersions(data) {
|
||||
@@ -179,6 +183,13 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_clearConnectTimer() {
|
||||
if (this._connectTimer !== null) {
|
||||
clearTimeout(this._connectTimer)
|
||||
this._connectTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
_onOpen() {
|
||||
if (!this._ws) {
|
||||
return Promise.reject(new DisconnectedError())
|
||||
@@ -282,8 +293,13 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
this._clearConnectTimer()
|
||||
this._clearReconnectTimer()
|
||||
return new Promise((resolve, reject) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this._connectTimer = setTimeout(() => {
|
||||
reject(new ConnectionError(`Error: connect() timed out after ${this._connectionTimeout} ms. ` +
|
||||
`If your internet connection is working, the rippled server may be blocked or inaccessible.`))
|
||||
}, this._connectionTimeout)
|
||||
if (!this._url) {
|
||||
reject(new ConnectionError(
|
||||
'Cannot connect because no server was specified'))
|
||||
@@ -291,7 +307,7 @@ class Connection extends EventEmitter {
|
||||
if (this._state === WebSocket.OPEN) {
|
||||
resolve()
|
||||
} else if (this._state === WebSocket.CONNECTING) {
|
||||
this._ws.once('open', resolve)
|
||||
this._ws.once('open', () => resolve)
|
||||
} else {
|
||||
this._ws = this._createWebSocket()
|
||||
// when an error causes the connection to close, the close event
|
||||
@@ -311,9 +327,20 @@ class Connection extends EventEmitter {
|
||||
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
|
||||
resolve, reject)
|
||||
this._ws.once('close', this._onUnexpectedCloseBound)
|
||||
this._ws.once('open', () => this._onOpen().then(resolve, reject))
|
||||
this._ws.once('open', () => {
|
||||
return this._onOpen().then(resolve, reject)
|
||||
})
|
||||
}
|
||||
})
|
||||
// Once we have a resolution or rejection, clear the timeout timer as no
|
||||
// longer needed.
|
||||
.then(() => {
|
||||
this._clearConnectTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
this._clearConnectTimer()
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
disconnect(): Promise<void> {
|
||||
@@ -322,6 +349,7 @@ class Connection extends EventEmitter {
|
||||
|
||||
_disconnect(calledByUser): Promise<void> {
|
||||
if (calledByUser) {
|
||||
this._clearConnectTimer()
|
||||
this._clearReconnectTimer()
|
||||
this._retry = 0
|
||||
}
|
||||
@@ -456,6 +484,10 @@ class Connection extends EventEmitter {
|
||||
this._whenReady(this._send(message)).then(() => {
|
||||
const delay = timeout || this._timeout
|
||||
timer = setTimeout(() => _reject(new TimeoutError()), delay)
|
||||
// Node.js won't exit if a timer is still running, so we tell Node to ignore (Node will still wait for the request to complete)
|
||||
if (timer.unref) {
|
||||
timer.unref()
|
||||
}
|
||||
}).catch(_reject)
|
||||
})
|
||||
}
|
||||
|
||||
49
src/common/hashes/README.md
Normal file
49
src/common/hashes/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# XRP Ledger Hashes
|
||||
|
||||
Methods to hash XRP Ledger objects
|
||||
|
||||
## Methods
|
||||
|
||||
### computeBinaryTransactionHash = (txBlobHex: string): string
|
||||
|
||||
Compute the hash of a binary transaction blob.
|
||||
|
||||
### computeTransactionHash = (txJSON: any): string
|
||||
|
||||
Compute the hash of a transaction in txJSON format.
|
||||
|
||||
### computeBinaryTransactionSigningHash = (txBlobHex: string): string
|
||||
|
||||
### computeTransactionSigningHash = (txJSON: any): string
|
||||
|
||||
### computeAccountHash = (address: string): string
|
||||
|
||||
Compute the hash of an account, given the account's classic address (starting with `r`).
|
||||
|
||||
### computeSignerListHash = (address: string): string
|
||||
|
||||
Compute the hash of an account's SignerList.
|
||||
|
||||
### computeOrderHash = (address: string, sequence: number): string
|
||||
|
||||
Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.
|
||||
|
||||
### computeTrustlineHash = (address1: string, address2: string, currency: string): string
|
||||
|
||||
Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code.
|
||||
|
||||
### computeTransactionTreeHash = (transactions: any[]): string
|
||||
|
||||
### computeStateTreeHash = (entries: any[]): string
|
||||
|
||||
### computeLedgerHash = (ledgerHeader): string
|
||||
|
||||
Compute the hash of a ledger.
|
||||
|
||||
### computeEscrowHash = (address, sequence): string
|
||||
|
||||
Compute the hash of an escrow, given the owner's classic address (starting with `r`) and the account sequence number of the `EscrowCreate` escrow transaction.
|
||||
|
||||
### computePaymentChannelHash = (address, dstAddress, sequence): string
|
||||
|
||||
Compute the hash of a payment channel, given the owner's classic address (starting with `r`), the classic address of the destination, and the account sequence number of the `PaymentChannelCreate` payment channel transaction.
|
||||
40
src/common/hashes/hash-prefix.ts
Normal file
40
src/common/hashes/hash-prefix.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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".
|
||||
*/
|
||||
|
||||
enum HashPrefix {
|
||||
// transaction plus signature to give transaction ID
|
||||
TRANSACTION_ID = 0x54584E00, // 'TXN'
|
||||
|
||||
// transaction plus metadata
|
||||
TRANSACTION_NODE = 0x534E4400, // 'TND'
|
||||
|
||||
// inner node in tree
|
||||
INNER_NODE = 0x4D494E00, // 'MIN'
|
||||
|
||||
// leaf node in tree
|
||||
LEAF_NODE = 0x4D4C4E00, // 'MLN'
|
||||
|
||||
// inner transaction to sign
|
||||
TRANSACTION_SIGN = 0x53545800, // 'STX'
|
||||
|
||||
// inner transaction to sign (TESTNET)
|
||||
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
|
||||
|
||||
// inner transaction to multisign
|
||||
TRANSACTION_MULTISIGN = 0x534D5400, // 'SMT'
|
||||
|
||||
// ledger
|
||||
LEDGER = 0x4C575200 // 'LWR'
|
||||
}
|
||||
|
||||
export default HashPrefix
|
||||
155
src/common/hashes/index.ts
Normal file
155
src/common/hashes/index.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAccountID} from 'ripple-address-codec'
|
||||
import sha512Half from './sha512Half'
|
||||
import HashPrefix from './hash-prefix'
|
||||
import {SHAMap, NodeType} from './shamap'
|
||||
import {encode} from 'ripple-binary-codec'
|
||||
import ledgerspaces from './ledgerspaces'
|
||||
|
||||
const padLeftZero = (string: string, length: number): string => {
|
||||
return Array(length - string.length + 1).join('0') + string
|
||||
}
|
||||
|
||||
const intToHex = (integer: number, byteLength: number): string => {
|
||||
return padLeftZero(Number(integer).toString(16), byteLength * 2)
|
||||
}
|
||||
|
||||
const bytesToHex = (bytes: number[]): string => {
|
||||
return Buffer.from(bytes).toString('hex')
|
||||
}
|
||||
|
||||
const bigintToHex = (integerString: string | number | BigNumber, byteLength: number): string => {
|
||||
const hex = (new BigNumber(integerString)).toString(16)
|
||||
return padLeftZero(hex, byteLength * 2)
|
||||
}
|
||||
|
||||
const ledgerSpaceHex = (name: string): string => {
|
||||
return intToHex(ledgerspaces[name].charCodeAt(0), 2)
|
||||
}
|
||||
|
||||
const addressToHex = (address: string): string => {
|
||||
return (Buffer.from(decodeAccountID(address))).toString('hex')
|
||||
}
|
||||
|
||||
const currencyToHex = (currency: string): string => {
|
||||
if (currency.length === 3) {
|
||||
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
||||
bytes[12] = currency.charCodeAt(0) & 0xff
|
||||
bytes[13] = currency.charCodeAt(1) & 0xff
|
||||
bytes[14] = currency.charCodeAt(2) & 0xff
|
||||
return bytesToHex(bytes)
|
||||
}
|
||||
return currency
|
||||
}
|
||||
|
||||
const addLengthPrefix = (hex: string): string => {
|
||||
const length = hex.length / 2
|
||||
if (length <= 192) {
|
||||
return bytesToHex([length]) + hex
|
||||
} else if (length <= 12480) {
|
||||
const x = length - 193
|
||||
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
|
||||
} else if (length <= 918744) {
|
||||
const x = length - 12481
|
||||
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex
|
||||
}
|
||||
throw new Error('Variable integer overflow.')
|
||||
}
|
||||
|
||||
export const computeBinaryTransactionHash = (txBlobHex: string): string => {
|
||||
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
|
||||
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionSigningHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionSigningHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeAccountHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
|
||||
}
|
||||
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('signerList') +
|
||||
addressToHex(address) +
|
||||
'00000000') // uint32(0) signer list index
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
|
||||
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
export const computeTrustlineHash = (address1: string, address2: string, currency: string): string => {
|
||||
const address1Hex = addressToHex(address1)
|
||||
const address2Hex = addressToHex(address2)
|
||||
|
||||
const swap = (new BigNumber(address1Hex, 16)).isGreaterThan(
|
||||
new BigNumber(address2Hex, 16))
|
||||
const lowAddressHex = swap ? address2Hex : address1Hex
|
||||
const highAddressHex = swap ? address1Hex : address2Hex
|
||||
|
||||
const prefix = ledgerSpaceHex('rippleState')
|
||||
return sha512Half(prefix + lowAddressHex + highAddressHex +
|
||||
currencyToHex(currency))
|
||||
}
|
||||
|
||||
export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
transactions.forEach(txJSON => {
|
||||
const txBlobHex = encode(txJSON)
|
||||
const metaHex = encode(txJSON.metaData)
|
||||
const txHash = computeBinaryTransactionHash(txBlobHex)
|
||||
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
|
||||
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
|
||||
})
|
||||
|
||||
return shamap.hash
|
||||
}
|
||||
|
||||
export const computeStateTreeHash = (entries: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
entries.forEach(ledgerEntry => {
|
||||
const data = encode(ledgerEntry)
|
||||
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
|
||||
})
|
||||
|
||||
return shamap.hash
|
||||
}
|
||||
|
||||
// see rippled Ledger::updateHash()
|
||||
export const computeLedgerHash = (ledgerHeader): string => {
|
||||
const prefix = HashPrefix.LEDGER.toString(16).toUpperCase()
|
||||
return sha512Half(prefix +
|
||||
intToHex(ledgerHeader.ledger_index, 4) +
|
||||
bigintToHex(ledgerHeader.total_coins, 8) +
|
||||
ledgerHeader.parent_hash +
|
||||
ledgerHeader.transaction_hash +
|
||||
ledgerHeader.account_hash +
|
||||
intToHex(ledgerHeader.parent_close_time, 4) +
|
||||
intToHex(ledgerHeader.close_time, 4) +
|
||||
intToHex(ledgerHeader.close_time_resolution, 1) +
|
||||
intToHex(ledgerHeader.close_flags, 1)
|
||||
)
|
||||
}
|
||||
|
||||
export const computeEscrowHash = (address, sequence): string => {
|
||||
return sha512Half(ledgerSpaceHex('escrow') + addressToHex(address) +
|
||||
intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
|
||||
return sha512Half(ledgerSpaceHex('paychan') + addressToHex(address) +
|
||||
addressToHex(dstAddress) + intToHex(sequence, 4))
|
||||
}
|
||||
25
src/common/hashes/ledgerspaces.ts
Normal file
25
src/common/hashes/ledgerspaces.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export default {
|
||||
account : 'a',
|
||||
dirNode : 'd',
|
||||
generatorMap : 'g',
|
||||
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',
|
||||
signerList : 'S',
|
||||
escrow : 'u',
|
||||
paychan : 'x'
|
||||
}
|
||||
7
src/common/hashes/sha512Half.ts
Normal file
7
src/common/hashes/sha512Half.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {createHash} from 'crypto'
|
||||
|
||||
const sha512Half = (hex: string): string => {
|
||||
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
|
||||
}
|
||||
|
||||
export default sha512Half
|
||||
173
src/common/hashes/shamap.ts
Normal file
173
src/common/hashes/shamap.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import hashPrefix from './hash-prefix'
|
||||
import sha512Half from './sha512Half'
|
||||
const HEX_ZERO = '0000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
export enum NodeType {
|
||||
INNER = 1,
|
||||
TRANSACTION_NO_METADATA = 2,
|
||||
TRANSACTION_METADATA = 3,
|
||||
ACCOUNT_STATE = 4
|
||||
}
|
||||
|
||||
export abstract class Node {
|
||||
/**
|
||||
* Abstract constructor representing a node in a SHAMap tree.
|
||||
* Can be either InnerNode or Leaf.
|
||||
* @constructor
|
||||
*/
|
||||
public constructor() {}
|
||||
|
||||
public addItem(_tag: string, _node: Node): void {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#addItem.')
|
||||
}
|
||||
public get hash(): string|void {
|
||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
|
||||
}
|
||||
}
|
||||
|
||||
export class InnerNode extends Node {
|
||||
public leaves: { [slot: number]: Node }
|
||||
public type: NodeType
|
||||
public depth: number
|
||||
public empty: boolean
|
||||
|
||||
/**
|
||||
* Define an Inner (non-leaf) node in a SHAMap tree.
|
||||
* @param {number} depth i.e. how many parent inner nodes
|
||||
* @constructor
|
||||
*/
|
||||
public constructor(depth: number = 0) {
|
||||
super()
|
||||
this.leaves = {}
|
||||
this.type = NodeType.INNER
|
||||
this.depth = depth
|
||||
this.empty = true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tag equates to a ledger entry `index`
|
||||
* @param {Node} node to add
|
||||
* @return {void}
|
||||
*/
|
||||
public addItem(tag: string, node: Node): void {
|
||||
const existingNode = this.getNode(parseInt(tag[this.depth],16))
|
||||
if (existingNode) {
|
||||
// A node already exists in this slot
|
||||
if (existingNode instanceof InnerNode) {
|
||||
// There is an inner node, so we need to go deeper
|
||||
existingNode.addItem(tag, node)
|
||||
} else if (existingNode instanceof Leaf) {
|
||||
if (existingNode.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
|
||||
const newInnerNode = new InnerNode(this.depth + 1)
|
||||
|
||||
// Parent new and existing node
|
||||
newInnerNode.addItem(existingNode.tag, existingNode)
|
||||
newInnerNode.addItem(tag, node)
|
||||
|
||||
// And place the newly created inner node in the slot
|
||||
this.setNode(parseInt(tag[this.depth], 16), newInnerNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Neat, we have a nice open spot for the new node
|
||||
this.setNode(parseInt(tag[this.depth], 16), node)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the node that is currently in a given slot.
|
||||
* @param {number} slot a number 0-15
|
||||
* @param {Node} node to place
|
||||
* @return {void}
|
||||
*/
|
||||
public setNode(slot: number, node: Node): void {
|
||||
if (slot < 0 || slot > 15) {
|
||||
throw new Error ('Invalid slot: slot must be between 0-15.')
|
||||
}
|
||||
this.leaves[slot] = node
|
||||
this.empty = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node that is currently in a given slot.
|
||||
* @param {number} slot a number 0-15
|
||||
* @return {Node}
|
||||
*/
|
||||
public getNode(slot: number): Node {
|
||||
if (slot < 0 || slot > 15) {
|
||||
throw new Error ('Invalid slot: slot must be between 0-15.')
|
||||
}
|
||||
return this.leaves[slot]
|
||||
}
|
||||
|
||||
public get hash(): string {
|
||||
if (this.empty) return HEX_ZERO
|
||||
let hex = ''
|
||||
for (let i = 0; i < 16; i++) {
|
||||
hex += this.leaves[i] ? this.leaves[i].hash : HEX_ZERO
|
||||
}
|
||||
const prefix = hashPrefix.INNER_NODE.toString(16)
|
||||
return sha512Half(prefix + hex)
|
||||
}
|
||||
}
|
||||
|
||||
export class Leaf extends Node {
|
||||
public tag: string
|
||||
public type: NodeType
|
||||
public data: string
|
||||
|
||||
/**
|
||||
* Leaf node in a SHAMap tree.
|
||||
* @param {string} tag equates to a ledger entry `index`
|
||||
* @param {string} data hex of account state, transaction etc
|
||||
* @param {number} type one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc
|
||||
* @constructor
|
||||
*/
|
||||
public constructor(tag: string, data: string, type: NodeType) {
|
||||
super()
|
||||
this.tag = tag
|
||||
this.type = type
|
||||
this.data = data
|
||||
}
|
||||
|
||||
public get hash(): string|void {
|
||||
switch (this.type) {
|
||||
case NodeType.ACCOUNT_STATE:
|
||||
const leafPrefix = hashPrefix.LEAF_NODE.toString(16)
|
||||
return sha512Half(leafPrefix + this.data + this.tag)
|
||||
case NodeType.TRANSACTION_NO_METADATA:
|
||||
const txIDPrefix = hashPrefix.TRANSACTION_ID.toString(16)
|
||||
return sha512Half(txIDPrefix + this.data)
|
||||
case NodeType.TRANSACTION_METADATA:
|
||||
const txNodePrefix = hashPrefix.TRANSACTION_NODE.toString(16)
|
||||
return sha512Half(txNodePrefix + this.data + this.tag)
|
||||
default:
|
||||
throw new Error('Tried to hash a SHAMap node of unknown type.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SHAMap {
|
||||
public root: InnerNode
|
||||
|
||||
/**
|
||||
* SHAMap tree.
|
||||
* @constructor
|
||||
*/
|
||||
public constructor() {
|
||||
this.root = new InnerNode(0)
|
||||
}
|
||||
|
||||
public addItem(tag: string, data: string, type: NodeType): void {
|
||||
this.root.addItem(tag, new Leaf(tag, data, type))
|
||||
}
|
||||
|
||||
public get hash(): string {
|
||||
return this.root.hash
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,35 @@ import * as constants from './constants'
|
||||
import * as errors from './errors'
|
||||
import * as validate from './validate'
|
||||
import * as serverInfo from './serverinfo'
|
||||
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
|
||||
export function ensureClassicAddress(account: string): string {
|
||||
if (isValidXAddress(account)) {
|
||||
const {
|
||||
classicAddress,
|
||||
tag
|
||||
} = xAddressToClassicAddress(account)
|
||||
|
||||
// Except for special cases, X-addresses used for requests
|
||||
// must not have an embedded tag. In other words,
|
||||
// `tag` should be `false`.
|
||||
if (tag !== false) {
|
||||
throw new Error('This command does not support the use of a tag. Use an address without a tag.')
|
||||
}
|
||||
|
||||
// For rippled requests that use an account, always use a classic address.
|
||||
return classicAddress
|
||||
} else {
|
||||
return account
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
constants,
|
||||
errors,
|
||||
validate,
|
||||
serverInfo
|
||||
}
|
||||
|
||||
export {
|
||||
dropsToXrp,
|
||||
xrpToDrops,
|
||||
@@ -20,4 +42,3 @@ export {
|
||||
} from './utils'
|
||||
export {default as Connection} from './connection'
|
||||
export {txFlags} from './txflags'
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ class RangeSet {
|
||||
}
|
||||
|
||||
addRange(start: number, end: number) {
|
||||
assert(start <= end, `invalid range ${start} <= ${end}`)
|
||||
assert.ok(start <= end, `invalid range ${start} <= ${end}`)
|
||||
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as _ from 'lodash'
|
||||
import * as assert from 'assert'
|
||||
const {Validator} = require('jsonschema')
|
||||
import {ValidationError} from './errors'
|
||||
import {isValidAddress} from 'ripple-address-codec'
|
||||
import {isValidClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
import {isValidSecret} from './utils'
|
||||
|
||||
function loadSchemas() {
|
||||
@@ -34,6 +34,8 @@ function loadSchemas() {
|
||||
require('./schemas/objects/destination-address-tag.json'),
|
||||
require('./schemas/objects/transaction-hash.json'),
|
||||
require('./schemas/objects/address.json'),
|
||||
require('./schemas/objects/x-address.json'),
|
||||
require('./schemas/objects/classic-address.json'),
|
||||
require('./schemas/objects/adjustment.json'),
|
||||
require('./schemas/objects/quality.json'),
|
||||
require('./schemas/objects/amount.json'),
|
||||
@@ -121,17 +123,28 @@ function loadSchemas() {
|
||||
]
|
||||
const titles = schemas.map(schema => schema.title)
|
||||
const duplicates = _.keys(_.pickBy(_.countBy(titles), count => count > 1))
|
||||
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
|
||||
assert.ok(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
|
||||
const validator = new Validator()
|
||||
// Register custom format validators that ignore undefined instances
|
||||
// since jsonschema will still call the format validator on a missing
|
||||
// (optional) property
|
||||
validator.customFormats.address = function(instance) {
|
||||
|
||||
// This relies on "format": "xAddress" in `x-address.json`!
|
||||
validator.customFormats.xAddress = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
}
|
||||
return isValidXAddress(instance)
|
||||
}
|
||||
|
||||
// This relies on "format": "classicAddress" in `classic-address.json`!
|
||||
validator.customFormats.classicAddress = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
}
|
||||
return isValidAddress(instance)
|
||||
}
|
||||
|
||||
validator.customFormats.secret = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
@@ -158,6 +171,10 @@ function schemaValidate(schemaName: string, object: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidAddress(address: string): boolean {
|
||||
return isValidXAddress(address) || isValidClassicAddress(address)
|
||||
}
|
||||
|
||||
export {
|
||||
schemaValidate,
|
||||
isValidSecret,
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
"type": "string",
|
||||
"enum": ["ecdsa-secp256k1", "ed25519"],
|
||||
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
|
||||
},
|
||||
"test": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
|
||||
},
|
||||
"includeClassicAddress": {
|
||||
"type": "boolean",
|
||||
"description": "If `true`, return the classic address, in addition to the X-address."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
33
src/common/schemas/input/generate-x-address.json
Normal file
33
src/common/schemas/input/generate-x-address.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "generateXAddressParameters",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options to control how the address and secret are generated.",
|
||||
"properties": {
|
||||
"entropy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"description": "The entropy to use to generate the seed."
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
"enum": ["ecdsa-secp256k1", "ed25519"],
|
||||
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
|
||||
},
|
||||
"test": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "address",
|
||||
"description": "A Ripple account address",
|
||||
"description": "An account address on the XRP Ledger",
|
||||
"type": "string",
|
||||
"format": "address",
|
||||
"link": "address",
|
||||
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{25,34}$"
|
||||
"oneOf": [
|
||||
{"$ref": "xAddress"},
|
||||
{"$ref": "classicAddress"}
|
||||
]
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/classic-address.json
Normal file
9
src/common/schemas/objects/classic-address.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "classicAddress",
|
||||
"description": "A classic address (Account ID) for the XRP Ledger",
|
||||
"type": "string",
|
||||
"format": "classicAddress",
|
||||
"link": "classic-address",
|
||||
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{24,34}$"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "tag",
|
||||
"description": "An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.",
|
||||
"description": "An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.",
|
||||
"type": "integer",
|
||||
"$ref": "uint32"
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/x-address.json
Normal file
9
src/common/schemas/objects/x-address.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "xAddress",
|
||||
"description": "An XRP Ledger address in X-address format",
|
||||
"type": "string",
|
||||
"format": "xAddress",
|
||||
"link": "x-address",
|
||||
"pattern": "^[XT][1-9A-HJ-NP-Za-km-z]{46}$"
|
||||
}
|
||||
@@ -3,16 +3,24 @@
|
||||
"title": "generateAddress",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xAddress": {
|
||||
"$ref": "xAddress",
|
||||
"description": "A randomly generated XRP Ledger address in X-address format."
|
||||
},
|
||||
"classicAddress": {
|
||||
"$ref": "classicAddress",
|
||||
"description": "A randomly generated XRP Ledger Account ID (classic address)."
|
||||
},
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "A randomly generated Ripple account address."
|
||||
"$ref": "classicAddress",
|
||||
"description": "Deprecated: Use `classicAddress` instead."
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret corresponding to the `address`."
|
||||
"description": "The secret corresponding to the address."
|
||||
}
|
||||
},
|
||||
"required": ["address", "secret"],
|
||||
"required": ["xAddress", "classicAddress", "address", "secret"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
18
src/common/schemas/output/generate-x-address.json
Normal file
18
src/common/schemas/output/generate-x-address.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "generateXAddress",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xAddress": {
|
||||
"$ref": "xAddress",
|
||||
"description": "A randomly generated XRP Ledger address in X-address format."
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret corresponding to the address."
|
||||
}
|
||||
},
|
||||
"required": ["xAddress", "secret"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -17,7 +17,7 @@ function dropsToXrp(drops: string | BigNumber): string {
|
||||
if (typeof drops === 'string') {
|
||||
if (!drops.match(/^-?[0-9]*\.?[0-9]*$/)) {
|
||||
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
|
||||
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
|
||||
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
|
||||
} else if (drops === '.') {
|
||||
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
|
||||
` should be a BigNumber or string-encoded number.`)
|
||||
@@ -51,7 +51,7 @@ function xrpToDrops(xrp: string | BigNumber): string {
|
||||
if (typeof xrp === 'string') {
|
||||
if (!xrp.match(/^-?[0-9]*\.?[0-9]*$/)) {
|
||||
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
|
||||
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
|
||||
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
|
||||
} else if (xrp === '.') {
|
||||
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
|
||||
` should be a BigNumber or string-encoded number.`)
|
||||
@@ -83,7 +83,7 @@ function xrpToDrops(xrp: string | BigNumber): string {
|
||||
` too many decimal places.`)
|
||||
}
|
||||
|
||||
return (new BigNumber(xrp)).times(1000000.0).floor().toString(10)
|
||||
return (new BigNumber(xrp)).times(1000000.0).integerValue(BigNumber.ROUND_FLOOR).toString(10)
|
||||
}
|
||||
|
||||
function toRippledAmount(amount: Amount): RippledAmount {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {validate, removeUndefined, dropsToXrp} from '../common'
|
||||
import {validate, removeUndefined, dropsToXrp, ensureClassicAddress} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
import {AccountInfoResponse} from '../common/types/commands/account_info'
|
||||
|
||||
@@ -34,6 +34,11 @@ export default async function getAccountInfo(
|
||||
): Promise<FormattedGetAccountInfoResponse> {
|
||||
// 1. Validate
|
||||
validate.getAccountInfo({address, options})
|
||||
|
||||
// Only support retrieving account info without a tag,
|
||||
// since account info is not distinguished by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as utils from './utils'
|
||||
import {validate} from '../common'
|
||||
import {validate, ensureClassicAddress} from '../common'
|
||||
import {Connection} from '../common'
|
||||
import {GetTrustlinesOptions} from './trustlines'
|
||||
import {FormattedTrustline} from '../common/types/objects/trustlines'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
|
||||
export type Balance = {
|
||||
value: string,
|
||||
currency: string,
|
||||
@@ -52,6 +51,13 @@ function getBalances(this: RippleAPI, address: string, options: GetTrustlinesOpt
|
||||
): Promise<GetBalances> {
|
||||
validate.getTrustlines({address, options})
|
||||
|
||||
// Only support retrieving balances without a tag,
|
||||
// since we currently do not calculate balances
|
||||
// on a per-tag basis. Apps must interpret and
|
||||
// use tags independent of the XRP Ledger, comparing
|
||||
// with the XRP Ledger's balance as an accounting check.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
return Promise.all([
|
||||
getLedgerVersionHelper(this.connection, options.ledgerVersion).then(
|
||||
ledgerVersion =>
|
||||
|
||||
@@ -33,6 +33,6 @@ export default async function getOrders(
|
||||
ledger_index: options.ledgerVersion || await this.getLedgerVersion(),
|
||||
limit: options.limit
|
||||
})
|
||||
// 3. Return Formatted Response
|
||||
// 3. Return Formatted Response, from the perspective of `address`
|
||||
return formatResponse(address, responses)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export type FormattedAccountOrder = {
|
||||
// TODO: remove this function once rippled provides quality directly
|
||||
function computeQuality(takerGets, takerPays) {
|
||||
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value)
|
||||
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString()
|
||||
return quotient.precision(16, BigNumber.ROUND_HALF_UP).toString()
|
||||
}
|
||||
|
||||
// rippled 'account_offers' returns a different format for orders than 'tx'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as assert from 'assert'
|
||||
|
||||
function parseOrderCancellation(tx: any): object {
|
||||
assert(tx.TransactionType === 'OfferCancel')
|
||||
assert.ok(tx.TransactionType === 'OfferCancel')
|
||||
return {
|
||||
orderSequence: tx.OfferSequence
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export type FormattedCheckCancel = {
|
||||
}
|
||||
|
||||
function parseCheckCancel(tx: any): FormattedCheckCancel {
|
||||
assert(tx.TransactionType === 'CheckCancel')
|
||||
assert.ok(tx.TransactionType === 'CheckCancel')
|
||||
|
||||
return removeUndefined({
|
||||
checkID: tx.CheckID
|
||||
|
||||
@@ -23,7 +23,7 @@ export type FormattedCheckCash = {
|
||||
}
|
||||
|
||||
function parseCheckCash(tx: any): FormattedCheckCash {
|
||||
assert(tx.TransactionType === 'CheckCash')
|
||||
assert.ok(tx.TransactionType === 'CheckCash')
|
||||
|
||||
return removeUndefined({
|
||||
checkID: tx.CheckID,
|
||||
|
||||
@@ -24,7 +24,7 @@ export type FormattedCheckCreate = {
|
||||
}
|
||||
|
||||
function parseCheckCreate(tx: any): FormattedCheckCreate {
|
||||
assert(tx.TransactionType === 'CheckCreate')
|
||||
assert.ok(tx.TransactionType === 'CheckCreate')
|
||||
|
||||
return removeUndefined({
|
||||
destination: tx.Destination,
|
||||
|
||||
@@ -10,7 +10,7 @@ export type FormattedDepositPreauth = {
|
||||
}
|
||||
|
||||
function parseDepositPreauth(tx: any): FormattedDepositPreauth {
|
||||
assert(tx.TransactionType === 'DepositPreauth')
|
||||
assert.ok(tx.TransactionType === 'DepositPreauth')
|
||||
|
||||
return removeUndefined({
|
||||
authorize: tx.Authorize,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
|
||||
import {removeUndefined} from '../../common'
|
||||
|
||||
function parseEscrowCancellation(tx: any): object {
|
||||
assert(tx.TransactionType === 'EscrowCancel')
|
||||
assert.ok(tx.TransactionType === 'EscrowCancel')
|
||||
|
||||
return removeUndefined({
|
||||
memos: parseMemos(tx),
|
||||
|
||||
@@ -4,7 +4,7 @@ import {parseTimestamp, parseMemos} from './utils'
|
||||
import {removeUndefined} from '../../common'
|
||||
|
||||
function parseEscrowCreation(tx: any): object {
|
||||
assert(tx.TransactionType === 'EscrowCreate')
|
||||
assert.ok(tx.TransactionType === 'EscrowCreate')
|
||||
|
||||
return removeUndefined({
|
||||
amount: parseAmount(tx.Amount).value,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
|
||||
import {removeUndefined} from '../../common'
|
||||
|
||||
function parseEscrowExecution(tx: any): object {
|
||||
assert(tx.TransactionType === 'EscrowFinish')
|
||||
assert.ok(tx.TransactionType === 'EscrowFinish')
|
||||
|
||||
return removeUndefined({
|
||||
memos: parseMemos(tx),
|
||||
|
||||
@@ -8,7 +8,7 @@ function parseField(info, value) {
|
||||
return Buffer.from(value, 'hex').toString('ascii')
|
||||
}
|
||||
if (info.shift) {
|
||||
return (new BigNumber(value)).shift(-info.shift).toNumber()
|
||||
return (new BigNumber(value)).shiftedBy(-info.shift).toNumber()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
const flags = txFlags.OfferCreate
|
||||
|
||||
function parseOrder(tx: OfferCreateTransaction): FormattedOrderSpecification {
|
||||
assert(tx.TransactionType === 'OfferCreate')
|
||||
assert.ok(tx.TransactionType === 'OfferCreate')
|
||||
|
||||
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
|
||||
const takerGetsAmount = parseAmount(tx.TakerGets)
|
||||
|
||||
@@ -4,7 +4,7 @@ import parseAmount from './amount'
|
||||
const claimFlags = txFlags.PaymentChannelClaim
|
||||
|
||||
function parsePaymentChannelClaim(tx: any): object {
|
||||
assert(tx.TransactionType === 'PaymentChannelClaim')
|
||||
assert.ok(tx.TransactionType === 'PaymentChannelClaim')
|
||||
|
||||
return removeUndefined({
|
||||
channel: tx.Channel,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
|
||||
import parseAmount from './amount'
|
||||
|
||||
function parsePaymentChannelCreate(tx: any): object {
|
||||
assert(tx.TransactionType === 'PaymentChannelCreate')
|
||||
assert.ok(tx.TransactionType === 'PaymentChannelCreate')
|
||||
|
||||
return removeUndefined({
|
||||
amount: parseAmount(tx.Amount).value,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
|
||||
import parseAmount from './amount'
|
||||
|
||||
function parsePaymentChannelFund(tx: any): object {
|
||||
assert(tx.TransactionType === 'PaymentChannelFund')
|
||||
assert.ok(tx.TransactionType === 'PaymentChannelFund')
|
||||
|
||||
return removeUndefined({
|
||||
channel: tx.Channel,
|
||||
|
||||
@@ -19,7 +19,7 @@ function removeGenericCounterparty(amount, address) {
|
||||
|
||||
// Payment specification
|
||||
function parsePayment(tx: any): object {
|
||||
assert(tx.TransactionType === 'Payment')
|
||||
assert.ok(tx.TransactionType === 'Payment')
|
||||
|
||||
const source = {
|
||||
address: tx.Account,
|
||||
|
||||
@@ -7,7 +7,7 @@ import parseFields from './fields'
|
||||
function getAccountRootModifiedNode(tx: any) {
|
||||
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
|
||||
node.ModifiedNode.LedgerEntryType === 'AccountRoot')
|
||||
assert(modifiedNodes.length === 1)
|
||||
assert.ok(modifiedNodes.length === 1)
|
||||
return modifiedNodes[0].ModifiedNode
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ function parseFlags(tx: any): any {
|
||||
|
||||
function parseSettings(tx: any) {
|
||||
const txType = tx.TransactionType
|
||||
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
|
||||
assert.ok(txType === 'AccountSet' || txType === 'SetRegularKey' ||
|
||||
txType === 'SignerListSet')
|
||||
|
||||
return _.assign({}, parseFlags(tx), parseFields(tx))
|
||||
|
||||
@@ -14,7 +14,7 @@ function parseFlag(flagsValue, trueValue, falseValue) {
|
||||
}
|
||||
|
||||
function parseTrustline(tx: any): object {
|
||||
assert(tx.TransactionType === 'TrustSet')
|
||||
assert.ok(tx.TransactionType === 'TrustSet')
|
||||
|
||||
return removeUndefined({
|
||||
limit: tx.LimitAmount.value,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash'
|
||||
import transactionParser = require('ripple-lib-transactionparser')
|
||||
import transactionParser from 'ripple-lib-transactionparser'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as common from '../../common'
|
||||
import parseAmount from './amount'
|
||||
@@ -15,14 +15,14 @@ function adjustQualityForXRP(
|
||||
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0)
|
||||
const shift = numeratorShift - denominatorShift
|
||||
return shift === 0 ? quality :
|
||||
(new BigNumber(quality)).shift(shift).toString()
|
||||
(new BigNumber(quality)).shiftedBy(shift).toString()
|
||||
}
|
||||
|
||||
function parseQuality(quality?: number|null): number|undefined {
|
||||
if (typeof quality !== 'number') {
|
||||
return undefined
|
||||
}
|
||||
return (new BigNumber(quality)).shift(-9).toNumber()
|
||||
return (new BigNumber(quality)).shiftedBy(-9).toNumber()
|
||||
}
|
||||
|
||||
function parseTimestamp(rippleTime?: number|null): string|undefined {
|
||||
|
||||
@@ -73,7 +73,7 @@ function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string
|
||||
// Add XRP "path" only if the source acct has enough XRP to make the payment
|
||||
const destinationAmount = paths.destination_amount
|
||||
// @ts-ignore: destinationAmount can be a currency amount object! Fix!
|
||||
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
|
||||
if ((new BigNumber(xrpBalance)).isGreaterThanOrEqualTo(destinationAmount)) {
|
||||
paths.alternatives.unshift({
|
||||
paths_computed: [],
|
||||
source_amount: paths.destination_amount
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as _ from 'lodash'
|
||||
import parseFields from './parse/fields'
|
||||
import {validate, constants} from '../common'
|
||||
import {validate, constants, ensureClassicAddress} from '../common'
|
||||
import {FormattedSettings} from '../common/types/objects'
|
||||
import {AccountInfoResponse} from '../common/types/commands'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
const AccountFlags = constants.AccountFlags
|
||||
|
||||
export type SettingsOptions = {
|
||||
@@ -38,6 +39,11 @@ export async function getSettings(
|
||||
): Promise<FormattedSettings> {
|
||||
// 1. Validate
|
||||
validate.getSettings({address, options})
|
||||
|
||||
// Only support retrieving settings without a tag,
|
||||
// since settings do not distinguish by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
|
||||
@@ -30,8 +30,12 @@ function attachTransactionDate(connection: Connection, tx: any
|
||||
|
||||
if (!ledgerVersion) {
|
||||
return new Promise(() => {
|
||||
throw new errors.NotFoundError(
|
||||
'ledger_index and LedgerSequence not found in tx')
|
||||
const error = new errors.NotFoundError(
|
||||
'Transaction has not been validated yet; try again later')
|
||||
error.data = {
|
||||
details: '(ledger_index and LedgerSequence not found in tx)'
|
||||
}
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import * as _ from 'lodash'
|
||||
import binary = require('ripple-binary-codec')
|
||||
const {computeTransactionHash} = require('ripple-hashes')
|
||||
import binary from 'ripple-binary-codec';
|
||||
import {computeTransactionHash} from '../common/hashes'
|
||||
import * as utils from './utils'
|
||||
import parseTransaction from './parse/transaction'
|
||||
import getTransaction from './transaction'
|
||||
import {validate, errors, Connection} from '../common'
|
||||
import {validate, errors, Connection, ensureClassicAddress} from '../common'
|
||||
import {FormattedTransactionType} from '../transaction/types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
|
||||
export type TransactionsOptions = {
|
||||
start?: string,
|
||||
limit?: number,
|
||||
@@ -167,6 +166,12 @@ function getTransactions(this: RippleAPI, address: string, options: Transactions
|
||||
): Promise<GetTransactionsResponse> {
|
||||
validate.getTransactions({address, options})
|
||||
|
||||
// Only support retrieving transactions without a tag,
|
||||
// since we currently do not filter transactions based
|
||||
// on tag. Apps must interpret and use tags
|
||||
// independently, filtering transactions if needed.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
const defaults = {maxLedgerVersion: -1}
|
||||
if (options.start) {
|
||||
return getTransaction.call(this, options.start).then(tx => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash'
|
||||
import {validate} from '../common'
|
||||
import {validate, ensureClassicAddress} from '../common'
|
||||
import parseAccountTrustline from './parse/account-trustline'
|
||||
import {RippleAPI} from '..'
|
||||
import {FormattedTrustline} from '../common/types/objects/trustlines'
|
||||
@@ -20,11 +20,16 @@ async function getTrustlines(
|
||||
): Promise<FormattedTrustline[]> {
|
||||
// 1. Validate
|
||||
validate.getTrustlines({address, options})
|
||||
const ledgerVersion = await this.getLedgerVersion()
|
||||
|
||||
// Only support retrieving trustlines without a tag,
|
||||
// since it does not make sense to filter trustlines
|
||||
// by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const responses = await this._requestAll('account_lines', {
|
||||
account: address,
|
||||
ledger_index: ledgerVersion,
|
||||
ledger_index: await this.getLedgerVersion(),
|
||||
limit: options.limit,
|
||||
peer: options.counterparty
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as common from '../common'
|
||||
import {Connection} from '../common'
|
||||
import {FormattedTransactionType} from '../transaction/types'
|
||||
import {Issue} from '../common/types/objects'
|
||||
import {RippleAPI} from '..'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
export type RecursiveData = {
|
||||
marker: string,
|
||||
@@ -14,7 +14,7 @@ export type RecursiveData = {
|
||||
export type Getter = (marker?: string, limit?: number) => Promise<RecursiveData>
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
assert(min <= max, 'Illegal clamp bounds')
|
||||
assert.ok(min <= max, 'Illegal clamp bounds')
|
||||
return Math.min(Math.max(value, min), max)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import {deriveKeypair, deriveAddress} from 'ripple-keypairs'
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
|
||||
function deriveXAddress(options: {publicKey: string, tag: number | false, test: boolean}): string {
|
||||
const classicAddress = deriveAddress(options.publicKey)
|
||||
return classicAddressToXAddress(classicAddress, options.tag, options.test)
|
||||
}
|
||||
|
||||
export {
|
||||
deriveKeypair,
|
||||
deriveAddress
|
||||
deriveAddress,
|
||||
deriveXAddress
|
||||
}
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import * as common from '../common'
|
||||
const {errors, validate} = common
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import {errors, validate} from '../common'
|
||||
|
||||
export type GeneratedAddress = {
|
||||
secret: string,
|
||||
address: string
|
||||
xAddress: string,
|
||||
classicAddress?: string,
|
||||
address?: string, // @deprecated Use `classicAddress` instead.
|
||||
secret: string
|
||||
}
|
||||
|
||||
function generateAddressAPI(options?: any): GeneratedAddress {
|
||||
export interface GenerateAddressOptions {
|
||||
// The entropy to use to generate the seed.
|
||||
entropy?: Uint8Array,
|
||||
|
||||
// The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519',
|
||||
|
||||
// Specifies whether the address is intended for use on a test network such as Testnet or Devnet.
|
||||
// If `true`, the address should only be used for testing, and will start with `T`.
|
||||
// If `false` (default), the address should only be used on mainnet, and will start with `X`.
|
||||
test?: boolean,
|
||||
|
||||
// If `true`, return the classic address, in addition to the X-address.
|
||||
includeClassicAddress?: boolean
|
||||
}
|
||||
|
||||
function generateAddressAPI(options: GenerateAddressOptions): GeneratedAddress {
|
||||
validate.generateAddress({options})
|
||||
try {
|
||||
const secret = keypairs.generateSeed(options)
|
||||
const keypair = keypairs.deriveKeypair(secret)
|
||||
const address = keypairs.deriveAddress(keypair.publicKey)
|
||||
return {secret, address}
|
||||
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
||||
const returnValue: any = {
|
||||
xAddress: classicAddressToXAddress(classicAddress, false, options && options.test),
|
||||
secret
|
||||
}
|
||||
if (options.includeClassicAddress) {
|
||||
returnValue.classicAddress = classicAddress
|
||||
returnValue.address = classicAddress
|
||||
}
|
||||
return returnValue
|
||||
} catch (error) {
|
||||
throw new errors.UnexpectedError(error.message)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash'
|
||||
import hashes = require('ripple-hashes')
|
||||
import {computeLedgerHash, computeTransactionTreeHash, computeStateTreeHash} from '../common/hashes'
|
||||
import * as common from '../common'
|
||||
|
||||
function convertLedgerHeader(header): any {
|
||||
@@ -22,11 +22,11 @@ function convertLedgerHeader(header): any {
|
||||
|
||||
function hashLedgerHeader(ledgerHeader) {
|
||||
const header = convertLedgerHeader(ledgerHeader)
|
||||
return hashes.computeLedgerHash(header)
|
||||
return computeLedgerHash(header)
|
||||
}
|
||||
|
||||
function computeTransactionHash(ledger, version,
|
||||
options: ComputeLedgerHashOptions) {
|
||||
function computeTransactionHash(ledger,
|
||||
options: ComputeLedgerHeaderHashOptions) {
|
||||
let transactions: any[]
|
||||
if (ledger.rawTransactions) {
|
||||
transactions = JSON.parse(ledger.rawTransactions)
|
||||
@@ -56,7 +56,7 @@ function computeTransactionHash(ledger, version,
|
||||
tx.meta ? {metaData: tx.meta} : {})
|
||||
return renameMeta
|
||||
})
|
||||
const transactionHash = hashes.computeTransactionTreeHash(txs, version)
|
||||
const transactionHash = computeTransactionTreeHash(txs)
|
||||
if (ledger.transactionHash !== undefined
|
||||
&& ledger.transactionHash !== transactionHash) {
|
||||
throw new common.errors.ValidationError('transactionHash in header'
|
||||
@@ -68,8 +68,8 @@ function computeTransactionHash(ledger, version,
|
||||
return transactionHash
|
||||
}
|
||||
|
||||
function computeStateHash(ledger, version,
|
||||
options: ComputeLedgerHashOptions) {
|
||||
function computeStateHash(ledger,
|
||||
options: ComputeLedgerHeaderHashOptions) {
|
||||
if (ledger.rawState === undefined) {
|
||||
if (options.computeTreeHashes) {
|
||||
throw new common.errors.ValidationError('rawState'
|
||||
@@ -78,7 +78,7 @@ function computeStateHash(ledger, version,
|
||||
return ledger.stateHash
|
||||
}
|
||||
const state = JSON.parse(ledger.rawState)
|
||||
const stateHash = hashes.computeStateTreeHash(state, version)
|
||||
const stateHash = computeStateTreeHash(state)
|
||||
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
|
||||
throw new common.errors.ValidationError('stateHash in header'
|
||||
+ ' does not match computed hash of state')
|
||||
@@ -86,20 +86,17 @@ function computeStateHash(ledger, version,
|
||||
return stateHash
|
||||
}
|
||||
|
||||
const sLCF_SHAMapV2 = 0x02
|
||||
|
||||
export type ComputeLedgerHashOptions = {
|
||||
export type ComputeLedgerHeaderHashOptions = {
|
||||
computeTreeHashes?: boolean
|
||||
}
|
||||
|
||||
function computeLedgerHash(ledger: any,
|
||||
options: ComputeLedgerHashOptions = {}): string {
|
||||
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
|
||||
function computeLedgerHeaderHash(ledger: any,
|
||||
options: ComputeLedgerHeaderHashOptions = {}): string {
|
||||
const subhashes = {
|
||||
transactionHash: computeTransactionHash(ledger, version, options),
|
||||
stateHash: computeStateHash(ledger, version, options)
|
||||
transactionHash: computeTransactionHash(ledger, options),
|
||||
stateHash: computeStateHash(ledger, options)
|
||||
}
|
||||
return hashLedgerHeader(_.assign({}, ledger, subhashes))
|
||||
}
|
||||
|
||||
export default computeLedgerHash
|
||||
export default computeLedgerHeaderHash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as common from '../common'
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import binary = require('ripple-binary-codec')
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import binary from 'ripple-binary-codec'
|
||||
const {validate, xrpToDrops} = common
|
||||
|
||||
function signPaymentChannelClaim(channel: string, amount: string,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import binary = require('ripple-binary-codec')
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import binary from 'ripple-binary-codec'
|
||||
import {validate, xrpToDrops} from '../common'
|
||||
|
||||
function verifyPaymentChannelClaim(channel: string, amount: string,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as _ from 'lodash'
|
||||
import binary = require('ripple-binary-codec')
|
||||
import binary from 'ripple-binary-codec'
|
||||
import * as utils from './utils'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import {decodeAccountID} from 'ripple-address-codec'
|
||||
import {validate} from '../common'
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
const hex = (Buffer.from(decodeAccountID(address))).toString('hex')
|
||||
return new BigNumber(hex, 16)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Amount, Adjustment, MaxAdjustment,
|
||||
MinAdjustment, Memo} from '../common/types/objects'
|
||||
import {xrpToDrops} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
import {getClassicAccountAndTag, ClassicAccountAndTag} from './utils'
|
||||
|
||||
|
||||
export interface Payment {
|
||||
@@ -84,15 +85,49 @@ function createMaximalAmount(amount: Amount): Amount {
|
||||
return _.assign({}, amount, {value: maxValue})
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an address and tag:
|
||||
* 1. Get the classic account and tag;
|
||||
* 2. If a tag is provided:
|
||||
* 2a. If the address was an X-address, validate that the X-address has the expected tag;
|
||||
* 2b. If the address was a classic address, return `expectedTag` as the tag.
|
||||
* 3. If we do not want to use a tag in this case,
|
||||
* set the tag in the return value to `undefined`.
|
||||
*
|
||||
* @param address The address to parse.
|
||||
* @param expectedTag If provided, and the `Account` is an X-address,
|
||||
* this method throws an error if `expectedTag`
|
||||
* does not match the tag of the X-address.
|
||||
* @returns {ClassicAccountAndTag}
|
||||
* The classic account and tag.
|
||||
*/
|
||||
function validateAndNormalizeAddress(address: string, expectedTag: number | undefined): ClassicAccountAndTag {
|
||||
const classicAddress = getClassicAccountAndTag(address, expectedTag)
|
||||
classicAddress.tag = classicAddress.tag === false ? undefined : classicAddress.tag
|
||||
return classicAddress
|
||||
}
|
||||
|
||||
function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
): TransactionJSON {
|
||||
const payment = _.cloneDeep(paymentArgument)
|
||||
applyAnyCounterpartyEncoding(payment)
|
||||
|
||||
if (address !== payment.source.address) {
|
||||
const sourceAddressAndTag = validateAndNormalizeAddress(payment.source.address, payment.source.tag)
|
||||
const addressToVerifyAgainst = validateAndNormalizeAddress(address, undefined)
|
||||
|
||||
if (addressToVerifyAgainst.classicAccount !== sourceAddressAndTag.classicAccount) {
|
||||
throw new ValidationError('address must match payment.source.address')
|
||||
}
|
||||
|
||||
if (addressToVerifyAgainst.tag !== undefined &&
|
||||
sourceAddressAndTag.tag !== undefined &&
|
||||
addressToVerifyAgainst.tag !== sourceAddressAndTag.tag) {
|
||||
throw new ValidationError(
|
||||
'address includes a tag that does not match payment.source.tag')
|
||||
}
|
||||
|
||||
const destinationAddressAndTag = validateAndNormalizeAddress(payment.destination.address, payment.destination.tag)
|
||||
|
||||
if (
|
||||
(isMaxAdjustment(payment.source) && isMinAdjustment(payment.destination))
|
||||
||
|
||||
@@ -119,8 +154,8 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
|
||||
const txJSON: any = {
|
||||
TransactionType: 'Payment',
|
||||
Account: payment.source.address,
|
||||
Destination: payment.destination.address,
|
||||
Account: sourceAddressAndTag.classicAccount,
|
||||
Destination: destinationAddressAndTag.classicAccount,
|
||||
Amount: toRippledAmount(amount),
|
||||
Flags: 0
|
||||
}
|
||||
@@ -128,11 +163,11 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
if (payment.invoiceID !== undefined) {
|
||||
txJSON.InvoiceID = payment.invoiceID
|
||||
}
|
||||
if (payment.source.tag !== undefined) {
|
||||
txJSON.SourceTag = payment.source.tag
|
||||
if (sourceAddressAndTag.tag !== undefined) {
|
||||
txJSON.SourceTag = sourceAddressAndTag.tag
|
||||
}
|
||||
if (payment.destination.tag !== undefined) {
|
||||
txJSON.DestinationTag = payment.destination.tag
|
||||
if (destinationAddressAndTag.tag !== undefined) {
|
||||
txJSON.DestinationTag = destinationAddressAndTag.tag
|
||||
}
|
||||
if (payment.memos !== undefined) {
|
||||
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
|
||||
|
||||
@@ -10,7 +10,7 @@ import {RippleAPI} from '..'
|
||||
|
||||
function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSettings) {
|
||||
const keys = Object.keys(values)
|
||||
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
|
||||
assert.ok(keys.length === 1, 'ERROR: can only set one setting per transaction')
|
||||
const flagName = keys[0]
|
||||
const value = values[flagName]
|
||||
const index = AccountFlagIndices[flagName]
|
||||
@@ -62,7 +62,7 @@ function setTransactionFields(txJSON: utils.TransactionJSON, input: FormattedSet
|
||||
*/
|
||||
|
||||
function convertTransferRate(transferRate: number): number {
|
||||
return (new BigNumber(transferRate)).shift(9).toNumber()
|
||||
return (new BigNumber(transferRate)).shiftedBy(9).toNumber()
|
||||
}
|
||||
|
||||
function formatSignerEntry(signer: WeightedSigner): object {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import * as isEqual from '../common/js/lodash.isequal'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import * as utils from './utils'
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import binaryCodec = require('ripple-binary-codec')
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import binaryCodec from 'ripple-binary-codec'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
import {SignOptions, KeyPair} from './types'
|
||||
import {BigNumber} from 'bignumber.js'
|
||||
import {xrpToDrops} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
const validate = utils.common.validate
|
||||
const validate = utils.common.validate
|
||||
|
||||
function computeSignature(tx: object, privateKey: string, signAs?: string) {
|
||||
const signingData = signAs
|
||||
@@ -60,6 +60,72 @@ function signWithKeypair(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two objects and creates a diff.
|
||||
*
|
||||
* @param a An object to compare.
|
||||
* @param b The other object to compare with.
|
||||
*
|
||||
* @returns An object containing the differences between the two objects.
|
||||
*/
|
||||
function objectDiff(a: object, b: object): object {
|
||||
const diffs = {}
|
||||
|
||||
// Compare two items and push non-matches to object
|
||||
const compare = function (i1: any, i2: any, k: string): void {
|
||||
const type1 = Object.prototype.toString.call(i1)
|
||||
const type2 = Object.prototype.toString.call(i2)
|
||||
if (type2 === '[object Undefined]') {
|
||||
diffs[k] = null // Indicate that the item has been removed
|
||||
return
|
||||
}
|
||||
if (type1 !== type2) {
|
||||
diffs[k] = i2 // Indicate that the item has changed types
|
||||
return
|
||||
}
|
||||
if (type1 === '[object Object]') {
|
||||
const objDiff = objectDiff(i1, i2)
|
||||
if (Object.keys(objDiff).length > 0) {
|
||||
diffs[k] = objDiff
|
||||
}
|
||||
return
|
||||
}
|
||||
if (type1 === '[object Array]') {
|
||||
if (!isEqual(i1, i2)) {
|
||||
diffs[k] = i2 // If arrays do not match, add second item to diffs
|
||||
}
|
||||
return
|
||||
}
|
||||
if (type1 === '[object Function]') {
|
||||
if (i1.toString() !== i2.toString()) {
|
||||
diffs[k] = i2 // If functions differ, add second one to diffs
|
||||
}
|
||||
return
|
||||
}
|
||||
if (i1 !== i2) {
|
||||
diffs[k] = i2
|
||||
}
|
||||
}
|
||||
|
||||
// Check items in first object
|
||||
for (const key in a) {
|
||||
if (a.hasOwnProperty(key)) {
|
||||
compare(a[key], b[key], key)
|
||||
}
|
||||
}
|
||||
|
||||
// Get items that are in the second object but not the first
|
||||
for (const key in b) {
|
||||
if (b.hasOwnProperty(key)) {
|
||||
if (!a[key] && a[key] !== b[key]) {
|
||||
diffs[key] = b[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diffs
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a serialized transaction, remove the fields that are added during the signing process,
|
||||
* and verify that it matches the transaction prior to signing.
|
||||
@@ -93,11 +159,12 @@ function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): vo
|
||||
|
||||
if (!isEqual(decoded, tx)) {
|
||||
const error = new utils.common.errors.ValidationError(
|
||||
'Serialized transaction does not match original txJSON'
|
||||
'Serialized transaction does not match original txJSON. See `error.data`'
|
||||
)
|
||||
error.data = {
|
||||
decoded,
|
||||
tx
|
||||
tx,
|
||||
diff: objectDiff(tx, decoded)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
@@ -116,7 +183,7 @@ function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): vo
|
||||
function checkFee(api: RippleAPI, txFee: string): void {
|
||||
const fee = new BigNumber(txFee)
|
||||
const maxFeeDrops = xrpToDrops(api._maxFeeXRP)
|
||||
if (fee.greaterThan(maxFeeDrops)) {
|
||||
if (fee.isGreaterThan(maxFeeDrops)) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
`"Fee" should not exceed "${maxFeeDrops}". ` +
|
||||
'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
function convertQuality(quality) {
|
||||
return (new BigNumber(quality)).shift(9).truncated().toNumber()
|
||||
return (new BigNumber(quality)).shiftedBy(9).integerValue(BigNumber.ROUND_DOWN).toNumber()
|
||||
}
|
||||
|
||||
function createTrustlineTransaction(account: string,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as common from '../common'
|
||||
import {Memo, RippledAmount} from '../common/types/objects'
|
||||
const txFlags = common.txFlags
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
import {ValidationError} from '../common/errors'
|
||||
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
|
||||
const txFlags = common.txFlags
|
||||
const TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD = ['Payment', 'CheckCreate', 'EscrowCreate', 'PaymentChannelCreate']
|
||||
|
||||
export type ApiMemo = {
|
||||
MemoData?: string,
|
||||
@@ -56,6 +59,49 @@ function scaleValue(value, multiplier, extra = 0) {
|
||||
return (new BigNumber(value)).times(multiplier).plus(extra).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClassicAccountAndTag
|
||||
* @property {string} classicAccount - The classic account address.
|
||||
* @property {number | false | undefined } tag - The destination tag;
|
||||
* `false` if no tag should be used;
|
||||
* `undefined` if the input could not specify whether a tag should be used.
|
||||
*/
|
||||
export interface ClassicAccountAndTag {
|
||||
classicAccount: string,
|
||||
tag: number | false | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an address (account), get the classic account and tag.
|
||||
* If an `expectedTag` is provided:
|
||||
* 1. If the `Account` is an X-address, validate that the tags match.
|
||||
* 2. If the `Account` is a classic address, return `expectedTag` as the tag.
|
||||
*
|
||||
* @param Account The address to parse.
|
||||
* @param expectedTag If provided, and the `Account` is an X-address,
|
||||
* this method throws an error if `expectedTag`
|
||||
* does not match the tag of the X-address.
|
||||
* @returns {ClassicAccountAndTag}
|
||||
* The classic account and tag.
|
||||
*/
|
||||
function getClassicAccountAndTag(Account: string, expectedTag?: number): ClassicAccountAndTag {
|
||||
if (isValidXAddress(Account)) {
|
||||
const classic = xAddressToClassicAddress(Account)
|
||||
if (expectedTag !== undefined && classic.tag !== expectedTag) {
|
||||
throw new ValidationError('address includes a tag that does not match the tag specified in the transaction')
|
||||
}
|
||||
return {
|
||||
classicAccount: classic.classicAddress,
|
||||
tag: classic.tag
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
classicAccount: Account,
|
||||
tag: expectedTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
instructions: Instructions
|
||||
): Promise<Prepare> {
|
||||
@@ -68,110 +114,159 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
'" exists in instance when not allowed'))
|
||||
}
|
||||
|
||||
// To remove the signer list, SignerEntries field should be omitted.
|
||||
const newTxJSON = Object.assign({}, txJSON)
|
||||
|
||||
// To remove the signer list, `SignerEntries` field should be omitted.
|
||||
if (txJSON['SignerQuorum'] === 0) {
|
||||
delete txJSON.SignerEntries;
|
||||
delete newTxJSON.SignerEntries
|
||||
}
|
||||
|
||||
const account = txJSON.Account
|
||||
setCanonicalFlag(txJSON)
|
||||
// Sender:
|
||||
const {classicAccount, tag: sourceTag} = getClassicAccountAndTag(txJSON.Account)
|
||||
newTxJSON.Account = classicAccount
|
||||
if (sourceTag !== undefined) {
|
||||
if (txJSON.SourceTag && txJSON.SourceTag !== sourceTag) {
|
||||
return Promise.reject(new ValidationError(
|
||||
'The `SourceTag`, if present, must match the tag of the `Account` X-address'))
|
||||
}
|
||||
if (sourceTag) {
|
||||
newTxJSON.SourceTag = sourceTag
|
||||
}
|
||||
}
|
||||
|
||||
function prepareMaxLedgerVersion(): Promise<TransactionJSON> {
|
||||
// Destination:
|
||||
if (typeof txJSON.Destination === 'string') {
|
||||
const {classicAccount: destinationAccount, tag: destinationTag} = getClassicAccountAndTag(txJSON.Destination)
|
||||
newTxJSON.Destination = destinationAccount
|
||||
if (destinationTag !== undefined) {
|
||||
if (TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD.includes(txJSON.TransactionType)) {
|
||||
if (txJSON.DestinationTag && txJSON.DestinationTag !== destinationTag) {
|
||||
return Promise.reject(new ValidationError(
|
||||
'The Payment `DestinationTag`, if present, must match the tag of the `Destination` X-address'))
|
||||
}
|
||||
if (destinationTag) {
|
||||
newTxJSON.DestinationTag = destinationTag
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertToClassicAccountIfPresent(fieldName: string): void {
|
||||
const account = txJSON[fieldName]
|
||||
if (typeof account === 'string') {
|
||||
const {classicAccount: ca} = getClassicAccountAndTag(account)
|
||||
newTxJSON[fieldName] = ca
|
||||
}
|
||||
}
|
||||
|
||||
// DepositPreauth:
|
||||
convertToClassicAccountIfPresent('Authorize')
|
||||
convertToClassicAccountIfPresent('Unauthorize')
|
||||
|
||||
// EscrowCancel, EscrowFinish:
|
||||
convertToClassicAccountIfPresent('Owner')
|
||||
|
||||
// SetRegularKey:
|
||||
convertToClassicAccountIfPresent('RegularKey')
|
||||
|
||||
setCanonicalFlag(newTxJSON)
|
||||
|
||||
function prepareMaxLedgerVersion(): Promise<void> {
|
||||
// Up to one of the following is allowed:
|
||||
// txJSON.LastLedgerSequence
|
||||
// instructions.maxLedgerVersion
|
||||
// instructions.maxLedgerVersionOffset
|
||||
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
|
||||
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
|
||||
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersion`' +
|
||||
' in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
|
||||
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
|
||||
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersionOffset`' +
|
||||
' in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.LastLedgerSequence) {
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.LastLedgerSequence) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (instructions.maxLedgerVersion !== undefined) {
|
||||
if (instructions.maxLedgerVersion !== null) {
|
||||
txJSON.LastLedgerSequence = instructions.maxLedgerVersion
|
||||
newTxJSON.LastLedgerSequence = instructions.maxLedgerVersion
|
||||
}
|
||||
return Promise.resolve(txJSON)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const offset = instructions.maxLedgerVersionOffset !== undefined ?
|
||||
instructions.maxLedgerVersionOffset : 3
|
||||
return api.connection.getLedgerVersion().then(ledgerVersion => {
|
||||
txJSON.LastLedgerSequence = ledgerVersion + offset
|
||||
return txJSON
|
||||
newTxJSON.LastLedgerSequence = ledgerVersion + offset
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
function prepareFee(): Promise<TransactionJSON> {
|
||||
function prepareFee(): Promise<void> {
|
||||
// instructions.fee is scaled (for multi-signed transactions) while txJSON.Fee is not.
|
||||
// Due to this difference, we do NOT allow both to be set, as the behavior would be complex and
|
||||
// potentially ambiguous.
|
||||
// Furthermore, txJSON.Fee is in drops while instructions.fee is in XRP, which would just add to
|
||||
// the confusion. It is simpler to require that only one is used.
|
||||
if (txJSON.Fee && instructions.fee) {
|
||||
if (newTxJSON.Fee && instructions.fee) {
|
||||
return Promise.reject(new ValidationError('`Fee` in txJSON and `fee` in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.Fee) {
|
||||
if (newTxJSON.Fee) {
|
||||
// txJSON.Fee is set. Use this value and do not scale it.
|
||||
return Promise.resolve(txJSON)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const multiplier = instructions.signersCount === undefined ? 1 :
|
||||
instructions.signersCount + 1
|
||||
if (instructions.fee !== undefined) {
|
||||
const fee = new BigNumber(instructions.fee)
|
||||
if (fee.greaterThan(api._maxFeeXRP)) {
|
||||
if (fee.isGreaterThan(api._maxFeeXRP)) {
|
||||
return Promise.reject(new ValidationError(`Fee of ${fee.toString(10)} XRP exceeds ` +
|
||||
`max of ${api._maxFeeXRP} XRP. To use this fee, increase ` +
|
||||
'`maxFeeXRP` in the RippleAPI constructor.'))
|
||||
}
|
||||
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
|
||||
return Promise.resolve(txJSON)
|
||||
newTxJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const cushion = api._feeCushion
|
||||
return api.getFee(cushion).then(fee => {
|
||||
return api.connection.getFeeRef().then(feeRef => {
|
||||
const extraFee =
|
||||
(txJSON.TransactionType !== 'EscrowFinish' ||
|
||||
txJSON.Fulfillment === undefined) ? 0 :
|
||||
(newTxJSON.TransactionType !== 'EscrowFinish' ||
|
||||
newTxJSON.Fulfillment === undefined) ? 0 :
|
||||
(cushion * feeRef * (32 + Math.floor(
|
||||
Buffer.from(txJSON.Fulfillment, 'hex').length / 16)))
|
||||
Buffer.from(newTxJSON.Fulfillment, 'hex').length / 16)))
|
||||
const feeDrops = common.xrpToDrops(fee)
|
||||
const maxFeeXRP = instructions.maxFee ?
|
||||
BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
|
||||
const maxFeeDrops = common.xrpToDrops(maxFeeXRP)
|
||||
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
|
||||
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
|
||||
newTxJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
|
||||
|
||||
return txJSON
|
||||
return
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function prepareSequence(): Promise<TransactionJSON> {
|
||||
async function prepareSequence(): Promise<void> {
|
||||
if (instructions.sequence !== undefined) {
|
||||
if (txJSON.Sequence === undefined || instructions.sequence === txJSON.Sequence) {
|
||||
txJSON.Sequence = instructions.sequence
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.Sequence === undefined || instructions.sequence === newTxJSON.Sequence) {
|
||||
newTxJSON.Sequence = instructions.sequence
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
// Both txJSON.Sequence and instructions.sequence are defined, and they are NOT equal
|
||||
return Promise.reject(new ValidationError('`Sequence` in txJSON must match `sequence` in `instructions`'))
|
||||
}
|
||||
}
|
||||
if (txJSON.Sequence !== undefined) {
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.Sequence !== undefined) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
try {
|
||||
// Consider requesting from the 'current' ledger (instead of 'validated').
|
||||
const response = await api.request('account_info', {
|
||||
account
|
||||
account: classicAccount
|
||||
})
|
||||
txJSON.Sequence = response.account_data.Sequence
|
||||
return Promise.resolve(txJSON)
|
||||
newTxJSON.Sequence = response.account_data.Sequence
|
||||
return Promise.resolve()
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
@@ -181,7 +276,7 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
prepareMaxLedgerVersion(),
|
||||
prepareFee(),
|
||||
prepareSequence()
|
||||
]).then(() => formatPrepareResponse(txJSON))
|
||||
]).then(() => formatPrepareResponse(newTxJSON))
|
||||
}
|
||||
|
||||
function convertStringToHex(string: string): string {
|
||||
@@ -203,5 +298,6 @@ export {
|
||||
convertMemo,
|
||||
prepareTransaction,
|
||||
common,
|
||||
setCanonicalFlag
|
||||
setCanonicalFlag,
|
||||
getClassicAccountAndTag
|
||||
}
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
'use strict'; // eslint-disable-line
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert-diff');
|
||||
const setupAPI = require('./setup-api');
|
||||
const RippleAPI = require('ripple-api').RippleAPI;
|
||||
const validate = RippleAPI._PRIVATE.validate;
|
||||
const fixtures = require('./fixtures');
|
||||
const requests = fixtures.requests;
|
||||
const responses = fixtures.responses;
|
||||
const addresses = require('./fixtures/addresses');
|
||||
const hashes = require('./fixtures/hashes');
|
||||
import assert from 'assert-diff';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import _ from 'lodash';
|
||||
import { RippleAPI } from 'ripple-api';
|
||||
import { RecursiveData } from 'ripple-api/ledger/utils';
|
||||
import binary from 'ripple-binary-codec';
|
||||
import { requests, responses } from './fixtures';
|
||||
import addresses from './fixtures/addresses';
|
||||
import hashes from './fixtures/hashes';
|
||||
import ledgerClosed from './fixtures/rippled/ledger-close-newer.json';
|
||||
import setupAPI from './setup-api';
|
||||
const {validate, schemaValidator} = RippleAPI._PRIVATE;
|
||||
const address = addresses.ACCOUNT;
|
||||
const utils = RippleAPI._PRIVATE.ledgerUtils;
|
||||
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
|
||||
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
|
||||
const binary = require('ripple-binary-codec');
|
||||
const BigNumber = require('bignumber.js');
|
||||
assert.options.strict = true;
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000;
|
||||
|
||||
function unused() {
|
||||
}
|
||||
|
||||
function closeLedger(connection) {
|
||||
connection._ws.emit('message', JSON.stringify(ledgerClosed));
|
||||
}
|
||||
@@ -147,17 +140,17 @@ describe('RippleAPI', function () {
|
||||
|
||||
assert.throws(() => {
|
||||
this.api.xrpToDrops('.')
|
||||
}, /xrpToDrops\: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
||||
}, /xrpToDrops: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
||||
})
|
||||
|
||||
it('throws with an amount more than one decimal point', function () {
|
||||
assert.throws(() => {
|
||||
this.api.xrpToDrops('1.0.0')
|
||||
}, /xrpToDrops:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
||||
}, /xrpToDrops: invalid value '1\.0\.0'/)
|
||||
|
||||
assert.throws(() => {
|
||||
this.api.xrpToDrops('...')
|
||||
}, /xrpToDrops:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
||||
}, /xrpToDrops: invalid value '\.\.\.'/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -261,17 +254,17 @@ describe('RippleAPI', function () {
|
||||
|
||||
assert.throws(() => {
|
||||
this.api.dropsToXrp('.')
|
||||
}, /dropsToXrp\: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
||||
}, /dropsToXrp: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
||||
})
|
||||
|
||||
it('throws with an amount more than one decimal point', function () {
|
||||
assert.throws(() => {
|
||||
this.api.dropsToXrp('1.0.0')
|
||||
}, /dropsToXrp:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
||||
}, /dropsToXrp: invalid value '1\.0\.0'/)
|
||||
|
||||
assert.throws(() => {
|
||||
this.api.dropsToXrp('...')
|
||||
}, /dropsToXrp:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
|
||||
}, /dropsToXrp: invalid value '\.\.\.'/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -311,7 +304,7 @@ describe('RippleAPI', function () {
|
||||
it('throws with an invalid secret', function (){
|
||||
assert.throws(() => {
|
||||
this.api.deriveKeypair('...');
|
||||
}, /^Error\: Non\-base58 character$/)
|
||||
}, /^Error: Non-base58 character$/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -996,7 +989,7 @@ describe('RippleAPI', function () {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format');
|
||||
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account is not exactly one from <xAddress>,<classicAddress>');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
@@ -1020,7 +1013,7 @@ describe('RippleAPI', function () {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.Account does not conform to the "address" format');
|
||||
assert.strictEqual(err.message, 'instance.Account is not exactly one from <xAddress>,<classicAddress>');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
@@ -1364,7 +1357,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects promise and does not throw when field is missing', function (done) {
|
||||
@@ -1393,7 +1386,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects promise and does not throw when fee exceeds maxFeeXRP', function (done) {
|
||||
@@ -1427,7 +1420,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('preparePayment - XRP to XRP no partial', function (done) {
|
||||
@@ -1442,7 +1435,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('preparePayment - address must match payment.source.address', function (done) {
|
||||
@@ -1457,7 +1450,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('preparePayment - wrong amount', function (done) {
|
||||
@@ -1472,7 +1465,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('preparePayment - throws when fee exceeds 2 XRP', function (done) {
|
||||
@@ -1492,7 +1485,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1581,7 +1574,7 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('prepareOrder - invalid', function (done) {
|
||||
const request = requests.prepareOrder.sell;
|
||||
const request = Object.assign({}, requests.prepareOrder.sell);
|
||||
delete request.direction; // Make invalid
|
||||
try {
|
||||
this.api.prepareOrder(address, request, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
||||
@@ -1593,7 +1586,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareOrderCancellation', function () {
|
||||
@@ -1620,7 +1613,7 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('prepareOrderCancellation - invalid', function (done) {
|
||||
const request = requests.prepareOrderCancellation.withMemos;
|
||||
const request = Object.assign({}, requests.prepareOrderCancellation.withMemos);
|
||||
delete request.orderSequence; // Make invalid
|
||||
try {
|
||||
this.api.prepareOrderCancellation(address, request).then(prepared => {
|
||||
@@ -1632,7 +1625,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareTrustline - simple', function () {
|
||||
@@ -1654,7 +1647,7 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('prepareTrustline - invalid', function (done) {
|
||||
const trustline = requests.prepareTrustline.complex;
|
||||
const trustline = Object.assign({}, requests.prepareTrustline.complex);
|
||||
delete trustline.limit; // Make invalid
|
||||
try {
|
||||
this.api.prepareTrustline(
|
||||
@@ -1667,7 +1660,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareSettings', function () {
|
||||
@@ -1764,7 +1757,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareSettings - signers no weights', function () {
|
||||
@@ -1820,7 +1813,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareEscrowCreation', function () {
|
||||
@@ -1855,7 +1848,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareEscrowExecution', function () {
|
||||
@@ -1888,7 +1881,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareEscrowExecution - no fulfillment', function (done) {
|
||||
@@ -1903,7 +1896,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('prepareEscrowCancellation', function () {
|
||||
@@ -2507,7 +2500,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects Promise on preparePaymentChannelClaim with no signature', function (done) {
|
||||
@@ -2522,7 +2515,7 @@ describe('RippleAPI', function () {
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
it('sign', function () {
|
||||
@@ -2679,6 +2672,37 @@ describe('RippleAPI', function () {
|
||||
);
|
||||
});
|
||||
|
||||
it('sign - throws when encoded tx does not match decoded tx - prepared order', async function () {
|
||||
const order = {
|
||||
direction: 'sell',
|
||||
quantity: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
value: '3.140000'
|
||||
},
|
||||
totalPrice: {
|
||||
currency: 'XRP',
|
||||
value: '31415'
|
||||
}
|
||||
};
|
||||
const prepared = await this.api.prepareOrder('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', order, {
|
||||
sequence: 123
|
||||
});
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
try {
|
||||
this.api.sign(prepared.txJSON, secret);
|
||||
return Promise.reject(new Error('api.sign should have thrown'));
|
||||
} catch (error) {
|
||||
assert.equal(error.name, 'ValidationError');
|
||||
assert.equal(error.message, 'Serialized transaction does not match original txJSON. See `error.data`');
|
||||
assert.deepEqual(error.data.diff, {
|
||||
TakerGets: {
|
||||
value: '3.14'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('sign - throws when encoded tx does not match decoded tx - AccountSet', function () {
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
const request = {
|
||||
@@ -2927,7 +2951,7 @@ describe('RippleAPI', function () {
|
||||
const options = {
|
||||
includeRawTransaction: true
|
||||
}
|
||||
const expected = responses.getTransaction.settings
|
||||
const expected = Object.assign({}, responses.getTransaction.settings) // Avoid mutating test fixture
|
||||
expected.rawTransaction = "{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Fee\":\"10\",\"Flags\":2147483648,\"Sequence\":1,\"SetFlag\":2,\"SigningPubKey\":\"03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D\",\"TransactionType\":\"AccountSet\",\"TxnSignature\":\"3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226\",\"date\":460832270,\"hash\":\"4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B\",\"inLedger\":8206418,\"ledger_index\":8206418,\"meta\":{\"AffectedNodes\":[{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Balance\":\"29999990\",\"Flags\":786432,\"OwnerCount\":0,\"Sequence\":2},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F\",\"PreviousFields\":{\"Balance\":\"30000000\",\"Flags\":4194304,\"Sequence\":1},\"PreviousTxnID\":\"3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3\",\"PreviousTxnLgrSeq\":8206397}}],\"TransactionIndex\":5,\"TransactionResult\":\"tesSUCCESS\"},\"validated\":true}"
|
||||
return this.api.getTransaction(hash, options).then(
|
||||
_.partial(checkResult, expected,
|
||||
@@ -3099,14 +3123,14 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('getTransaction - ledger_index not found', function () {
|
||||
it('getTransaction - transaction not validated', function () {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11';
|
||||
return this.api.getTransaction(hash).then(() => {
|
||||
assert(false, 'Should throw NotFoundError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.NotFoundError);
|
||||
assert(error.message.indexOf('ledger_index') !== -1);
|
||||
assert(error.message.indexOf('Transaction has not been validated yet') !== -1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3416,6 +3440,7 @@ describe('RippleAPI', function () {
|
||||
_.partial(checkResult, responses.getTrustlines.all, 'getTrustlines'));
|
||||
});
|
||||
|
||||
// @deprecated See corresponding test in `x-address-api-test.js`
|
||||
it('generateAddress', function () {
|
||||
function random() {
|
||||
return _.fill(Array(16), 0);
|
||||
@@ -3545,9 +3570,9 @@ describe('RippleAPI', function () {
|
||||
taker: address
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
assert.deepEqual(orderbook, responses.getOrderbook.normal);
|
||||
});
|
||||
@@ -3581,9 +3606,9 @@ describe('RippleAPI', function () {
|
||||
taker: address
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
assert.deepEqual(orderbook, responses.getOrderbook.withXRP);
|
||||
});
|
||||
@@ -3611,7 +3636,7 @@ describe('RippleAPI', function () {
|
||||
.dividedBy(order.specification.quantity.value)
|
||||
.toString();
|
||||
}
|
||||
assert((new BigNumber(rate)).greaterThanOrEqualTo(previousRate),
|
||||
assert((new BigNumber(rate)).isGreaterThanOrEqualTo(previousRate),
|
||||
'Rates must be sorted from least to greatest: ' +
|
||||
rate + ' should be >= ' + previousRate);
|
||||
previousRate = rate;
|
||||
@@ -3649,9 +3674,9 @@ describe('RippleAPI', function () {
|
||||
taker: myAddress
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
assert.deepStrictEqual([], orderbook.bids);
|
||||
return checkSortingOfOrders(orderbook.asks);
|
||||
@@ -3682,9 +3707,9 @@ describe('RippleAPI', function () {
|
||||
taker: myAddress
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
return checkSortingOfOrders(orderbook.bids) && checkSortingOfOrders(orderbook.asks);
|
||||
});
|
||||
@@ -3719,9 +3744,9 @@ describe('RippleAPI', function () {
|
||||
taker: address
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
|
||||
const bidRates = orderbook.bids.map(bid => bid.properties.makerExchangeRate);
|
||||
@@ -3763,9 +3788,9 @@ describe('RippleAPI', function () {
|
||||
taker: address
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
|
||||
const orders = _.flatten([orderbook.bids, orderbook.asks]);
|
||||
@@ -3810,9 +3835,9 @@ describe('RippleAPI', function () {
|
||||
taker: address
|
||||
})
|
||||
]
|
||||
).then((directOfferResults, reverseOfferResults) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
|
||||
).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
|
||||
|
||||
assert(
|
||||
@@ -3869,7 +3894,7 @@ describe('RippleAPI', function () {
|
||||
.dividedBy(order.specification.quantity.value)
|
||||
.toString();
|
||||
}
|
||||
assert((new BigNumber(rate)).greaterThanOrEqualTo(previousRate),
|
||||
assert((new BigNumber(rate)).isGreaterThanOrEqualTo(previousRate),
|
||||
'Rates must be sorted from least to greatest: ' +
|
||||
rate + ' should be >= ' + previousRate);
|
||||
previousRate = rate;
|
||||
@@ -4517,8 +4542,7 @@ describe('RippleAPI', function () {
|
||||
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44'
|
||||
});
|
||||
assert.throws(() => {
|
||||
const hash = this.api.computeLedgerHash(ledger);
|
||||
unused(hash);
|
||||
this.api.computeLedgerHash(ledger);
|
||||
}, /does not match computed hash of state/);
|
||||
});
|
||||
});
|
||||
@@ -4545,20 +4569,21 @@ describe('RippleAPI', function () {
|
||||
|
||||
it('ledger utils - renameCounterpartyToIssuerInOrder', function () {
|
||||
const order = {
|
||||
taker_gets: { counterparty: '1' },
|
||||
taker_pays: { counterparty: '1' }
|
||||
taker_gets: { counterparty: '1', currency: 'XRP' },
|
||||
taker_pays: { counterparty: '1', currency: 'XRP' }
|
||||
};
|
||||
const expected = {
|
||||
taker_gets: { issuer: '1' },
|
||||
taker_pays: { issuer: '1' }
|
||||
taker_gets: { issuer: '1', currency: 'XRP' },
|
||||
taker_pays: { issuer: '1', currency: 'XRP' }
|
||||
};
|
||||
assert.deepEqual(utils.renameCounterpartyToIssuerInOrder(order), expected);
|
||||
});
|
||||
|
||||
it('ledger utils - compareTransactions', function () {
|
||||
// @ts-ignore
|
||||
assert.strictEqual(utils.compareTransactions({}, {}), 0);
|
||||
let first = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
||||
let second = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
|
||||
let first: any = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
|
||||
let second: any = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
|
||||
|
||||
assert.strictEqual(utils.compareTransactions(first, second), -1);
|
||||
|
||||
@@ -4574,13 +4599,13 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('ledger utils - getRecursive', function () {
|
||||
function getter(marker, limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (marker === undefined) {
|
||||
resolve({ marker: 'A', limit: limit, results: [1] });
|
||||
} else {
|
||||
function getter(marker) {
|
||||
return new Promise<RecursiveData>((resolve, reject) => {
|
||||
if (marker !== undefined) {
|
||||
reject(new Error());
|
||||
return;
|
||||
}
|
||||
resolve({ marker: 'A', results: [1] });
|
||||
});
|
||||
}
|
||||
return utils.getRecursive(getter, 10).then(() => {
|
||||
@@ -4722,22 +4747,26 @@ describe('RippleAPI - offline', function () {
|
||||
assert.throws(() => api.computeLedgerHash(header));
|
||||
});
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
it('RippleAPI - implicit server port', function () {
|
||||
const api = new RippleAPI({ server: 'wss://s1.ripple.com' });
|
||||
new RippleAPI({ server: 'wss://s1.ripple.com' });
|
||||
});
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
it('RippleAPI invalid options', function () {
|
||||
assert.throws(() => new RippleAPI({ invalid: true }));
|
||||
assert.throws(() => new RippleAPI({ invalid: true } as any));
|
||||
});
|
||||
|
||||
it('RippleAPI valid options', function () {
|
||||
const api = new RippleAPI({ server: 'wss://s:1' });
|
||||
assert.deepEqual(api.connection._url, 'wss://s:1');
|
||||
const privateConnectionUrl = (api.connection as any)._url;
|
||||
assert.deepEqual(privateConnectionUrl, 'wss://s:1');
|
||||
});
|
||||
|
||||
it('RippleAPI invalid server uri', function () {
|
||||
assert.throws(() => new RippleAPI({ server: 'wss//s:1' }));
|
||||
});
|
||||
|
||||
xit('RippleAPI connect() times out after 2 seconds', function () {
|
||||
// TODO: Use a timer mock like https://jestjs.io/docs/en/timer-mocks
|
||||
// to test that connect() times out after 2 seconds.
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,9 @@
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert-diff');
|
||||
const setupAPI = require('./setup-api');
|
||||
const responses = require('./fixtures').responses;
|
||||
const ledgerClosed = require('./fixtures/rippled/ledger-close');
|
||||
const RippleAPI = require('ripple-api').RippleAPI;
|
||||
import _ from 'lodash';
|
||||
import assert from 'assert-diff';
|
||||
import setupAPI from './setup-api';
|
||||
import {responses} from './fixtures';
|
||||
import ledgerClosed from './fixtures/rippled/ledger-close.json';
|
||||
import {RippleAPI} from 'ripple-api';
|
||||
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
|
||||
|
||||
const TIMEOUT = 20000;
|
||||
@@ -1,19 +1,14 @@
|
||||
'use strict'; // eslint-disable-line
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
|
||||
const _ = require('lodash');
|
||||
const net = require('net');
|
||||
const assert = require('assert-diff');
|
||||
const setupAPI = require('./setup-api');
|
||||
const RippleAPI = require('ripple-api').RippleAPI;
|
||||
import _ from 'lodash';
|
||||
import net from 'net';
|
||||
import assert from 'assert-diff';
|
||||
import setupAPI from './setup-api';
|
||||
import {RippleAPI} from 'ripple-api';
|
||||
import ledgerClose from './fixtures/rippled/ledger-close.json';
|
||||
const utils = RippleAPI._PRIVATE.ledgerUtils;
|
||||
const ledgerClose = require('./fixtures/rippled/ledger-close.json');
|
||||
|
||||
|
||||
const TIMEOUT = 200000; // how long before each test case times out
|
||||
|
||||
function unused() {
|
||||
}
|
||||
const isBrowser = (process as any).browser;
|
||||
|
||||
function createServer() {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -34,14 +29,14 @@ describe('Connection', function() {
|
||||
afterEach(setupAPI.teardown);
|
||||
|
||||
it('default options', function() {
|
||||
const connection = new utils.common.Connection('url');
|
||||
const connection: any = new utils.common.Connection('url');
|
||||
assert.strictEqual(connection._url, 'url');
|
||||
assert(_.isUndefined(connection._proxyURL));
|
||||
assert(_.isUndefined(connection._authorization));
|
||||
});
|
||||
|
||||
it('trace', function() {
|
||||
const connection = new utils.common.Connection('url', {trace: true});
|
||||
const connection: any = new utils.common.Connection('url', {trace: true});
|
||||
const message1 = '{"type": "transaction"}';
|
||||
const message2 = '{"type": "path_find"}';
|
||||
const messages = [];
|
||||
@@ -60,11 +55,11 @@ describe('Connection', function() {
|
||||
});
|
||||
|
||||
it('with proxy', function(done) {
|
||||
if (process.browser) {
|
||||
if (isBrowser) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
createServer().then(server => {
|
||||
createServer().then((server: any) => {
|
||||
const port = server.address().port;
|
||||
const expect = 'CONNECT localhost';
|
||||
server.on('connection', socket => {
|
||||
@@ -72,6 +67,7 @@ describe('Connection', function() {
|
||||
const got = data.toString('ascii', 0, expect.length);
|
||||
assert.strictEqual(got, expect);
|
||||
server.close();
|
||||
connection.disconnect();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -81,10 +77,10 @@ describe('Connection', function() {
|
||||
authorization: 'authorization',
|
||||
trustedCertificates: ['path/to/pem']
|
||||
};
|
||||
const connection =
|
||||
new utils.common.Connection(this.api.connection._url, options);
|
||||
connection.connect().catch(done);
|
||||
connection.connect().catch(done);
|
||||
const connection = new utils.common.Connection(this.api.connection._url, options);
|
||||
connection.connect().catch((err) => {
|
||||
assert(err instanceof this.api.errors.NotConnectedError);
|
||||
});
|
||||
}, done);
|
||||
});
|
||||
|
||||
@@ -109,7 +105,7 @@ describe('Connection', function() {
|
||||
it('should throw NotConnectedError if server not responding ', function(
|
||||
done
|
||||
) {
|
||||
if (process.browser) {
|
||||
if (isBrowser) {
|
||||
const phantomTest = /PhantomJS/;
|
||||
if (phantomTest.test(navigator.userAgent)) {
|
||||
// inside PhantomJS this one just hangs, so skip as not very relevant
|
||||
@@ -154,7 +150,6 @@ describe('Connection', function() {
|
||||
|
||||
it('DisconnectedError on send', function() {
|
||||
this.api.connection._ws.send = function(message, options, callback) {
|
||||
unused(message, options);
|
||||
callback({message: 'not connected'});
|
||||
};
|
||||
return this.api.getServerInfo().then(() => {
|
||||
@@ -215,7 +210,7 @@ describe('Connection', function() {
|
||||
});
|
||||
|
||||
it('reconnect on several unexpected close', function(done) {
|
||||
if (process.browser) {
|
||||
if (isBrowser) {
|
||||
const phantomTest = /PhantomJS/;
|
||||
if (phantomTest.test(navigator.userAgent)) {
|
||||
// inside PhantomJS this one just hangs, so skip as not very relevant
|
||||
@@ -315,21 +310,17 @@ describe('Connection', function() {
|
||||
});
|
||||
|
||||
it('Cannot connect because no server', function() {
|
||||
const connection = new utils.common.Connection();
|
||||
const connection = new utils.common.Connection(undefined as string);
|
||||
return connection.connect().then(() => {
|
||||
assert(false, 'Should throw ConnectionError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.ConnectionError);
|
||||
assert(error instanceof this.api.errors.ConnectionError, 'Should throw ConnectionError');
|
||||
});
|
||||
});
|
||||
|
||||
it('connect multiserver error', function() {
|
||||
const options = {
|
||||
servers: ['wss://server1.com', 'wss://server2.com']
|
||||
};
|
||||
assert.throws(function() {
|
||||
const api = new RippleAPI(options);
|
||||
unused(api);
|
||||
new RippleAPI({servers: ['wss://server1.com', 'wss://server2.com']} as any);
|
||||
}, this.api.errors.RippleError);
|
||||
});
|
||||
|
||||
2
test/fixtures/addresses.js
vendored
2
test/fixtures/addresses.js
vendored
@@ -1,6 +1,8 @@
|
||||
'use strict';
|
||||
module.exports = {
|
||||
ACCOUNT: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
ACCOUNT_X: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
|
||||
ACCOUNT_T: 'T719a5UwUCnEs54UsxG9CJYYDhwmFCqkr7wxCcNcfZ6p5GZ',
|
||||
OTHER_ACCOUNT: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
THIRD_ACCOUNT: 'rwBYyfufTzk77zUSKEu4MvixfarC35av1J',
|
||||
FOURTH_ACCOUNT: 'rJnZ4YHCUsHvQu7R6mZohevKJDHFzVD6Zr',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
|
||||
4
test/fixtures/responses/generate-x-address.json
vendored
Normal file
4
test/fixtures/responses/generate-x-address.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
1
test/fixtures/responses/index.js
vendored
1
test/fixtures/responses/index.js
vendored
@@ -5,6 +5,7 @@ function buildList(options) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateXAddress: require('./generate-x-address.json'),
|
||||
generateAddress: require('./generate-address.json'),
|
||||
getAccountInfo: require('./get-account-info.json'),
|
||||
getAccountObjects: require('./get-account-objects.json'),
|
||||
|
||||
1
test/fixtures/rippled/ledger-full-40000.json
vendored
Normal file
1
test/fixtures/rippled/ledger-full-40000.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
test/fixtures/rippled/ledger-full-7501326.json
vendored
Normal file
1
test/fixtures/rippled/ledger-full-7501326.json
vendored
Normal file
File diff suppressed because one or more lines are too long
123
test/hashes-test.ts
Normal file
123
test/hashes-test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import assert from 'assert';
|
||||
import fs from 'fs';
|
||||
import * as hashes from '../src/common/hashes';
|
||||
|
||||
/**
|
||||
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder
|
||||
*/
|
||||
function createLedgerTest(ledgerIndex: number) {
|
||||
describe(String(ledgerIndex), function() {
|
||||
var path = __dirname + '/fixtures/rippled/ledger-full-' + ledgerIndex + '.json';
|
||||
|
||||
var ledgerRaw = fs.readFileSync(path, {encoding: 'utf8'});
|
||||
var ledgerJSON = JSON.parse(ledgerRaw);
|
||||
|
||||
var hasAccounts = Array.isArray(ledgerJSON.accountState)
|
||||
&& ledgerJSON.accountState.length > 0;
|
||||
|
||||
if (hasAccounts) {
|
||||
it('has account_hash of ' + ledgerJSON.account_hash, function() {
|
||||
assert.equal(ledgerJSON.account_hash,
|
||||
hashes.computeStateTreeHash(ledgerJSON.accountState));
|
||||
});
|
||||
}
|
||||
it('has transaction_hash of ' + ledgerJSON.transaction_hash, function() {
|
||||
assert.equal(ledgerJSON.transaction_hash,
|
||||
hashes.computeTransactionTreeHash(ledgerJSON.transactions));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe('Ledger', function() {
|
||||
// This is the first recorded ledger with a non empty transaction set
|
||||
createLedgerTest(38129);
|
||||
// Because, why not.
|
||||
createLedgerTest(40000);
|
||||
// 1311 AffectedNodes, no accounts
|
||||
createLedgerTest(7501326);
|
||||
|
||||
describe('calcAccountRootEntryHash', function() {
|
||||
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
||||
var actualEntryHash = hashes.computeAccountHash(account);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcRippleStateEntryHash', function() {
|
||||
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
|
||||
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
||||
var currency = 'USD';
|
||||
|
||||
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
||||
var actualEntryHash1 = hashes.computeTrustlineHash(
|
||||
account1, account2, currency);
|
||||
var actualEntryHash2 = hashes.computeTrustlineHash(
|
||||
account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
||||
});
|
||||
|
||||
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
|
||||
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
||||
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
||||
var currency = 'UAM';
|
||||
|
||||
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
||||
var actualEntryHash1 = hashes.computeTrustlineHash(
|
||||
account1, account2, currency);
|
||||
var actualEntryHash2 = hashes.computeTrustlineHash(
|
||||
account2, account1, currency);
|
||||
|
||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcOfferEntryHash', function() {
|
||||
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
|
||||
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
||||
var sequence = 137;
|
||||
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
||||
var actualEntryHash = hashes.computeOrderHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeSignerListHash', function() {
|
||||
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
||||
var expectedEntryHash = '778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF';
|
||||
var actualEntryHash = hashes.computeSignerListHash(account);
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcEscrowEntryHash', function() {
|
||||
it('will calculate the Escrow entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x, sequence 84', function() {
|
||||
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
|
||||
var sequence = 84;
|
||||
var expectedEntryHash = '61E8E8ED53FA2CEBE192B23897071E9A75217BF5A410E9CB5B45AAB7AECA567A';
|
||||
var actualEntryHash = hashes.computeEscrowHash(account, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calcPaymentChannelEntryHash', function() {
|
||||
it('will calculate the PaymentChannel entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x and rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso, sequence 82', function() {
|
||||
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
|
||||
var dstAccount = 'rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso'
|
||||
var sequence = 82;
|
||||
var expectedEntryHash = 'E35708503B3C3143FB522D749AAFCC296E8060F0FB371A9A56FAE0B1ED127366';
|
||||
var actualEntryHash = hashes.computePaymentChannelHash(account, dstAccount, sequence);
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,3 +3,4 @@
|
||||
--slow 500
|
||||
--require ts-node/register
|
||||
--require source-map-support/register
|
||||
./test/*.{ts,js}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const RangeSet = require('ripple-api').RippleAPI._PRIVATE.RangeSet;
|
||||
import assert from 'assert';
|
||||
import {RippleAPI} from 'ripple-api';
|
||||
const RangeSet = RippleAPI._PRIVATE.RangeSet;
|
||||
|
||||
describe('RangeSet', function() {
|
||||
it('addRange()/addValue()', function() {
|
||||
61
test/shamap-test.ts
Normal file
61
test/shamap-test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import assert from 'assert';
|
||||
import {SHAMap, NodeType} from '../src/common/hashes/shamap';
|
||||
const TYPE_TRANSACTION_NO_METADATA = NodeType.TRANSACTION_NO_METADATA
|
||||
|
||||
var HEX_ZERO = '00000000000000000000000000000000' +
|
||||
'00000000000000000000000000000000';
|
||||
|
||||
/**
|
||||
* Generates data to hash for testing
|
||||
*/
|
||||
function intToVuc(v: number): string {
|
||||
var ret = '';
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
ret += '0';
|
||||
ret += v.toString(16).toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function fillShamapTest(shamap: any, keys: string[], hashes: string[]) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var data = intToVuc(i);
|
||||
shamap.addItem(keys[i].toUpperCase(), data, TYPE_TRANSACTION_NO_METADATA);
|
||||
assert.equal(shamap.hash, hashes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
describe('SHAMap', function() {
|
||||
|
||||
describe('#addItem', function() {
|
||||
it('will add new nodes to v1', function() {
|
||||
var keys = [
|
||||
'b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
|
||||
'292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8'
|
||||
];
|
||||
|
||||
var hashesv1 = [
|
||||
'B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F',
|
||||
'FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266',
|
||||
'4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75',
|
||||
'7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07',
|
||||
'395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E',
|
||||
'D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4',
|
||||
'76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615',
|
||||
'DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5'
|
||||
];
|
||||
|
||||
|
||||
var shamapv1 = new SHAMap();
|
||||
assert.equal(shamapv1.hash, HEX_ZERO);
|
||||
fillShamapTest(shamapv1, keys, hashesv1);
|
||||
});
|
||||
});
|
||||
});
|
||||
6
test/tsconfig.json
Normal file
6
test/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
}
|
||||
}
|
||||
4034
test/x-address-api-test.js
Normal file
4034
test/x-address-api-test.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,26 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"pretty": true,
|
||||
"lib": [
|
||||
"es2017"
|
||||
],
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
||||
"declaration": true,
|
||||
"declarationMap": true /* Added 2019-04-13 */,
|
||||
"sourceMap": true,
|
||||
"noEmitOnError": true /* Added 2019-04-13 */,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": true,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"preserveConstEnums": false,
|
||||
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"suppressImplicitAnyIndexErrors": false
|
||||
"esModuleInterop": true,
|
||||
"suppressImplicitAnyIndexErrors": false,
|
||||
"resolveJsonModule": true,
|
||||
"preserveSymlinks": true
|
||||
}
|
||||
}
|
||||
|
||||
76
tslint.json
76
tslint.json
@@ -1,76 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"tslint-eslint-rules"
|
||||
],
|
||||
"rules": {
|
||||
"ban": [true, ["alert"]],
|
||||
"no-arg": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-console": false,
|
||||
"no-constant-condition": true,
|
||||
"no-control-regex": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-case": true,
|
||||
"no-empty": true,
|
||||
"no-empty-character-class": true,
|
||||
"no-eval": true,
|
||||
"no-ex-assign": true,
|
||||
"no-extra-boolean-cast": true,
|
||||
"no-extra-semi": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-inner-declarations": [true, "functions"],
|
||||
"no-invalid-regexp": true,
|
||||
// this rule would cause problems with mocha test cases,
|
||||
"no-invalid-this": false,
|
||||
"no-irregular-whitespace": true,
|
||||
"ter-no-irregular-whitespace": true,
|
||||
"label-position": true,
|
||||
"indent": [true, "spaces", 2],
|
||||
"linebreak-style": [true, "unix"],
|
||||
"no-multi-spaces": true,
|
||||
"no-consecutive-blank-lines": [true, 2],
|
||||
"no-unused-expression": true,
|
||||
"no-construct": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-regex-spaces": true,
|
||||
"no-shadowed-variable": true,
|
||||
"ter-no-sparse-arrays": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-string-throw": true,
|
||||
"no-unexpected-multiline": true,
|
||||
"no-unused-variable": [true, {"ignore-pattern": "^_"}],
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"no-magic-numbers": false,
|
||||
"array-bracket-spacing": [true, "never"],
|
||||
"ter-arrow-body-style": false,
|
||||
"ter-arrow-parens": [true, "as-needed"],
|
||||
"ter-arrow-spacing": true,
|
||||
"block-spacing": true,
|
||||
"brace-style": [true, "1tbs", {"allowSingleLine": true}],
|
||||
"variable-name": false,
|
||||
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
|
||||
"cyclomatic-complexity": [false, 11],
|
||||
"curly": [true, "all"],
|
||||
"switch-default": false,
|
||||
"eofline": true,
|
||||
"triple-equals": true,
|
||||
"forin": false,
|
||||
"handle-callback-err": true,
|
||||
"ter-max-len": [true, 120],
|
||||
"new-parens": true,
|
||||
"object-curly-spacing": [true, "never"],
|
||||
"object-literal-shorthand": false,
|
||||
"one-variable-per-declaration": [true, "ignore-for-loop"],
|
||||
"ter-prefer-arrow-callback": false,
|
||||
"prefer-const": true,
|
||||
"object-literal-key-quotes": false,
|
||||
"quotemark": [true, "single"],
|
||||
"radix": true,
|
||||
"semicolon": [true, "never"],
|
||||
"space-in-parens": [true, "never"],
|
||||
"comment-format": [true, "check-space"],
|
||||
"use-isnan": true,
|
||||
"valid-typeof": true
|
||||
}
|
||||
}
|
||||
50
webpack.config.js
Normal file
50
webpack.config.js
Normal file
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
|
||||
|
||||
function getDefaultConfiguration() {
|
||||
return {
|
||||
cache: true,
|
||||
performance: { hints: false },
|
||||
stats: 'errors-only',
|
||||
externals: [{
|
||||
'lodash': '_'
|
||||
}],
|
||||
entry: './dist/npm/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: path.join(__dirname, 'build/'),
|
||||
filename: `ripple-lib.default.js`,
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet$/, './wallet-web'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^.*setup-api$/, './setup-api-web'),
|
||||
],
|
||||
module: {
|
||||
rules: []
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.json']
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = [
|
||||
function(env, argv) {
|
||||
const config = getDefaultConfiguration();
|
||||
config.mode = 'development';
|
||||
config.output.filename = `ripple-latest.js`;
|
||||
return config;
|
||||
},
|
||||
function(env, argv) {
|
||||
const config = getDefaultConfiguration();
|
||||
config.mode = 'production';
|
||||
config.output.filename = `ripple-latest.min.js`;
|
||||
if (process.argv.includes('--analyze')) {
|
||||
config.plugins.push(new BundleAnalyzerPlugin());
|
||||
}
|
||||
return config;
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user