mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-12 16:45:49 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb2a497dee | ||
|
|
988381d584 | ||
|
|
0b163eae23 | ||
|
|
fadfd4e06c | ||
|
|
eb521faa8d | ||
|
|
14e6bf5ef9 | ||
|
|
b6bddd3b0e | ||
|
|
d5ed9b6cf5 | ||
|
|
7a9912d4e0 | ||
|
|
4d2ddceb4e | ||
|
|
ae2aed675a | ||
|
|
1d7cb41218 | ||
|
|
76db002eb5 | ||
|
|
ebb64ba177 | ||
|
|
1a685e2b68 | ||
|
|
9c561885a1 | ||
|
|
612e98b198 | ||
|
|
0a41d5ccf1 | ||
|
|
ba8fe1f32c | ||
|
|
0f840876a5 | ||
|
|
ac3900b6f4 | ||
|
|
5e138b9937 | ||
|
|
82d50cd903 | ||
|
|
c5b1d4daac | ||
|
|
b4a30d49d8 | ||
|
|
229360d1b9 | ||
|
|
d627d362af | ||
|
|
7ca7a07942 | ||
|
|
09f81fa3cd | ||
|
|
b03bf9c7f1 | ||
|
|
4d696369fe | ||
|
|
bcb80ea5f5 | ||
|
|
1be2ee5875 |
@@ -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.
|
||||
|
||||
@@ -36,6 +36,10 @@ Warning: Use at your own risk.
|
||||
|
||||
List of XRPL validators, nodes, and testnet validators.
|
||||
|
||||
- **[XRP Scan - XRP Ledger explorer](https://http://xrpscan.com)**
|
||||
|
||||
XRP Ledger explorer, metrics and analytics.
|
||||
|
||||
## Send and request payments
|
||||
|
||||
- **[XRP Tip Bot](https://www.xrptipbot.com/)**
|
||||
|
||||
146
Gulpfile.js
146
Gulpfile.js
@@ -6,15 +6,12 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const gulp = require('gulp');
|
||||
const rename = require('gulp-rename');
|
||||
const webpack = require('webpack');
|
||||
const bump = require('gulp-bump');
|
||||
const argv = require('yargs').argv;
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
var pkg = require('./package.json');
|
||||
const pkg = require('./package.json');
|
||||
|
||||
var uglifyOptions = {
|
||||
const uglifyOptions = {
|
||||
mangle: {
|
||||
reserved: ['_', 'RippleError', 'RippledError', 'UnexpectedError',
|
||||
'LedgerVersionError', 'ConnectionError', 'NotConnectedError',
|
||||
@@ -36,7 +33,7 @@ function getWebpackConfig(extension, overrides) {
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: path.join(__dirname, 'build/'),
|
||||
filename: ['ripple-', extension].join(pkg.version)
|
||||
filename: `ripple-${pkg.version}${extension}`
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
|
||||
@@ -69,7 +66,7 @@ function getWebpackConfig(extension, overrides) {
|
||||
return _.assign({}, defaults, overrides);
|
||||
}
|
||||
|
||||
function webpackConfigForWebTest(testFileName, path) {
|
||||
function webpackConfigForWebTest(testFileName) {
|
||||
var match = testFileName.match(/\/?([^\/]*)-test.js$/);
|
||||
if (!match) {
|
||||
assert(false, 'wrong filename:' + testFileName);
|
||||
@@ -83,28 +80,13 @@ function webpackConfigForWebTest(testFileName, path) {
|
||||
entry: testFileName,
|
||||
output: {
|
||||
library: match[1].replace(/-/g, '_'),
|
||||
path: './test-compiled-for-web/' + (path ? path : ''),
|
||||
path: path.join(__dirname, 'test-compiled-for-web/'),
|
||||
filename: match[1] + '-test.js'
|
||||
}
|
||||
};
|
||||
return getWebpackConfig('.js', configOverrides);
|
||||
}
|
||||
|
||||
gulp.task('build-tests', function(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);
|
||||
});
|
||||
|
||||
function createLink(from, to) {
|
||||
if (fs.existsSync(to)) {
|
||||
fs.unlinkSync(to);
|
||||
@@ -120,11 +102,22 @@ function createBuildLink(callback) {
|
||||
};
|
||||
}
|
||||
|
||||
gulp.task('build', function(callback) {
|
||||
webpack(getWebpackConfig('.js'), createBuildLink(callback));
|
||||
});
|
||||
function watch(callback) {
|
||||
gulp.watch('src/*', gulp.series(buildDebug));
|
||||
callback();
|
||||
}
|
||||
|
||||
gulp.task('build-min', function(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() {
|
||||
@@ -132,86 +125,27 @@ gulp.task('build-min', function(callback) {
|
||||
'./build/ripple-latest-min.js');
|
||||
callback();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('build-debug', function(callback) {
|
||||
const webpackConfig = getWebpackConfig('-debug.js', {devtool: 'eval'});
|
||||
webpackConfig.plugins.unshift(new webpack.LoaderOptionsPlugin({debug: true}));
|
||||
webpack(webpackConfig, callback);
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate a WebPack external for a given unavailable module which replaces
|
||||
* that module's constructor with an error-thrower
|
||||
*/
|
||||
function buildUseError(cons) {
|
||||
return ('var {<CONS>:function(){throw new Error('
|
||||
+ '"Class is unavailable in this build: <CONS>")}}')
|
||||
.replace(new RegExp('<CONS>', 'g'), cons);
|
||||
}
|
||||
|
||||
gulp.task('build-core', function(callback) {
|
||||
var configOverrides = {
|
||||
cache: false,
|
||||
entry: './src/remote.ts',
|
||||
externals: [{
|
||||
'./transaction': buildUseError('Transaction'),
|
||||
'./orderbook': buildUseError('OrderBook'),
|
||||
'./account': buildUseError('Account'),
|
||||
'./serializedobject': buildUseError('SerializedObject')
|
||||
}],
|
||||
plugins: [
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
||||
webpack(getWebpackConfig('-core.js', configOverrides), callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', ['build'], function() {
|
||||
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
|
||||
.pipe(rename('ripple.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-min', ['build-min'], function() {
|
||||
return gulp.src(['./build/ripple-', '-min.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-min.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-debug', ['build-debug'], function() {
|
||||
return gulp.src(['./build/ripple-', '-debug.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-debug.js'))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-version', function() {
|
||||
gulp.src('./dist/bower/bower.json')
|
||||
.pipe(bump({version: pkg.version}))
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug',
|
||||
'bower-version']);
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/*', ['build-debug']);
|
||||
});
|
||||
|
||||
gulp.task('version-bump', function() {
|
||||
if (!argv.type) {
|
||||
throw new Error('No type found, pass it in using the --type argument');
|
||||
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);
|
||||
}
|
||||
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({type: argv.type}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
exports.watch = watch;
|
||||
exports.build = build;
|
||||
exports.buildDebug = buildDebug;
|
||||
exports.buildMin = buildMin;
|
||||
exports.buildTests = buildTests;
|
||||
|
||||
gulp.task('version-beta', function() {
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({version: pkg.version + '-beta'}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('default', ['build', 'build-debug', 'build-min']);
|
||||
exports.default = gulp.parallel(build, buildDebug, buildMin);
|
||||
|
||||
110
HISTORY.md
110
HISTORY.md
@@ -1,5 +1,115 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
## 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.2 (2019-09-03)
|
||||
|
||||
* Export and document `rippleTimeToISO8601()` method
|
||||
* Add type for isValidAddress (fixes error TS7016, #1032)
|
||||
* Docs: update recommended Node.js version (#1031)
|
||||
|
||||
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1) or later.
|
||||
|
||||
## 1.3.1 (2019-08-26)
|
||||
|
||||
* Upgrade to gulp 4 (#1030)
|
||||
* Remove http server (unused)
|
||||
* Update dependencies
|
||||
|
||||
There are no changes in the browser version with this release. The npm package for Node.js should be slightly smaller.
|
||||
|
||||
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1).
|
||||
|
||||
## 1.3.0 (2019-08-16)
|
||||
|
||||
### Bug fixes:
|
||||
|
||||
* Breaking change: Fix `getServerInfo` (#1012)
|
||||
|
||||
Before:
|
||||
```
|
||||
{
|
||||
// ...
|
||||
"load": {
|
||||
"jobTypes": {
|
||||
"0": {"jobType": "untrustedValidation", "perSecond": 10},
|
||||
"1": {"jobType": "ledgerData", "peakTime": 2, "perSecond": 2},
|
||||
"2": {"inProgress": 1, "jobType": "clientCommand", "perSecond": 1},
|
||||
"3": {"jobType": "transaction", "perSecond": 1},
|
||||
// ...
|
||||
},
|
||||
"threads": 6
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After:
|
||||
```
|
||||
{
|
||||
// ...
|
||||
"load": {
|
||||
"jobTypes": [
|
||||
{
|
||||
"jobType": "untrustedValidation",
|
||||
"perSecond": 8
|
||||
},
|
||||
{
|
||||
"avgTime": 2,
|
||||
"jobType": "ledgerData",
|
||||
"peakTime": 72,
|
||||
"perSecond": 2
|
||||
},
|
||||
{
|
||||
"inProgress": 1,
|
||||
"jobType": "clientCommand",
|
||||
"perSecond": 1
|
||||
},
|
||||
{
|
||||
"jobType": "transaction",
|
||||
"perSecond": 1
|
||||
},
|
||||
// ...
|
||||
],
|
||||
"threads": 6
|
||||
},
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
* Sign method - verify accurate encoding (#1026)
|
||||
* In previous versions, the following could be encoded incorrectly:
|
||||
* Amounts of XRP with more than 6 decimal places
|
||||
* Amounts of XRP drops with any decimal places
|
||||
* In versions 1.2.5 and 1.3.0, amounts that are invalid in this way will throw an error
|
||||
* Expand `APIOptions` by extending `ConnectionOptions` (#1018, fixes #1017)
|
||||
* Fix docs for destination.address (#1011)
|
||||
|
||||
### New features:
|
||||
|
||||
* Support removing a signer list (#1021)
|
||||
* Export `setCanonicalFlag` method from `src/transaction/utils`
|
||||
|
||||
### Improvements:
|
||||
|
||||
* Clean up phrasing in schema descriptions (#1023)
|
||||
* Improve docs
|
||||
* Update dependencies
|
||||
|
||||
Since this release fixes a bug that could cause transactions to be serialized incorrectly during the signing process, it has also been simultaneously released as version 1.2.5 (patch release).
|
||||
|
||||
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1).
|
||||
|
||||
## 1.2.4 (2019-06-06)
|
||||
|
||||
* Update README.md
|
||||
|
||||
@@ -14,7 +14,7 @@ A JavaScript API for interacting with the XRP Ledger
|
||||
|
||||
## Getting Started
|
||||
|
||||
See also: [RippleAPI Beginners Guide](https://ripple.com/build/rippleapi-beginners-guide/)
|
||||
See also: [RippleAPI Beginners Guide](https://xrpl.org/get-started-with-rippleapi-for-javascript.html)
|
||||
|
||||
### Requirements
|
||||
|
||||
|
||||
162
docs/index.md
162
docs/index.md
@@ -91,6 +91,7 @@
|
||||
- [xrpToDrops](#xrptodrops)
|
||||
- [dropsToXrp](#dropstoxrp)
|
||||
- [iso8601ToRippleTime](#iso8601torippletime)
|
||||
- [rippleTimeToISO8601](#rippletimetoiso8601)
|
||||
- [txFlags](#txflags)
|
||||
- [schemaValidator](#schemavalidator)
|
||||
- [schemaValidate](#schemavalidate)
|
||||
@@ -141,7 +142,7 @@ api.connect().then(() => {
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
|
||||
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
|
||||
|
||||
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
|
||||
@@ -273,7 +274,7 @@ A *balance* is an amount than can have a negative value.
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies, or "drops" for the smallest unit of XRP.
|
||||
counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds (omitted if `currency` is "XRP" or "drops")
|
||||
counterparty | [address](#address) | *Optional* The XRP Ledger address of the account that owes or is owed the funds (omitted if `currency` is "XRP" or "drops")
|
||||
value | [value](#value) | *Optional* The quantity of the currency, denoted as a string to retain floating point precision
|
||||
|
||||
# Transaction Overview
|
||||
@@ -375,15 +376,14 @@ 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 is exclusive with source.maxAmount)
|
||||
*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.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
|
||||
*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) | The address to receive at.
|
||||
*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.* address | [address](#address) | The address to send to.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
|
||||
*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.
|
||||
limitQuality | boolean | *Optional* Only take paths where all the conversions have an input:output ratio that is equal or better than the ratio of destination.amount:source.maxAmount.
|
||||
@@ -536,7 +536,7 @@ requireAuthorization | boolean | *Optional* If set, this account must individual
|
||||
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
|
||||
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
|
||||
*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 | Weights of signatures for each signer.
|
||||
*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[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
@@ -1639,8 +1639,8 @@ options | object | *Optional* Options to filter the resulting transactions.
|
||||
*options.* limit | integer | *Optional* If specified, return at most this many transactions.
|
||||
*options.* maxLedgerVersion | integer | *Optional* Return only transactions in this ledger version or lower.
|
||||
*options.* maxLedgerVersion | string | *Optional* Return only transactions in this ledger version or lower.
|
||||
*options.* minLedgerVersion | integer | *Optional* Return only transactions in this ledger verion or higher.
|
||||
*options.* minLedgerVersion | string | *Optional* Return only transactions in this ledger verion or higher.
|
||||
*options.* minLedgerVersion | integer | *Optional* Return only transactions in this ledger version or higher.
|
||||
*options.* minLedgerVersion | string | *Optional* Return only transactions in this ledger version or higher.
|
||||
*options.* start | string | *Optional* If specified, this transaction will be the first transaction in the result. You cannot use `start` with `minLedgerVersion` or `maxLedgerVersion`. When `start` is specified, these ledger versions will be determined internally.
|
||||
*options.* types | array\<[transactionType](#transaction-types)\> | *Optional* Only return transactions of the specified [Transaction Types](#transaction-types).
|
||||
|
||||
@@ -2021,7 +2021,7 @@ Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies
|
||||
value | [signedValue](#value) | The balance on the trustline
|
||||
counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds.
|
||||
counterparty | [address](#address) | *Optional* The XRP Ledger address of the account that owes or is owed the funds.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -2172,7 +2172,7 @@ Returns aggregate balances by currency plus a breakdown of assets and obligation
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
address | [address](#address) | The Ripple address of the account to get the balance sheet of.
|
||||
address | [address](#address) | The XRP Ledger address of the account to get the balance sheet of.
|
||||
options | object | *Optional* Options to determine how the balances will be calculated.
|
||||
*options.* excludeAddresses | array\<[address](#address)\> | *Optional* Addresses to exclude from the balance totals.
|
||||
*options.* ledgerVersion | integer | *Optional* Get the balance sheet as of this historical ledger version.
|
||||
@@ -2270,14 +2270,14 @@ Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
pathfind | object | Specification of a pathfind request.
|
||||
*pathfind.* source | object | Properties of the source of funds.
|
||||
*pathfind.source.* address | [address](#address) | The Ripple address of the account where funds will come from.
|
||||
*pathfind.source.* address | [address](#address) | The XRP Ledger address of the account where funds will come from.
|
||||
*pathfind.source.* amount | [laxAmount](#amount) | *Optional* The amount of funds to send.
|
||||
*pathfind.source.* currencies | array | *Optional* An array of currencies (with optional counterparty) that may be used in the payment paths.
|
||||
*pathfind.source.* currencies[] | object | A currency with optional counterparty.
|
||||
*pathfind.source.currencies[].* currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies
|
||||
*pathfind.source.currencies[].* counterparty | [address](#address) | *Optional* The counterparty for the currency; if omitted any counterparty may be used.
|
||||
*pathfind.* destination | object | Properties of the destination of funds.
|
||||
*pathfind.destination.* address | [address](#address) | The address to send to.
|
||||
*pathfind.destination.* address | [address](#address) | An address representing the destination of the transaction.
|
||||
*pathfind.destination.* amount | [laxLaxAmount](#amount) | The amount to be received by the receiver (`value` may be ommitted if a source amount is specified).
|
||||
|
||||
### Return Value
|
||||
@@ -2288,15 +2288,14 @@ 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 is exclusive with source.maxAmount)
|
||||
*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.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
|
||||
*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) | The address to receive at.
|
||||
*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.* address | [address](#address) | The address to send to.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
|
||||
*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.
|
||||
|
||||
### Example
|
||||
@@ -2390,7 +2389,7 @@ Returns open orders for the specified account. Open orders are orders that have
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
address | [address](#address) | The Ripple address of the account to get open orders for.
|
||||
address | [address](#address) | The XRP Ledger address of the account to get open orders for.
|
||||
options | object | *Optional* Options that determine what orders will be returned.
|
||||
*options.* ledgerVersion | integer | *Optional* Return orders as of this historical ledger version.
|
||||
*options.* ledgerVersion | string | *Optional* Return orders as of this historical ledger version.
|
||||
@@ -3905,7 +3904,7 @@ requireAuthorization | boolean | *Optional* If set, this account must individual
|
||||
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
|
||||
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
|
||||
*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 | Weights of signatures for each signer.
|
||||
*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[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
@@ -5397,12 +5396,14 @@ This method can sign any of [the transaction types supported by ripple-binary-co
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
txJSON | string | Transaction represented as a JSON string in rippled format.
|
||||
keypair | object | *Optional* The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).
|
||||
keypair | object | *Optional* The private and public key of the account that is initiating the transaction. (This field cannot be used with secret).
|
||||
*keypair.* privateKey | privateKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 private key.
|
||||
*keypair.* publicKey | publicKey | The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key.
|
||||
options | object | *Optional* Options that control the type of signature that will be generated.
|
||||
*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 is exclusive with keypair).
|
||||
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field cannot be used with keypair).
|
||||
|
||||
Please note that when this method is used for multisigning, the `options` parameter is not *Optional* anymore. It will be compulsory. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -5430,6 +5431,93 @@ 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
|
||||
|
||||
@@ -5862,6 +5950,34 @@ api.iso8601ToRippleTime('2017-02-17T15:04:57Z');
|
||||
540659097
|
||||
```
|
||||
|
||||
## rippleTimeToISO8601
|
||||
|
||||
`rippleTimeToISO8601(rippleTime: number): string`
|
||||
|
||||
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
|
||||
|
||||
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
|
||||
|
||||
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
|
||||
|
||||
### Parameters
|
||||
|
||||
`rippleTime`: A number of seconds since the Ripple Epoch.
|
||||
|
||||
### Return Value
|
||||
|
||||
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
api.rippleTimeToISO8601(540659097);
|
||||
```
|
||||
|
||||
```json
|
||||
'2017-02-17T15:04:57.000Z'
|
||||
```
|
||||
|
||||
## txFlags
|
||||
|
||||
`txFlags.TRANSACTION_TYPE.FLAG`
|
||||
|
||||
@@ -26,7 +26,7 @@ api.connect().then(() => {
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
|
||||
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
|
||||
|
||||
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<% include computeLedgerHash.md.ejs %>
|
||||
<% include xrpToDropsAndDropsToXrp.md.ejs %>
|
||||
<% include iso8601ToRippleTime.md.ejs %>
|
||||
<% include rippleTimeToISO8601.md.ejs %>
|
||||
<% include txFlags.md.ejs %>
|
||||
<% include schemaValidator.md.ejs %>
|
||||
<% include events.md.ejs %>
|
||||
|
||||
27
docs/src/rippleTimeToISO8601.md.ejs
Normal file
27
docs/src/rippleTimeToISO8601.md.ejs
Normal file
@@ -0,0 +1,27 @@
|
||||
## rippleTimeToISO8601
|
||||
|
||||
`rippleTimeToISO8601(rippleTime: number): string`
|
||||
|
||||
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
|
||||
|
||||
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
|
||||
|
||||
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
|
||||
|
||||
### Parameters
|
||||
|
||||
`rippleTime`: A number of seconds since the Ripple Epoch.
|
||||
|
||||
### Return Value
|
||||
|
||||
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
api.rippleTimeToISO8601(540659097);
|
||||
```
|
||||
|
||||
```json
|
||||
'2017-02-17T15:04:57.000Z'
|
||||
```
|
||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.2.4",
|
||||
"version": "1.3.4",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -17,17 +17,17 @@
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.85",
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^3.2.0",
|
||||
"bignumber.js": "^4.1.0",
|
||||
"https-proxy-agent": "2.2.1",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^2.0.1",
|
||||
"ripple-binary-codec": "0.2.1",
|
||||
"ripple-hashes": "0.3.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ripple-address-codec": "^3.0.4",
|
||||
"ripple-binary-codec": "^0.2.4",
|
||||
"ripple-keypairs": "^0.10.1",
|
||||
"ripple-lib-transactionparser": "0.7.1",
|
||||
"ripple-lib-transactionparser": "0.8.0",
|
||||
"ws": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -36,13 +36,10 @@
|
||||
"doctoc": "^0.15.0",
|
||||
"ejs": "^2.3.4",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"gulp": "^3.8.10",
|
||||
"gulp-bump": "^0.1.13",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"jayson": "^1.2.2",
|
||||
"gulp": "^4.0.2",
|
||||
"json-loader": "^0.5.2",
|
||||
"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",
|
||||
@@ -51,10 +48,9 @@
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-eslint-rules": "^4.1.1",
|
||||
"typescript": "3.4.2",
|
||||
"typescript": "^3.6.4",
|
||||
"uglifyjs-webpack-plugin": "^1.1.4",
|
||||
"webpack": "3.12.0",
|
||||
"yargs": "13.2.2"
|
||||
"webpack": "3.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
@@ -67,8 +63,7 @@
|
||||
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
|
||||
"lint": "tslint -p ./",
|
||||
"perf": "./scripts/perf_test.sh",
|
||||
"start": "node scripts/http.js",
|
||||
"sauce": "node scripts/sauce-runner.js"
|
||||
"start": "node scripts/http.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -45,7 +45,6 @@ unittest() {
|
||||
|
||||
integrationtest() {
|
||||
mocha test/integration/integration-test.js
|
||||
mocha test/integration/http-integration-test.js
|
||||
|
||||
# run integration tests in PhantomJS
|
||||
#gulp build-tests build-min
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const createHTTPServer = require('../dist/npm/http').createHTTPServer;
|
||||
const port = 5990;
|
||||
const serverUrl = 'wss://s1.ripple.com';
|
||||
|
||||
|
||||
function main() {
|
||||
const server = createHTTPServer({server: serverUrl}, port);
|
||||
server.start().then(() => {
|
||||
console.log('Server started on port ' + String(port));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
main();
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
validate,
|
||||
xrpToDrops,
|
||||
dropsToXrp,
|
||||
rippleTimeToISO8601,
|
||||
iso8601ToRippleTime,
|
||||
txFlags
|
||||
} from './common'
|
||||
@@ -72,8 +73,9 @@ import * as schemaValidator from './common/schema-validator'
|
||||
import {getServerInfo, getFee} from './common/serverinfo'
|
||||
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
import {ConnectionOptions} from './common/connection'
|
||||
|
||||
export type APIOptions = {
|
||||
export interface APIOptions extends ConnectionOptions {
|
||||
server?: string,
|
||||
feeCushion?: number,
|
||||
maxFeeXRP?: string,
|
||||
@@ -336,6 +338,7 @@ class RippleAPI extends EventEmitter {
|
||||
|
||||
xrpToDrops = xrpToDrops
|
||||
dropsToXrp = dropsToXrp
|
||||
rippleTimeToISO8601 = rippleTimeToISO8601
|
||||
iso8601ToRippleTime = iso8601ToRippleTime
|
||||
txFlags = txFlags
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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,
|
||||
|
||||
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 {decodeAddress} 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(decodeAddress(address))).toString('hex')
|
||||
}
|
||||
|
||||
const currencyToHex = (currency: string): string => {
|
||||
if (currency.length === 3) {
|
||||
let 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)).greaterThan(
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class RangeSet {
|
||||
}
|
||||
|
||||
addRange(start: number, end: number) {
|
||||
assert(start <= end, 'invalid range')
|
||||
assert.ok(start <= end, `invalid range ${start} <= ${end}`)
|
||||
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]))
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ 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
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The Ripple address of the account to get the balance sheet of."
|
||||
"description": "The XRP Ledger address of the account to get the balance sheet of."
|
||||
},
|
||||
"options": {
|
||||
"properties": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The Ripple address of the account to get open orders for."
|
||||
"description": "The XRP Ledger address of the account to get open orders for."
|
||||
},
|
||||
"options": {
|
||||
"description": "Options that determine what orders will be returned.",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The Ripple address of the account where funds will come from."
|
||||
"description": "The XRP Ledger address of the account where funds will come from."
|
||||
},
|
||||
"amount": {
|
||||
"$ref": "laxAmount",
|
||||
@@ -49,7 +49,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The address to send to."
|
||||
"description": "An address representing the destination of the transaction."
|
||||
},
|
||||
"amount": {
|
||||
"$ref": "laxLaxAmount",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"minLedgerVersion": {
|
||||
"$ref": "ledgerVersion",
|
||||
"description": "Return only transactions in this ledger verion or higher."
|
||||
"description": "Return only transactions in this ledger version or higher."
|
||||
},
|
||||
"maxLedgerVersion": {
|
||||
"$ref": "ledgerVersion",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret of the account that is initiating the transaction. (This field is exclusive with keypair)."
|
||||
"description": "The secret of the account that is initiating the transaction. (This field cannot be used with keypair)."
|
||||
},
|
||||
"keypair": {
|
||||
"type": "object",
|
||||
@@ -24,7 +24,7 @@
|
||||
"description": "The uppercase hexadecimal representation of the secp256k1 or Ed25519 public key."
|
||||
}
|
||||
},
|
||||
"description": "The private and public key of the account that is initiating the transaction. (This field is exclusive with secret).",
|
||||
"description": "The private and public key of the account that is initiating the transaction. (This field cannot be used with secret).",
|
||||
"required": ["privateKey", "publicKey"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"$ref": "currency"
|
||||
},
|
||||
"counterparty": {
|
||||
"description": "The Ripple address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\" or \"drops\")",
|
||||
"description": "The XRP Ledger address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\" or \"drops\")",
|
||||
"$ref": "address"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"$ref": "currency"
|
||||
},
|
||||
"counterparty": {
|
||||
"description": "The Ripple address of the account that owes or is owed the funds.",
|
||||
"description": "The XRP Ledger address of the account that owes or is owed the funds.",
|
||||
"$ref": "address"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The address to receive at."
|
||||
"description": "An address representing the destination of the transaction."
|
||||
},
|
||||
"tag": {"$ref": "tag"}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The address to receive at."
|
||||
"description": "An address representing the destination of the transaction."
|
||||
},
|
||||
"amount": {
|
||||
"$ref": "laxAmount",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"maxAmount": {
|
||||
"$ref": "laxAmount",
|
||||
"description": "The maximum amount to send. (This field is exclusive with source.amount)"
|
||||
"description": "The maximum amount to send. (This field cannot be used with source.amount)"
|
||||
},
|
||||
"tag": {"$ref": "tag"}
|
||||
},
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
"properties": {
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "The address to send to."
|
||||
"description": "An address representing the destination of the transaction."
|
||||
},
|
||||
"minAmount": {
|
||||
"$ref": "laxAmount",
|
||||
"description": "The minimum amount to be delivered. (This field is exclusive with destination.amount)"
|
||||
"description": "The minimum amount to be delivered. (This field cannot be used with destination.amount)"
|
||||
},
|
||||
"tag": {"$ref": "tag"}
|
||||
},
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
"maxItems": 8
|
||||
}
|
||||
},
|
||||
"required": ["threshold", "weights"],
|
||||
"required": ["threshold"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"transferRate": {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"amount": {
|
||||
"$ref": "laxAmount",
|
||||
"description": "An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)"
|
||||
"description": "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)"
|
||||
},
|
||||
"tag": {"$ref": "tag"}
|
||||
},
|
||||
|
||||
@@ -103,6 +103,7 @@ function toRippledAmount(amount: Amount): RippledAmount {
|
||||
|
||||
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
|
||||
if (typeof obj === 'object') {
|
||||
const accumulator = Array.isArray(obj) ? [] : {}
|
||||
let newKey
|
||||
return _.reduce(obj, (result, value, key) => {
|
||||
newKey = key
|
||||
@@ -113,7 +114,7 @@ function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
|
||||
}
|
||||
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value)
|
||||
return result
|
||||
}, {})
|
||||
}, accumulator)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
84
src/http.ts
84
src/http.ts
@@ -1,84 +0,0 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import * as assert from 'assert'
|
||||
import * as _ from 'lodash'
|
||||
import * as jayson from 'jayson'
|
||||
import {RippleAPI} from './api'
|
||||
|
||||
|
||||
/* istanbul ignore next */
|
||||
function createHTTPServer(options, httpPort) {
|
||||
const rippleAPI = new RippleAPI(options)
|
||||
|
||||
const methodNames = _.filter(_.keys(RippleAPI.prototype), k => {
|
||||
return typeof RippleAPI.prototype[k] === 'function'
|
||||
&& k !== 'connect'
|
||||
&& k !== 'disconnect'
|
||||
&& k !== 'constructor'
|
||||
&& k !== 'RippleAPI'
|
||||
})
|
||||
|
||||
function applyPromiseWithCallback(fnName, callback, args_) {
|
||||
try {
|
||||
let args = args_
|
||||
if (!_.isArray(args_)) {
|
||||
const fnParameters = jayson.Utils.getParameterNames(rippleAPI[fnName])
|
||||
args = fnParameters.map(name => args_[name])
|
||||
const defaultArgs = _.omit(args_, fnParameters)
|
||||
assert(_.size(defaultArgs) <= 1,
|
||||
'Function must have no more than one default argument')
|
||||
if (_.size(defaultArgs) > 0) {
|
||||
args.push(defaultArgs[_.keys(defaultArgs)[0]])
|
||||
}
|
||||
}
|
||||
Promise.resolve(rippleAPI[fnName](...args))
|
||||
.then(res => callback(null, res))
|
||||
.catch(err => {
|
||||
callback({code: 99, message: err.message, data: {name: err.name}})
|
||||
})
|
||||
} catch (err) {
|
||||
callback({code: 99, message: err.message, data: {name: err.name}})
|
||||
}
|
||||
}
|
||||
|
||||
const methods = {}
|
||||
_.forEach(methodNames, fn => {
|
||||
methods[fn] = jayson.Method((args, cb) => {
|
||||
applyPromiseWithCallback(fn, cb, args)
|
||||
}, {collect: true})
|
||||
})
|
||||
|
||||
const server = jayson.server(methods)
|
||||
let httpServer = null
|
||||
|
||||
return {
|
||||
server: server,
|
||||
start: function() {
|
||||
if (httpServer !== null) {
|
||||
return Promise.reject('Already started')
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
rippleAPI.connect().then(() => {
|
||||
httpServer = server.http()
|
||||
httpServer.listen(httpPort, resolve)
|
||||
})
|
||||
})
|
||||
},
|
||||
stop: function() {
|
||||
if (httpServer === null) {
|
||||
return Promise.reject('Not started')
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
rippleAPI.disconnect()
|
||||
httpServer.close(() => {
|
||||
httpServer = null
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
createHTTPServer
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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'
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import * as common from '../common'
|
||||
const {errors, validate} = common
|
||||
|
||||
|
||||
@@ -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,10 +1,10 @@
|
||||
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 {validate} from '../common'
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
|
||||
@@ -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]
|
||||
@@ -89,12 +89,17 @@ function createSettingsTransactionWithoutMemos(
|
||||
}
|
||||
|
||||
if (settings.signers !== undefined) {
|
||||
return {
|
||||
const setSignerList = {
|
||||
TransactionType: 'SignerListSet',
|
||||
Account: account,
|
||||
SignerQuorum: settings.signers.threshold,
|
||||
SignerEntries: settings.signers.weights.map(formatSignerEntry)
|
||||
SignerEntries: [],
|
||||
SignerQuorum: settings.signers.threshold
|
||||
};
|
||||
|
||||
if (settings.signers.weights !== undefined) {
|
||||
setSignerList.SignerEntries = settings.signers.weights.map(formatSignerEntry);
|
||||
}
|
||||
return setSignerList;
|
||||
}
|
||||
|
||||
const txJSON: SettingsTransaction = {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import isEqual from 'lodash.isequal'
|
||||
import * as utils from './utils'
|
||||
import keypairs = require('ripple-keypairs')
|
||||
import binary = 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
|
||||
? binary.encodeForMultisigning(tx, signAs)
|
||||
: binary.encodeForSigning(tx)
|
||||
? binaryCodec.encodeForMultisigning(tx, signAs)
|
||||
: binaryCodec.encodeForSigning(tx)
|
||||
return keypairs.sign(signingData, privateKey)
|
||||
}
|
||||
|
||||
@@ -32,7 +33,155 @@ function signWithKeypair(
|
||||
)
|
||||
}
|
||||
|
||||
const fee = new BigNumber(tx.Fee)
|
||||
checkFee(api, tx.Fee)
|
||||
|
||||
const txToSignAndEncode = Object.assign({}, tx)
|
||||
|
||||
txToSignAndEncode.SigningPubKey = options.signAs ? '' : keypair.publicKey
|
||||
|
||||
if (options.signAs) {
|
||||
const signer = {
|
||||
Account: options.signAs,
|
||||
SigningPubKey: keypair.publicKey,
|
||||
TxnSignature: computeSignature(txToSignAndEncode, keypair.privateKey, options.signAs)
|
||||
}
|
||||
txToSignAndEncode.Signers = [{Signer: signer}]
|
||||
} else {
|
||||
txToSignAndEncode.TxnSignature = computeSignature(txToSignAndEncode, keypair.privateKey)
|
||||
}
|
||||
|
||||
const serialized = binaryCodec.encode(txToSignAndEncode)
|
||||
|
||||
checkTxSerialization(serialized, tx)
|
||||
|
||||
return {
|
||||
signedTransaction: serialized,
|
||||
id: computeBinaryTransactionHash(serialized)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param {string} serialized A signed and serialized transaction.
|
||||
* @param {utils.TransactionJSON} tx The transaction prior to signing.
|
||||
*
|
||||
* @returns {void} This method does not return a value, but throws an error if the check fails.
|
||||
*/
|
||||
function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): void {
|
||||
// Decode the serialized transaction:
|
||||
const decoded = binaryCodec.decode(serialized)
|
||||
|
||||
// ...And ensure it is equal to the original tx, except:
|
||||
// - It must have a TxnSignature or Signers (multisign).
|
||||
if (!decoded.TxnSignature && !decoded.Signers) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
'Serialized transaction must have a TxnSignature or Signers property'
|
||||
)
|
||||
}
|
||||
// - We know that the original tx did not have TxnSignature, so we should delete it:
|
||||
delete decoded.TxnSignature
|
||||
// - We know that the original tx did not have Signers, so if it exists, we should delete it:
|
||||
delete decoded.Signers
|
||||
|
||||
// - If SigningPubKey was not in the original tx, then we should delete it.
|
||||
// But if it was in the original tx, then we should ensure that it has not been changed.
|
||||
if (!tx.SigningPubKey) {
|
||||
delete decoded.SigningPubKey
|
||||
}
|
||||
|
||||
if (!isEqual(decoded, tx)) {
|
||||
const error = new utils.common.errors.ValidationError(
|
||||
'Serialized transaction does not match original txJSON. See `error.data`'
|
||||
)
|
||||
error.data = {
|
||||
decoded,
|
||||
tx,
|
||||
diff: objectDiff(tx, decoded)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a given transaction fee does not exceed maxFeeXRP (in drops).
|
||||
*
|
||||
* See https://xrpl.org/rippleapi-reference.html#parameters
|
||||
*
|
||||
* @param {RippleAPI} api A RippleAPI instance.
|
||||
* @param {string} txFee The transaction fee in drops, encoded as a string.
|
||||
*
|
||||
* @returns {void} This method does not return a value, but throws an error if the check fails.
|
||||
*/
|
||||
function checkFee(api: RippleAPI, txFee: string): void {
|
||||
const fee = new BigNumber(txFee)
|
||||
const maxFeeDrops = xrpToDrops(api._maxFeeXRP)
|
||||
if (fee.greaterThan(maxFeeDrops)) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
@@ -40,25 +189,6 @@ function signWithKeypair(
|
||||
'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.'
|
||||
)
|
||||
}
|
||||
|
||||
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey
|
||||
|
||||
if (options.signAs) {
|
||||
const signer = {
|
||||
Account: options.signAs,
|
||||
SigningPubKey: keypair.publicKey,
|
||||
TxnSignature: computeSignature(tx, keypair.privateKey, options.signAs)
|
||||
}
|
||||
tx.Signers = [{Signer: signer}]
|
||||
} else {
|
||||
tx.TxnSignature = computeSignature(tx, keypair.privateKey)
|
||||
}
|
||||
|
||||
const serialized = binary.encode(tx)
|
||||
return {
|
||||
signedTransaction: serialized,
|
||||
id: computeBinaryTransactionHash(serialized)
|
||||
}
|
||||
}
|
||||
|
||||
function sign(
|
||||
|
||||
@@ -34,7 +34,17 @@ function formatPrepareResponse(txJSON: any): Prepare {
|
||||
}
|
||||
}
|
||||
|
||||
function setCanonicalFlag(txJSON) {
|
||||
/**
|
||||
* Set the `tfFullyCanonicalSig` flag on a transaction.
|
||||
*
|
||||
* See https://xrpl.org/transaction-malleability.html
|
||||
*
|
||||
* @param {TransactionJSON} txJSON The transaction object to modify.
|
||||
* This method will modify object's `Flags` property, or add it if it does not exist.
|
||||
*
|
||||
* @returns {void} This method mutates the original txJSON and does not return a value.
|
||||
*/
|
||||
function setCanonicalFlag(txJSON: TransactionJSON): void {
|
||||
txJSON.Flags |= txFlags.Universal.FullyCanonicalSig
|
||||
|
||||
// JavaScript converts operands to 32-bit signed ints before doing bitwise
|
||||
@@ -58,6 +68,11 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
'" exists in instance when not allowed'))
|
||||
}
|
||||
|
||||
// To remove the signer list, SignerEntries field should be omitted.
|
||||
if (txJSON['SignerQuorum'] === 0) {
|
||||
delete txJSON.SignerEntries
|
||||
}
|
||||
|
||||
const account = txJSON.Account
|
||||
setCanonicalFlag(txJSON)
|
||||
|
||||
@@ -187,5 +202,6 @@ export {
|
||||
convertStringToHex,
|
||||
convertMemo,
|
||||
prepareTransaction,
|
||||
common
|
||||
common,
|
||||
setCanonicalFlag
|
||||
}
|
||||
|
||||
171
test/api-test.js
171
test/api-test.js
@@ -1767,19 +1767,15 @@ describe('RippleAPI', function () {
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareSettings - signers no weights', function (done) {
|
||||
it('prepareSettings - signers no weights', function () {
|
||||
const settings = requests.prepareSettings.signers.noWeights;
|
||||
try {
|
||||
this.api.prepareSettings(address, settings, instructionsWithMaxLedgerVersionOffset).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.settings.signers requires property "weights"');
|
||||
done();
|
||||
}).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));
|
||||
};
|
||||
const localInstructions = _.defaults({
|
||||
signersCount: 1
|
||||
}, instructionsWithMaxLedgerVersionOffset);
|
||||
return this.api.prepareSettings(
|
||||
address, settings, localInstructions).then(
|
||||
_.partial(checkResult, responses.prepareSettings.noWeights,
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - fee for multisign', function () {
|
||||
@@ -1792,6 +1788,17 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - no signer list', function () {
|
||||
const settings = requests.prepareSettings.noSignerEntries;
|
||||
const localInstructions = _.defaults({
|
||||
signersCount: 1
|
||||
}, instructionsWithMaxLedgerVersionOffset);
|
||||
return this.api.prepareSettings(
|
||||
address, settings, localInstructions).then(
|
||||
_.partial(checkResult, responses.prepareSettings.noSignerList,
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - invalid', function (done) {
|
||||
// domain must be a string
|
||||
const settings = Object.assign({},
|
||||
@@ -2601,6 +2608,146 @@ describe('RippleAPI', function () {
|
||||
assert.deepEqual(signature, responses.sign.signAs);
|
||||
});
|
||||
|
||||
it('sign - succeeds - prepared payment', async function () {
|
||||
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
|
||||
source: {
|
||||
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
maxAmount: {
|
||||
value: '1',
|
||||
currency: 'drops'
|
||||
}
|
||||
},
|
||||
destination: {
|
||||
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
|
||||
amount: {
|
||||
value: '1',
|
||||
currency: 'drops'
|
||||
}
|
||||
}
|
||||
});
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
const result = this.api.sign(payment.txJSON, secret);
|
||||
const expectedResult = {
|
||||
signedTransaction:
|
||||
'12000022800000002400000017201B008694F261400000000000000168400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100A9C91D4CFAE45686146EE0B56D4C53A2E7C2D672FB834D43E0BE2D2E9106519A022075DDA2F92DE552B0C45D83D4E6D35889B3FBF51BFBBD9B25EBF70DE3C96D0D6681145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648',
|
||||
id:
|
||||
'88D6B913C66279EA31ADC25C5806C48B2D4E5680261666790A736E1961217700'
|
||||
};
|
||||
assert.deepEqual(result, expectedResult);
|
||||
schemaValidator.schemaValidate('sign', result);
|
||||
});
|
||||
|
||||
it('sign - succeeds - no flags', async function () {
|
||||
const txJSON = '{"TransactionType":"Payment","Account":"r45Rev1EXGxy2hAUmJPCne97KUE7qyrD3j","Destination":"rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r","Amount":"20000000","Sequence":1,"Fee":"12"}';
|
||||
const secret = 'shotKgaEotpcYsshSE39vmSnBDRim';
|
||||
const result = this.api.sign(txJSON, secret);
|
||||
const expectedResult = {
|
||||
signedTransaction:
|
||||
'1200002400000001614000000001312D0068400000000000000C7321022B05847086686F9D0499B13136B94AD4323EE1B67D4C429ECC987AB35ACFA34574473045022100C104B7B97C31FACA4597E7D6FCF13BD85BD11375963A62A0AC45B0061236E39802207784F157F6A98DFC85B051CDDF61CC3084C4F5750B82674801C8E9950280D1998114EE3046A5DDF8422C40DDB93F1D522BB4FE6419158314FDB08D07AAA0EB711793A3027304D688E10C3648',
|
||||
id:
|
||||
'0596925967F541BF332FF6756645B2576A9858414B5B363DC3D34915BE8A70D6'
|
||||
};
|
||||
const decoded = binary.decode(result.signedTransaction);
|
||||
assert(decoded.Flags === undefined, `Flags = ${decoded.Flags}, should be undefined`);
|
||||
assert.deepEqual(result, expectedResult);
|
||||
schemaValidator.schemaValidate('sign', result);
|
||||
});
|
||||
|
||||
it('sign - throws when encoded tx does not match decoded tx - prepared payment', async function () {
|
||||
const payment = await this.api.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', {
|
||||
source: {
|
||||
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
maxAmount: {
|
||||
value: '1.1234567',
|
||||
currency: 'drops'
|
||||
}
|
||||
},
|
||||
destination: {
|
||||
address: 'rQ3PTWGLCbPz8ZCicV5tCX3xuymojTng5r',
|
||||
amount: {
|
||||
value: '1.1234567',
|
||||
currency: 'drops'
|
||||
}
|
||||
}
|
||||
});
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
assert.throws(
|
||||
() => {
|
||||
this.api.sign(payment.txJSON, secret);
|
||||
},
|
||||
/^Error: 1\.1234567 is an illegal amount/
|
||||
);
|
||||
});
|
||||
|
||||
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 = {
|
||||
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1.2\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
||||
"instructions": {
|
||||
"fee": "0.0000012",
|
||||
"sequence": 23,
|
||||
"maxLedgerVersion": 8820051
|
||||
}
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
this.api.sign(request.txJSON, secret);
|
||||
},
|
||||
/Error: 1\.2 is an illegal amount/
|
||||
);
|
||||
});
|
||||
|
||||
it('sign - throws when encoded tx does not match decoded tx - higher fee', function () {
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
const request = {
|
||||
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"1123456.7\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
|
||||
"instructions": {
|
||||
"fee": "1.1234567",
|
||||
"sequence": 23,
|
||||
"maxLedgerVersion": 8820051
|
||||
}
|
||||
}
|
||||
|
||||
assert.throws(
|
||||
() => {
|
||||
this.api.sign(request.txJSON, secret);
|
||||
},
|
||||
/Error: 1123456\.7 is an illegal amount/
|
||||
);
|
||||
});
|
||||
|
||||
it('sign - throws when Fee exceeds maxFeeXRP (in drops)', function () {
|
||||
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
|
||||
const request = {
|
||||
|
||||
1
test/fixtures/requests/index.js
vendored
1
test/fixtures/requests/index.js
vendored
@@ -22,6 +22,7 @@ module.exports = {
|
||||
},
|
||||
prepareSettings: {
|
||||
domain: require('./prepare-settings'),
|
||||
noSignerEntries: require('./prepare-settings-no-signer-entries'),
|
||||
signers: {
|
||||
normal: require('./prepare-settings-signers'),
|
||||
noThreshold: require('./prepare-settings-signers-no-threshold'),
|
||||
|
||||
5
test/fixtures/requests/prepare-settings-no-signer-entries.json
vendored
Normal file
5
test/fixtures/requests/prepare-settings-no-signer-entries.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"signers": {
|
||||
"threshold": 0
|
||||
}
|
||||
}
|
||||
4
test/fixtures/responses/index.js
vendored
4
test/fixtures/responses/index.js
vendored
@@ -122,7 +122,9 @@ module.exports = {
|
||||
noInstructions: require('./prepare-settings-no-instructions.json'),
|
||||
signed: require('./prepare-settings-signed.json'),
|
||||
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json'),
|
||||
signers: require('./prepare-settings-signers.json')
|
||||
signers: require('./prepare-settings-signers.json'),
|
||||
noSignerList: require('./prepare-settings-no-signer-list.json'),
|
||||
noWeights: require('./prepare-settings-no-weight.json')
|
||||
},
|
||||
prepareCheckCreate: {
|
||||
normal: require('./prepare-check-create'),
|
||||
|
||||
8
test/fixtures/responses/prepare-settings-no-signer-list.json
vendored
Normal file
8
test/fixtures/responses/prepare-settings-no-signer-list.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":0,\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"24\",\"Sequence\":23}",
|
||||
"instructions": {
|
||||
"fee": "0.000024",
|
||||
"sequence": 23,
|
||||
"maxLedgerVersion": 8820051
|
||||
}
|
||||
}
|
||||
8
test/fixtures/responses/prepare-settings-no-weight.json
vendored
Normal file
8
test/fixtures/responses/prepare-settings-no-weight.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":2,\"SignerEntries\":[],\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"24\",\"Sequence\":23}",
|
||||
"instructions": {
|
||||
"fee": "0.000024",
|
||||
"sequence": 23,
|
||||
"maxLedgerVersion": 8820051
|
||||
}
|
||||
}
|
||||
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
127
test/hashes-test.js
Normal file
127
test/hashes-test.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/* eslint-disable max-len, valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var hashes = require('../src/common/hashes');
|
||||
|
||||
/**
|
||||
* @param ledgerIndex {Number}
|
||||
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder
|
||||
*/
|
||||
function createLedgerTest(ledgerIndex) {
|
||||
describe(String(ledgerIndex), function() {
|
||||
var path = __dirname + '/fixtures/rippled/ledger-full-' + ledgerIndex + '.json';
|
||||
|
||||
var ledgerRaw = fs.readFileSync(path);
|
||||
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, 1));
|
||||
});
|
||||
}
|
||||
it('has transaction_hash of ' + ledgerJSON.transaction_hash, function() {
|
||||
assert.equal(ledgerJSON.transaction_hash,
|
||||
hashes.computeTransactionTreeHash(ledgerJSON.transactions, 1));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,202 +0,0 @@
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
'use strict';
|
||||
const assert = require('assert-diff');
|
||||
const _ = require('lodash');
|
||||
const jayson = require('jayson');
|
||||
|
||||
const RippleAPI = require('../../src').RippleAPI;
|
||||
const createHTTPServer = require('../../src/http').createHTTPServer;
|
||||
const {payTo, ledgerAccept} = require('./utils');
|
||||
|
||||
const apiFixtures = require('../fixtures');
|
||||
const apiRequests = apiFixtures.requests;
|
||||
const apiResponses = apiFixtures.responses;
|
||||
|
||||
const TIMEOUT = 20000; // how long before each test case times out
|
||||
|
||||
const serverUri = 'ws://127.0.0.1:6006';
|
||||
const apiOptions = {
|
||||
server: serverUri
|
||||
};
|
||||
|
||||
const httpPort = 3000;
|
||||
|
||||
function createClient() {
|
||||
return jayson.client.http({port: httpPort, hostname: 'localhost'});
|
||||
}
|
||||
|
||||
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
|
||||
|
||||
function makePositionalParams(params) {
|
||||
return params.map(value => value[_.keys(value)[0]]);
|
||||
}
|
||||
|
||||
function makeNamedParams(params) {
|
||||
return _.reduce(params, _.assign, {});
|
||||
}
|
||||
|
||||
function random() {
|
||||
return _.fill(Array(16), 0);
|
||||
}
|
||||
|
||||
|
||||
describe('http server integration tests', function() {
|
||||
this.timeout(TIMEOUT);
|
||||
|
||||
let server = null;
|
||||
let client = null;
|
||||
let paymentId = null;
|
||||
let newWallet = null;
|
||||
|
||||
function createTestInternal(testName, methodName, params, testFunc, id) {
|
||||
it(testName, function() {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.request(methodName, params, id,
|
||||
(err, result) => err ? reject(err) : resolve(testFunc(result)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createTest(name, params, testFunc, id) {
|
||||
createTestInternal(name + ' - positional params', name,
|
||||
makePositionalParams(params), testFunc, id);
|
||||
createTestInternal(name + ' - named params', name,
|
||||
makeNamedParams(params), testFunc, id);
|
||||
}
|
||||
|
||||
before(() => {
|
||||
this.api = new RippleAPI({server: serverUri});
|
||||
console.log('CONNECTING...');
|
||||
return this.api.connect().then(() => {
|
||||
console.log('CONNECTED...');
|
||||
})
|
||||
.then(() => ledgerAccept(this.api))
|
||||
.then(() => newWallet = this.api.generateAddress())
|
||||
.then(() => ledgerAccept(this.api))
|
||||
.then(() => payTo(this.api, newWallet.address))
|
||||
.then(paymentId_ => {
|
||||
paymentId = paymentId_;
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
server = createHTTPServer(apiOptions, httpPort);
|
||||
return server.start().then(() => {
|
||||
this.client = createClient();
|
||||
client = this.client;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
return server.stop();
|
||||
});
|
||||
|
||||
|
||||
createTest(
|
||||
'getLedgerVersion',
|
||||
[],
|
||||
result => assert(_.isNumber(result.result))
|
||||
);
|
||||
|
||||
createTest(
|
||||
'getServerInfo',
|
||||
[],
|
||||
result => assert(_.isNumber(result.result.validatedLedger.ledgerVersion))
|
||||
);
|
||||
|
||||
it('getTransaction', function() {
|
||||
const params = [{id: paymentId}];
|
||||
return new Promise((resolve, reject) => {
|
||||
client.request('getTransaction', makePositionalParams(params),
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
assert.strictEqual(result.result.id, paymentId);
|
||||
const outcome = result.result.outcome;
|
||||
assert.strictEqual(outcome.result, 'tesSUCCESS');
|
||||
assert.strictEqual(outcome.balanceChanges
|
||||
.rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh[0].value, '-4003218.000012');
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('getTransactions', function() {
|
||||
const params = [{address: newWallet.address}, {
|
||||
options: {
|
||||
binary: true,
|
||||
limit: 1
|
||||
}
|
||||
}];
|
||||
return new Promise((resolve, reject) => {
|
||||
client.request('getTransactions', makeNamedParams(params),
|
||||
(err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
assert.strictEqual(result.result.length, 1);
|
||||
assert.strictEqual(result.result[0].id, paymentId);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
createTest(
|
||||
'prepareSettings',
|
||||
[
|
||||
{address},
|
||||
{settings: apiRequests.prepareSettings.domain},
|
||||
{instructions: {
|
||||
maxFee: '0.000012',
|
||||
sequence: 23,
|
||||
maxLedgerVersion: 8820051
|
||||
}}
|
||||
],
|
||||
result => {
|
||||
const got = JSON.parse(result.result.txJSON);
|
||||
const expected = JSON.parse(apiResponses.prepareSettings.flags.txJSON);
|
||||
assert.deepEqual(got, expected);
|
||||
}
|
||||
);
|
||||
|
||||
createTest(
|
||||
'sign',
|
||||
[{txJSON: apiRequests.sign.normal.txJSON},
|
||||
{secret: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'}],
|
||||
result => assert.deepEqual(result.result, apiResponses.sign.normal)
|
||||
);
|
||||
|
||||
createTest(
|
||||
'sign',
|
||||
[{txJSON: apiRequests.sign.normal.txJSON},
|
||||
{keypair: {
|
||||
privateKey: '00ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A',
|
||||
publicKey: '02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8' }}],
|
||||
result => assert.deepEqual(result.result, apiResponses.sign.normal)
|
||||
);
|
||||
|
||||
createTest(
|
||||
'generateAddress',
|
||||
[{options: {entropy: random()}}],
|
||||
result => assert.deepEqual(result.result, apiResponses.generateAddress)
|
||||
);
|
||||
|
||||
createTest(
|
||||
'computeLedgerHash',
|
||||
[{ledger: _.assign({}, apiResponses.getLedger.full,
|
||||
{parentCloseTime: apiResponses.getLedger.full.closeTime})
|
||||
}],
|
||||
result => {
|
||||
assert.strictEqual(result.result,
|
||||
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
|
||||
}
|
||||
);
|
||||
|
||||
createTest(
|
||||
'isConnected',
|
||||
[],
|
||||
result => assert(result.result)
|
||||
);
|
||||
|
||||
});
|
||||
71
test/shamap-test.js
Normal file
71
test/shamap-test.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/* eslint-disable max-len, valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var SHAMap = require('../src/common/hashes/shamap').SHAMap;
|
||||
var TYPE_TRANSACTION_NO_METADATA = require('../src/common/hashes/shamap').NodeType.TRANSACTION_NO_METADATA
|
||||
|
||||
var HEX_ZERO = '00000000000000000000000000000000' +
|
||||
'00000000000000000000000000000000';
|
||||
|
||||
/**
|
||||
* Generates data to hash for testing
|
||||
* @param {number} v int value
|
||||
* @returns {string} 64 length hex string
|
||||
*/
|
||||
function intToVuc(v) {
|
||||
var ret = '';
|
||||
|
||||
for (var i = 0; i < 32; i++) {
|
||||
ret += '0';
|
||||
ret += v.toString(16).toUpperCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shamap {Object}
|
||||
* @param keys {Array}
|
||||
* @param hashes {Array}
|
||||
*/
|
||||
function fillShamapTest(shamap, keys, hashes) {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,26 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"pretty": true,
|
||||
"lib": [
|
||||
"es2017"
|
||||
],
|
||||
"target": "es6",
|
||||
"target": "es2017",
|
||||
"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",
|
||||
"esModuleInterop": true,
|
||||
"suppressImplicitAnyIndexErrors": false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user