mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-14 09:35:48 +00:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5bad5d28e | ||
|
|
9b7d255200 | ||
|
|
7a027bdd93 | ||
|
|
439a611a9e | ||
|
|
5f92b230aa | ||
|
|
29bc5303ae | ||
|
|
5314e5e7e9 | ||
|
|
c88462c99b | ||
|
|
0b552f1a7e | ||
|
|
552635a3c7 | ||
|
|
ca769bee39 | ||
|
|
b7ca0a0a14 | ||
|
|
84097a3179 | ||
|
|
8ba36b2588 | ||
|
|
e1d4ebc5f6 | ||
|
|
9e712d6089 | ||
|
|
90bea3dc6b | ||
|
|
bf480bb971 | ||
|
|
a94b48be50 | ||
|
|
abed42d848 | ||
|
|
3d6e795ca5 | ||
|
|
a3cbe8e9d4 | ||
|
|
0a2000098a | ||
|
|
a2348b5133 | ||
|
|
20d2f9d894 | ||
|
|
321f908e76 | ||
|
|
49875cb0e5 | ||
|
|
1074c00b60 | ||
|
|
8a8b10541e | ||
|
|
46cf4d677c | ||
|
|
94587d7515 | ||
|
|
14ec58ef9a | ||
|
|
8f4f6f3de0 | ||
|
|
f8c0ac3ce0 | ||
|
|
8ebad98912 | ||
|
|
08429b6110 | ||
|
|
0e128e15f1 | ||
|
|
b77a12fd0d | ||
|
|
a98526b398 | ||
|
|
5639bf9d48 | ||
|
|
c626685103 | ||
|
|
e233d15fbb | ||
|
|
a5d83900d9 | ||
|
|
d8dbeedcc2 | ||
|
|
56b67d62a3 | ||
|
|
dc084b4bd9 | ||
|
|
1bc0eab7ae | ||
|
|
ca14d1b108 | ||
|
|
d6757aced2 | ||
|
|
5c84eed292 | ||
|
|
b648387a57 | ||
|
|
5232f95c3f | ||
|
|
0742960ec4 | ||
|
|
4c41b7f8df | ||
|
|
c5d0c24237 | ||
|
|
8b5c51ceaa | ||
|
|
c09bceb66a | ||
|
|
e7afd3ec76 | ||
|
|
3d30be3472 | ||
|
|
e08367365f | ||
|
|
6692fbeed4 | ||
|
|
36a9e7a7cf | ||
|
|
49b5ff5fd9 | ||
|
|
1bde56a11a | ||
|
|
f47d7b6935 | ||
|
|
2dbad40a34 | ||
|
|
39f6a51794 | ||
|
|
7ec128c2e4 | ||
|
|
b55f0e849e | ||
|
|
f158390ba1 | ||
|
|
e4b245104a | ||
|
|
789497b07e | ||
|
|
5cf01ba099 | ||
|
|
e8669891f8 | ||
|
|
ac0f265a5b | ||
|
|
fcd6b430e1 | ||
|
|
f3ad8a9b80 | ||
|
|
43ff824da1 | ||
|
|
b8022610ca | ||
|
|
03defe203a | ||
|
|
aedcbe56b3 | ||
|
|
c365db460a | ||
|
|
cfdc4752d0 | ||
|
|
e1964ac5ed | ||
|
|
e17ab9cd8f | ||
|
|
edc15b8727 | ||
|
|
034f8d41fc | ||
|
|
0fa70db1e1 | ||
|
|
fa7ba9b72b | ||
|
|
3a20123e0f | ||
|
|
03510d1bc4 | ||
|
|
8b116f637a | ||
|
|
9c49de6552 | ||
|
|
842347bcab | ||
|
|
628b9c4853 | ||
|
|
d60b6ee33f | ||
|
|
4f4fcbbc70 | ||
|
|
cc896670dc | ||
|
|
0cf5ce1416 | ||
|
|
3a3ff8a65e | ||
|
|
9f183a6dfc | ||
|
|
6b572ca862 | ||
|
|
d075ec6716 | ||
|
|
0c98082b25 |
25
.eslintrc.json
Normal file
25
.eslintrc.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"no-useless-constructor": 0,
|
||||
"no-unused-vars": 0,
|
||||
"no-prototype-builtins": 0,
|
||||
"require-atomic-updates": 0
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
lib-cov
|
||||
coverage.html
|
||||
src
|
||||
dist/bower
|
||||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"parser": "typescript",
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"quoteProps": "consistent"
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
- 8
|
||||
- 10
|
||||
- 11
|
||||
- 12
|
||||
- 13
|
||||
script:
|
||||
- yarn compile
|
||||
- yarn test
|
||||
- yarn build
|
||||
- yarn lint
|
||||
|
||||
@@ -36,7 +36,7 @@ Warning: Use at your own risk.
|
||||
|
||||
List of XRPL validators, nodes, and testnet validators.
|
||||
|
||||
- **[XRP Scan - XRP Ledger explorer](https://http://xrpscan.com)**
|
||||
- **[XRP Scan - XRP Ledger explorer](https://xrpscan.com)**
|
||||
|
||||
XRP Ledger explorer, metrics and analytics.
|
||||
|
||||
@@ -60,6 +60,10 @@ Warning: Use at your own risk.
|
||||
|
||||
## Wallets and wallet tools
|
||||
|
||||
- **[XRP Toolkit](https://www.xrptoolkit.com)**
|
||||
|
||||
A web interface to the XRP Ledger, supporting both hardware and software wallets.
|
||||
|
||||
- **[Toast Wallet](https://toastwallet.com/)**
|
||||
|
||||
A free, open source XRP Wallet for iOS, Android, Windows, Mac and Linux.
|
||||
|
||||
151
Gulpfile.js
151
Gulpfile.js
@@ -1,151 +0,0 @@
|
||||
/* eslint-disable no-var, no-param-reassign */
|
||||
/* these eslint rules are disabled because gulp does not support babel yet */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
const gulp = require('gulp');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const pkg = require('./package.json');
|
||||
|
||||
const uglifyOptions = {
|
||||
mangle: {
|
||||
reserved: ['_', 'RippleError', 'RippledError', 'UnexpectedError',
|
||||
'LedgerVersionError', 'ConnectionError', 'NotConnectedError',
|
||||
'DisconnectedError', 'TimeoutError', 'ResponseFormatError',
|
||||
'ValidationError', 'NotFoundError', 'MissingLedgerHistoryError',
|
||||
'PendingLedgerVersionError'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
function getWebpackConfig(extension, overrides) {
|
||||
overrides = overrides || {};
|
||||
let defaults = {
|
||||
cache: true,
|
||||
externals: [{
|
||||
'lodash': '_'
|
||||
}],
|
||||
entry: './src/index.ts',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: path.join(__dirname, 'build/'),
|
||||
filename: `ripple-${pkg.version}${extension}`
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet$/, './wallet-web'),
|
||||
new webpack.NormalModuleReplacementPlugin(/^.*setup-api$/,
|
||||
'./setup-api-web')
|
||||
],
|
||||
module: {
|
||||
rules: [{
|
||||
test: /jayson/,
|
||||
use: 'null',
|
||||
}, {
|
||||
test: /\.ts$/,
|
||||
use: [{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
composite: false,
|
||||
declaration: false,
|
||||
declarationMap: false
|
||||
}
|
||||
},
|
||||
}],
|
||||
}]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.ts', '.js' ]
|
||||
},
|
||||
};
|
||||
return _.assign({}, defaults, overrides);
|
||||
}
|
||||
|
||||
function webpackConfigForWebTest(testFileName) {
|
||||
var match = testFileName.match(/\/?([^\/]*)-test.js$/);
|
||||
if (!match) {
|
||||
assert(false, 'wrong filename:' + testFileName);
|
||||
}
|
||||
var configOverrides = {
|
||||
externals: [{
|
||||
'lodash': '_',
|
||||
'ripple-api': 'ripple',
|
||||
'net': 'null'
|
||||
}],
|
||||
entry: testFileName,
|
||||
output: {
|
||||
library: match[1].replace(/-/g, '_'),
|
||||
path: path.join(__dirname, 'test-compiled-for-web/'),
|
||||
filename: match[1] + '-test.js'
|
||||
}
|
||||
};
|
||||
return getWebpackConfig('.js', configOverrides);
|
||||
}
|
||||
|
||||
function createLink(from, to) {
|
||||
if (fs.existsSync(to)) {
|
||||
fs.unlinkSync(to);
|
||||
}
|
||||
fs.linkSync(from, to);
|
||||
}
|
||||
|
||||
function createBuildLink(callback) {
|
||||
return function(err, res) {
|
||||
createLink('./build/ripple-' + pkg.version + '.js',
|
||||
'./build/ripple-latest.js');
|
||||
callback(err, res);
|
||||
};
|
||||
}
|
||||
|
||||
function watch(callback) {
|
||||
gulp.watch('src/*', gulp.series(buildDebug));
|
||||
callback();
|
||||
}
|
||||
|
||||
function build(callback) {
|
||||
webpack(getWebpackConfig('.js'), createBuildLink(callback));
|
||||
}
|
||||
|
||||
function buildDebug(callback) {
|
||||
const webpackConfig = getWebpackConfig('-debug.js', {devtool: 'eval'});
|
||||
webpackConfig.plugins.unshift(new webpack.LoaderOptionsPlugin({debug: true}));
|
||||
webpack(webpackConfig, callback);
|
||||
}
|
||||
|
||||
function buildMin(callback) {
|
||||
const webpackConfig = getWebpackConfig('-min.js');
|
||||
webpackConfig.plugins.push(new UglifyJsPlugin({uglifyOptions}));
|
||||
webpack(webpackConfig, function() {
|
||||
createLink('./build/ripple-' + pkg.version + '-min.js',
|
||||
'./build/ripple-latest-min.js');
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function buildTests(callback) {
|
||||
var times = 0;
|
||||
function done() {
|
||||
if (++times >= 5) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
webpack(webpackConfigForWebTest('./test/rangeset-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/connection-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/api-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/broadcast-api-test.js'), done);
|
||||
webpack(webpackConfigForWebTest('./test/integration/integration-test.js',
|
||||
'integration/'), done);
|
||||
}
|
||||
|
||||
exports.watch = watch;
|
||||
exports.build = build;
|
||||
exports.buildDebug = buildDebug;
|
||||
exports.buildMin = buildMin;
|
||||
exports.buildTests = buildTests;
|
||||
|
||||
exports.default = gulp.parallel(build, buildDebug, buildMin);
|
||||
63
HISTORY.md
63
HISTORY.md
@@ -1,5 +1,68 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
## 1.5.0 (2019-12-14)
|
||||
|
||||
* Add support for `WalletLocator` (#1083)
|
||||
* Types: Move and de-dupe `TransactionJSON` type (#1096)
|
||||
* This resolves an error surfaced by [TypeScript 3.7](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#local-and-imported-type-declarations-now-conflict)
|
||||
* Add a heartbeat to detect hung connections (#1101)
|
||||
* Dependencies
|
||||
* Update TypeScript version (#1096)
|
||||
* Update ripple-lib-transactionparser to 0.8.1 (#1097)
|
||||
* Update ripple-binary-codec to 0.2.5
|
||||
* Update webpack (#1112)
|
||||
* Require node 8 and yarn (#1107)
|
||||
* Testing: Refactor and add unit tests
|
||||
* Fix some errors caught by the improved tests
|
||||
|
||||
## 1.4.2 (2019-11-14)
|
||||
|
||||
* Add support for tick size (#1090) (thanks @RareData)
|
||||
* Update email hash default to allow proper clearing (#1089) (thanks @RareData)
|
||||
* Fix Unhandled Promise Rejection Warning on message `_send`
|
||||
* Add an immediate catch to the `_send` promise passed to `_whenReady` in case there is rejection before async handlers are added (#1092) (thanks @nickewansmith)
|
||||
* Docs improvements
|
||||
* Add XRP Toolkit reference (#1088)
|
||||
* Internal improvements
|
||||
* Add a prettier config
|
||||
* Update Node.js Testing Versions (#1085)
|
||||
* Testing matrix based on: https://nodejs.org/en/about/releases/
|
||||
- Node 11 is no longer supported (not LTS)
|
||||
- Node 12 added (active LTS)
|
||||
- Node 13 added ("current" release)
|
||||
|
||||
## 1.4.1 (2019-11-06)
|
||||
|
||||
* Compatibility: Change TypeScript compile target back to `es6` (#1071)
|
||||
* WARNING: This allows for the use of Node v6, which is no longer supported by Node.js, as it was end-of-life'd in April 2019
|
||||
* We recommend updating to Node v8/v10 ASAP in order to get security updates and fixes from the Node.js team
|
||||
* We are not actively running tests against Node v6 (ref #1076)
|
||||
* Docs: `getAccountObjects` doc fix
|
||||
* Dependencies:
|
||||
* Update `bignumber.js`
|
||||
* Update `ripple-keypairs`
|
||||
* Update `ws`
|
||||
* Build process: Update `webpack` flow
|
||||
|
||||
## 1.4.0 (2019-10-28)
|
||||
|
||||
* Unref timer so it does not hang the Node.js process
|
||||
* Add a 2-second timeout for connect()
|
||||
* Improve getTransaction() error when tx has not been validated yet
|
||||
* Add support for the new X-address format
|
||||
* Fix error in Safari, Chrome 78, Firefox 70
|
||||
* Some error messages have changed slightly. For example:
|
||||
* `-instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format`
|
||||
* `+instance.Account is not of a type(s) string,instance.Account is not exactly one from <xAddress>,<classicAddress>`
|
||||
|
||||
### Internal improvements
|
||||
|
||||
* Reduce dependency size
|
||||
* Move tests to TypeScript
|
||||
* Replace tslint with eslint
|
||||
* Update https-proxy-agent
|
||||
* Add tests
|
||||
|
||||
## 1.3.4 (2019-10-18)
|
||||
|
||||
* Update ripple-lib-transactionparser
|
||||
|
||||
50
README.md
50
README.md
@@ -1,9 +1,13 @@
|
||||
# ripple-lib
|
||||
# ripple-lib (RippleAPI)
|
||||
|
||||
A JavaScript API for interacting with the XRP Ledger
|
||||
A JavaScript/TypeScript API for interacting with the XRP Ledger
|
||||
|
||||
[](https://www.npmjs.org/package/ripple-lib)
|
||||
|
||||
This is the recommended library for integrating a JavaScript/TypeScript app with the XRP Ledger, especially if you intend to use advanced functionality such as IOUs, payment paths, the decentralized exchange, account settings, payment channels, escrows, multi-signing, and more.
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications](APPLICATIONS.md) that use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
|
||||
### Features
|
||||
|
||||
+ Connect to a `rippled` server from Node.js or a web browser
|
||||
@@ -12,10 +16,6 @@ A JavaScript API for interacting with the XRP Ledger
|
||||
+ Sign and submit transactions to the XRP Ledger
|
||||
+ Type definitions for TypeScript
|
||||
|
||||
## Getting Started
|
||||
|
||||
See also: [RippleAPI Beginners Guide](https://xrpl.org/get-started-with-rippleapi-for-javascript.html)
|
||||
|
||||
### Requirements
|
||||
|
||||
+ **[Node v10](https://nodejs.org/)** is recommended. Other versions may work but are not frequently tested.
|
||||
@@ -28,9 +28,11 @@ In an existing project (with `package.json`), install `ripple-lib`:
|
||||
$ yarn add ripple-lib
|
||||
```
|
||||
|
||||
Then see the [documentation](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md) and [code samples](https://github.com/ripple/ripple-lib/tree/develop/docs/samples).
|
||||
## Documentation
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications](APPLICATIONS.md) that use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
+ [RippleAPI Beginners Guide](https://xrpl.org/get-started-with-rippleapi-for-javascript.html)
|
||||
+ [RippleAPI Full Reference Documentation](https://xrpl.org/rippleapi-reference.html) ([in this repo](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md))
|
||||
+ [Code Samples](https://github.com/ripple/ripple-lib/tree/develop/docs/samples)
|
||||
|
||||
### Mailing Lists
|
||||
|
||||
@@ -44,35 +46,41 @@ If you're using the XRP Ledger in production, you should run a [rippled server](
|
||||
|
||||
## Development
|
||||
|
||||
To build the library for Node.js:
|
||||
```
|
||||
$ yarn compile
|
||||
```
|
||||
|
||||
The TypeScript compiler will [output](./tsconfig.json#L7) the resulting JS files in `./dist/npm/`.
|
||||
|
||||
To build the library for the browser:
|
||||
To build the library for Node.js and the browser:
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
Gulp will [output](./Gulpfile.js) the resulting JS files in `./build/`.
|
||||
The TypeScript compiler will [output](./tsconfig.json#L7) the resulting JS files in `./dist/npm/`.
|
||||
|
||||
webpack will output the resulting JS files in `./build/`.
|
||||
|
||||
For details, see the `scripts` in `package.json`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Unit Tests
|
||||
|
||||
1. Clone the repository
|
||||
2. `cd` into the repository and install dependencies with `yarn install`
|
||||
3. `yarn test`
|
||||
|
||||
Also, run `yarn lint` to lint the code with `tslint`.
|
||||
### Linting
|
||||
|
||||
Run `yarn lint` to lint the code with `tslint`.
|
||||
|
||||
## Generating Documentation
|
||||
|
||||
The continuous integration tests require that the documentation stays up-to-date. If you make changes to the JSON schemas, fixtures, or documentation sources, you must update the documentation by running `yarn run docgen`.
|
||||
Do not edit `./docs/index.md` directly because it is a generated file.
|
||||
|
||||
Instead, edit the appropriate `.md.ejs` files in `./docs/src/`.
|
||||
|
||||
If you make changes to the JSON schemas, fixtures, or documentation sources, update the documentation by running `yarn run docgen`.
|
||||
|
||||
## More Information
|
||||
|
||||
+ [RippleAPI Reference](https://developers.ripple.com/rippleapi-reference.html) - XRP Ledger Dev Portal
|
||||
+ [XRP Ledger Dev Portal](https://developers.ripple.com/)
|
||||
+ [ripple-lib-announce mailing list](https://groups.google.com/forum/#!forum/ripple-lib-announce) - subscribe for release announcements
|
||||
+ [RippleAPI Reference](https://xrpl.org/rippleapi-reference.html) - XRP Ledger Dev Portal
|
||||
+ [XRP Ledger Dev Portal](https://xrpl.org/)
|
||||
|
||||
[](https://travis-ci.org/ripple/ripple-lib)
|
||||
|
||||
131
docs/index.md
131
docs/index.md
@@ -80,6 +80,7 @@
|
||||
- [sign](#sign)
|
||||
- [combine](#combine)
|
||||
- [submit](#submit)
|
||||
- [generateXAddress](#generatexaddress)
|
||||
- [generateAddress](#generateaddress)
|
||||
- [isValidAddress](#isvalidaddress)
|
||||
- [isValidSecret](#isvalidsecret)
|
||||
@@ -105,7 +106,8 @@
|
||||
|
||||
# Introduction
|
||||
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript/TypeScript.
|
||||
|
||||
Using RippleAPI, you can:
|
||||
|
||||
* [Query transactions from the XRP Ledger history](#gettransaction)
|
||||
@@ -114,6 +116,10 @@ Using RippleAPI, you can:
|
||||
* [Generate a new XRP Ledger Address](#generateaddress)
|
||||
* ... and [much more](#api-methods).
|
||||
|
||||
This page contains documentation for ripple-lib. To use ripple-lib with npm/yarn, begin with the [Getting Started](https://github.com/ripple/ripple-lib#getting-started) steps.
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications that use `ripple-lib`](https://github.com/ripple/ripple-lib/blob/develop/APPLICATIONS.md). Open a PR to add your app or project to the list!
|
||||
|
||||
## Boilerplate
|
||||
|
||||
Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) to wrap your custom code using RippleAPI.
|
||||
@@ -225,7 +231,19 @@ Methods that depend on the state of the XRP Ledger are unavailable in offline mo
|
||||
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
|
||||
```
|
||||
|
||||
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
|
||||
```json
|
||||
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
|
||||
```
|
||||
|
||||
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
|
||||
|
||||
### Classic Address
|
||||
|
||||
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
|
||||
|
||||
### X-address
|
||||
|
||||
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
|
||||
|
||||
## Account Sequence Number
|
||||
|
||||
@@ -377,12 +395,12 @@ Name | Type | Description
|
||||
source | object | The source of the funds to be sent.
|
||||
*source.* address | [address](#address) | The address to send from.
|
||||
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
|
||||
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
|
||||
destination | object | The destination of the funds to be sent.
|
||||
*destination.* address | [address](#address) | An address representing the destination of the transaction.
|
||||
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
|
||||
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
|
||||
allowPartialPayment | boolean | *Optional* If true, this payment should proceed even if the whole amount cannot be delivered due to a lack of liquidity or a lack of funds in the source account.
|
||||
invoiceID | string | *Optional* A 256-bit hash that can be used to identify a particular payment.
|
||||
@@ -523,7 +541,7 @@ defaultRipple | boolean | *Optional* Enable [rippling](https://ripple.com/build/
|
||||
depositAuth | boolean | *Optional* Enable [Deposit Authorization](https://ripple.com/build/deposit-authorization/) on this account. If set, transactions cannot send value of any kind to this account unless the sender of those transactions is the account itself. (Requires the [DepositAuth amendment](https://ripple.com/build/known-amendments/#depositauth))
|
||||
disableMasterKey | boolean | *Optional* Disallows use of the master key to sign transactions for this account. To disable the master key, you must authorize the transaction by signing it with the master key pair. You cannot use a regular key pair or a multi-signature. You can re-enable the master key pair using a regular key pair or multi-signature. See [AccountSet](https://developers.ripple.com/accountset.html).
|
||||
disallowIncomingXRP | boolean | *Optional* Indicates that client applications should not send XRP to this account. Not enforced by rippled.
|
||||
domain | string | *Optional* The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase.
|
||||
domain | string | *Optional* The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase.
|
||||
emailHash | string,null | *Optional* Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear.
|
||||
enableTransactionIDTracking | boolean | *Optional* Track the ID of this account’s most recent transaction.
|
||||
globalFreeze | boolean | *Optional* Freeze all assets issued by this account.
|
||||
@@ -538,9 +556,11 @@ signers | object | *Optional* Settings that determine what sets of accounts can
|
||||
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#address) | A Ripple account address
|
||||
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
tickSize | string | *Optional* Tick size to use for offers involving a currency issued by this address. The exchange rates of those offers is rounded to this many significant digits. Valid values are 3 to 15 inclusive, or 0 to disable.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
walletLocator | string,null | *Optional* Transaction hash or any other 64 character hexadecimal string, that may or may not represent the result of a hash operation. Use `null` to clear.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -775,12 +795,14 @@ signature | string | *Optional* Signed claim authorizing withdrawal of XRP from
|
||||
|
||||
# rippled APIs
|
||||
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for all online functionality. With ripple-lib version 1.0.0 and higher, you can easily access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for online functionality. In addition to ripple-lib's own methods, you can also access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
* Use `hasNextPage()` to determine whether a response has more pages. This is true when the response includes a [`marker` field](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
* Use `requestNextPage()` to request the next page of data.
|
||||
|
||||
When using rippled APIs:
|
||||
|
||||
* [Specify XRP amounts in drops](https://developers.ripple.com/basic-data-types.html#specifying-currency-amounts).
|
||||
* [Specify timestamps as the number of seconds since the "Ripple Epoch"](https://developers.ripple.com/basic-data-types.html#specifying-time).
|
||||
* Instead of `counterparty`, use `issuer`.
|
||||
@@ -851,7 +873,7 @@ Returns the response from invoking the specified command, with the specified opt
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -1686,6 +1708,7 @@ return api.getTransactions(address).then(transaction => {
|
||||
},
|
||||
"outcome": {
|
||||
"result": "tesSUCCESS",
|
||||
"timestamp": "2019-04-01T07:39:01.000Z",
|
||||
"fee": "0.00001",
|
||||
"deliveredAmount": {
|
||||
"currency": "USD",
|
||||
@@ -1779,6 +1802,7 @@ return api.getTransactions(address).then(transaction => {
|
||||
},
|
||||
"outcome": {
|
||||
"result": "tesSUCCESS",
|
||||
"timestamp": "2019-04-01T07:39:01.000Z",
|
||||
"fee": "0.00001",
|
||||
"deliveredAmount": {
|
||||
"currency": "USD",
|
||||
@@ -2289,12 +2313,12 @@ Name | Type | Description
|
||||
source | object | Properties of the source of the payment.
|
||||
*source.* address | [address](#address) | The address to send from.
|
||||
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
|
||||
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
|
||||
destination | object | Properties of the destination of the payment.
|
||||
*destination.* address | [address](#address) | An address representing the destination of the transaction.
|
||||
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
|
||||
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
|
||||
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
|
||||
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
|
||||
paths | string | The paths of trustlines and orders to use in executing the payment.
|
||||
|
||||
@@ -3891,7 +3915,7 @@ defaultRipple | boolean | *Optional* Enable [rippling](https://ripple.com/build/
|
||||
depositAuth | boolean | *Optional* Enable [Deposit Authorization](https://ripple.com/build/deposit-authorization/) on this account. If set, transactions cannot send value of any kind to this account unless the sender of those transactions is the account itself. (Requires the [DepositAuth amendment](https://ripple.com/build/known-amendments/#depositauth))
|
||||
disableMasterKey | boolean | *Optional* Disallows use of the master key to sign transactions for this account. To disable the master key, you must authorize the transaction by signing it with the master key pair. You cannot use a regular key pair or a multi-signature. You can re-enable the master key pair using a regular key pair or multi-signature. See [AccountSet](https://developers.ripple.com/accountset.html).
|
||||
disallowIncomingXRP | boolean | *Optional* Indicates that client applications should not send XRP to this account. Not enforced by rippled.
|
||||
domain | string | *Optional* The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase.
|
||||
domain | string | *Optional* The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase.
|
||||
emailHash | string,null | *Optional* Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear.
|
||||
enableTransactionIDTracking | boolean | *Optional* Track the ID of this account’s most recent transaction.
|
||||
globalFreeze | boolean | *Optional* Freeze all assets issued by this account.
|
||||
@@ -3906,9 +3930,11 @@ signers | object | *Optional* Settings that determine what sets of accounts can
|
||||
*signers.* threshold | integer | A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
|
||||
*signers.* weights | array | *Optional* Weights of signatures for each signer.
|
||||
*signers.* weights[] | object | An association of an address and a weight.
|
||||
*signers.weights[].* address | [address](#address) | A Ripple account address
|
||||
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
|
||||
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
tickSize | string | *Optional* Tick size to use for offers involving a currency issued by this address. The exchange rates of those offers is rounded to this many significant digits. Valid values are 3 to 15 inclusive, or 0 to disable.
|
||||
transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
|
||||
walletLocator | string,null | *Optional* Transaction hash or any other 64 character hexadecimal string, that may or may not represent the result of a hash operation. Use `null` to clear.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -3924,8 +3950,10 @@ return api.getSettings(address).then(settings =>
|
||||
"requireDestinationTag": true,
|
||||
"disallowIncomingXRP": true,
|
||||
"emailHash": "23463B99B62A72F26ED677CC556C44E8",
|
||||
"walletLocator": "00000000000000000000000000000000000000000000000000000000DEADBEEF",
|
||||
"domain": "example.com",
|
||||
"transferRate": 1.002,
|
||||
"tickSize": 5,
|
||||
"signers": {
|
||||
"threshold": 3,
|
||||
"weights": [
|
||||
@@ -4029,12 +4057,13 @@ limit | integer | *Optional* (May be omitted) The limit that was used in this re
|
||||
validated | boolean | *Optional* If included and set to true, the information in this request comes from a validated ledger version. Otherwise, the information is subject to change.
|
||||
|
||||
The types of objects that may be returned include:
|
||||
* Offer objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* RippleState objects for trust lines where this account's side is not in the default state.
|
||||
* A SignerList object if the account has multi-signing enabled.
|
||||
* Escrow objects for held payments that have not yet been executed or canceled.
|
||||
* PayChannel objects for open payment channels.
|
||||
* Check objects for pending checks.
|
||||
|
||||
* `Offer` objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* `RippleState` objects for trust lines where this account's side is not in the default state.
|
||||
* A `SignerList` object if the account has multi-signing enabled.
|
||||
* `Escrow` objects for held payments that have not yet been executed or canceled.
|
||||
* `PayChannel` objects for open payment channels.
|
||||
* `Check` objects for pending checks.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -5403,7 +5432,7 @@ options | object | *Optional* Options that control the type of signature that wi
|
||||
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
|
||||
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field cannot be used with keypair).
|
||||
|
||||
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.
|
||||
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -5431,6 +5460,7 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Example (multisigning)
|
||||
|
||||
```javascript
|
||||
@@ -5620,9 +5650,9 @@ return api.submit(signedTransaction)
|
||||
```
|
||||
|
||||
|
||||
## generateAddress
|
||||
## generateXAddress
|
||||
|
||||
`generateAddress(): {address: string, secret: string}`
|
||||
`generateXAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
@@ -5633,6 +5663,7 @@ Name | Type | Description
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -5640,8 +5671,8 @@ This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
address | [address](#address) | A randomly generated Ripple account address.
|
||||
secret | secret string | The secret corresponding to the `address`.
|
||||
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
|
||||
secret | secret string | The secret corresponding to the address.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -5652,6 +5683,52 @@ return api.generateAddress();
|
||||
|
||||
```json
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## generateAddress
|
||||
|
||||
`generateAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* includeClassicAddress | boolean | *Optional* If `true`, return the classic address, in addition to the X-address.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
|
||||
classicAddress | [classicAddress](#classic-address) | A randomly generated XRP Ledger Account ID (classic address).
|
||||
address | [classicAddress](#classic-address) | Deprecated: Use `classicAddress` instead.
|
||||
secret | secret string | The secret corresponding to the address.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
```
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
@@ -5662,7 +5739,7 @@ return api.generateAddress();
|
||||
|
||||
`isValidAddress(address: string): boolean`
|
||||
|
||||
Checks if the specified string contains a valid address.
|
||||
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,19 @@
|
||||
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
|
||||
```
|
||||
|
||||
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
|
||||
```json
|
||||
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
|
||||
```
|
||||
|
||||
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
|
||||
|
||||
### Classic Address
|
||||
|
||||
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
|
||||
|
||||
### X-address
|
||||
|
||||
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
|
||||
|
||||
## Account Sequence Number
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## generateAddress
|
||||
|
||||
`generateAddress(): {address: string, secret: string}`
|
||||
`generateAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
|
||||
23
docs/src/generateXAddress.md.ejs
Normal file
23
docs/src/generateXAddress.md.ejs
Normal file
@@ -0,0 +1,23 @@
|
||||
## generateXAddress
|
||||
|
||||
`generateXAddress(options?: object): {address: string, secret: string}`
|
||||
|
||||
Generate a new XRP Ledger address and corresponding secret.
|
||||
|
||||
### Parameters
|
||||
|
||||
<%- renderSchema('input/generate-x-address.json') %>
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
|
||||
<%- renderSchema('output/generate-x-address.json') %>
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
```
|
||||
|
||||
<%- renderFixture('responses/generate-x-address.json') %>
|
||||
@@ -15,12 +15,13 @@ This method returns a promise that resolves with an object with the following st
|
||||
<%- renderSchema('output/get-account-objects.json') %>
|
||||
|
||||
The types of objects that may be returned include:
|
||||
* Offer objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* RippleState objects for trust lines where this account's side is not in the default state.
|
||||
* A SignerList object if the account has multi-signing enabled.
|
||||
* Escrow objects for held payments that have not yet been executed or canceled.
|
||||
* PayChannel objects for open payment channels.
|
||||
* Check objects for pending checks.
|
||||
|
||||
* `Offer` objects for orders that are currently live, unfunded, or expired but not yet removed.
|
||||
* `RippleState` objects for trust lines where this account's side is not in the default state.
|
||||
* A `SignerList` object if the account has multi-signing enabled.
|
||||
* `Escrow` objects for held payments that have not yet been executed or canceled.
|
||||
* `PayChannel` objects for open payment channels.
|
||||
* `Check` objects for pending checks.
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
<% include sign.md.ejs %>
|
||||
<% include combine.md.ejs %>
|
||||
<% include submit.md.ejs %>
|
||||
<% include generateXAddress.md.ejs %>
|
||||
<% include generateAddress.md.ejs %>
|
||||
<% include isValidAddress.md.ejs %>
|
||||
<% include isValidSecret.md.ejs %>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Introduction
|
||||
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript.
|
||||
RippleAPI (ripple-lib) is the official client library to the XRP Ledger. Currently, RippleAPI is only available in JavaScript/TypeScript.
|
||||
|
||||
Using RippleAPI, you can:
|
||||
|
||||
* [Query transactions from the XRP Ledger history](#gettransaction)
|
||||
@@ -8,3 +9,7 @@ Using RippleAPI, you can:
|
||||
* [Submit](#submit) transactions to the XRP Ledger, including [Payments](#payment), [Orders](#order), [Settings changes](#settings), and [other types](#transaction-types)
|
||||
* [Generate a new XRP Ledger Address](#generateaddress)
|
||||
* ... and [much more](#api-methods).
|
||||
|
||||
This page contains documentation for ripple-lib. To use ripple-lib with npm/yarn, begin with the [Getting Started](https://github.com/ripple/ripple-lib#getting-started) steps.
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications that use `ripple-lib`](https://github.com/ripple/ripple-lib/blob/develop/APPLICATIONS.md). Open a PR to add your app or project to the list!
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`isValidAddress(address: string): boolean`
|
||||
|
||||
Checks if the specified string contains a valid address.
|
||||
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Returns the response from invoking the specified command, with the specified opt
|
||||
|
||||
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
|
||||
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
|
||||
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
|
||||
|
||||
### Return Value
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# rippled APIs
|
||||
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for all online functionality. With ripple-lib version 1.0.0 and higher, you can easily access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
ripple-lib relies on [rippled APIs](https://ripple.com/build/rippled-apis/) for online functionality. In addition to ripple-lib's own methods, you can also access rippled APIs through ripple-lib. Use the `request()`, `hasNextPage()`, and `requestNextPage()` methods:
|
||||
|
||||
* Use `request()` to issue any `rippled` command, including `account_currencies`, `subscribe`, and `unsubscribe`. [Full list of API Methods](https://ripple.com/build/rippled-apis/#api-methods).
|
||||
* Use `hasNextPage()` to determine whether a response has more pages. This is true when the response includes a [`marker` field](https://ripple.com/build/rippled-apis/#markers-and-pagination).
|
||||
* Use `requestNextPage()` to request the next page of data.
|
||||
|
||||
When using rippled APIs:
|
||||
|
||||
* [Specify XRP amounts in drops](https://developers.ripple.com/basic-data-types.html#specifying-currency-amounts).
|
||||
* [Specify timestamps as the number of seconds since the "Ripple Epoch"](https://developers.ripple.com/basic-data-types.html#specifying-time).
|
||||
* Instead of `counterparty`, use `issuer`.
|
||||
|
||||
@@ -15,6 +15,8 @@ This method can sign any of [the transaction types supported by ripple-binary-co
|
||||
|
||||
<%- renderSchema("input/sign.json") %>
|
||||
|
||||
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
@@ -31,3 +33,91 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||
```
|
||||
|
||||
<%- renderFixture("responses/sign.json") %>
|
||||
|
||||
### Example (multisigning)
|
||||
|
||||
```javascript
|
||||
const RippleAPI = require('ripple-lib').RippleAPI;
|
||||
|
||||
// jon's address will have a multi-signing setup with a quorum of 2
|
||||
const jon = {
|
||||
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
|
||||
};
|
||||
const aya = {
|
||||
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
|
||||
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
|
||||
};
|
||||
const bran = {
|
||||
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
|
||||
};
|
||||
|
||||
// Setup the signers list with a quorum of 2
|
||||
const multiSignSetupTransaction = {
|
||||
"Flags": 0,
|
||||
"TransactionType": "SignerListSet",
|
||||
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
|
||||
"Fee": "120",
|
||||
"SignerQuorum": 2,
|
||||
"SignerEntries": [
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
|
||||
"SignerWeight": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
|
||||
"SignerWeight": 1
|
||||
}
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
// a transaction which requires multi signing
|
||||
const multiSignPaymentTransaction = {
|
||||
TransactionType: 'Payment',
|
||||
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
|
||||
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
|
||||
Amount: '88000000'
|
||||
};
|
||||
|
||||
const api = new RippleAPI({
|
||||
server: 'wss://s.altnet.rippletest.net:51233'
|
||||
});
|
||||
|
||||
api.connect().then(() => {
|
||||
// adding the multi signing feature to jon's account
|
||||
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
|
||||
console.log(prepared);
|
||||
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
|
||||
api.submit(jonSign).then( response => {
|
||||
console.log(response.resultCode, response.resultMessage);
|
||||
|
||||
// multi sign a transaction
|
||||
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
|
||||
console.log(prepared);
|
||||
|
||||
// Aya and Bran sign it too but with 'signAs' set to their own account
|
||||
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
|
||||
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
|
||||
|
||||
// signatures are combined and submitted
|
||||
let combinedTx = api.combine([ayaSign, branSign]);
|
||||
api.submit(combinedTx.signedTransaction).then(response => {
|
||||
console.log(response.tx_json.hash);
|
||||
return api.disconnect();
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
}).catch(console.error)
|
||||
}).catch(console.error);
|
||||
}).catch(console.error);
|
||||
```
|
||||
|
||||
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
|
||||
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
|
||||
```
|
||||
[ValidationError(txJSON is not the same for all signedTransactions)]
|
||||
```
|
||||
|
||||
65
package.json
65
package.json
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.3.4",
|
||||
"version": "1.5.0",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||
"files": [
|
||||
"dist/npm/*",
|
||||
"build/*"
|
||||
"build/ripple-latest-min.js"
|
||||
],
|
||||
"main": "dist/npm/",
|
||||
"types": "dist/npm/index.d.ts",
|
||||
@@ -18,50 +18,52 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^3.2.0",
|
||||
"bignumber.js": "^4.1.0",
|
||||
"https-proxy-agent": "2.2.1",
|
||||
"@types/ws": "^6.0.3",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"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.8.0",
|
||||
"ws": "^3.3.1"
|
||||
"ripple-address-codec": "^4.0.0",
|
||||
"ripple-binary-codec": "^0.2.5",
|
||||
"ripple-keypairs": "^0.11.0",
|
||||
"ripple-lib-transactionparser": "0.8.1",
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "11.13.0",
|
||||
"assert-diff": "^1.0.1",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^12.12.5",
|
||||
"@typescript-eslint/eslint-plugin": "^2.3.3",
|
||||
"@typescript-eslint/parser": "^2.3.3",
|
||||
"assert-diff": "^2.0.3",
|
||||
"doctoc": "^0.15.0",
|
||||
"ejs": "^2.3.4",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"gulp": "^4.0.2",
|
||||
"json-loader": "^0.5.2",
|
||||
"eslint": "^6.5.1",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"json-schema-to-markdown-table": "^0.4.0",
|
||||
"mocha": "6.2.0",
|
||||
"mocha-junit-reporter": "^1.9.1",
|
||||
"null-loader": "^0.1.1",
|
||||
"nyc": "^14.1.1",
|
||||
"source-map-support": "0.5.12",
|
||||
"ts-loader": "^3.2.0",
|
||||
"ts-node": "8.0.3",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-eslint-rules": "^4.1.1",
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "^3.6.4",
|
||||
"uglifyjs-webpack-plugin": "^1.1.4",
|
||||
"webpack": "3.12.0"
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp",
|
||||
"build:schemas": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/",
|
||||
"build:lib": "tsc --build",
|
||||
"build:web": "webpack",
|
||||
"build": "yarn build:schemas && yarn build:lib && yarn build:web",
|
||||
"analyze": "yarn build:web --analyze",
|
||||
"watch": "yarn build:lib --watch",
|
||||
"clean": "rm -rf dist/npm",
|
||||
"doctoc": "doctoc docs/index.md --title '# RippleAPI Reference' --github --maxlevel 2",
|
||||
"docgen": "node --harmony scripts/build_docs.js",
|
||||
"clean": "rm -rf dist/npm",
|
||||
"compile": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc --build",
|
||||
"watch": "tsc -w",
|
||||
"prepublish": "npm run clean && npm run compile && npm run build",
|
||||
"prepublish": "yarn clean && yarn build",
|
||||
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
|
||||
"lint": "tslint -p ./",
|
||||
"test:watch": "TS_NODE_PROJECT=src/tsconfig.json mocha --watch --reporter dot",
|
||||
"lint": "eslint src/**/*.ts 'test/*-test.{ts,js}'",
|
||||
"perf": "./scripts/perf_test.sh",
|
||||
"start": "node scripts/http.js"
|
||||
},
|
||||
@@ -71,6 +73,7 @@
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=6.12.3"
|
||||
"node": ">=8",
|
||||
"yarn": "^1.15.2"
|
||||
}
|
||||
}
|
||||
|
||||
45
src/api.ts
45
src/api.ts
@@ -7,7 +7,8 @@ import {
|
||||
dropsToXrp,
|
||||
rippleTimeToISO8601,
|
||||
iso8601ToRippleTime,
|
||||
txFlags
|
||||
txFlags,
|
||||
ensureClassicAddress
|
||||
} from './common'
|
||||
import {
|
||||
connect,
|
||||
@@ -46,8 +47,8 @@ import prepareSettings from './transaction/settings'
|
||||
import sign from './transaction/sign'
|
||||
import combine from './transaction/combine'
|
||||
import submit from './transaction/submit'
|
||||
import {generateAddressAPI} from './offline/generate-address'
|
||||
import {deriveKeypair, deriveAddress} from './offline/derive'
|
||||
import {generateAddressAPI, GenerateAddressOptions, GeneratedAddress} from './offline/generate-address'
|
||||
import {deriveKeypair, deriveAddress, deriveXAddress} from './offline/derive'
|
||||
import computeLedgerHash from './offline/ledgerhash'
|
||||
import signPaymentChannelClaim from './offline/sign-payment-channel-claim'
|
||||
import verifyPaymentChannelClaim from './offline/verify-payment-channel-claim'
|
||||
@@ -61,8 +62,9 @@ import {
|
||||
BookOffersRequest, BookOffersResponse,
|
||||
GatewayBalancesRequest, GatewayBalancesResponse,
|
||||
LedgerRequest, LedgerResponse,
|
||||
LedgerDataRequest, LedgerDataResponse,
|
||||
LedgerEntryRequest, LedgerEntryResponse,
|
||||
ServerInfoRequest, ServerInfoResponse
|
||||
ServerInfoRequest, ServerInfoResponse,
|
||||
} from './common/types/commands'
|
||||
|
||||
|
||||
@@ -74,6 +76,7 @@ import {getServerInfo, getFee} from './common/serverinfo'
|
||||
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
import {ConnectionOptions} from './common/connection'
|
||||
import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec'
|
||||
|
||||
export interface APIOptions extends ConnectionOptions {
|
||||
server?: string,
|
||||
@@ -138,7 +141,14 @@ class RippleAPI extends EventEmitter {
|
||||
this.emit('connected')
|
||||
})
|
||||
this.connection.on('disconnected', code => {
|
||||
this.emit('disconnected', code)
|
||||
let finalCode = code;
|
||||
// This is a backwards-compatible fix for this change in the ws library:
|
||||
// https://github.com/websockets/ws/issues/1257
|
||||
// TODO: Remove in next major, breaking version
|
||||
if (finalCode === 1005) {
|
||||
finalCode = 1000;
|
||||
}
|
||||
this.emit('disconnected', finalCode)
|
||||
})
|
||||
} else {
|
||||
// use null object pattern to provide better error message if user
|
||||
@@ -166,6 +176,8 @@ class RippleAPI extends EventEmitter {
|
||||
Promise<GatewayBalancesResponse>
|
||||
async request(command: 'ledger', params: LedgerRequest):
|
||||
Promise<LedgerResponse>
|
||||
async request(command: 'ledger_data', params?: LedgerDataRequest):
|
||||
Promise<LedgerDataResponse>
|
||||
async request(command: 'ledger_entry', params: LedgerEntryRequest):
|
||||
Promise<LedgerEntryResponse>
|
||||
async request(command: 'server_info', params?: ServerInfoRequest):
|
||||
@@ -175,7 +187,8 @@ class RippleAPI extends EventEmitter {
|
||||
async request(command: string, params: any = {}): Promise<any> {
|
||||
return this.connection.request({
|
||||
...params,
|
||||
command
|
||||
command,
|
||||
account: params.account ? ensureClassicAddress(params.account) : undefined
|
||||
})
|
||||
}
|
||||
|
||||
@@ -195,7 +208,7 @@ class RippleAPI extends EventEmitter {
|
||||
command: string,
|
||||
params: object = {},
|
||||
currentResponse: T
|
||||
): Promise<object> {
|
||||
): Promise<T> {
|
||||
if (!currentResponse.marker) {
|
||||
return Promise.reject(
|
||||
new errors.NotFoundError('response does not have a next page')
|
||||
@@ -288,6 +301,15 @@ class RippleAPI extends EventEmitter {
|
||||
return results
|
||||
}
|
||||
|
||||
// @deprecated Use X-addresses instead
|
||||
generateAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
|
||||
return generateAddressAPI({...options, includeClassicAddress: true})
|
||||
}
|
||||
|
||||
generateXAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
|
||||
return generateAddressAPI(options)
|
||||
}
|
||||
|
||||
connect = connect
|
||||
disconnect = disconnect
|
||||
isConnected = isConnected
|
||||
@@ -328,7 +350,6 @@ class RippleAPI extends EventEmitter {
|
||||
combine = combine
|
||||
submit = submit
|
||||
|
||||
generateAddress = generateAddressAPI
|
||||
deriveKeypair = deriveKeypair
|
||||
deriveAddress = deriveAddress
|
||||
computeLedgerHash = computeLedgerHash
|
||||
@@ -336,6 +357,14 @@ class RippleAPI extends EventEmitter {
|
||||
verifyPaymentChannelClaim = verifyPaymentChannelClaim
|
||||
errors = errors
|
||||
|
||||
static deriveXAddress = deriveXAddress
|
||||
|
||||
// RippleAPI.deriveClassicAddress (static) is a new name for api.deriveAddress
|
||||
static deriveClassicAddress = deriveAddress
|
||||
|
||||
static isValidXAddress = isValidXAddress
|
||||
static isValidClassicAddress = isValidClassicAddress
|
||||
|
||||
xrpToDrops = xrpToDrops
|
||||
dropsToXrp = dropsToXrp
|
||||
rippleTimeToISO8601 = rippleTimeToISO8601
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
|
||||
import * as _ from 'lodash'
|
||||
import {RippleAPI} from './api'
|
||||
import {RippleAPI, APIOptions} from './api'
|
||||
|
||||
class RippleAPIBroadcast extends RippleAPI {
|
||||
|
||||
ledgerVersion: number | undefined = undefined
|
||||
private _apis: RippleAPI[]
|
||||
|
||||
constructor(servers, options) {
|
||||
constructor(servers, options: APIOptions = {}) {
|
||||
super(options)
|
||||
|
||||
const apis: RippleAPI[] = servers.map(server => new RippleAPI(
|
||||
|
||||
@@ -7,11 +7,14 @@ function setPrototypeOf(object, prototype) {
|
||||
}
|
||||
|
||||
function getConstructorName(object: object): string {
|
||||
// hack for internet explorer
|
||||
if (!object.constructor.name) {
|
||||
return object.constructor.toString().match(/^function\s+([^(]*)/)![1]
|
||||
if (object.constructor.name) {
|
||||
return object.constructor.name
|
||||
}
|
||||
return object.constructor.name
|
||||
// try to guess it on legacy browsers (ie)
|
||||
const constructorString = object.constructor.toString()
|
||||
const functionConstructor = constructorString.match(/^function\s+([^(]*)/)
|
||||
const classConstructor = constructorString.match(/^class\s([^\s]*)/)
|
||||
return functionConstructor ? functionConstructor[1] : classConstructor[1]
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {RippledError, DisconnectedError, NotConnectedError,
|
||||
RippledNotInitializedError} from './errors'
|
||||
|
||||
export interface ConnectionOptions {
|
||||
trace?: boolean,
|
||||
trace?: boolean
|
||||
proxy?: string
|
||||
proxyAuthorization?: string
|
||||
authorization?: string
|
||||
@@ -16,7 +16,8 @@ export interface ConnectionOptions {
|
||||
key?: string
|
||||
passphrase?: string
|
||||
certificate?: string
|
||||
timeout?: number
|
||||
timeout?: number,
|
||||
connectionTimeout?: number
|
||||
}
|
||||
|
||||
class Connection extends EventEmitter {
|
||||
@@ -38,11 +39,14 @@ class Connection extends EventEmitter {
|
||||
private _availableLedgerVersions = new RangeSet()
|
||||
private _nextRequestID: number = 1
|
||||
private _retry: number = 0
|
||||
private _retryTimer: null|NodeJS.Timer = null
|
||||
private _connectTimer: null|NodeJS.Timeout = null
|
||||
private _retryTimer: null|NodeJS.Timeout = null
|
||||
private _heartbeatInterval: null|NodeJS.Timeout = null;
|
||||
private _onOpenErrorBound: null| null|((...args: any[]) => void) = null
|
||||
private _onUnexpectedCloseBound: null|((...args: any[]) => void) = null
|
||||
private _fee_base: null|number = null
|
||||
private _fee_ref: null|number = null
|
||||
private _connectionTimeout: number
|
||||
|
||||
constructor(url, options: ConnectionOptions = {}) {
|
||||
super()
|
||||
@@ -61,6 +65,7 @@ class Connection extends EventEmitter {
|
||||
this._passphrase = options.passphrase
|
||||
this._certificate = options.certificate
|
||||
this._timeout = options.timeout || (20 * 1000)
|
||||
this._connectionTimeout = options.connectionTimeout || 2000
|
||||
}
|
||||
|
||||
_updateLedgerVersions(data) {
|
||||
@@ -179,6 +184,13 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
_clearConnectTimer() {
|
||||
if (this._connectTimer !== null) {
|
||||
clearTimeout(this._connectTimer)
|
||||
this._connectTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
_onOpen() {
|
||||
if (!this._ws) {
|
||||
return Promise.reject(new DisconnectedError())
|
||||
@@ -282,16 +294,26 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
|
||||
connect(): Promise<void> {
|
||||
this._clearConnectTimer()
|
||||
this._clearReconnectTimer()
|
||||
return new Promise((resolve, reject) => {
|
||||
this._clearHeartbeatInterval()
|
||||
return new Promise<void>((_resolve, reject) => {
|
||||
this._connectTimer = setTimeout(() => {
|
||||
reject(new ConnectionError(`Error: connect() timed out after ${this._connectionTimeout} ms. ` +
|
||||
`If your internet connection is working, the rippled server may be blocked or inaccessible.`))
|
||||
}, this._connectionTimeout)
|
||||
if (!this._url) {
|
||||
reject(new ConnectionError(
|
||||
'Cannot connect because no server was specified'))
|
||||
}
|
||||
const resolve = () => {
|
||||
this._startHeartbeatInterval();
|
||||
_resolve();
|
||||
}
|
||||
if (this._state === WebSocket.OPEN) {
|
||||
resolve()
|
||||
} else if (this._state === WebSocket.CONNECTING) {
|
||||
this._ws.once('open', resolve)
|
||||
this._ws.once('open', () => resolve)
|
||||
} else {
|
||||
this._ws = this._createWebSocket()
|
||||
// when an error causes the connection to close, the close event
|
||||
@@ -311,9 +333,20 @@ class Connection extends EventEmitter {
|
||||
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
|
||||
resolve, reject)
|
||||
this._ws.once('close', this._onUnexpectedCloseBound)
|
||||
this._ws.once('open', () => this._onOpen().then(resolve, reject))
|
||||
this._ws.once('open', () => {
|
||||
return this._onOpen().then(resolve, reject)
|
||||
})
|
||||
}
|
||||
})
|
||||
// Once we have a resolution or rejection, clear the timeout timer as no
|
||||
// longer needed.
|
||||
.then(() => {
|
||||
this._clearConnectTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
this._clearConnectTimer()
|
||||
throw err;
|
||||
})
|
||||
}
|
||||
|
||||
disconnect(): Promise<void> {
|
||||
@@ -321,7 +354,9 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
|
||||
_disconnect(calledByUser): Promise<void> {
|
||||
this._clearHeartbeatInterval()
|
||||
if (calledByUser) {
|
||||
this._clearConnectTimer()
|
||||
this._clearReconnectTimer()
|
||||
this._retry = 0
|
||||
}
|
||||
@@ -349,11 +384,34 @@ class Connection extends EventEmitter {
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
// NOTE: We currently have a "reconnecting" event, but that only triggers through
|
||||
// _retryConnect, which was written in a way that is required to run as an internal
|
||||
// part of the post-disconnect connect() flow.
|
||||
// See: https://github.com/ripple/ripple-lib/pull/1101#issuecomment-565360423
|
||||
this.emit('reconnect');
|
||||
return this.disconnect().then(() => this.connect())
|
||||
}
|
||||
|
||||
private _clearHeartbeatInterval = () => {
|
||||
clearInterval(this._heartbeatInterval);
|
||||
}
|
||||
|
||||
private _startHeartbeatInterval = () => {
|
||||
this._clearHeartbeatInterval()
|
||||
this._heartbeatInterval = setInterval(() => this._heartbeat(), 1000 * 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* A heartbeat is just a "ping" command, sent on an interval.
|
||||
* If this succeeds, we're good. If it fails, disconnect so that the consumer can reconnect, if desired.
|
||||
*/
|
||||
private _heartbeat = () => {
|
||||
return this.request({command: "ping"}).catch(() => this.reconnect());
|
||||
}
|
||||
|
||||
_whenReady<T>(promise: Promise<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
promise.catch(reject);
|
||||
if (!this._shouldBeConnected) {
|
||||
reject(new NotConnectedError())
|
||||
} else if (this._state === WebSocket.OPEN && this._isReady) {
|
||||
@@ -456,6 +514,10 @@ class Connection extends EventEmitter {
|
||||
this._whenReady(this._send(message)).then(() => {
|
||||
const delay = timeout || this._timeout
|
||||
timer = setTimeout(() => _reject(new TimeoutError()), delay)
|
||||
// Node.js won't exit if a timer is still running, so we tell Node to ignore (Node will still wait for the request to complete)
|
||||
if (timer.unref) {
|
||||
timer.unref()
|
||||
}
|
||||
}).catch(_reject)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,10 +74,12 @@ const AccountFlagIndices = {
|
||||
|
||||
const AccountFields = {
|
||||
EmailHash: {name: 'emailHash', encoding: 'hex',
|
||||
length: 32, defaults: '0'},
|
||||
length: 32, defaults: '00000000000000000000000000000000'},
|
||||
WalletLocator: {name: 'walletLocator'},
|
||||
MessageKey: {name: 'messageKey'},
|
||||
Domain: {name: 'domain', encoding: 'hex'},
|
||||
TransferRate: {name: 'transferRate', defaults: 0, shift: 9}
|
||||
TransferRate: {name: 'transferRate', defaults: 0, shift: 9},
|
||||
TickSize: {name: 'tickSize', defaults: 0}
|
||||
}
|
||||
|
||||
export {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import {decodeAccountID} from 'ripple-address-codec'
|
||||
import sha512Half from './sha512Half'
|
||||
import HashPrefix from './hash-prefix'
|
||||
import {SHAMap, NodeType} from './shamap'
|
||||
@@ -28,12 +28,12 @@ const ledgerSpaceHex = (name: string): string => {
|
||||
}
|
||||
|
||||
const addressToHex = (address: string): string => {
|
||||
return (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
return (Buffer.from(decodeAccountID(address))).toString('hex')
|
||||
}
|
||||
|
||||
const currencyToHex = (currency: string): string => {
|
||||
if (currency.length === 3) {
|
||||
let bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
||||
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
||||
bytes[12] = currency.charCodeAt(0) & 0xff
|
||||
bytes[13] = currency.charCodeAt(1) & 0xff
|
||||
bytes[14] = currency.charCodeAt(2) & 0xff
|
||||
@@ -81,7 +81,7 @@ export const computeAccountHash = (address: string): string => {
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('signerList') +
|
||||
addressToHex(address) +
|
||||
'00000000' /* uint32(0) signer list index */)
|
||||
'00000000') // uint32(0) signer list index
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
@@ -93,7 +93,7 @@ export const computeTrustlineHash = (address1: string, address2: string, currenc
|
||||
const address1Hex = addressToHex(address1)
|
||||
const address2Hex = addressToHex(address2)
|
||||
|
||||
const swap = (new BigNumber(address1Hex, 16)).greaterThan(
|
||||
const swap = (new BigNumber(address1Hex, 16)).isGreaterThan(
|
||||
new BigNumber(address2Hex, 16))
|
||||
const lowAddressHex = swap ? address2Hex : address1Hex
|
||||
const highAddressHex = swap ? address1Hex : address2Hex
|
||||
@@ -106,7 +106,7 @@ export const computeTrustlineHash = (address1: string, address2: string, currenc
|
||||
export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
transactions.forEach((txJSON) => {
|
||||
transactions.forEach(txJSON => {
|
||||
const txBlobHex = encode(txJSON)
|
||||
const metaHex = encode(txJSON.metaData)
|
||||
const txHash = computeBinaryTransactionHash(txBlobHex)
|
||||
@@ -120,7 +120,7 @@ export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
export const computeStateTreeHash = (entries: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
entries.forEach((ledgerEntry) => {
|
||||
entries.forEach(ledgerEntry => {
|
||||
const data = encode(ledgerEntry)
|
||||
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
|
||||
})
|
||||
|
||||
@@ -2,13 +2,35 @@ import * as constants from './constants'
|
||||
import * as errors from './errors'
|
||||
import * as validate from './validate'
|
||||
import * as serverInfo from './serverinfo'
|
||||
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
|
||||
export function ensureClassicAddress(account: string): string {
|
||||
if (isValidXAddress(account)) {
|
||||
const {
|
||||
classicAddress,
|
||||
tag
|
||||
} = xAddressToClassicAddress(account)
|
||||
|
||||
// Except for special cases, X-addresses used for requests
|
||||
// must not have an embedded tag. In other words,
|
||||
// `tag` should be `false`.
|
||||
if (tag !== false) {
|
||||
throw new Error('This command does not support the use of a tag. Use an address without a tag.')
|
||||
}
|
||||
|
||||
// For rippled requests that use an account, always use a classic address.
|
||||
return classicAddress
|
||||
} else {
|
||||
return account
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
constants,
|
||||
errors,
|
||||
validate,
|
||||
serverInfo
|
||||
}
|
||||
|
||||
export {
|
||||
dropsToXrp,
|
||||
xrpToDrops,
|
||||
@@ -20,4 +42,3 @@ export {
|
||||
} from './utils'
|
||||
export {default as Connection} from './connection'
|
||||
export {txFlags} from './txflags'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as _ from 'lodash'
|
||||
import * as assert from 'assert'
|
||||
const {Validator} = require('jsonschema')
|
||||
import {ValidationError} from './errors'
|
||||
import {isValidAddress} from 'ripple-address-codec'
|
||||
import {isValidClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
import {isValidSecret} from './utils'
|
||||
|
||||
function loadSchemas() {
|
||||
@@ -34,6 +34,8 @@ function loadSchemas() {
|
||||
require('./schemas/objects/destination-address-tag.json'),
|
||||
require('./schemas/objects/transaction-hash.json'),
|
||||
require('./schemas/objects/address.json'),
|
||||
require('./schemas/objects/x-address.json'),
|
||||
require('./schemas/objects/classic-address.json'),
|
||||
require('./schemas/objects/adjustment.json'),
|
||||
require('./schemas/objects/quality.json'),
|
||||
require('./schemas/objects/amount.json'),
|
||||
@@ -126,12 +128,23 @@ function loadSchemas() {
|
||||
// Register custom format validators that ignore undefined instances
|
||||
// since jsonschema will still call the format validator on a missing
|
||||
// (optional) property
|
||||
validator.customFormats.address = function(instance) {
|
||||
|
||||
// This relies on "format": "xAddress" in `x-address.json`!
|
||||
validator.customFormats.xAddress = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
}
|
||||
return isValidXAddress(instance)
|
||||
}
|
||||
|
||||
// This relies on "format": "classicAddress" in `classic-address.json`!
|
||||
validator.customFormats.classicAddress = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
}
|
||||
return isValidAddress(instance)
|
||||
}
|
||||
|
||||
validator.customFormats.secret = function(instance) {
|
||||
if (instance === undefined) {
|
||||
return true
|
||||
@@ -158,6 +171,10 @@ function schemaValidate(schemaName: string, object: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
function isValidAddress(address: string): boolean {
|
||||
return isValidXAddress(address) || isValidClassicAddress(address)
|
||||
}
|
||||
|
||||
export {
|
||||
schemaValidate,
|
||||
isValidSecret,
|
||||
|
||||
@@ -20,6 +20,14 @@
|
||||
"type": "string",
|
||||
"enum": ["ecdsa-secp256k1", "ed25519"],
|
||||
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
|
||||
},
|
||||
"test": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
|
||||
},
|
||||
"includeClassicAddress": {
|
||||
"type": "boolean",
|
||||
"description": "If `true`, return the classic address, in addition to the X-address."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
33
src/common/schemas/input/generate-x-address.json
Normal file
33
src/common/schemas/input/generate-x-address.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "generateXAddressParameters",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"options": {
|
||||
"type": "object",
|
||||
"description": "Options to control how the address and secret are generated.",
|
||||
"properties": {
|
||||
"entropy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"description": "The entropy to use to generate the seed."
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
"enum": ["ecdsa-secp256k1", "ed25519"],
|
||||
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
|
||||
},
|
||||
"test": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "address",
|
||||
"description": "A Ripple account address",
|
||||
"description": "An account address on the XRP Ledger",
|
||||
"type": "string",
|
||||
"format": "address",
|
||||
"link": "address",
|
||||
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{25,34}$"
|
||||
"oneOf": [
|
||||
{"$ref": "xAddress"},
|
||||
{"$ref": "classicAddress"}
|
||||
]
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/classic-address.json
Normal file
9
src/common/schemas/objects/classic-address.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "classicAddress",
|
||||
"description": "A classic address (Account ID) for the XRP Ledger",
|
||||
"type": "string",
|
||||
"format": "classicAddress",
|
||||
"link": "classic-address",
|
||||
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{24,34}$"
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
},
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": " The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase."
|
||||
"description": "The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase."
|
||||
},
|
||||
"emailHash": {
|
||||
"description": "Hash of an email address to be used for generating an avatar image. Conventionally, clients use Gravatar to display this image. Use `null` to clear.",
|
||||
@@ -30,6 +30,13 @@
|
||||
{"$ref": "hash128"}
|
||||
]
|
||||
},
|
||||
"walletLocator": {
|
||||
"description": "Transaction hash or any other 64 character hexadecimal string, that may or may not represent the result of a hash operation. Use `null` to clear.",
|
||||
"oneOf": [
|
||||
{"type": "null"},
|
||||
{"$ref": "hash256"}
|
||||
]
|
||||
},
|
||||
"enableTransactionIDTracking": {
|
||||
"type": "boolean",
|
||||
"description": "Track the ID of this account’s most recent transaction."
|
||||
@@ -98,11 +105,15 @@
|
||||
"additionalProperties": false
|
||||
},
|
||||
"transferRate": {
|
||||
"description": " The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.",
|
||||
"description": "The fee to charge when users transfer this account’s issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.",
|
||||
"oneOf": [
|
||||
{"type": "null"},
|
||||
{"type": "number", "minimum": 1, "maximum": 4.294967295}
|
||||
]
|
||||
},
|
||||
"tickSize": {
|
||||
"description": "Tick size to use for offers involving a currency issued by this address. The exchange rates of those offers is rounded to this many significant digits. Valid values are 3 to 15 inclusive, or 0 to disable.",
|
||||
"enum": [0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "tag",
|
||||
"description": "An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.",
|
||||
"description": "An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.",
|
||||
"type": "integer",
|
||||
"$ref": "uint32"
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/x-address.json
Normal file
9
src/common/schemas/objects/x-address.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "xAddress",
|
||||
"description": "An XRP Ledger address in X-address format",
|
||||
"type": "string",
|
||||
"format": "xAddress",
|
||||
"link": "x-address",
|
||||
"pattern": "^[XT][1-9A-HJ-NP-Za-km-z]{46}$"
|
||||
}
|
||||
@@ -3,16 +3,24 @@
|
||||
"title": "generateAddress",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xAddress": {
|
||||
"$ref": "xAddress",
|
||||
"description": "A randomly generated XRP Ledger address in X-address format."
|
||||
},
|
||||
"classicAddress": {
|
||||
"$ref": "classicAddress",
|
||||
"description": "A randomly generated XRP Ledger Account ID (classic address)."
|
||||
},
|
||||
"address": {
|
||||
"$ref": "address",
|
||||
"description": "A randomly generated Ripple account address."
|
||||
"$ref": "classicAddress",
|
||||
"description": "Deprecated: Use `classicAddress` instead."
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret corresponding to the `address`."
|
||||
"description": "The secret corresponding to the address."
|
||||
}
|
||||
},
|
||||
"required": ["address", "secret"],
|
||||
"required": ["xAddress", "classicAddress", "address", "secret"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
18
src/common/schemas/output/generate-x-address.json
Normal file
18
src/common/schemas/output/generate-x-address.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "generateXAddress",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xAddress": {
|
||||
"$ref": "xAddress",
|
||||
"description": "A randomly generated XRP Ledger address in X-address format."
|
||||
},
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"format": "secret",
|
||||
"description": "The secret corresponding to the address."
|
||||
}
|
||||
},
|
||||
"required": ["xAddress", "secret"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
@@ -29,7 +29,8 @@ export type GetServerInfoResponse = {
|
||||
reserveIncrementXRP: string,
|
||||
ledgerVersion: number
|
||||
},
|
||||
validationQuorum: number
|
||||
validationQuorum: number,
|
||||
networkLedger?: string
|
||||
}
|
||||
|
||||
function renameKeys(object, mapping) {
|
||||
|
||||
@@ -5,5 +5,6 @@ export * from './account_offers'
|
||||
export * from './book_offers'
|
||||
export * from './gateway_balances'
|
||||
export * from './ledger'
|
||||
export * from './ledger_data'
|
||||
export * from './ledger_entry'
|
||||
export * from './server_info'
|
||||
|
||||
12
src/common/types/commands/ledger_data.ts
Normal file
12
src/common/types/commands/ledger_data.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LedgerData } from '../objects'
|
||||
|
||||
export interface LedgerDataRequest {
|
||||
id?: any
|
||||
ledger_hash?: string
|
||||
ledger_index?: string
|
||||
binary?: boolean
|
||||
limit?: number
|
||||
marker?: string
|
||||
}
|
||||
|
||||
export type LedgerDataResponse = LedgerData;
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './adjustments'
|
||||
export * from './amounts'
|
||||
export * from './ledger'
|
||||
export * from './ledger_data'
|
||||
export * from './ledger_entries'
|
||||
export * from './memos'
|
||||
export * from './orders'
|
||||
|
||||
6
src/common/types/objects/ledger_data.ts
Normal file
6
src/common/types/objects/ledger_data.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface LedgerData {
|
||||
ledger_index: string
|
||||
ledger_hash: string
|
||||
marker: string
|
||||
state: ({ data?: string; LedgerEntryType?: string; index: string } & any)[]
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export interface AccountRootLedgerEntry {
|
||||
RegularKey?: string,
|
||||
TickSize?: number,
|
||||
TransferRate?: number,
|
||||
WalletLocator?: string, // DEPRECATED
|
||||
WalletLocator?: string,
|
||||
WalletSize?: number // DEPRECATED
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export type FormattedSettings = {
|
||||
disallowIncomingXRP?: boolean,
|
||||
domain?: string,
|
||||
emailHash?: string|null,
|
||||
walletLocator?: string|null,
|
||||
enableTransactionIDTracking?: boolean,
|
||||
globalFreeze?: boolean,
|
||||
memos?: Memo[],
|
||||
@@ -27,5 +28,6 @@ export type FormattedSettings = {
|
||||
requireAuthorization?: boolean,
|
||||
requireDestinationTag?: boolean,
|
||||
signers?: Signers,
|
||||
transferRate?: number|null
|
||||
transferRate?: number|null,
|
||||
tickSize?: number
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ function isValidSecret(secret: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function dropsToXrp(drops: string | BigNumber): string {
|
||||
function dropsToXrp(drops: BigNumber.Value): string {
|
||||
if (typeof drops === 'string') {
|
||||
if (!drops.match(/^-?[0-9]*\.?[0-9]*$/)) {
|
||||
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
|
||||
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
|
||||
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
|
||||
} else if (drops === '.') {
|
||||
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
|
||||
` should be a BigNumber or string-encoded number.`)
|
||||
@@ -47,11 +47,11 @@ function dropsToXrp(drops: string | BigNumber): string {
|
||||
return (new BigNumber(drops)).dividedBy(1000000.0).toString(10)
|
||||
}
|
||||
|
||||
function xrpToDrops(xrp: string | BigNumber): string {
|
||||
function xrpToDrops(xrp: BigNumber.Value): string {
|
||||
if (typeof xrp === 'string') {
|
||||
if (!xrp.match(/^-?[0-9]*\.?[0-9]*$/)) {
|
||||
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
|
||||
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
|
||||
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
|
||||
} else if (xrp === '.') {
|
||||
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
|
||||
` should be a BigNumber or string-encoded number.`)
|
||||
@@ -83,7 +83,7 @@ function xrpToDrops(xrp: string | BigNumber): string {
|
||||
` too many decimal places.`)
|
||||
}
|
||||
|
||||
return (new BigNumber(xrp)).times(1000000.0).floor().toString(10)
|
||||
return (new BigNumber(xrp)).times(1000000.0).integerValue(BigNumber.ROUND_FLOOR).toString(10)
|
||||
}
|
||||
|
||||
function toRippledAmount(amount: Amount): RippledAmount {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {validate, removeUndefined, dropsToXrp} from '../common'
|
||||
import {validate, removeUndefined, dropsToXrp, ensureClassicAddress} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
import {AccountInfoResponse} from '../common/types/commands/account_info'
|
||||
|
||||
@@ -34,6 +34,11 @@ export default async function getAccountInfo(
|
||||
): Promise<FormattedGetAccountInfoResponse> {
|
||||
// 1. Validate
|
||||
validate.getAccountInfo({address, options})
|
||||
|
||||
// Only support retrieving account info without a tag,
|
||||
// since account info is not distinguished by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import * as utils from './utils'
|
||||
import {validate} from '../common'
|
||||
import {validate, ensureClassicAddress} from '../common'
|
||||
import {Connection} from '../common'
|
||||
import {GetTrustlinesOptions} from './trustlines'
|
||||
import {FormattedTrustline} from '../common/types/objects/trustlines'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
|
||||
export type Balance = {
|
||||
value: string,
|
||||
currency: string,
|
||||
@@ -52,6 +51,13 @@ function getBalances(this: RippleAPI, address: string, options: GetTrustlinesOpt
|
||||
): Promise<GetBalances> {
|
||||
validate.getTrustlines({address, options})
|
||||
|
||||
// Only support retrieving balances without a tag,
|
||||
// since we currently do not calculate balances
|
||||
// on a per-tag basis. Apps must interpret and
|
||||
// use tags independent of the XRP Ledger, comparing
|
||||
// with the XRP Ledger's balance as an accounting check.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
return Promise.all([
|
||||
getLedgerVersionHelper(this.connection, options.ledgerVersion).then(
|
||||
ledgerVersion =>
|
||||
|
||||
@@ -33,6 +33,6 @@ export default async function getOrders(
|
||||
ledger_index: options.ledgerVersion || await this.getLedgerVersion(),
|
||||
limit: options.limit
|
||||
})
|
||||
// 3. Return Formatted Response
|
||||
// 3. Return Formatted Response, from the perspective of `address`
|
||||
return formatResponse(address, responses)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export type FormattedAccountOrder = {
|
||||
// TODO: remove this function once rippled provides quality directly
|
||||
function computeQuality(takerGets, takerPays) {
|
||||
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value)
|
||||
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString()
|
||||
return quotient.precision(16, BigNumber.ROUND_HALF_UP).toString()
|
||||
}
|
||||
|
||||
// rippled 'account_offers' returns a different format for orders than 'tx'
|
||||
|
||||
@@ -8,7 +8,7 @@ function parseField(info, value) {
|
||||
return Buffer.from(value, 'hex').toString('ascii')
|
||||
}
|
||||
if (info.shift) {
|
||||
return (new BigNumber(value)).shift(-info.shift).toNumber()
|
||||
return (new BigNumber(value)).shiftedBy(-info.shift).toNumber()
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@ function adjustQualityForXRP(
|
||||
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0)
|
||||
const shift = numeratorShift - denominatorShift
|
||||
return shift === 0 ? quality :
|
||||
(new BigNumber(quality)).shift(shift).toString()
|
||||
(new BigNumber(quality)).shiftedBy(shift).toString()
|
||||
}
|
||||
|
||||
function parseQuality(quality?: number|null): number|undefined {
|
||||
if (typeof quality !== 'number') {
|
||||
return undefined
|
||||
}
|
||||
return (new BigNumber(quality)).shift(-9).toNumber()
|
||||
return (new BigNumber(quality)).shiftedBy(-9).toNumber()
|
||||
}
|
||||
|
||||
function parseTimestamp(rippleTime?: number|null): string|undefined {
|
||||
|
||||
@@ -73,7 +73,7 @@ function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string
|
||||
// Add XRP "path" only if the source acct has enough XRP to make the payment
|
||||
const destinationAmount = paths.destination_amount
|
||||
// @ts-ignore: destinationAmount can be a currency amount object! Fix!
|
||||
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
|
||||
if ((new BigNumber(xrpBalance)).isGreaterThanOrEqualTo(destinationAmount)) {
|
||||
paths.alternatives.unshift({
|
||||
paths_computed: [],
|
||||
source_amount: paths.destination_amount
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import * as _ from 'lodash'
|
||||
import parseFields from './parse/fields'
|
||||
import {validate, constants} from '../common'
|
||||
import {validate, constants, ensureClassicAddress} from '../common'
|
||||
import {FormattedSettings} from '../common/types/objects'
|
||||
import {AccountInfoResponse} from '../common/types/commands'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
const AccountFlags = constants.AccountFlags
|
||||
|
||||
export type SettingsOptions = {
|
||||
@@ -38,6 +39,11 @@ export async function getSettings(
|
||||
): Promise<FormattedSettings> {
|
||||
// 1. Validate
|
||||
validate.getSettings({address, options})
|
||||
|
||||
// Only support retrieving settings without a tag,
|
||||
// since settings do not distinguish by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const response = await this.request('account_info', {
|
||||
account: address,
|
||||
|
||||
@@ -30,8 +30,12 @@ function attachTransactionDate(connection: Connection, tx: any
|
||||
|
||||
if (!ledgerVersion) {
|
||||
return new Promise(() => {
|
||||
throw new errors.NotFoundError(
|
||||
'ledger_index and LedgerSequence not found in tx')
|
||||
const error = new errors.NotFoundError(
|
||||
'Transaction has not been validated yet; try again later')
|
||||
error.data = {
|
||||
details: '(ledger_index and LedgerSequence not found in tx)'
|
||||
}
|
||||
throw error
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,10 @@ import {computeTransactionHash} from '../common/hashes'
|
||||
import * as utils from './utils'
|
||||
import parseTransaction from './parse/transaction'
|
||||
import getTransaction from './transaction'
|
||||
import {validate, errors, Connection} from '../common'
|
||||
import {validate, errors, Connection, ensureClassicAddress} from '../common'
|
||||
import {FormattedTransactionType} from '../transaction/types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
|
||||
export type TransactionsOptions = {
|
||||
start?: string,
|
||||
limit?: number,
|
||||
@@ -167,6 +166,12 @@ function getTransactions(this: RippleAPI, address: string, options: Transactions
|
||||
): Promise<GetTransactionsResponse> {
|
||||
validate.getTransactions({address, options})
|
||||
|
||||
// Only support retrieving transactions without a tag,
|
||||
// since we currently do not filter transactions based
|
||||
// on tag. Apps must interpret and use tags
|
||||
// independently, filtering transactions if needed.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
const defaults = {maxLedgerVersion: -1}
|
||||
if (options.start) {
|
||||
return getTransaction.call(this, options.start).then(tx => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash'
|
||||
import {validate} from '../common'
|
||||
import {validate, ensureClassicAddress} from '../common'
|
||||
import parseAccountTrustline from './parse/account-trustline'
|
||||
import {RippleAPI} from '..'
|
||||
import {FormattedTrustline} from '../common/types/objects/trustlines'
|
||||
@@ -20,11 +20,16 @@ async function getTrustlines(
|
||||
): Promise<FormattedTrustline[]> {
|
||||
// 1. Validate
|
||||
validate.getTrustlines({address, options})
|
||||
const ledgerVersion = await this.getLedgerVersion()
|
||||
|
||||
// Only support retrieving trustlines without a tag,
|
||||
// since it does not make sense to filter trustlines
|
||||
// by tag.
|
||||
address = ensureClassicAddress(address)
|
||||
|
||||
// 2. Make Request
|
||||
const responses = await this._requestAll('account_lines', {
|
||||
account: address,
|
||||
ledger_index: ledgerVersion,
|
||||
ledger_index: await this.getLedgerVersion(),
|
||||
limit: options.limit,
|
||||
peer: options.counterparty
|
||||
})
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import {deriveKeypair, deriveAddress} from 'ripple-keypairs'
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
|
||||
function deriveXAddress(options: {publicKey: string, tag: number | false, test: boolean}): string {
|
||||
const classicAddress = deriveAddress(options.publicKey)
|
||||
return classicAddressToXAddress(classicAddress, options.tag, options.test)
|
||||
}
|
||||
|
||||
export {
|
||||
deriveKeypair,
|
||||
deriveAddress
|
||||
deriveAddress,
|
||||
deriveXAddress
|
||||
}
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import * as common from '../common'
|
||||
const {errors, validate} = common
|
||||
import {errors, validate} from '../common'
|
||||
|
||||
export type GeneratedAddress = {
|
||||
secret: string,
|
||||
address: string
|
||||
xAddress: string,
|
||||
classicAddress?: string,
|
||||
address?: string, // @deprecated Use `classicAddress` instead.
|
||||
secret: string
|
||||
}
|
||||
|
||||
function generateAddressAPI(options?: any): GeneratedAddress {
|
||||
export interface GenerateAddressOptions {
|
||||
// The entropy to use to generate the seed.
|
||||
entropy?: Uint8Array | number[],
|
||||
|
||||
// The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519',
|
||||
|
||||
// Specifies whether the address is intended for use on a test network such as Testnet or Devnet.
|
||||
// If `true`, the address should only be used for testing, and will start with `T`.
|
||||
// If `false` (default), the address should only be used on mainnet, and will start with `X`.
|
||||
test?: boolean,
|
||||
|
||||
// If `true`, return the classic address, in addition to the X-address.
|
||||
includeClassicAddress?: boolean
|
||||
}
|
||||
|
||||
function generateAddressAPI(options: GenerateAddressOptions): GeneratedAddress {
|
||||
validate.generateAddress({options})
|
||||
try {
|
||||
const secret = keypairs.generateSeed(options)
|
||||
const keypair = keypairs.deriveKeypair(secret)
|
||||
const address = keypairs.deriveAddress(keypair.publicKey)
|
||||
return {secret, address}
|
||||
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
||||
const returnValue: any = {
|
||||
xAddress: classicAddressToXAddress(classicAddress, false, options && options.test),
|
||||
secret
|
||||
}
|
||||
if (options.includeClassicAddress) {
|
||||
returnValue.classicAddress = classicAddress
|
||||
returnValue.address = classicAddress
|
||||
}
|
||||
return returnValue
|
||||
} catch (error) {
|
||||
throw new errors.UnexpectedError(error.message)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {TransactionJSON, prepareTransaction} from './utils'
|
||||
import {prepareTransaction} from './utils'
|
||||
import {validate} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
export type CheckCancelParameters = {
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as _ from 'lodash'
|
||||
import binary from 'ripple-binary-codec'
|
||||
import * as utils from './utils'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import {decodeAccountID} from 'ripple-address-codec'
|
||||
import {validate} from '../common'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
const hex = (Buffer.from(decodeAccountID(address))).toString('hex')
|
||||
return new BigNumber(hex, 16)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Memo} from '../common/types/objects'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
@@ -15,7 +15,7 @@ export type EscrowExecution = {
|
||||
|
||||
function createEscrowExecutionTransaction(account: string,
|
||||
payment: EscrowExecution
|
||||
): utils.TransactionJSON {
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
TransactionType: 'EscrowFinish',
|
||||
Account: account,
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as utils from './utils'
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
const claimFlags = utils.common.txFlags.PaymentChannelClaim
|
||||
import {validate, xrpToDrops} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
export type PaymentChannelClaim = {
|
||||
@@ -17,8 +17,8 @@ export type PaymentChannelClaim = {
|
||||
|
||||
function createPaymentChannelClaimTransaction(account: string,
|
||||
claim: PaymentChannelClaim
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: utils.TransactionJSON = {
|
||||
): TransactionJSON {
|
||||
const txJSON: TransactionJSON = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelClaim',
|
||||
Channel: claim.channel,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as utils from './utils'
|
||||
import {validate, iso8601ToRippleTime, xrpToDrops} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
export type PaymentChannelCreate = {
|
||||
@@ -15,7 +15,7 @@ export type PaymentChannelCreate = {
|
||||
|
||||
function createPaymentChannelCreateTransaction(account: string,
|
||||
paymentChannel: PaymentChannelCreate
|
||||
): utils.TransactionJSON {
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelCreate',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as utils from './utils'
|
||||
import {validate, iso8601ToRippleTime, xrpToDrops} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
export type PaymentChannelFund = {
|
||||
@@ -11,8 +11,8 @@ export type PaymentChannelFund = {
|
||||
|
||||
function createPaymentChannelFundTransaction(account: string,
|
||||
fund: PaymentChannelFund
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: utils.TransactionJSON = {
|
||||
): TransactionJSON {
|
||||
const txJSON: TransactionJSON = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelFund',
|
||||
Channel: fund.channel,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {Amount, Adjustment, MaxAdjustment,
|
||||
MinAdjustment, Memo} from '../common/types/objects'
|
||||
import {xrpToDrops} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
import {getClassicAccountAndTag, ClassicAccountAndTag} from './utils'
|
||||
|
||||
|
||||
export interface Payment {
|
||||
@@ -84,15 +85,49 @@ function createMaximalAmount(amount: Amount): Amount {
|
||||
return _.assign({}, amount, {value: maxValue})
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an address and tag:
|
||||
* 1. Get the classic account and tag;
|
||||
* 2. If a tag is provided:
|
||||
* 2a. If the address was an X-address, validate that the X-address has the expected tag;
|
||||
* 2b. If the address was a classic address, return `expectedTag` as the tag.
|
||||
* 3. If we do not want to use a tag in this case,
|
||||
* set the tag in the return value to `undefined`.
|
||||
*
|
||||
* @param address The address to parse.
|
||||
* @param expectedTag If provided, and the `Account` is an X-address,
|
||||
* this method throws an error if `expectedTag`
|
||||
* does not match the tag of the X-address.
|
||||
* @returns {ClassicAccountAndTag}
|
||||
* The classic account and tag.
|
||||
*/
|
||||
function validateAndNormalizeAddress(address: string, expectedTag: number | undefined): ClassicAccountAndTag {
|
||||
const classicAddress = getClassicAccountAndTag(address, expectedTag)
|
||||
classicAddress.tag = classicAddress.tag === false ? undefined : classicAddress.tag
|
||||
return classicAddress
|
||||
}
|
||||
|
||||
function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
): TransactionJSON {
|
||||
const payment = _.cloneDeep(paymentArgument)
|
||||
applyAnyCounterpartyEncoding(payment)
|
||||
|
||||
if (address !== payment.source.address) {
|
||||
const sourceAddressAndTag = validateAndNormalizeAddress(payment.source.address, payment.source.tag)
|
||||
const addressToVerifyAgainst = validateAndNormalizeAddress(address, undefined)
|
||||
|
||||
if (addressToVerifyAgainst.classicAccount !== sourceAddressAndTag.classicAccount) {
|
||||
throw new ValidationError('address must match payment.source.address')
|
||||
}
|
||||
|
||||
if (addressToVerifyAgainst.tag !== undefined &&
|
||||
sourceAddressAndTag.tag !== undefined &&
|
||||
addressToVerifyAgainst.tag !== sourceAddressAndTag.tag) {
|
||||
throw new ValidationError(
|
||||
'address includes a tag that does not match payment.source.tag')
|
||||
}
|
||||
|
||||
const destinationAddressAndTag = validateAndNormalizeAddress(payment.destination.address, payment.destination.tag)
|
||||
|
||||
if (
|
||||
(isMaxAdjustment(payment.source) && isMinAdjustment(payment.destination))
|
||||
||
|
||||
@@ -119,8 +154,8 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
|
||||
const txJSON: any = {
|
||||
TransactionType: 'Payment',
|
||||
Account: payment.source.address,
|
||||
Destination: payment.destination.address,
|
||||
Account: sourceAddressAndTag.classicAccount,
|
||||
Destination: destinationAddressAndTag.classicAccount,
|
||||
Amount: toRippledAmount(amount),
|
||||
Flags: 0
|
||||
}
|
||||
@@ -128,11 +163,11 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
if (payment.invoiceID !== undefined) {
|
||||
txJSON.InvoiceID = payment.invoiceID
|
||||
}
|
||||
if (payment.source.tag !== undefined) {
|
||||
txJSON.SourceTag = payment.source.tag
|
||||
if (sourceAddressAndTag.tag !== undefined) {
|
||||
txJSON.SourceTag = sourceAddressAndTag.tag
|
||||
}
|
||||
if (payment.destination.tag !== undefined) {
|
||||
txJSON.DestinationTag = payment.destination.tag
|
||||
if (destinationAddressAndTag.tag !== undefined) {
|
||||
txJSON.DestinationTag = destinationAddressAndTag.tag
|
||||
}
|
||||
if (payment.memos !== undefined) {
|
||||
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
|
||||
|
||||
@@ -4,11 +4,11 @@ import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
const AccountFlagIndices = utils.common.constants.AccountFlagIndices
|
||||
const AccountFields = utils.common.constants.AccountFields
|
||||
import {Instructions, Prepare, SettingsTransaction} from './types'
|
||||
import {Instructions, Prepare, SettingsTransaction, TransactionJSON} from './types'
|
||||
import {FormattedSettings, WeightedSigner} from '../common/types/objects'
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSettings) {
|
||||
function setTransactionFlags(txJSON: TransactionJSON, values: FormattedSettings) {
|
||||
const keys = Object.keys(values)
|
||||
assert.ok(keys.length === 1, 'ERROR: can only set one setting per transaction')
|
||||
const flagName = keys[0]
|
||||
@@ -24,7 +24,7 @@ function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSet
|
||||
}
|
||||
|
||||
// Sets `null` fields to their `default`.
|
||||
function setTransactionFields(txJSON: utils.TransactionJSON, input: FormattedSettings) {
|
||||
function setTransactionFields(txJSON: TransactionJSON, input: FormattedSettings) {
|
||||
const fieldSchema = AccountFields
|
||||
for (const fieldName in fieldSchema) {
|
||||
const field = fieldSchema[fieldName]
|
||||
@@ -62,7 +62,7 @@ function setTransactionFields(txJSON: utils.TransactionJSON, input: FormattedSet
|
||||
*/
|
||||
|
||||
function convertTransferRate(transferRate: number): number {
|
||||
return (new BigNumber(transferRate)).shift(9).toNumber()
|
||||
return (new BigNumber(transferRate)).shiftedBy(9).toNumber()
|
||||
}
|
||||
|
||||
function formatSignerEntry(signer: WeightedSigner): object {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as utils from './utils'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import binaryCodec from 'ripple-binary-codec'
|
||||
import {computeBinaryTransactionHash} from '../common/hashes'
|
||||
import {SignOptions, KeyPair} from './types'
|
||||
import {SignOptions, KeyPair, TransactionJSON} from './types'
|
||||
import {BigNumber} from 'bignumber.js'
|
||||
import {xrpToDrops} from '../common'
|
||||
import {RippleAPI} from '..'
|
||||
@@ -131,11 +131,11 @@ function objectDiff(a: object, b: object): object {
|
||||
* 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.
|
||||
* @param {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 {
|
||||
function checkTxSerialization(serialized: string, tx: TransactionJSON): void {
|
||||
// Decode the serialized transaction:
|
||||
const decoded = binaryCodec.decode(serialized)
|
||||
|
||||
@@ -183,7 +183,7 @@ function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): vo
|
||||
function checkFee(api: RippleAPI, txFee: string): void {
|
||||
const fee = new BigNumber(txFee)
|
||||
const maxFeeDrops = xrpToDrops(api._maxFeeXRP)
|
||||
if (fee.greaterThan(maxFeeDrops)) {
|
||||
if (fee.isGreaterThan(maxFeeDrops)) {
|
||||
throw new utils.common.errors.ValidationError(
|
||||
`"Fee" should not exceed "${maxFeeDrops}". ` +
|
||||
'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import {RippleAPI} from '..'
|
||||
|
||||
function convertQuality(quality) {
|
||||
return (new BigNumber(quality)).shift(9).truncated().toNumber()
|
||||
return (new BigNumber(quality)).shiftedBy(9).integerValue(BigNumber.ROUND_DOWN).toNumber()
|
||||
}
|
||||
|
||||
function createTrustlineTransaction(account: string,
|
||||
|
||||
@@ -9,10 +9,16 @@ import {
|
||||
} from '../common/types/objects'
|
||||
import {
|
||||
ApiMemo,
|
||||
TransactionJSON
|
||||
} from './utils'
|
||||
|
||||
export type TransactionJSON = TransactionJSON
|
||||
export type TransactionJSON = {
|
||||
Account: string,
|
||||
TransactionType: string,
|
||||
Memos?: {Memo: ApiMemo}[],
|
||||
Flags?: number,
|
||||
Fulfillment?: string,
|
||||
[Field: string]: string | number | Array<any> | RippledAmount | undefined
|
||||
}
|
||||
|
||||
export type Instructions = {
|
||||
sequence?: number,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as common from '../common'
|
||||
import {Memo, RippledAmount} from '../common/types/objects'
|
||||
const txFlags = common.txFlags
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Memo} from '../common/types/objects'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {RippleAPI} from '..'
|
||||
import {ValidationError} from '../common/errors'
|
||||
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
|
||||
|
||||
const txFlags = common.txFlags
|
||||
const TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD = ['Payment', 'CheckCreate', 'EscrowCreate', 'PaymentChannelCreate']
|
||||
|
||||
export type ApiMemo = {
|
||||
MemoData?: string,
|
||||
@@ -12,15 +15,6 @@ export type ApiMemo = {
|
||||
MemoFormat?: string
|
||||
}
|
||||
|
||||
export type TransactionJSON = {
|
||||
Account: string,
|
||||
TransactionType: string,
|
||||
Memos?: {Memo: ApiMemo}[],
|
||||
Flags?: number,
|
||||
Fulfillment?: string,
|
||||
[Field: string]: string | number | Array<any> | RippledAmount | undefined
|
||||
}
|
||||
|
||||
function formatPrepareResponse(txJSON: any): Prepare {
|
||||
const instructions = {
|
||||
fee: common.dropsToXrp(txJSON.Fee),
|
||||
@@ -56,6 +50,49 @@ function scaleValue(value, multiplier, extra = 0) {
|
||||
return (new BigNumber(value)).times(multiplier).plus(extra).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} ClassicAccountAndTag
|
||||
* @property {string} classicAccount - The classic account address.
|
||||
* @property {number | false | undefined } tag - The destination tag;
|
||||
* `false` if no tag should be used;
|
||||
* `undefined` if the input could not specify whether a tag should be used.
|
||||
*/
|
||||
export interface ClassicAccountAndTag {
|
||||
classicAccount: string,
|
||||
tag: number | false | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an address (account), get the classic account and tag.
|
||||
* If an `expectedTag` is provided:
|
||||
* 1. If the `Account` is an X-address, validate that the tags match.
|
||||
* 2. If the `Account` is a classic address, return `expectedTag` as the tag.
|
||||
*
|
||||
* @param Account The address to parse.
|
||||
* @param expectedTag If provided, and the `Account` is an X-address,
|
||||
* this method throws an error if `expectedTag`
|
||||
* does not match the tag of the X-address.
|
||||
* @returns {ClassicAccountAndTag}
|
||||
* The classic account and tag.
|
||||
*/
|
||||
function getClassicAccountAndTag(Account: string, expectedTag?: number): ClassicAccountAndTag {
|
||||
if (isValidXAddress(Account)) {
|
||||
const classic = xAddressToClassicAddress(Account)
|
||||
if (expectedTag !== undefined && classic.tag !== expectedTag) {
|
||||
throw new ValidationError('address includes a tag that does not match the tag specified in the transaction')
|
||||
}
|
||||
return {
|
||||
classicAccount: classic.classicAddress,
|
||||
tag: classic.tag
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
classicAccount: Account,
|
||||
tag: expectedTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
instructions: Instructions
|
||||
): Promise<Prepare> {
|
||||
@@ -68,110 +105,159 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
'" exists in instance when not allowed'))
|
||||
}
|
||||
|
||||
// To remove the signer list, SignerEntries field should be omitted.
|
||||
const newTxJSON = Object.assign({}, txJSON)
|
||||
|
||||
// To remove the signer list, `SignerEntries` field should be omitted.
|
||||
if (txJSON['SignerQuorum'] === 0) {
|
||||
delete txJSON.SignerEntries
|
||||
delete newTxJSON.SignerEntries
|
||||
}
|
||||
|
||||
const account = txJSON.Account
|
||||
setCanonicalFlag(txJSON)
|
||||
// Sender:
|
||||
const {classicAccount, tag: sourceTag} = getClassicAccountAndTag(txJSON.Account)
|
||||
newTxJSON.Account = classicAccount
|
||||
if (sourceTag !== undefined) {
|
||||
if (txJSON.SourceTag && txJSON.SourceTag !== sourceTag) {
|
||||
return Promise.reject(new ValidationError(
|
||||
'The `SourceTag`, if present, must match the tag of the `Account` X-address'))
|
||||
}
|
||||
if (sourceTag) {
|
||||
newTxJSON.SourceTag = sourceTag
|
||||
}
|
||||
}
|
||||
|
||||
function prepareMaxLedgerVersion(): Promise<TransactionJSON> {
|
||||
// Destination:
|
||||
if (typeof txJSON.Destination === 'string') {
|
||||
const {classicAccount: destinationAccount, tag: destinationTag} = getClassicAccountAndTag(txJSON.Destination)
|
||||
newTxJSON.Destination = destinationAccount
|
||||
if (destinationTag !== undefined) {
|
||||
if (TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD.includes(txJSON.TransactionType)) {
|
||||
if (txJSON.DestinationTag && txJSON.DestinationTag !== destinationTag) {
|
||||
return Promise.reject(new ValidationError(
|
||||
'The Payment `DestinationTag`, if present, must match the tag of the `Destination` X-address'))
|
||||
}
|
||||
if (destinationTag) {
|
||||
newTxJSON.DestinationTag = destinationTag
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertToClassicAccountIfPresent(fieldName: string): void {
|
||||
const account = txJSON[fieldName]
|
||||
if (typeof account === 'string') {
|
||||
const {classicAccount: ca} = getClassicAccountAndTag(account)
|
||||
newTxJSON[fieldName] = ca
|
||||
}
|
||||
}
|
||||
|
||||
// DepositPreauth:
|
||||
convertToClassicAccountIfPresent('Authorize')
|
||||
convertToClassicAccountIfPresent('Unauthorize')
|
||||
|
||||
// EscrowCancel, EscrowFinish:
|
||||
convertToClassicAccountIfPresent('Owner')
|
||||
|
||||
// SetRegularKey:
|
||||
convertToClassicAccountIfPresent('RegularKey')
|
||||
|
||||
setCanonicalFlag(newTxJSON)
|
||||
|
||||
function prepareMaxLedgerVersion(): Promise<void> {
|
||||
// Up to one of the following is allowed:
|
||||
// txJSON.LastLedgerSequence
|
||||
// instructions.maxLedgerVersion
|
||||
// instructions.maxLedgerVersionOffset
|
||||
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
|
||||
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
|
||||
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersion`' +
|
||||
' in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
|
||||
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
|
||||
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersionOffset`' +
|
||||
' in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.LastLedgerSequence) {
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.LastLedgerSequence) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (instructions.maxLedgerVersion !== undefined) {
|
||||
if (instructions.maxLedgerVersion !== null) {
|
||||
txJSON.LastLedgerSequence = instructions.maxLedgerVersion
|
||||
newTxJSON.LastLedgerSequence = instructions.maxLedgerVersion
|
||||
}
|
||||
return Promise.resolve(txJSON)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const offset = instructions.maxLedgerVersionOffset !== undefined ?
|
||||
instructions.maxLedgerVersionOffset : 3
|
||||
return api.connection.getLedgerVersion().then(ledgerVersion => {
|
||||
txJSON.LastLedgerSequence = ledgerVersion + offset
|
||||
return txJSON
|
||||
newTxJSON.LastLedgerSequence = ledgerVersion + offset
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
function prepareFee(): Promise<TransactionJSON> {
|
||||
function prepareFee(): Promise<void> {
|
||||
// instructions.fee is scaled (for multi-signed transactions) while txJSON.Fee is not.
|
||||
// Due to this difference, we do NOT allow both to be set, as the behavior would be complex and
|
||||
// potentially ambiguous.
|
||||
// Furthermore, txJSON.Fee is in drops while instructions.fee is in XRP, which would just add to
|
||||
// the confusion. It is simpler to require that only one is used.
|
||||
if (txJSON.Fee && instructions.fee) {
|
||||
if (newTxJSON.Fee && instructions.fee) {
|
||||
return Promise.reject(new ValidationError('`Fee` in txJSON and `fee` in `instructions` cannot both be set'))
|
||||
}
|
||||
if (txJSON.Fee) {
|
||||
if (newTxJSON.Fee) {
|
||||
// txJSON.Fee is set. Use this value and do not scale it.
|
||||
return Promise.resolve(txJSON)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const multiplier = instructions.signersCount === undefined ? 1 :
|
||||
instructions.signersCount + 1
|
||||
if (instructions.fee !== undefined) {
|
||||
const fee = new BigNumber(instructions.fee)
|
||||
if (fee.greaterThan(api._maxFeeXRP)) {
|
||||
if (fee.isGreaterThan(api._maxFeeXRP)) {
|
||||
return Promise.reject(new ValidationError(`Fee of ${fee.toString(10)} XRP exceeds ` +
|
||||
`max of ${api._maxFeeXRP} XRP. To use this fee, increase ` +
|
||||
'`maxFeeXRP` in the RippleAPI constructor.'))
|
||||
}
|
||||
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
|
||||
return Promise.resolve(txJSON)
|
||||
newTxJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
|
||||
return Promise.resolve()
|
||||
}
|
||||
const cushion = api._feeCushion
|
||||
return api.getFee(cushion).then(fee => {
|
||||
return api.connection.getFeeRef().then(feeRef => {
|
||||
const extraFee =
|
||||
(txJSON.TransactionType !== 'EscrowFinish' ||
|
||||
txJSON.Fulfillment === undefined) ? 0 :
|
||||
(newTxJSON.TransactionType !== 'EscrowFinish' ||
|
||||
newTxJSON.Fulfillment === undefined) ? 0 :
|
||||
(cushion * feeRef * (32 + Math.floor(
|
||||
Buffer.from(txJSON.Fulfillment, 'hex').length / 16)))
|
||||
Buffer.from(newTxJSON.Fulfillment, 'hex').length / 16)))
|
||||
const feeDrops = common.xrpToDrops(fee)
|
||||
const maxFeeXRP = instructions.maxFee ?
|
||||
BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
|
||||
const maxFeeDrops = common.xrpToDrops(maxFeeXRP)
|
||||
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
|
||||
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
|
||||
newTxJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
|
||||
|
||||
return txJSON
|
||||
return
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function prepareSequence(): Promise<TransactionJSON> {
|
||||
async function prepareSequence(): Promise<void> {
|
||||
if (instructions.sequence !== undefined) {
|
||||
if (txJSON.Sequence === undefined || instructions.sequence === txJSON.Sequence) {
|
||||
txJSON.Sequence = instructions.sequence
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.Sequence === undefined || instructions.sequence === newTxJSON.Sequence) {
|
||||
newTxJSON.Sequence = instructions.sequence
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
// Both txJSON.Sequence and instructions.sequence are defined, and they are NOT equal
|
||||
return Promise.reject(new ValidationError('`Sequence` in txJSON must match `sequence` in `instructions`'))
|
||||
}
|
||||
}
|
||||
if (txJSON.Sequence !== undefined) {
|
||||
return Promise.resolve(txJSON)
|
||||
if (newTxJSON.Sequence !== undefined) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
try {
|
||||
// Consider requesting from the 'current' ledger (instead of 'validated').
|
||||
const response = await api.request('account_info', {
|
||||
account
|
||||
account: classicAccount
|
||||
})
|
||||
txJSON.Sequence = response.account_data.Sequence
|
||||
return Promise.resolve(txJSON)
|
||||
newTxJSON.Sequence = response.account_data.Sequence
|
||||
return Promise.resolve()
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
@@ -181,7 +267,7 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
prepareMaxLedgerVersion(),
|
||||
prepareFee(),
|
||||
prepareSequence()
|
||||
]).then(() => formatPrepareResponse(txJSON))
|
||||
]).then(() => formatPrepareResponse(newTxJSON))
|
||||
}
|
||||
|
||||
function convertStringToHex(string: string): string {
|
||||
@@ -203,5 +289,6 @@ export {
|
||||
convertMemo,
|
||||
prepareTransaction,
|
||||
common,
|
||||
setCanonicalFlag
|
||||
setCanonicalFlag,
|
||||
getClassicAccountAndTag
|
||||
}
|
||||
|
||||
4774
test/api-test.js
4774
test/api-test.js
File diff suppressed because it is too large
Load Diff
29
test/api/combine/index.ts
Normal file
29
test/api/combine/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import assert from 'assert-diff'
|
||||
import binary from 'ripple-binary-codec'
|
||||
import requests from '../../fixtures/requests'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
const { combine: REQUEST_FIXTURES } = requests
|
||||
const { combine: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'combine': async (api, address) => {
|
||||
const combined = api.combine(REQUEST_FIXTURES.setDomain)
|
||||
assertResultMatch(combined, RESPONSE_FIXTURES.single, 'sign')
|
||||
},
|
||||
|
||||
'combine - different transactions': async (api, address) => {
|
||||
const request = [REQUEST_FIXTURES.setDomain[0]]
|
||||
const tx = binary.decode(REQUEST_FIXTURES.setDomain[0])
|
||||
tx.Flags = 0
|
||||
request.push(binary.encode(tx))
|
||||
assert.throws(() => {
|
||||
api.combine(request)
|
||||
}, /txJSON is not the same for all signedTransactions/)
|
||||
}
|
||||
}
|
||||
191
test/api/computeLedgerHash/index.ts
Normal file
191
test/api/computeLedgerHash/index.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import assert from 'assert-diff'
|
||||
import requests from '../../fixtures/requests'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
const { computeLedgerHash: REQUEST_FIXTURES } = requests
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'given corrupt data - should fail': async (api, address) => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeState: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 38129
|
||||
}
|
||||
const ledger = await api.getLedger(request)
|
||||
assert.strictEqual(
|
||||
// @ts-ignore
|
||||
ledger.transactions[0].rawTransaction,
|
||||
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}'
|
||||
)
|
||||
// @ts-ignore - Change Amount to 12000000000
|
||||
ledger.transactions[0].rawTransaction =
|
||||
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"12000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}'
|
||||
ledger.parentCloseTime = ledger.closeTime
|
||||
let hash
|
||||
try {
|
||||
hash = api.computeLedgerHash(ledger, { computeTreeHashes: true })
|
||||
} catch (error) {
|
||||
assert(error instanceof api.errors.ValidationError)
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'transactionHash in header does not match computed hash of transactions'
|
||||
)
|
||||
assert.deepStrictEqual(error.data, {
|
||||
transactionHashInHeader:
|
||||
'DB83BF807416C5B3499A73130F843CF615AB8E797D79FE7D330ADF1BFA93951A',
|
||||
computedHashOfTransactions:
|
||||
'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68'
|
||||
})
|
||||
return
|
||||
}
|
||||
assert(
|
||||
false,
|
||||
'Should throw ValidationError instead of producing hash: ' + hash
|
||||
)
|
||||
},
|
||||
|
||||
'given ledger without raw transactions - should throw': async (
|
||||
api,
|
||||
address
|
||||
) => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeState: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 38129
|
||||
}
|
||||
const ledger = await api.getLedger(request)
|
||||
assert.strictEqual(
|
||||
// @ts-ignore
|
||||
ledger.transactions[0].rawTransaction,
|
||||
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}'
|
||||
)
|
||||
// Delete rawTransaction
|
||||
// @ts-ignore - Delete rawTransaction
|
||||
delete ledger.transactions[0].rawTransaction
|
||||
ledger.parentCloseTime = ledger.closeTime
|
||||
let hash
|
||||
try {
|
||||
hash = api.computeLedgerHash(ledger, { computeTreeHashes: true })
|
||||
} catch (error) {
|
||||
assert(error instanceof api.errors.ValidationError)
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'ledger' + ' is missing raw transactions'
|
||||
)
|
||||
return
|
||||
}
|
||||
assert(
|
||||
false,
|
||||
'Should throw ValidationError instead of producing hash: ' + hash
|
||||
)
|
||||
},
|
||||
|
||||
'given ledger without state or transactions - only compute ledger hash': async (
|
||||
api,
|
||||
address
|
||||
) => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeState: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 38129
|
||||
}
|
||||
const ledger = await api.getLedger(request)
|
||||
assert.strictEqual(
|
||||
// @ts-ignore
|
||||
ledger.transactions[0].rawTransaction,
|
||||
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","meta":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}'
|
||||
)
|
||||
ledger.parentCloseTime = ledger.closeTime
|
||||
const computeLedgerHash = api.computeLedgerHash
|
||||
const ValidationError = api.errors.ValidationError
|
||||
function testCompute(ledger, expectedError) {
|
||||
let hash = computeLedgerHash(ledger)
|
||||
assert.strictEqual(
|
||||
hash,
|
||||
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E'
|
||||
)
|
||||
// fail if required to compute tree hashes
|
||||
try {
|
||||
hash = computeLedgerHash(ledger, { computeTreeHashes: true })
|
||||
} catch (error) {
|
||||
assert(error instanceof ValidationError)
|
||||
assert.strictEqual(error.message, expectedError)
|
||||
return
|
||||
}
|
||||
assert(
|
||||
false,
|
||||
'Should throw ValidationError instead of producing hash: ' + hash
|
||||
)
|
||||
}
|
||||
|
||||
const transactions = ledger.transactions
|
||||
delete ledger.transactions
|
||||
testCompute(ledger, 'transactions property is missing from the ledger')
|
||||
delete ledger.rawState
|
||||
testCompute(ledger, 'transactions property is missing from the ledger')
|
||||
ledger.transactions = transactions
|
||||
testCompute(ledger, 'rawState property is missing from the ledger')
|
||||
},
|
||||
|
||||
'wrong hash': async (api, address) => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeState: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 38129
|
||||
}
|
||||
const ledger = await api.getLedger(request)
|
||||
assertResultMatch(ledger, responses.getLedger.full, 'getLedger')
|
||||
const newLedger = {
|
||||
...ledger,
|
||||
parentCloseTime: ledger.closeTime,
|
||||
stateHash:
|
||||
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44'
|
||||
}
|
||||
assert.throws(() => {
|
||||
api.computeLedgerHash(newLedger)
|
||||
}, /does not match computed hash of state/)
|
||||
},
|
||||
|
||||
'computeLedgerHash': async (api, address) => {
|
||||
// const api = new RippleAPI()
|
||||
const header = REQUEST_FIXTURES.header
|
||||
const ledgerHash = api.computeLedgerHash(header)
|
||||
assert.strictEqual(
|
||||
ledgerHash,
|
||||
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349'
|
||||
)
|
||||
},
|
||||
|
||||
'computeLedgerHash - with transactions': async (api, address) => {
|
||||
// const api = new RippleAPI()
|
||||
const header = {
|
||||
...REQUEST_FIXTURES.header,
|
||||
transactionHash: undefined,
|
||||
rawTransactions: JSON.stringify(REQUEST_FIXTURES.transactions)
|
||||
}
|
||||
const ledgerHash = api.computeLedgerHash(header)
|
||||
assert.strictEqual(
|
||||
ledgerHash,
|
||||
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349'
|
||||
)
|
||||
},
|
||||
|
||||
'computeLedgerHash - incorrent transaction_hash': async (api, address) => {
|
||||
// const api = new RippleAPI()
|
||||
const header = Object.assign({}, REQUEST_FIXTURES.header, {
|
||||
transactionHash:
|
||||
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9'
|
||||
})
|
||||
header.rawTransactions = JSON.stringify(REQUEST_FIXTURES.transactions)
|
||||
assert.throws(() => api.computeLedgerHash(header))
|
||||
}
|
||||
}
|
||||
29
test/api/constructor/index.ts
Normal file
29
test/api/constructor/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
import { RippleAPI } from 'ripple-api'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'RippleAPI - implicit server port': () => {
|
||||
new RippleAPI({ server: 'wss://s1.ripple.com' })
|
||||
},
|
||||
|
||||
'RippleAPI invalid options': () => {
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
assert.throws(() => new RippleAPI({ invalid: true }))
|
||||
},
|
||||
|
||||
'RippleAPI valid options': () => {
|
||||
const api = new RippleAPI({ server: 'wss://s:1' })
|
||||
const privateConnectionUrl = (api.connection as any)._url
|
||||
assert.deepEqual(privateConnectionUrl, 'wss://s:1')
|
||||
},
|
||||
|
||||
'RippleAPI invalid server uri': () => {
|
||||
assert.throws(() => new RippleAPI({ server: 'wss//s:1' }))
|
||||
}
|
||||
}
|
||||
16
test/api/deriveAddress/index.ts
Normal file
16
test/api/deriveAddress/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'returns address for public key': async (api, address) => {
|
||||
var address = api.deriveAddress(
|
||||
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06'
|
||||
)
|
||||
assert.equal(address, 'rLczgQHxPhWtjkaQqn3Q6UM8AbRbbRvs5K')
|
||||
}
|
||||
}
|
||||
39
test/api/deriveKeypair/index.ts
Normal file
39
test/api/deriveKeypair/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'returns keypair for secret': async (api, address) => {
|
||||
var keypair = api.deriveKeypair('snsakdSrZSLkYpCXxfRkS4Sh96PMK')
|
||||
assert.equal(
|
||||
keypair.privateKey,
|
||||
'008850736302221AFD59FF9CA1A29D4975F491D726249302EE48A3078A8934D335'
|
||||
)
|
||||
assert.equal(
|
||||
keypair.publicKey,
|
||||
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06'
|
||||
)
|
||||
},
|
||||
|
||||
'returns keypair for ed25519 secret': async (api, address) => {
|
||||
var keypair = api.deriveKeypair('sEdV9eHWbibBnTj7b1H5kHfPfv7gudx')
|
||||
assert.equal(
|
||||
keypair.privateKey,
|
||||
'ED5C2EF6C2E3200DFA6B72F47935C7F64D35453646EA34919192538F458C7BC30F'
|
||||
)
|
||||
assert.equal(
|
||||
keypair.publicKey,
|
||||
'ED0805EC4E728DB87C0CA6C420751F296C57A5F42D02E9E6150CE60694A44593E5'
|
||||
)
|
||||
},
|
||||
|
||||
'throws with an invalid secret': async (api, address) => {
|
||||
assert.throws(() => {
|
||||
api.deriveKeypair('...')
|
||||
}, /^Error: Non-base58 character$/)
|
||||
}
|
||||
}
|
||||
31
test/api/deriveXAddress/index.ts
Normal file
31
test/api/deriveXAddress/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
import { RippleAPI } from '../../../src'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'returns address for public key': async (api, address) => {
|
||||
assert.equal(
|
||||
RippleAPI.deriveXAddress({
|
||||
publicKey:
|
||||
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06',
|
||||
tag: false,
|
||||
test: false
|
||||
}),
|
||||
'XVZVpQj8YSVpNyiwXYSqvQoQqgBttTxAZwMcuJd4xteQHyt'
|
||||
)
|
||||
assert.equal(
|
||||
RippleAPI.deriveXAddress({
|
||||
publicKey:
|
||||
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06',
|
||||
tag: false,
|
||||
test: true
|
||||
}),
|
||||
'TVVrSWtmQQssgVcmoMBcFQZKKf56QscyWLKnUyiuZW8ALU4'
|
||||
)
|
||||
}
|
||||
}
|
||||
119
test/api/dropsToXrp/index.ts
Normal file
119
test/api/dropsToXrp/index.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'works with a typical amount': async api => {
|
||||
const xrp = api.dropsToXrp('2000000')
|
||||
assert.strictEqual(xrp, '2', '2 million drops equals 2 XRP')
|
||||
},
|
||||
'works with fractions': async api => {
|
||||
let xrp = api.dropsToXrp('3456789')
|
||||
assert.strictEqual(xrp, '3.456789', '3,456,789 drops equals 3.456789 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('3400000')
|
||||
assert.strictEqual(xrp, '3.4', '3,400,000 drops equals 3.4 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('1')
|
||||
assert.strictEqual(xrp, '0.000001', '1 drop equals 0.000001 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('1.0')
|
||||
assert.strictEqual(xrp, '0.000001', '1.0 drops equals 0.000001 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('1.00')
|
||||
assert.strictEqual(xrp, '0.000001', '1.00 drops equals 0.000001 XRP')
|
||||
},
|
||||
'works with zero': async api => {
|
||||
let xrp = api.dropsToXrp('0')
|
||||
assert.strictEqual(xrp, '0', '0 drops equals 0 XRP')
|
||||
|
||||
// negative zero is equivalent to zero
|
||||
xrp = api.dropsToXrp('-0')
|
||||
assert.strictEqual(xrp, '0', '-0 drops equals 0 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('0.00')
|
||||
assert.strictEqual(xrp, '0', '0.00 drops equals 0 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('000000000')
|
||||
assert.strictEqual(xrp, '0', '000000000 drops equals 0 XRP')
|
||||
},
|
||||
'works with a negative value': async api => {
|
||||
const xrp = api.dropsToXrp('-2000000')
|
||||
assert.strictEqual(xrp, '-2', '-2 million drops equals -2 XRP')
|
||||
},
|
||||
'works with a value ending with a decimal point': async api => {
|
||||
let xrp = api.dropsToXrp('2000000.')
|
||||
assert.strictEqual(xrp, '2', '2000000. drops equals 2 XRP')
|
||||
|
||||
xrp = api.dropsToXrp('-2000000.')
|
||||
assert.strictEqual(xrp, '-2', '-2000000. drops equals -2 XRP')
|
||||
},
|
||||
'works with BigNumber objects': async api => {
|
||||
let xrp = api.dropsToXrp(new BigNumber(2000000))
|
||||
assert.strictEqual(xrp, '2', '(BigNumber) 2 million drops equals 2 XRP')
|
||||
|
||||
xrp = api.dropsToXrp(new BigNumber(-2000000))
|
||||
assert.strictEqual(xrp, '-2', '(BigNumber) -2 million drops equals -2 XRP')
|
||||
|
||||
xrp = api.dropsToXrp(new BigNumber(2345678))
|
||||
assert.strictEqual(
|
||||
xrp,
|
||||
'2.345678',
|
||||
'(BigNumber) 2,345,678 drops equals 2.345678 XRP'
|
||||
)
|
||||
|
||||
xrp = api.dropsToXrp(new BigNumber(-2345678))
|
||||
assert.strictEqual(
|
||||
xrp,
|
||||
'-2.345678',
|
||||
'(BigNumber) -2,345,678 drops equals -2.345678 XRP'
|
||||
)
|
||||
},
|
||||
'works with a number': async api => {
|
||||
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
|
||||
let xrp = api.dropsToXrp(2000000)
|
||||
assert.strictEqual(xrp, '2', '(number) 2 million drops equals 2 XRP')
|
||||
xrp = api.dropsToXrp(-2000000)
|
||||
assert.strictEqual(xrp, '-2', '(number) -2 million drops equals -2 XRP')
|
||||
},
|
||||
'throws with an amount with too many decimal places': async api => {
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('1.2')
|
||||
}, /has too many decimal places/)
|
||||
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('0.10')
|
||||
}, /has too many decimal places/)
|
||||
},
|
||||
'throws with an invalid value': async api => {
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('FOO')
|
||||
}, /invalid value/)
|
||||
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('1e-7')
|
||||
}, /invalid value/)
|
||||
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('2,0')
|
||||
}, /invalid value/)
|
||||
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('.')
|
||||
}, /dropsToXrp: invalid value '\.', should be a BigNumber or string-encoded number\./)
|
||||
},
|
||||
'throws with an amount more than one decimal point': async api => {
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('1.0.0')
|
||||
}, /dropsToXrp: invalid value '1\.0\.0'/)
|
||||
|
||||
assert.throws(() => {
|
||||
api.dropsToXrp('...')
|
||||
}, /dropsToXrp: invalid value '\.\.\.'/)
|
||||
}
|
||||
}
|
||||
19
test/api/errors/index.ts
Normal file
19
test/api/errors/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'RippleError with data': async (api, address) => {
|
||||
const error = new api.errors.RippleError('_message_', '_data_')
|
||||
assert.strictEqual(error.toString(), "[RippleError(_message_, '_data_')]")
|
||||
},
|
||||
|
||||
'NotFoundError default message': async (api, address) => {
|
||||
const error = new api.errors.NotFoundError()
|
||||
assert.strictEqual(error.toString(), '[NotFoundError(Not found)]')
|
||||
}
|
||||
}
|
||||
384
test/api/formatBidsAndAsks/index.ts
Normal file
384
test/api/formatBidsAndAsks/index.ts
Normal file
@@ -0,0 +1,384 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import assert from 'assert-diff'
|
||||
import { RippleAPI } from 'ripple-api'
|
||||
import requests from '../../fixtures/requests'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
function checkSortingOfOrders(orders) {
|
||||
let previousRate = '0'
|
||||
for (var i = 0; i < orders.length; i++) {
|
||||
const order = orders[i]
|
||||
let rate
|
||||
|
||||
// We calculate the quality of output/input here as a test.
|
||||
// This won't hold in general because when output and input amounts get tiny,
|
||||
// the quality can differ significantly. However, the offer stays in the
|
||||
// order book where it was originally placed. It would be more consistent
|
||||
// to check the quality from the offer book, but for the test data set,
|
||||
// this calculation holds.
|
||||
|
||||
if (order.specification.direction === 'buy') {
|
||||
rate = new BigNumber(order.specification.quantity.value)
|
||||
.dividedBy(order.specification.totalPrice.value)
|
||||
.toString()
|
||||
} else {
|
||||
rate = new BigNumber(order.specification.totalPrice.value)
|
||||
.dividedBy(order.specification.quantity.value)
|
||||
.toString()
|
||||
}
|
||||
assert(
|
||||
new BigNumber(rate).isGreaterThanOrEqualTo(previousRate),
|
||||
'Rates must be sorted from least to greatest: ' +
|
||||
rate +
|
||||
' should be >= ' +
|
||||
previousRate
|
||||
)
|
||||
previousRate = rate
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'normal': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
counter: {
|
||||
currency: 'BTC',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
assert.deepEqual(orderbook, responses.getOrderbook.normal)
|
||||
})
|
||||
},
|
||||
|
||||
'with XRP': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw'
|
||||
},
|
||||
counter: {
|
||||
currency: 'XRP'
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
assert.deepEqual(orderbook, responses.getOrderbook.withXRP)
|
||||
})
|
||||
},
|
||||
|
||||
'sample XRP/JPY book has orders sorted correctly': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
// the first currency in pair
|
||||
currency: 'XRP'
|
||||
},
|
||||
counter: {
|
||||
currency: 'JPY',
|
||||
counterparty: 'rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS'
|
||||
}
|
||||
}
|
||||
|
||||
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR'
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 400, // must match `test/fixtures/rippled/requests/1-taker_gets-XRP-taker_pays-JPY.json`
|
||||
taker: myAddress
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 400, // must match `test/fixtures/rippled/requests/2-taker_gets-JPY-taker_pays-XRP.json`
|
||||
taker: myAddress
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
assert.deepStrictEqual([], orderbook.bids)
|
||||
return checkSortingOfOrders(orderbook.asks)
|
||||
})
|
||||
},
|
||||
|
||||
'sample USD/XRP book has orders sorted correctly': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
counter: { currency: 'XRP' },
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
|
||||
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR'
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 400, // must match `test/fixtures/rippled/requests/1-taker_gets-XRP-taker_pays-JPY.json`
|
||||
taker: myAddress
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 400, // must match `test/fixtures/rippled/requests/2-taker_gets-JPY-taker_pays-XRP.json`
|
||||
taker: myAddress
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
return (
|
||||
checkSortingOfOrders(orderbook.bids) &&
|
||||
checkSortingOfOrders(orderbook.asks)
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
'sorted so that best deals come first': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
counter: {
|
||||
currency: 'BTC',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
|
||||
const bidRates = orderbook.bids.map(
|
||||
bid => bid.properties.makerExchangeRate
|
||||
)
|
||||
const askRates = orderbook.asks.map(
|
||||
ask => ask.properties.makerExchangeRate
|
||||
)
|
||||
// makerExchangeRate = quality = takerPays.value/takerGets.value
|
||||
// so the best deal for the taker is the lowest makerExchangeRate
|
||||
// bids and asks should be sorted so that the best deals come first
|
||||
assert.deepEqual(bidRates.map(x => Number(x)).sort(), bidRates)
|
||||
assert.deepEqual(askRates.map(x => Number(x)).sort(), askRates)
|
||||
})
|
||||
},
|
||||
|
||||
'currency & counterparty are correct': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
counter: {
|
||||
currency: 'BTC',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
|
||||
const orders = [...orderbook.bids, ...orderbook.asks]
|
||||
orders.forEach(order => {
|
||||
const quantity = order.specification.quantity
|
||||
const totalPrice = order.specification.totalPrice
|
||||
const { base, counter } = requests.getOrderbook.normal
|
||||
assert.strictEqual(quantity.currency, base.currency)
|
||||
assert.strictEqual(quantity.counterparty, base.counterparty)
|
||||
assert.strictEqual(totalPrice.currency, counter.currency)
|
||||
assert.strictEqual(totalPrice.counterparty, counter.counterparty)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
'direction is correct for bids and asks': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
},
|
||||
counter: {
|
||||
currency: 'BTC',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
}),
|
||||
api.request('book_offers', {
|
||||
taker_gets: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.counter),
|
||||
taker_pays: RippleAPI.renameCounterpartyToIssuer(orderbookInfo.base),
|
||||
ledger_index: 'validated',
|
||||
limit: 20,
|
||||
taker: address
|
||||
})
|
||||
]).then(([directOfferResults, reverseOfferResults]) => {
|
||||
const directOffers = (directOfferResults
|
||||
? directOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const reverseOffers = (reverseOfferResults
|
||||
? reverseOfferResults.offers
|
||||
: []
|
||||
).reduce((acc, res) => acc.concat(res), [])
|
||||
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [
|
||||
...directOffers,
|
||||
...reverseOffers
|
||||
])
|
||||
|
||||
assert(orderbook.bids.every(bid => bid.specification.direction === 'buy'))
|
||||
assert(
|
||||
orderbook.asks.every(ask => ask.specification.direction === 'sell')
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
30
test/api/generateAddress/index.ts
Normal file
30
test/api/generateAddress/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { TestSuite } from '../../utils'
|
||||
const { generateAddress: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateAddress': async (api, address) => {
|
||||
function random(): number[] {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
assert.deepEqual(
|
||||
api.generateAddress({ entropy: random() }),
|
||||
RESPONSE_FIXTURES
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress invalid': async (api, address) => {
|
||||
assert.throws(() => {
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
api.generateAddress({ entropy: random() })
|
||||
}, api.errors.UnexpectedError)
|
||||
}
|
||||
}
|
||||
29
test/api/generateXAddress/index.ts
Normal file
29
test/api/generateXAddress/index.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateXAddress': async (api, address) => {
|
||||
function random() {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
assert.deepEqual(
|
||||
api.generateXAddress({ entropy: random() }),
|
||||
responses.generateXAddress
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress invalid': async (api, address) => {
|
||||
assert.throws(() => {
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
api.generateXAddress({ entropy: random() })
|
||||
}, api.errors.UnexpectedError)
|
||||
}
|
||||
}
|
||||
27
test/api/getAccountInfo/index.ts
Normal file
27
test/api/getAccountInfo/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getAccountInfo': async (api, address) => {
|
||||
const result = await api.getAccountInfo(address)
|
||||
assertResultMatch(result, responses.getAccountInfo, 'getAccountInfo')
|
||||
},
|
||||
|
||||
'getAccountInfo - options undefined': async (api, address) => {
|
||||
const result = await api.getAccountInfo(address, undefined)
|
||||
assertResultMatch(result, responses.getAccountInfo, 'getAccountInfo')
|
||||
},
|
||||
|
||||
'getAccountInfo - invalid options': async (api, address) => {
|
||||
await assertRejects(
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
api.getAccountInfo(address, { invalid: 'options' }),
|
||||
api.errors.ValidationError
|
||||
)
|
||||
}
|
||||
}
|
||||
21
test/api/getAccountObjects/index.ts
Normal file
21
test/api/getAccountObjects/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
const { getAccountObjects: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getAccountObjects': async (api, address) => {
|
||||
const result = await api.getAccountObjects(address)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES, 'AccountObjectsResponse')
|
||||
},
|
||||
|
||||
'getAccountObjects - invalid options': async (api, address) => {
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
const result = await api.getAccountObjects(address, { invalid: 'options' })
|
||||
assertResultMatch(result, RESPONSE_FIXTURES, 'AccountObjectsResponse')
|
||||
}
|
||||
}
|
||||
26
test/api/getBalanceSheet/index.ts
Normal file
26
test/api/getBalanceSheet/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getBalanceSheet': async (api, address) => {
|
||||
await api.getBalanceSheet(address)
|
||||
},
|
||||
|
||||
'getBalanceSheet - invalid options': async (api, address) => {
|
||||
await assertRejects(
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
api.getBalanceSheet(address, { invalid: 'options' }),
|
||||
api.errors.ValidationError
|
||||
)
|
||||
},
|
||||
|
||||
'getBalanceSheet - empty': async (api, address) => {
|
||||
const options = { ledgerVersion: 123456 }
|
||||
const result = await api.getBalanceSheet(address, options)
|
||||
assertResultMatch(result, {}, 'getBalanceSheet')
|
||||
}
|
||||
}
|
||||
47
test/api/getBalances/index.ts
Normal file
47
test/api/getBalances/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getBalances': async (api, address) => {
|
||||
const result = await api.getBalances(address)
|
||||
assertResultMatch(result, responses.getBalances, 'getBalances')
|
||||
},
|
||||
|
||||
'getBalances - limit': async (api, address) => {
|
||||
const options = { limit: 3, ledgerVersion: 123456 }
|
||||
const expectedResponse = responses.getBalances.slice(0, 3)
|
||||
const result = await api.getBalances(address, options)
|
||||
assertResultMatch(result, expectedResponse, 'getBalances')
|
||||
},
|
||||
|
||||
'getBalances - limit & currency': async (api, address) => {
|
||||
const options = { currency: 'USD', limit: 3 }
|
||||
const expectedResponse = responses.getBalances
|
||||
.filter(item => item.currency === 'USD')
|
||||
.slice(0, 3)
|
||||
const result = await api.getBalances(address, options)
|
||||
assertResultMatch(result, expectedResponse, 'getBalances')
|
||||
},
|
||||
|
||||
'getBalances - limit & currency & issuer': async (api, address) => {
|
||||
const options = {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
limit: 3
|
||||
}
|
||||
const expectedResponse = responses.getBalances
|
||||
.filter(
|
||||
item =>
|
||||
item.currency === 'USD' &&
|
||||
item.counterparty === 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
)
|
||||
.slice(0, 3)
|
||||
const result = await api.getBalances(address, options)
|
||||
assertResultMatch(result, expectedResponse, 'getBalances')
|
||||
}
|
||||
}
|
||||
59
test/api/getFee/index.ts
Normal file
59
test/api/getFee/index.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getFee': async (api, address) => {
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '0.000012')
|
||||
},
|
||||
|
||||
'getFee default': async (api, address) => {
|
||||
api._feeCushion = undefined
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '0.000012')
|
||||
},
|
||||
|
||||
'getFee - high load_factor': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: { highLoadFactor: true }
|
||||
})
|
||||
)
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '2')
|
||||
},
|
||||
|
||||
'getFee - high load_factor with custom maxFeeXRP': async (api, address) => {
|
||||
// Ensure that overriding with high maxFeeXRP of '51540' causes no errors.
|
||||
// (fee will actually be 51539.607552)
|
||||
api._maxFeeXRP = '51540'
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: { highLoadFactor: true }
|
||||
})
|
||||
)
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '51539.607552')
|
||||
},
|
||||
|
||||
'getFee custom cushion': async (api, address) => {
|
||||
api._feeCushion = 1.4
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '0.000014')
|
||||
},
|
||||
|
||||
// This is not recommended since it may result in attempting to pay
|
||||
// less than the base fee. However, this test verifies the existing behavior.
|
||||
'getFee cushion less than 1.0': async (api, address) => {
|
||||
api._feeCushion = 0.9
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '0.000009')
|
||||
}
|
||||
}
|
||||
14
test/api/getFeeBase/index.ts
Normal file
14
test/api/getFeeBase/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'default test': async (api, address) => {
|
||||
const fee = await api.connection.getFeeBase()
|
||||
assert.strictEqual(fee, 10)
|
||||
}
|
||||
}
|
||||
14
test/api/getFeeRef/index.ts
Normal file
14
test/api/getFeeRef/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'default test': async (api, address) => {
|
||||
const fee = await api.connection.getFeeRef()
|
||||
assert.strictEqual(fee, 10)
|
||||
}
|
||||
}
|
||||
91
test/api/getLedger/index.ts
Normal file
91
test/api/getLedger/index.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import assert from 'assert-diff'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
import responses from '../../fixtures/responses'
|
||||
const { getLedger: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'simple test': async api => {
|
||||
const response = await api.getLedger()
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.header, 'getLedger')
|
||||
},
|
||||
'by hash': async api => {
|
||||
const response = await api.getLedger({
|
||||
ledgerHash:
|
||||
'15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D'
|
||||
})
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.headerByHash, 'getLedger')
|
||||
},
|
||||
'future ledger version': async api => {
|
||||
const response = await api.getLedger({ ledgerVersion: 14661789 })
|
||||
assert(!!response)
|
||||
},
|
||||
'with state as hashes': async api => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeAllData: false,
|
||||
includeState: true,
|
||||
ledgerVersion: 6
|
||||
}
|
||||
const response = await api.getLedger(request)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.withStateAsHashes,
|
||||
'getLedger'
|
||||
)
|
||||
},
|
||||
'with settings transaction': async api => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 4181996
|
||||
}
|
||||
const response = await api.getLedger(request)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.withSettingsTx, 'getLedger')
|
||||
},
|
||||
'with partial payment': async api => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 22420574
|
||||
}
|
||||
const response = await api.getLedger(request)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.withPartial, 'getLedger')
|
||||
},
|
||||
'pre 2014 with partial payment': async api => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 100001
|
||||
}
|
||||
const response = await api.getLedger(request)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.pre2014withPartial,
|
||||
'getLedger'
|
||||
)
|
||||
},
|
||||
'full, then computeLedgerHash': async api => {
|
||||
const request = {
|
||||
includeTransactions: true,
|
||||
includeState: true,
|
||||
includeAllData: true,
|
||||
ledgerVersion: 38129
|
||||
}
|
||||
const response = await api.getLedger(request)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.full, 'getLedger')
|
||||
const ledger = {
|
||||
...response,
|
||||
parentCloseTime: response.closeTime
|
||||
}
|
||||
const hash = api.computeLedgerHash(ledger, { computeTreeHashes: true })
|
||||
assert.strictEqual(
|
||||
hash,
|
||||
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E'
|
||||
)
|
||||
}
|
||||
}
|
||||
14
test/api/getLedgerVersion/index.ts
Normal file
14
test/api/getLedgerVersion/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import assert from 'assert-diff'
|
||||
import { TestSuite } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'default test': async (api, address) => {
|
||||
const ver = await api.getLedgerVersion()
|
||||
assert.strictEqual(ver, 8819951)
|
||||
}
|
||||
}
|
||||
150
test/api/getOrderbook/index.ts
Normal file
150
test/api/getOrderbook/index.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import requests from '../../fixtures/requests'
|
||||
import { TestSuite, assertResultMatch, assertRejects } from '../../utils'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
function checkSortingOfOrders(orders) {
|
||||
let previousRate = '0'
|
||||
for (var i = 0; i < orders.length; i++) {
|
||||
const order = orders[i]
|
||||
let rate
|
||||
|
||||
// We calculate the quality of output/input here as a test.
|
||||
// This won't hold in general because when output and input amounts get tiny,
|
||||
// the quality can differ significantly. However, the offer stays in the
|
||||
// order book where it was originally placed. It would be more consistent
|
||||
// to check the quality from the offer book, but for the test data set,
|
||||
// this calculation holds.
|
||||
|
||||
if (order.specification.direction === 'buy') {
|
||||
rate = new BigNumber(order.specification.quantity.value)
|
||||
.dividedBy(order.specification.totalPrice.value)
|
||||
.toString()
|
||||
} else {
|
||||
rate = new BigNumber(order.specification.totalPrice.value)
|
||||
.dividedBy(order.specification.quantity.value)
|
||||
.toString()
|
||||
}
|
||||
assert(
|
||||
new BigNumber(rate).isGreaterThanOrEqualTo(previousRate),
|
||||
'Rates must be sorted from least to greatest: ' +
|
||||
rate +
|
||||
' should be >= ' +
|
||||
previousRate
|
||||
)
|
||||
previousRate = rate
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'normal': async (api, address) => {
|
||||
const response = await api.getOrderbook(
|
||||
address,
|
||||
requests.getOrderbook.normal,
|
||||
{ limit: 20 }
|
||||
)
|
||||
assertResultMatch(response, responses.getOrderbook.normal, 'getOrderbook')
|
||||
},
|
||||
|
||||
'invalid options': async (api, address) => {
|
||||
assertRejects(
|
||||
api.getOrderbook(address, requests.getOrderbook.normal, {
|
||||
// @ts-ignore
|
||||
invalid: 'options'
|
||||
}),
|
||||
api.errors.ValidationError
|
||||
)
|
||||
},
|
||||
|
||||
'with XRP': async (api, address) => {
|
||||
const response = await api.getOrderbook(
|
||||
address,
|
||||
requests.getOrderbook.withXRP
|
||||
)
|
||||
assertResultMatch(response, responses.getOrderbook.withXRP, 'getOrderbook')
|
||||
},
|
||||
|
||||
'sample XRP/JPY book has orders sorted correctly': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
base: {
|
||||
// the first currency in pair
|
||||
currency: 'XRP'
|
||||
},
|
||||
counter: {
|
||||
currency: 'JPY',
|
||||
counterparty: 'rB3gZey7VWHYRqJHLoHDEJXJ2pEPNieKiS'
|
||||
}
|
||||
}
|
||||
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR'
|
||||
const response = await api.getOrderbook(myAddress, orderbookInfo)
|
||||
assert.deepStrictEqual([], response.bids)
|
||||
checkSortingOfOrders(response.asks)
|
||||
},
|
||||
|
||||
'sample USD/XRP book has orders sorted correctly': async (api, address) => {
|
||||
const orderbookInfo = {
|
||||
counter: { currency: 'XRP' },
|
||||
base: {
|
||||
currency: 'USD',
|
||||
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B'
|
||||
}
|
||||
}
|
||||
const myAddress = 'rE9qNjzJXpiUbVomdv7R4xhrXVeH2oVmGR'
|
||||
const response = await api.getOrderbook(myAddress, orderbookInfo)
|
||||
checkSortingOfOrders(response.bids)
|
||||
checkSortingOfOrders(response.asks)
|
||||
},
|
||||
|
||||
// WARNING: This test fails to catch the sorting bug, issue #766
|
||||
'sorted so that best deals come first [bad test]': async (api, address) => {
|
||||
const response = await api.getOrderbook(
|
||||
address,
|
||||
requests.getOrderbook.normal
|
||||
)
|
||||
const bidRates = response.bids.map(bid => bid.properties.makerExchangeRate)
|
||||
const askRates = response.asks.map(ask => ask.properties.makerExchangeRate)
|
||||
// makerExchangeRate = quality = takerPays.value/takerGets.value
|
||||
// so the best deal for the taker is the lowest makerExchangeRate
|
||||
// bids and asks should be sorted so that the best deals come first
|
||||
assert.deepEqual(
|
||||
bidRates.sort(x => Number(x)),
|
||||
bidRates
|
||||
)
|
||||
assert.deepEqual(
|
||||
askRates.sort(x => Number(x)),
|
||||
askRates
|
||||
)
|
||||
},
|
||||
|
||||
'currency & counterparty are correct': async (api, address) => {
|
||||
const response = await api.getOrderbook(
|
||||
address,
|
||||
requests.getOrderbook.normal
|
||||
)
|
||||
;[...response.bids, ...response.asks].forEach(order => {
|
||||
const quantity = order.specification.quantity
|
||||
const totalPrice = order.specification.totalPrice
|
||||
const { base, counter } = requests.getOrderbook.normal
|
||||
assert.strictEqual(quantity.currency, base.currency)
|
||||
assert.strictEqual(quantity.counterparty, base.counterparty)
|
||||
assert.strictEqual(totalPrice.currency, counter.currency)
|
||||
assert.strictEqual(totalPrice.counterparty, counter.counterparty)
|
||||
})
|
||||
},
|
||||
|
||||
'direction is correct for bids and asks': async (api, address) => {
|
||||
const response = await api.getOrderbook(
|
||||
address,
|
||||
requests.getOrderbook.normal
|
||||
)
|
||||
assert(response.bids.every(bid => bid.specification.direction === 'buy'))
|
||||
assert(response.asks.every(ask => ask.specification.direction === 'sell'))
|
||||
}
|
||||
}
|
||||
34
test/api/getOrders/index.ts
Normal file
34
test/api/getOrders/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
|
||||
export const config = {
|
||||
// TODO: The mock server right now returns a hard-coded string, no matter
|
||||
// what "Account" value you pass. We'll need it to support more accurate
|
||||
// responses before we can turn these tests on.
|
||||
skipXAddress: true
|
||||
}
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getOrders': async (api, address) => {
|
||||
const result = await api.getOrders(address)
|
||||
assertResultMatch(result, responses.getOrders, 'getOrders')
|
||||
},
|
||||
|
||||
'getOrders - limit': async (api, address) => {
|
||||
const result = await api.getOrders(address, { limit: 20 })
|
||||
assertResultMatch(result, responses.getOrders, 'getOrders')
|
||||
},
|
||||
|
||||
'getOrders - invalid options': async (api, address) => {
|
||||
await assertRejects(
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
api.getOrders(address, { invalid: 'options' }),
|
||||
api.errors.ValidationError
|
||||
)
|
||||
}
|
||||
}
|
||||
95
test/api/getPaths/index.ts
Normal file
95
test/api/getPaths/index.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import assert from 'assert-diff'
|
||||
import { assertResultMatch, assertRejects, TestSuite } from '../../utils'
|
||||
import requests from '../../fixtures/requests'
|
||||
import responses from '../../fixtures/responses'
|
||||
import addresses from '../../fixtures/addresses.json'
|
||||
const { getPaths: REQUEST_FIXTURES } = requests
|
||||
const { getPaths: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'simple test': async api => {
|
||||
const response = await api.getPaths(REQUEST_FIXTURES.normal)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.XrpToUsd, 'getPaths')
|
||||
},
|
||||
'queuing': async api => {
|
||||
const [normalResult, usdOnlyResult, xrpOnlyResult] = await Promise.all([
|
||||
api.getPaths(REQUEST_FIXTURES.normal),
|
||||
api.getPaths(REQUEST_FIXTURES.UsdToUsd),
|
||||
api.getPaths(REQUEST_FIXTURES.XrpToXrp)
|
||||
])
|
||||
assertResultMatch(normalResult, RESPONSE_FIXTURES.XrpToUsd, 'getPaths')
|
||||
assertResultMatch(usdOnlyResult, RESPONSE_FIXTURES.UsdToUsd, 'getPaths')
|
||||
assertResultMatch(xrpOnlyResult, RESPONSE_FIXTURES.XrpToXrp, 'getPaths')
|
||||
},
|
||||
// @TODO
|
||||
// need decide what to do with currencies/XRP:
|
||||
// if add 'XRP' in currencies, then there will be exception in
|
||||
// xrpToDrops function (called from toRippledAmount)
|
||||
'getPaths USD 2 USD': async api => {
|
||||
const response = await api.getPaths(REQUEST_FIXTURES.UsdToUsd)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.UsdToUsd, 'getPaths')
|
||||
},
|
||||
'getPaths XRP 2 XRP': async api => {
|
||||
const response = await api.getPaths(REQUEST_FIXTURES.XrpToXrp)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.XrpToXrp, 'getPaths')
|
||||
},
|
||||
'source with issuer': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.issuer),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'XRP 2 XRP - not enough': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.XrpToXrpNotEnough),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'invalid PathFind': async api => {
|
||||
assert.throws(() => {
|
||||
api.getPaths(REQUEST_FIXTURES.invalid)
|
||||
}, /Cannot specify both source.amount/)
|
||||
},
|
||||
'does not accept currency': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.NotAcceptCurrency),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'no paths': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.NoPaths),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'no paths source amount': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.NoPathsSource),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'no paths with source currencies': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths(REQUEST_FIXTURES.NoPathsWithCurrencies),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
'error: srcActNotFound': async api => {
|
||||
return assertRejects(
|
||||
api.getPaths({
|
||||
...REQUEST_FIXTURES.normal,
|
||||
source: { address: addresses.NOTFOUND }
|
||||
}),
|
||||
api.errors.RippleError
|
||||
)
|
||||
},
|
||||
'send all': async api => {
|
||||
const response = await api.getPaths(REQUEST_FIXTURES.sendAll)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.sendAll, 'getPaths')
|
||||
}
|
||||
}
|
||||
44
test/api/getPaymentChannel/index.ts
Normal file
44
test/api/getPaymentChannel/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
const { getPaymentChannel: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getPaymentChannel': async (api, address) => {
|
||||
const channelId =
|
||||
'E30E709CF009A1F26E0E5C48F7AA1BFB79393764F15FB108BDC6E06D3CBD8415'
|
||||
const result = await api.getPaymentChannel(channelId)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES.normal, 'getPaymentChannel')
|
||||
},
|
||||
|
||||
'getPaymentChannel - full': async (api, address) => {
|
||||
const channelId =
|
||||
'D77CD4713AA08195E6B6D0E5BC023DA11B052EBFF0B5B22EDA8AE85345BCF661'
|
||||
const result = await api.getPaymentChannel(channelId)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES.full, 'getPaymentChannel')
|
||||
},
|
||||
|
||||
'getPaymentChannel - not found': async (api, address) => {
|
||||
const channelId =
|
||||
'DFA557EA3497585BFE83F0F97CC8E4530BBB99967736BB95225C7F0C13ACE708'
|
||||
await assertRejects(
|
||||
api.getPaymentChannel(channelId),
|
||||
api.errors.RippledError,
|
||||
'entryNotFound'
|
||||
)
|
||||
},
|
||||
|
||||
'getPaymentChannel - wrong type': async (api, address) => {
|
||||
const channelId =
|
||||
'8EF9CCB9D85458C8D020B3452848BBB42EAFDDDB69A93DD9D1223741A4CA562B'
|
||||
await assertRejects(
|
||||
api.getPaymentChannel(channelId),
|
||||
api.errors.NotFoundError,
|
||||
'Payment channel ledger entry not found'
|
||||
)
|
||||
}
|
||||
}
|
||||
48
test/api/getServerInfo/index.ts
Normal file
48
test/api/getServerInfo/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite, assertRejects } from '../../utils'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'default': async (api, address) => {
|
||||
const serverInfo = await api.getServerInfo()
|
||||
assertResultMatch(serverInfo, responses.getServerInfo, 'getServerInfo')
|
||||
},
|
||||
|
||||
'error': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: { returnErrorOnServerInfo: true }
|
||||
})
|
||||
)
|
||||
try {
|
||||
await api.getServerInfo()
|
||||
throw new Error('Should throw NetworkError')
|
||||
} catch (err) {
|
||||
assert(err instanceof api.errors.RippledError)
|
||||
assert.equal(err.message, 'You are placing too much load on the server.')
|
||||
assert.equal(err.data.error, 'slowDown')
|
||||
}
|
||||
},
|
||||
|
||||
'no validated ledger': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: { serverInfoWithoutValidated: true }
|
||||
})
|
||||
)
|
||||
const serverInfo = await api.getServerInfo()
|
||||
assert.strictEqual(serverInfo.networkLedger, 'waiting')
|
||||
},
|
||||
|
||||
'getServerInfo - offline': async (api, address) => {
|
||||
await api.disconnect()
|
||||
return assertRejects(api.getServerInfo(), api.errors.NotConnectedError)
|
||||
}
|
||||
}
|
||||
28
test/api/getSettings/index.ts
Normal file
28
test/api/getSettings/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
const { getSettings: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getSettings': async (api, address) => {
|
||||
const result = await api.getSettings(address)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES, 'getSettings')
|
||||
},
|
||||
|
||||
'getSettings - options undefined': async (api, address) => {
|
||||
const result = await api.getSettings(address, undefined)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES, 'getSettings')
|
||||
},
|
||||
|
||||
'getSettings - invalid options': async (api, address) => {
|
||||
await assertRejects(
|
||||
// @ts-ignore - This is intentionally invalid
|
||||
api.getSettings(address, { invalid: 'options' }),
|
||||
api.errors.ValidationError
|
||||
)
|
||||
}
|
||||
}
|
||||
389
test/api/getTransaction/index.ts
Normal file
389
test/api/getTransaction/index.ts
Normal file
@@ -0,0 +1,389 @@
|
||||
import assert from 'assert-diff'
|
||||
import {
|
||||
MissingLedgerHistoryError,
|
||||
NotFoundError,
|
||||
UnexpectedError
|
||||
} from 'ripple-api/common/errors'
|
||||
import { PendingLedgerVersionError } from '../../../src/common/errors'
|
||||
import hashes from '../../fixtures/hashes.json'
|
||||
import responses from '../../fixtures/responses'
|
||||
import ledgerClosed from '../../fixtures/rippled/ledger-close-newer.json'
|
||||
import { assertRejects, assertResultMatch, TestSuite } from '../../utils'
|
||||
const { getTransaction: RESPONSE_FIXTURES } = responses
|
||||
|
||||
function closeLedger(connection) {
|
||||
connection._ws.emit('message', JSON.stringify(ledgerClosed))
|
||||
}
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'payment': async (api, address) => {
|
||||
const response = await api.getTransaction(hashes.VALID_TRANSACTION_HASH)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.payment, 'getTransaction')
|
||||
},
|
||||
|
||||
'payment - include raw transaction': async (api, address) => {
|
||||
const options = {
|
||||
includeRawTransaction: true
|
||||
}
|
||||
const response = await api.getTransaction(
|
||||
hashes.VALID_TRANSACTION_HASH,
|
||||
options
|
||||
)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.paymentIncludeRawTransaction,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'settings': async (api, address) => {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.settings, 'getTransaction')
|
||||
},
|
||||
|
||||
'settings - include raw transaction': async (api, address) => {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'
|
||||
const options = {
|
||||
includeRawTransaction: true
|
||||
}
|
||||
const expected = Object.assign({}, RESPONSE_FIXTURES.settings) // Avoid mutating test fixture
|
||||
expected.rawTransaction =
|
||||
'{"Account":"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe","Fee":"10","Flags":2147483648,"Sequence":1,"SetFlag":2,"SigningPubKey":"03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D","TransactionType":"AccountSet","TxnSignature":"3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226","date":460832270,"hash":"4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B","inLedger":8206418,"ledger_index":8206418,"meta":{"AffectedNodes":[{"ModifiedNode":{"FinalFields":{"Account":"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe","Balance":"29999990","Flags":786432,"OwnerCount":0,"Sequence":2},"LedgerEntryType":"AccountRoot","LedgerIndex":"3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F","PreviousFields":{"Balance":"30000000","Flags":4194304,"Sequence":1},"PreviousTxnID":"3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3","PreviousTxnLgrSeq":8206397}}],"TransactionIndex":5,"TransactionResult":"tesSUCCESS"},"validated":true}'
|
||||
const response = await api.getTransaction(hash, options)
|
||||
assertResultMatch(response, expected, 'getTransaction')
|
||||
},
|
||||
|
||||
'order': async (api, address) => {
|
||||
const hash =
|
||||
'10A6FB4A66EE80BED46AAE4815D7DC43B97E944984CCD5B93BCF3F8538CABC51'
|
||||
closeLedger(api.connection)
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.order, 'getTransaction')
|
||||
},
|
||||
|
||||
'sell order': async (api, address) => {
|
||||
const hash =
|
||||
'458101D51051230B1D56E9ACAFAA34451BF65FA000F95DF6F0FF5B3A62D83FC2'
|
||||
closeLedger(api.connection)
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.orderSell, 'getTransaction')
|
||||
},
|
||||
|
||||
'order cancellation': async (api, address) => {
|
||||
const hash =
|
||||
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'
|
||||
closeLedger(api.connection)
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.orderCancellation,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'order with expiration cancellation': async (api, address) => {
|
||||
const hash =
|
||||
'097B9491CC76B64831F1FEA82EAA93BCD728106D90B65A072C933888E946C40B'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.orderWithExpirationCancellation,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'trustline set': async (api, address) => {
|
||||
const hash =
|
||||
'635A0769BD94710A1F6A76CDE65A3BC661B20B798807D1BBBDADCEA26420538D'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.trustline, 'getTransaction')
|
||||
},
|
||||
|
||||
'trustline frozen off': async (api, address) => {
|
||||
const hash =
|
||||
'FE72FAD0FA7CA904FB6C633A1666EDF0B9C73B2F5A4555D37EEF2739A78A531B'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.trustlineFrozenOff,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'trustline no quality': async (api, address) => {
|
||||
const hash =
|
||||
'BAF1C678323C37CCB7735550C379287667D8288C30F83148AD3C1CB019FC9002'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.trustlineNoQuality,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'trustline add memo': async (api, address) => {
|
||||
const hash =
|
||||
'9D6AC5FD6545B2584885B85E36759EB6440CDD41B6C55859F84AFDEE2B428220'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.trustlineAddMemo,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'not validated': async (api, address) => {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10'
|
||||
await assertRejects(
|
||||
api.getTransaction(hash),
|
||||
NotFoundError,
|
||||
'Transaction not found'
|
||||
)
|
||||
},
|
||||
|
||||
'tracking on': async (api, address) => {
|
||||
const hash =
|
||||
'8925FC8844A1E930E2CC76AD0A15E7665AFCC5425376D548BB1413F484C31B8C'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.trackingOn, 'getTransaction')
|
||||
},
|
||||
|
||||
'tracking off': async (api, address) => {
|
||||
const hash =
|
||||
'C8C5E20DFB1BF533D0D81A2ED23F0A3CBD1EF2EE8A902A1D760500473CC9C582'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.trackingOff, 'getTransaction')
|
||||
},
|
||||
|
||||
'set regular key': async (api, address) => {
|
||||
const hash =
|
||||
'278E6687C1C60C6873996210A6523564B63F2844FB1019576C157353B1813E60'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.setRegularKey,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'not found in range': async (api, address) => {
|
||||
const hash =
|
||||
'809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E'
|
||||
const options = {
|
||||
minLedgerVersion: 32570,
|
||||
maxLedgerVersion: 32571
|
||||
}
|
||||
await assertRejects(api.getTransaction(hash, options), NotFoundError)
|
||||
},
|
||||
|
||||
'not found by hash': async (api, address) => {
|
||||
const hash = hashes.NOTFOUND_TRANSACTION_HASH
|
||||
|
||||
await assertRejects(api.getTransaction(hash), NotFoundError)
|
||||
},
|
||||
|
||||
'missing ledger history': async (api, address) => {
|
||||
const hash = hashes.NOTFOUND_TRANSACTION_HASH
|
||||
// make gaps in history
|
||||
closeLedger(api.connection)
|
||||
|
||||
await assertRejects(api.getTransaction(hash), MissingLedgerHistoryError)
|
||||
},
|
||||
|
||||
'missing ledger history with ledger range': async (api, address) => {
|
||||
const hash = hashes.NOTFOUND_TRANSACTION_HASH
|
||||
const options = {
|
||||
minLedgerVersion: 32569,
|
||||
maxLedgerVersion: 32571
|
||||
}
|
||||
await assertRejects(
|
||||
api.getTransaction(hash, options),
|
||||
MissingLedgerHistoryError
|
||||
)
|
||||
},
|
||||
|
||||
'not found - future maxLedgerVersion': async (api, address) => {
|
||||
const hash = hashes.NOTFOUND_TRANSACTION_HASH
|
||||
const options = {
|
||||
maxLedgerVersion: 99999999999
|
||||
}
|
||||
await assertRejects(
|
||||
api.getTransaction(hash, options),
|
||||
PendingLedgerVersionError,
|
||||
"maxLedgerVersion is greater than server's most recent validated ledger"
|
||||
)
|
||||
},
|
||||
|
||||
'transaction not validated': async (api, address) => {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'
|
||||
await assertRejects(
|
||||
api.getTransaction(hash),
|
||||
NotFoundError,
|
||||
/Transaction has not been validated yet/
|
||||
)
|
||||
},
|
||||
|
||||
'transaction ledger not found': async (api, address) => {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA12'
|
||||
await assertRejects(
|
||||
api.getTransaction(hash),
|
||||
NotFoundError,
|
||||
/ledger not found/
|
||||
)
|
||||
},
|
||||
|
||||
'ledger missing close time': async (api, address) => {
|
||||
const hash =
|
||||
'0F7ED9F40742D8A513AE86029462B7A6768325583DF8EE21B7EC663019DD6A04'
|
||||
closeLedger(api.connection)
|
||||
await assertRejects(api.getTransaction(hash), UnexpectedError)
|
||||
},
|
||||
|
||||
// Checks
|
||||
'CheckCreate': async (api, address) => {
|
||||
const hash =
|
||||
'605A2E2C8E48AECAF5C56085D1AEAA0348DC838CE122C9188F94EB19DA05C2FE'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.checkCreate, 'getTransaction')
|
||||
},
|
||||
|
||||
'CheckCancel': async (api, address) => {
|
||||
const hash =
|
||||
'B4105D1B2D83819647E4692B7C5843D674283F669524BD50C9614182E3A12CD4'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.checkCancel, 'getTransaction')
|
||||
},
|
||||
|
||||
'CheckCash': async (api, address) => {
|
||||
const hash =
|
||||
'8321208465F70BA52C28BCC4F646BAF3B012BA13B57576C0336F42D77E3E0749'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.checkCash, 'getTransaction')
|
||||
},
|
||||
|
||||
// Escrows
|
||||
'EscrowCreation': async (api, address) => {
|
||||
const hash =
|
||||
'144F272380BDB4F1BD92329A2178BABB70C20F59042C495E10BF72EBFB408EE1'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.escrowCreation,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'EscrowCancellation': async (api, address) => {
|
||||
const hash =
|
||||
'F346E542FFB7A8398C30A87B952668DAB48B7D421094F8B71776DA19775A3B22'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.escrowCancellation,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'EscrowExecution': async (api, address) => {
|
||||
const options = {
|
||||
minLedgerVersion: 10,
|
||||
maxLedgerVersion: 15
|
||||
}
|
||||
const hash =
|
||||
'CC5277137B3F25EE8B86259C83CB0EAADE818505E4E9BCBF19B1AC6FD136993B'
|
||||
const response = await api.getTransaction(hash, options)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.escrowExecution,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'EscrowExecution simple': async (api, address) => {
|
||||
const hash =
|
||||
'CC5277137B3F25EE8B86259C83CB0EAADE818505E4E9BCBF19B1AC6FD1369931'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.escrowExecutionSimple,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'PaymentChannelCreate': async (api, address) => {
|
||||
const hash =
|
||||
'0E9CA3AB1053FC0C1CBAA75F636FE1EC92F118C7056BBEF5D63E4C116458A16D'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.paymentChannelCreate,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'PaymentChannelFund': async (api, address) => {
|
||||
const hash =
|
||||
'CD053D8867007A6A4ACB7A432605FE476D088DCB515AFFC886CF2B4EB6D2AE8B'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.paymentChannelFund,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'PaymentChannelClaim': async (api, address) => {
|
||||
const hash =
|
||||
'81B9ECAE7195EB6E8034AEDF44D8415A7A803E14513FDBB34FA984AB37D59563'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.paymentChannelClaim,
|
||||
'getTransaction'
|
||||
)
|
||||
},
|
||||
|
||||
'no Meta': async (api, address) => {
|
||||
const hash =
|
||||
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'
|
||||
const response = await api.getTransaction(hash)
|
||||
assert.deepEqual(response, RESPONSE_FIXTURES.noMeta)
|
||||
},
|
||||
|
||||
'Unrecognized transaction type': async (api, address) => {
|
||||
const hash =
|
||||
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'
|
||||
closeLedger(api.connection)
|
||||
const response = await api.getTransaction(hash)
|
||||
assert.strictEqual(
|
||||
// @ts-ignore
|
||||
response.specification.UNAVAILABLE,
|
||||
'Unrecognized transaction type.'
|
||||
)
|
||||
},
|
||||
|
||||
'amendment': async (api, address) => {
|
||||
const hash =
|
||||
'A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.amendment)
|
||||
},
|
||||
|
||||
'feeUpdate': async (api, address) => {
|
||||
const hash =
|
||||
'C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.feeUpdate)
|
||||
}
|
||||
}
|
||||
168
test/api/getTransactions/index.ts
Normal file
168
test/api/getTransactions/index.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { RippleAPI } from 'ripple-api'
|
||||
import assert from 'assert-diff'
|
||||
import { assertResultMatch, TestSuite, assertRejects } from '../../utils'
|
||||
import responses from '../../fixtures/responses'
|
||||
import hashes from '../../fixtures/hashes.json'
|
||||
import addresses from '../../fixtures/addresses.json'
|
||||
const utils = RippleAPI._PRIVATE.ledgerUtils
|
||||
const { getTransactions: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'default': async (api, address) => {
|
||||
const options = { types: ['payment', 'order'], initiated: true, limit: 2 }
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.normal, 'getTransactions')
|
||||
},
|
||||
|
||||
'include raw transactions': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 2,
|
||||
includeRawTransactions: true
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
assertResultMatch(
|
||||
response,
|
||||
RESPONSE_FIXTURES.includeRawTransactions,
|
||||
'getTransactions'
|
||||
)
|
||||
},
|
||||
|
||||
'earliest first': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 2,
|
||||
earliestFirst: true
|
||||
}
|
||||
const expected = Array.from(RESPONSE_FIXTURES.normal as any[]).sort(
|
||||
utils.compareTransactions
|
||||
)
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assertResultMatch(response, expected, 'getTransactions')
|
||||
},
|
||||
|
||||
'earliest first with start option': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 2,
|
||||
start: hashes.VALID_TRANSACTION_HASH,
|
||||
earliestFirst: true
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
assert.strictEqual(response.length, 0)
|
||||
},
|
||||
|
||||
'gap': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 2,
|
||||
maxLedgerVersion: 348858000
|
||||
}
|
||||
return assertRejects(
|
||||
api.getTransactions(address, options),
|
||||
api.errors.MissingLedgerHistoryError
|
||||
)
|
||||
},
|
||||
|
||||
'tx not found': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 2,
|
||||
start: hashes.NOTFOUND_TRANSACTION_HASH,
|
||||
counterparty: address
|
||||
}
|
||||
return assertRejects(
|
||||
api.getTransactions(address, options),
|
||||
api.errors.NotFoundError
|
||||
)
|
||||
},
|
||||
|
||||
'filters': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: true,
|
||||
limit: 10,
|
||||
excludeFailures: true,
|
||||
counterparty: addresses.ISSUER
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assert.strictEqual(response.length, 10)
|
||||
response.forEach(t => assert(t.type === 'payment' || t.type === 'order'))
|
||||
response.forEach(t => assert(t.outcome.result === 'tesSUCCESS'))
|
||||
},
|
||||
|
||||
'filters for incoming': async (api, address) => {
|
||||
const options = {
|
||||
types: ['payment', 'order'],
|
||||
initiated: false,
|
||||
limit: 10,
|
||||
excludeFailures: true,
|
||||
counterparty: addresses.ISSUER
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assert.strictEqual(response.length, 10)
|
||||
response.forEach(t => assert(t.type === 'payment' || t.type === 'order'))
|
||||
response.forEach(t => assert(t.outcome.result === 'tesSUCCESS'))
|
||||
},
|
||||
|
||||
// this is the case where core.RippleError just falls
|
||||
// through the api to the user
|
||||
'error': async (api, address) => {
|
||||
const options = { types: ['payment', 'order'], initiated: true, limit: 13 }
|
||||
return assertRejects(
|
||||
api.getTransactions(address, options),
|
||||
api.errors.RippleError
|
||||
)
|
||||
},
|
||||
|
||||
// TODO: this doesn't test much, just that it doesn't crash
|
||||
'getTransactions with start option': async (api, address) => {
|
||||
const options = {
|
||||
start: hashes.VALID_TRANSACTION_HASH,
|
||||
earliestFirst: false,
|
||||
limit: 2
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.normal, 'getTransactions')
|
||||
},
|
||||
|
||||
'start transaction with zero ledger version': async (api, address) => {
|
||||
const options = {
|
||||
start: '4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA13',
|
||||
limit: 1
|
||||
}
|
||||
const response = await api.getTransactions(address, options)
|
||||
hack(response)
|
||||
assertResultMatch(response, [], 'getTransactions')
|
||||
},
|
||||
|
||||
'no options': async (api, address) => {
|
||||
const response = await api.getTransactions(addresses.OTHER_ACCOUNT)
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.one, 'getTransactions')
|
||||
}
|
||||
}
|
||||
|
||||
// This test relies on the binary (hex string) format, but computed fields like `date`
|
||||
// are not available in this format. To support this field, we need to 'hack' it into
|
||||
// the expected response. Long term, a better approach would be to use/test the json
|
||||
// format responses, instead of the binary.
|
||||
function hack(response) {
|
||||
response.forEach(element => {
|
||||
element.outcome.timestamp = "2019-04-01T07:39:01.000Z"
|
||||
})
|
||||
}
|
||||
31
test/api/getTrustlines/index.ts
Normal file
31
test/api/getTrustlines/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import addresses from '../../fixtures/addresses.json'
|
||||
import responses from '../../fixtures/responses'
|
||||
import { assertResultMatch, TestSuite } from '../../utils'
|
||||
const { getTrustlines: RESPONSE_FIXTURES } = responses
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
* - Check out the "TestSuite" type for documentation on the interface.
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'getTrustlines - filtered': async (api, address) => {
|
||||
const options = { currency: 'USD' }
|
||||
const result = await api.getTrustlines(address, options)
|
||||
assertResultMatch(result, RESPONSE_FIXTURES.filtered, 'getTrustlines')
|
||||
},
|
||||
|
||||
'getTrustlines - more than 400 items': async (api, address) => {
|
||||
const options = { limit: 401 }
|
||||
const result = await api.getTrustlines(addresses.THIRD_ACCOUNT, options)
|
||||
assertResultMatch(
|
||||
result,
|
||||
RESPONSE_FIXTURES.moreThan400Items,
|
||||
'getTrustlines'
|
||||
)
|
||||
},
|
||||
|
||||
'getTrustlines - no options': async (api, address) => {
|
||||
await api.getTrustlines(address)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user