Compare commits

...

18 Commits

Author SHA1 Message Date
Elliot Lee
860f6a6cd8 Release 1.0.0-beta.3 2018-07-17 22:39:00 -07:00
Elliot Lee
7a928804ec feat: add payment channel details to tx (2) (#920)
* Bump transactionparser to v0.7.1 and update output schema
* yarn docgen for required `threshold` and `weights`
2018-07-17 22:22:07 -07:00
adrianhopebailie
14444bea3f feat: add payment channel details to tx 2018-07-17 22:20:47 -07:00
Rome Reginelli
47a139fdff Fix text/plain MIMETYPE in examples with memos (#914) 2018-07-03 17:18:46 -07:00
Brandon Wilson
4e30b9b2fa Require threshold and weights in signers settings (#909)
Fixes #908
2018-07-02 16:27:03 -07:00
Elliot Lee
2112d4c0b3 Round XRP fee to 6 decimal places (#912)
* Round XRP fee to 6 decimal places

Fix #911
2018-06-28 17:59:00 -07:00
Elliot Lee
1d1132b7fa Release 1.0.0-beta.2 2018-06-08 08:11:00 -07:00
Elliot Lee
e07fa11923 Maximum fee values (#902)
* Add maxFeeXRP (default 2 XRP) as an optional RippleAPI constructor parameter
  - No calculated or specified fee can exceed this value
  - If the fee exceeds 2 XRP, throw a ValidationError
* sign() - throw ValidationError when Fee exceeds maxFeeXRP
* Document getFee parameters
* Explain new fee limits in HISTORY.md
* Deprecate `maxFee`
2018-06-07 23:29:24 -07:00
Elliot Lee
7c92adbf45 Release 1.0.0-beta.1 2018-05-24 20:13:26 -07:00
Elliot Lee
d55aa2339f Improve docs (raw order data) 2018-05-24 20:02:45 -07:00
Elliot Lee
95e39153da Bump node version to v8 2018-05-24 19:29:09 -07:00
Daniel Davis
65d8260908 Updated yarn command (#900)
`yarn install` deprecated in favor of `yarn add`
2018-05-23 23:05:09 -07:00
Elliot Lee
1aa9feda71 Allow specifying amounts in drops (#892)
* Accept "drops" in lieu of "XRP"
* Export xrpToDrops() and dropsToXrp()
* Throw our own validation errors instead of BigNumber Errors
2018-05-21 13:19:18 -07:00
Fred K. Schott
2e5b435b11 add server_info request typing (#895) 2018-05-17 16:37:17 -07:00
Elliot Lee
54f12862dc Improve errors (#893)
- `RippledError`: Include the full response from the `rippled` server.
- A new test ensures correct behavior when `streams` is not an array.
- `NotConnectedError` may be thrown with a different message than before.
2018-05-12 09:38:49 -07:00
Elliot Lee
226ef862ae Link to mailing lists 2018-05-10 16:25:05 -07:00
Elliot Lee
4a0d675726 Release 1.0.0-beta.0 2018-05-10 15:56:23 -07:00
Elliot Lee
b2b6715ac0 Add request(), hasNextPage(), and requestNextPage() (#887)
* Add support for all rippled APIs, including subscriptions.
* Add support for arbitrary stream message types.
* Note that rippled APIs take amounts in drops.
* request() will be available in ripple-lib version 1.0.0+
2018-05-10 15:43:56 -07:00
81 changed files with 1577 additions and 238 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v6 v8

View File

@@ -1,5 +1,107 @@
# ripple-lib Release History # ripple-lib Release History
## 1.0.0-beta.3 (2018-07-17)
+ For payment channel transactions, `getTransaction` includes a new
`channelChanges` property that describes the details of the payment channel.
(#920)
### Bug Fixes
+ A bug caused calculated fees to use too many decimal places. This was fixed by
rounding fees to 6 decimal places. (#912)
+ When using the Settings transaction to set up a multi-signing list, the
threshold and weights fields are required. (#909)
+ Docs: Fix the MIMETYPE in examples with memos. (#914)
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
460dbb521e24c44cb53dabc1a74feeca33d031b44d889dd5b51103ca92d51de6 ripple-1.0.0-beta.3-debug.js
cccfd24973c6b7990d9e933a589175dae26249825737fff4f2f73d8558a3f186 ripple-1.0.0-beta.3-min.js
0dc456a58fb078347d9920310621595905085595d73c2b8fe96bea73bcf35450 ripple-1.0.0-beta.3.js
```
## 1.0.0-beta.2 (2018-06-08)
### Breaking Changes
+ During transaction preparation, there is now a maximum fee. Also, when a transaction is signed, its fee is checked and an error is thrown if the fee exceeds the maximum. The default `maxFeeXRP` is `'2'` (2 XRP). Override this value in the RippleAPI constructor.
+ Attempting to prepare a transaction with an exact `fee` higher than `maxFeeXRP` causes a `ValidationError` to be thrown.
+ Attempting to sign a transaction with a fee higher than `maxFeeXRP` causes a `ValidationError` to be thrown.
+ The value returned by `getFee()` is capped at `maxFeeXRP`.
### Other Changes
+ In Transaction Instructions, the `maxFee` parameter is deprecated. Use the `maxFeeXRP` parameter in the RippleAPI constructor.
#### Overview of new fee limit
Most users of ripple-lib do not need to make any code changes to accommodate the new soft limit on fees. The limit is designed to protect against the most severe cases where an unintentionally high fee may be used.
+ When having ripple-lib provide the fee with a `prepare*` method, a maximum fee of `maxFeeXRP` (default 2 XRP) applies. You can prepare more economical transactions by setting a lower `maxFeeXRP`, or support high-priority transactions by setting a higher `maxFeeXRP` in the RippleAPI constructor.
+ When using `sign` with a Fee higher than `maxFeeXRP`, a `ValidationError` is thrown.
If you have any questions or concerns, please open an issue on GitHub.
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
ef348a2805098e61395b689b410cbf4bfd35e4d72e38c89f4ab74ec5e19793f5 ripple-1.0.0-beta.2-debug.js
ea33fd53df8c7176d5fbf52dae0b64aade7180860f26449062cdbefaf8bd4d9b ripple-1.0.0-beta.2-min.js
fe5cc6e97c9b8a1470dacb34f16a64255cd639a25381abe9db1ba79e102456f2 ripple-1.0.0-beta.2.js
```
## 1.0.0-beta.1 (2018-05-24)
### Breaking Changes
+ Amounts in drops and XRP are checked for validity. Some
methods may now throw a `BigNumber Error` or `ValidationError` if the amount
is invalid. This may include methods that previously did not throw.
+ Note that 1 drop is equivalent to 0.000001 XRP and 1 XRP is equivalent to 1,000,000 drops.
+ Using drops is recommended. All rippled APIs require XRP amounts to be
expressed in drops.
### Other Changes
+ Allow specifying amounts in drops for consistency with the `rippled`
APIs.
+ Export `xrpToDrops()` and `dropsToXrp()` functions.
+ Potentially breaking change: Improve errors. For example, `RippledError` now includes the full response from
the `rippled` server ([#687](https://github.com/ripple/ripple-lib/issues/687)). `NotConnectedError`
may be thrown with a different message than before.
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
a80ebb39e186640246306eadb2879147458c8271fd3c6cb32e6ef78d0b4b01a5 ripple-1.0.0-beta.1-debug.js
81bcc4b5fd6fd52220ed151242eaddd63eb29c4078845edc68f65b769557d126 ripple-1.0.0-beta.1-min.js
738b4d65b58cf4e3542fa396f8d319a24cd7d0b7aff5ff629a900e244f735ff4 ripple-1.0.0-beta.1.js
```
## 1.0.0-beta.0 (2018-05-10)
+ [Add `request`, `hasNextPage`, and
`requestNextPage`](https://github.com/ripple/ripple-lib/pull/887).
+ This provides support for all rippled APIs, including subscriptions.
When using rippled APIs, you must:
+ For all XRP amounts, use drops (1 drop = 0.000001 XRP).
+ Instead of `counterparty`, use `issuer`.
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
ab2094979a3d6b320c7bc22bc5946c50fa5e29af0976d352e7689b0a4d840c55 ripple-1.0.0-beta.0-debug.js
0e7f7d740606c2866ebf63776b13b41a555848e1a1419e2c8058d2e6c562d7fd ripple-1.0.0-beta.0-min.js
bd05e8806832ca4192aea7ba2d0362baa9f44605f8e8e6676acd25eb0b94b778 ripple-1.0.0-beta.0.js
```
## 0.22.0 (2018-05-10) ## 0.22.0 (2018-05-10)
+ [`getOrderbook` - return raw order data](https://github.com/ripple/ripple-lib/pull/886). The full `BookOffer` data is now provided under `data`. + [`getOrderbook` - return raw order data](https://github.com/ripple/ripple-lib/pull/886). The full `BookOffer` data is now provided under `data`.

View File

@@ -26,7 +26,17 @@ Install `ripple-lib`:
$ yarn add 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) 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).
### Mailing lists
We have a low-traffic mailing list for announcements of new ripple-lib releases. (About 1 email per week)
+ [Subscribe to ripple-lib-announce](https://groups.google.com/forum/#!forum/ripple-lib-announce)
If you're using the XRP Ledger in production, you should run a [rippled server](https://github.com/ripple/rippled) and subscribe to the ripple-server mailing list as well.
+ [Subscribe to ripple-server](https://groups.google.com/forum/#!forum/ripple-server)
## Running tests ## Running tests

View File

@@ -33,6 +33,11 @@
- [Payment Channel Create](#payment-channel-create) - [Payment Channel Create](#payment-channel-create)
- [Payment Channel Fund](#payment-channel-fund) - [Payment Channel Fund](#payment-channel-fund)
- [Payment Channel Claim](#payment-channel-claim) - [Payment Channel Claim](#payment-channel-claim)
- [rippled APIs](#rippled-apis)
- [Listening to streams](#listening-to-streams)
- [request](#request)
- [hasNextPage](#hasnextpage)
- [requestNextPage](#requestnextpage)
- [API Methods](#api-methods) - [API Methods](#api-methods)
- [connect](#connect) - [connect](#connect)
- [disconnect](#disconnect) - [disconnect](#disconnect)
@@ -84,7 +89,7 @@
# Introduction # Introduction
RippleAPI 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.
Using RippleAPI, you can: Using RippleAPI, you can:
* [Query transactions from the XRP Ledger history](#gettransaction) * [Query transactions from the XRP Ledger history](#gettransaction)
@@ -93,8 +98,6 @@ Using RippleAPI, you can:
* [Generate a new XRP Ledger Address](#generateaddress) * [Generate a new XRP Ledger Address](#generateaddress)
* ... and [much more](#api-methods). * ... and [much more](#api-methods).
RippleAPI only provides access to *validated*, *immutable* transaction data.
## Boilerplate ## Boilerplate
Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) to wrap your custom code using RippleAPI. Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_code) to wrap your custom code using RippleAPI.
@@ -149,6 +152,7 @@ authorization | string | *Optional* Username and password for HTTP basic authent
certificate | string | *Optional* A string containing the certificate key of the client in PEM format. (Can be an array of certificates). certificate | string | *Optional* A string containing the certificate key of the client in PEM format. (Can be an array of certificates).
feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`. feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`.
key | string | *Optional* A string containing the private key of the client in PEM format. (Can be an array of keys). key | string | *Optional* A string containing the private key of the client in PEM format. (Can be an array of keys).
maxFeeXRP | string | *Optional* Maximum fee to use with transactions, in XRP. Must be a string-encoded number. Defaults to `'2'`.
passphrase | string | *Optional* The passphrase for the private key of the client. passphrase | string | *Optional* The passphrase for the private key of the client.
proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server. proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server.
proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**. proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**.
@@ -164,7 +168,7 @@ If you omit the `server` parameter, RippleAPI operates [offline](#offline-functi
1. Install [Node.js](https://nodejs.org) and [Yarn](https://yarnpkg.com/en/docs/install). Most Linux distros have a package for Node.js; check that it's the version you want. 1. Install [Node.js](https://nodejs.org) and [Yarn](https://yarnpkg.com/en/docs/install). Most Linux distros have a package for Node.js; check that it's the version you want.
2. Use yarn to install RippleAPI: 2. Use yarn to install RippleAPI:
`yarn install ripple-lib` `yarn add ripple-lib`
After you have installed ripple-lib, you can create scripts using the [boilerplate](#boilerplate) and run them using the Node.js executable, typically named `node`: After you have installed ripple-lib, you can create scripts using the [boilerplate](#boilerplate) and run them using the Node.js executable, typically named `node`:
@@ -218,14 +222,13 @@ Currencies are represented as either 3-character currency codes or 40-character
## Value ## Value
A *value* is a quantity of a currency represented as a decimal string. Be careful: JavaScript's native number format does not have sufficient precision to represent all values. XRP has different precision from other currencies. A *value* is a quantity of a currency represented as a decimal string. Be careful: JavaScript's native number format does not have sufficient precision to represent all values. XRP has different precision from other currencies.
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11). **XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). This smallest unit is called a "drop". XRP has a maximum value of `100000000000` (1e11). Some RippleAPI methods accept XRP in order to maintain compatibility with older versions of the API. For consistency with the `rippled` APIs, we recommend formally specifying XRP values in *drops* in all API requests, and converting them to XRP for display. This is similar to Bitcoin's *satoshis* and Ethereum's *wei*. 1 XRP = 1,000,000 drops.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`. **Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount ## Amount
Example amount: Example 100.00 USD amount:
```json ```json
{ {
@@ -235,15 +238,16 @@ Example amount:
} }
``` ```
Example XRP amount: Example 3.0 XRP amount, in drops:
```json ```json
{ {
"currency": "XRP", "currency": "drops",
"value": "2000" "value": "3000000"
} }
``` ```
(Requires `ripple-lib` version 1.0.0 or higher.)
An *amount* is data structure representing a currency, a quantity of that currency, and the counterparty on the trustline that holds the value. For XRP, there is no counterparty. An *amount* is an object specifying a currency, a quantity of that currency, and the counterparty (issuer) on the trustline that holds the value. For XRP, there is no counterparty.
A *lax amount* allows the counterparty to be omitted for all currencies. If the counterparty is not specified in an amount within a transaction specification, then any counterparty may be used for that amount. A *lax amount* allows the counterparty to be omitted for all currencies. If the counterparty is not specified in an amount within a transaction specification, then any counterparty may be used for that amount.
@@ -253,8 +257,8 @@ A *balance* is an amount than can have a negative value.
Name | Type | Description Name | Type | Description
---- | ---- | ----------- ---- | ---- | -----------
currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies currency | [currency](#currency) | The three-character code or hexadecimal string used to denote currencies, or "drops" for the smallest unit of XRP.
counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds (omitted if `currency` is "XRP") counterparty | [address](#address) | *Optional* The Ripple address of the account that owes or is owed the funds (omitted if `currency` is "XRP" or "drops")
value | [value](#value) | *Optional* The quantity of the currency, denoted as a string to retain floating point precision value | [value](#value) | *Optional* The quantity of the currency, denoted as a string to retain floating point precision
# Transaction Overview # Transaction Overview
@@ -313,14 +317,14 @@ Transaction instructions indicate how to execute a transaction, complementary wi
Name | Type | Description Name | Type | Description
---- | ---- | ----------- ---- | ---- | -----------
fee | [value](#value) | *Optional* An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information. fee | [value](#value) | *Optional* An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
maxFee | [value](#value) | *Optional* The maximum fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information. maxFee | [value](#value) | *Optional* Deprecated: Use `maxFeeXRP` in the RippleAPI constructor instead. The maximum fee to pay for this transaction. If this exceeds `maxFeeXRP`, `maxFeeXRP` will be used instead. See [Transaction Fees](#transaction-fees) for more information.
maxLedgerVersion | integer,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version. maxLedgerVersion | integer,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version.
maxLedgerVersion | string,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version. maxLedgerVersion | string,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version.
maxLedgerVersionOffset | integer | *Optional* Offset from current validated ledger version to highest ledger version that the transaction can be included in. maxLedgerVersionOffset | integer | *Optional* Offset from current validated ledger version to highest ledger version that the transaction can be included in.
sequence | [sequence](#account-sequence-number) | *Optional* The initiating account's sequence number for this transaction. sequence | [sequence](#account-sequence-number) | *Optional* The initiating account's sequence number for this transaction.
signersCount | integer | *Optional* Number of signers that will be signing this transaction. signersCount | integer | *Optional* Number of signers that will be signing this transaction.
We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare*" method. We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
## Transaction ID ## Transaction ID
@@ -426,7 +430,7 @@ ripplingDisabled | boolean | *Optional* If true, payments cannot ripple through
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]
@@ -462,10 +466,10 @@ passive | boolean | *Optional* If enabled, the offer will not consume offers tha
"value": "10.1" "value": "10.1"
}, },
"totalPrice": { "totalPrice": {
"currency": "XRP", "currency": "drops",
"value": "2" "value": "2000000"
}, },
"passive": true, "passive": false,
"fillOrKill": true "fillOrKill": true
} }
``` ```
@@ -512,8 +516,8 @@ regularKey | [address](#address),null | *Optional* The public key of a new keypa
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances. requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances.
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag. requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning. signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | *Optional* 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.* 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 | array | Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight. *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) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold. *signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
@@ -528,7 +532,7 @@ transferRate | number,null | *Optional* The fee to charge when users transfer t
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]
@@ -629,8 +633,8 @@ invoiceID | string | *Optional* 256-bit hash, as a 64-character hexadecimal stri
{ {
"destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", "destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"sendMax": { "sendMax": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
} }
} }
``` ```
@@ -670,8 +674,8 @@ deliverMin | [laxAmount](#amount) | *Optional* Redeem the Check for at least thi
```json ```json
{ {
"amount": { "amount": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
}, },
"checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334" "checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334"
} }
@@ -750,6 +754,185 @@ 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).
* 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://ripple.com/build/rippled-apis/#specifying-currency-amounts). 1 XRP = 1000000 drops.
## Listening to streams
The `rippled` server can push updates to your client when various events happen. Refer to [Subscriptions in the `rippled` API docs](https://ripple.com/build/rippled-apis/#subscriptions) for details.
Note that the `streams` parameter for generic streams takes an array. For example, to subscribe to the `validations` stream, use `{ streams: [ 'validations' ] }`.
The string names of some generic streams to subscribe to are in the table below. (Refer to `rippled` for an up-to-date list of streams.)
Type | Description
---- | -----------
`server` | Sends a message whenever the status of the `rippled` server (for example, network connectivity) changes.
`ledger` | Sends a message whenever the consensus process declares a new validated ledger.
`transactions` | Sends a message whenever a transaction is included in a closed ledger.
`transactions_proposed` | Sends a message whenever a transaction is included in a closed ledger, as well as some transactions that have not yet been included in a validated ledger and may never be. Not all proposed transactions appear before validation. Even some transactions that don't succeed are included in validated ledgers because they take the anti-spam transaction fee.
`validations` | Sends a message whenever the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
`manifests` | Sends a message whenever the server receives a manifest.
`peer_status` | (Admin-only) Information about connected peer `rippled` servers, especially with regards to the consensus process.
When you subscribe to a stream, you must also listen to the relevant message type(s). Some of the available message types are in the table below. (Refer to `rippled` for an up-to-date list of message types.)
Type | Description
---- | -----------
`ledgerClosed` | Sent by the `ledger` stream when the consensus process declares a new fully validated ledger. The message identifies the ledger and provides some information about its contents.
`validationReceived` | Sent by the `validations` stream when the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
`manifestReceived` | Sent by the `manifests` stream when the server receives a manifest.
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://ripple.com/build/rippled-apis/#transaction-streams) for details.
`peerStatusChange` | (Admin-only) Reports a large amount of information on the activities of other `rippled` servers to which the server is connected.
To register your listener function, use `connection.on(type, handler)`.
Here is an example of listening for transactions on given account(s):
```
const account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // Replace with the account you want notifications for
api.connect().then(() => { // Omit this if you are already connected
// 'transaction' can be replaced with the relevant `type` from the table above
api.connection.on('transaction', (event) => {
// Do something useful with `event`
console.log(JSON.stringify(event, null, 2))
})
api.request('subscribe', {
accounts: [ account ]
}).then(response => {
if (response.status === 'success') {
console.log('Successfully subscribed')
}
}).catch(error => {
// Handle `error`
})
})
```
The subscription ends when you unsubscribe or the WebSocket connection is closed.
For full details, see [rippled Subscriptions](https://ripple.com/build/rippled-apis/#subscriptions).
## request
`request(command: string, options: object): Promise<object>`
Returns the response from invoking the specified command, with the specified options, on the connected rippled server.
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).
### Return Value
This method returns a promise that resolves with the response from rippled.
### Example
```javascript
// Replace 'ledger' with your desired rippled command
return api.request('ledger', {
ledger_index: 'validated'
}).then(response => {
/* Do something useful with response */
console.log(JSON.stringify(response, null, 2))
}).catch(console.error);
```
```json
{
"ledger": {
"accepted": true,
"account_hash": "F9E9653EA76EA0AEA58AC98A8E19EDCEC8299C2940519A190674FFAED3639A1F",
"close_flags": 0,
"close_time": 577999430,
"close_time_human": "2018-Apr-25 19:23:50",
"close_time_resolution": 10,
"closed": true,
"hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_index": "38217406",
"parent_close_time": 577999422,
"parent_hash": "B8B364C63EB9E13FDB89CB729FEF833089B8438CBEB8FC41744CB667209221B3",
"seqNum": "38217406",
"totalCoins": "99992286058637091",
"total_coins": "99992286058637091",
"transaction_hash": "5BDD3D2780C28FB2C91C3404BD8ED04786B764B1E18CF319888EDE2C09834726"
},
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_index": 38217406,
"validated": true
}
```
## hasNextPage
`hasNextPage(currentResponse): boolean`
Returns `true` when there are more pages available.
When there are more results than contained in the response, the response includes a `marker` field. You can use this convenience method, or check for `marker` yourself.
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
### Return Value
This method returns `true` if `currentResponse` includes a `marker`.
### Example
```javascript
return api.request('ledger_data', {
ledger_index: 'validated'
}).then(response => {
/* Do something useful with response */
if (api.hasNextPage(response)) {
/* There are more pages available */
}
}).catch(console.error);
```
## requestNextPage
`requestNextPage(command: string, params: object = {}, currentResponse: object): Promise<object>`
Requests the next page of data.
You can use this convenience method, or include `currentResponse.marker` in `params` yourself, when using `request`.
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
### Return Value
This method returns a promise that resolves with the next page of data from rippled.
If the response does not have a next page, the promise will reject with `new errors.NotFoundError('response does not have a next page')`.
### Example
```javascript
const command = 'ledger_data'
const params = {
ledger_index: 'validated'
}
return api.request(command, params).then(response => {
return api.requestNextPage(command, params, response)
}).then(response_page_2 => {
/* Do something useful with second page of response */
}).catch(console.error);
```
# API Methods # API Methods
## connect ## connect
@@ -894,7 +1077,9 @@ Returns the estimated transaction fee for the rippled server the RippleAPI insta
### Parameters ### Parameters
This method has no parameters. Name | Type | Description
---- | ---- | -----------
cushion | number | *Optional* The fee is the product of the base fee, the `load_factor`, and this cushion. Default is provided by the `RippleAPI` constructor's `feeCushion`.
### Return Value ### Return Value
@@ -968,8 +1153,8 @@ specification | object | A specification that would produce the same outcome as
outcome | object | The outcome of the transaction (what effects it had). outcome | object | The outcome of the transaction (what effects it had).
*outcome.* result | string | Result code returned by rippled. See [Transaction Results](https://ripple.com/build/transactions/#full-transaction-response-list) for a complete list. *outcome.* result | string | Result code returned by rippled. See [Transaction Results](https://ripple.com/build/transactions/#full-transaction-response-list) for a complete list.
*outcome.* fee | [value](#value) | The XRP fee that was charged for the transaction. *outcome.* fee | [value](#value) | The XRP fee that was charged for the transaction.
*outcome.balanceChanges.* \* | array\<[balance](#amount)\> | Key is the ripple address; value is an array of signed amounts representing changes of balances for that address. *outcome.balanceChanges.* \* | array\<[balance](#amount)\> | Key is the XRP Ledger address; value is an array of signed amounts representing changes of balances for that address.
*outcome.orderbookChanges.* \* | array | Key is the maker's ripple address; value is an array of changes *outcome.orderbookChanges.* \* | array | Key is the maker's XRP Ledger address; value is an array of changes
*outcome.orderbookChanges.* \*[] | object | A change to an order. *outcome.orderbookChanges.* \*[] | object | A change to an order.
*outcome.orderbookChanges.\*[].* direction | string | Equal to "buy" for buy orders and "sell" for sell orders. *outcome.orderbookChanges.\*[].* direction | string | Equal to "buy" for buy orders and "sell" for sell orders.
*outcome.orderbookChanges.\*[].* quantity | [amount](#amount) | The amount to be bought or sold by the maker. *outcome.orderbookChanges.\*[].* quantity | [amount](#amount) | The amount to be bought or sold by the maker.
@@ -981,6 +1166,7 @@ outcome | object | The outcome of the transaction (what effects it had).
*outcome.* ledgerVersion | integer | The ledger version that the transaction was validated in. *outcome.* ledgerVersion | integer | The ledger version that the transaction was validated in.
*outcome.* ledgerVersion | string | The ledger version that the transaction was validated in. *outcome.* ledgerVersion | string | The ledger version that the transaction was validated in.
*outcome.* indexInLedger | integer | The ordering index of the transaction in the ledger. *outcome.* indexInLedger | integer | The ordering index of the transaction in the ledger.
*outcome.* channelChanges | object | *Optional* Properties reflecting the details of the payment channel.
*outcome.* deliveredAmount | [amount](#amount) | *Optional* For payment transactions, it is impossible to reliably compute the actual delivered amount from the balanceChanges due to fixed precision. If the payment is not a partial payment and the transaction succeeded, the deliveredAmount should always be considered to be the amount specified in the transaction. *outcome.* deliveredAmount | [amount](#amount) | *Optional* For payment transactions, it is impossible to reliably compute the actual delivered amount from the balanceChanges due to fixed precision. If the payment is not a partial payment and the transaction succeeded, the deliveredAmount should always be considered to be the amount specified in the transaction.
*outcome.* timestamp | date-time string | *Optional* The timestamp when the transaction was validated. (May be missing when requesting transactions in binary mode.) *outcome.* timestamp | date-time string | *Optional* The timestamp when the transaction was validated. (May be missing when requesting transactions in binary mode.)
@@ -2288,9 +2474,9 @@ asks[] | object | An order in the order book.
*asks[].state.* fundedAmount | [amount](#amount) | How much of the amount the maker would have to pay that the maker currently holds. *asks[].state.* fundedAmount | [amount](#amount) | How much of the amount the maker would have to pay that the maker currently holds.
*asks[].state.* priceOfFundedAmount | [amount](#amount) | How much the `fundedAmount` would convert to through the exchange rate of this order. *asks[].state.* priceOfFundedAmount | [amount](#amount) | How much the `fundedAmount` would convert to through the exchange rate of this order.
### New in ripple-lib 0.22.0 and higher ### Raw order data
The response includes a `data` property containing the raw order data. This may include `owner_funds`, `Flags`, and other fields. (Requires ripple-lib 0.22.0 or higher.) The response includes a `data` property containing the raw order data. This may include `owner_funds`, `Flags`, and other fields.
For details, see the rippled method [book_offers](https://ripple.com/build/rippled-apis/#book-offers). For details, see the rippled method [book_offers](https://ripple.com/build/rippled-apis/#book-offers).
@@ -3381,8 +3567,8 @@ regularKey | [address](#address),null | *Optional* The public key of a new keypa
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances. requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances.
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag. requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning. signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | *Optional* 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.* 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 | array | Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight. *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) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold. *signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
@@ -4032,7 +4218,7 @@ const trustline = {
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]
@@ -4044,7 +4230,7 @@ return api.prepareTrustline(address, trustline).then(prepared =>
```json ```json
{ {
"txJSON": "{\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10000\"},\"Flags\":2149711872,\"QualityIn\":910000000,\"QualityOut\":870000000,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10000\"},\"Flags\":2149711872,\"QualityIn\":910000000,\"QualityOut\":870000000,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,
@@ -4089,6 +4275,8 @@ instructions | object | The instructions for how to execute the transaction afte
```javascript ```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'; const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
// Buy 10.10 USD (of the specified issuer) for 2.0 XRP (2000000 drops), fill or kill.
const order = { const order = {
"direction": "buy", "direction": "buy",
"quantity": { "quantity": {
@@ -4097,10 +4285,10 @@ const order = {
"value": "10.1" "value": "10.1"
}, },
"totalPrice": { "totalPrice": {
"currency": "XRP", "currency": "drops",
"value": "2" "value": "2000000"
}, },
"passive": true, "passive": false,
"fillOrKill": true "fillOrKill": true
}; };
return api.prepareOrder(address, order) return api.prepareOrder(address, order)
@@ -4110,7 +4298,7 @@ return api.prepareOrder(address, order)
```json ```json
{ {
"txJSON": "{\"Flags\":2147811328,\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":\"2000000\",\"TakerPays\":{\"value\":\"10.1\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"Flags\":2147745792,\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":\"2000000\",\"TakerPays\":{\"value\":\"10.1\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,
@@ -4213,7 +4401,7 @@ const settings = {
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]
@@ -4229,7 +4417,7 @@ return api.prepareSettings(address, settings)
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]
@@ -4616,8 +4804,8 @@ const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const checkCreate = { const checkCreate = {
"destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", "destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"sendMax": { "sendMax": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
} }
}; };
return api.prepareCheckCreate(address, checkCreate).then(prepared => return api.prepareCheckCreate(address, checkCreate).then(prepared =>
@@ -4729,8 +4917,8 @@ instructions | object | The instructions for how to execute the transaction afte
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'; const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const checkCash = { const checkCash = {
"amount": { "amount": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
}, },
"checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334" "checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334"
}; };

View File

@@ -19,14 +19,13 @@ Currencies are represented as either 3-character currency codes or 40-character
## Value ## Value
A *value* is a quantity of a currency represented as a decimal string. Be careful: JavaScript's native number format does not have sufficient precision to represent all values. XRP has different precision from other currencies. A *value* is a quantity of a currency represented as a decimal string. Be careful: JavaScript's native number format does not have sufficient precision to represent all values. XRP has different precision from other currencies.
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11). **XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). This smallest unit is called a "drop". XRP has a maximum value of `100000000000` (1e11). Some RippleAPI methods accept XRP in order to maintain compatibility with older versions of the API. For consistency with the `rippled` APIs, we recommend formally specifying XRP values in *drops* in all API requests, and converting them to XRP for display. This is similar to Bitcoin's *satoshis* and Ethereum's *wei*. 1 XRP = 1,000,000 drops.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`. **Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount ## Amount
Example amount: Example 100.00 USD amount:
```json ```json
{ {
@@ -36,15 +35,16 @@ Example amount:
} }
``` ```
Example XRP amount: Example 3.0 XRP amount, in drops:
```json ```json
{ {
"currency": "XRP", "currency": "drops",
"value": "2000" "value": "3000000"
} }
``` ```
(Requires `ripple-lib` version 1.0.0 or higher.)
An *amount* is data structure representing a currency, a quantity of that currency, and the counterparty on the trustline that holds the value. For XRP, there is no counterparty. An *amount* is an object specifying a currency, a quantity of that currency, and the counterparty (issuer) on the trustline that holds the value. For XRP, there is no counterparty.
A *lax amount* allows the counterparty to be omitted for all currencies. If the counterparty is not specified in an amount within a transaction specification, then any counterparty may be used for that amount. A *lax amount* allows the counterparty to be omitted for all currencies. If the counterparty is not specified in an amount within a transaction specification, then any counterparty may be used for that amount.

View File

@@ -55,7 +55,7 @@ If you omit the `server` parameter, RippleAPI operates [offline](#offline-functi
1. Install [Node.js](https://nodejs.org) and [Yarn](https://yarnpkg.com/en/docs/install). Most Linux distros have a package for Node.js; check that it's the version you want. 1. Install [Node.js](https://nodejs.org) and [Yarn](https://yarnpkg.com/en/docs/install). Most Linux distros have a package for Node.js; check that it's the version you want.
2. Use yarn to install RippleAPI: 2. Use yarn to install RippleAPI:
`yarn install ripple-lib` `yarn add ripple-lib`
After you have installed ripple-lib, you can create scripts using the [boilerplate](#boilerplate) and run them using the Node.js executable, typically named `node`: After you have installed ripple-lib, you can create scripts using the [boilerplate](#boilerplate) and run them using the Node.js executable, typically named `node`:

View File

@@ -6,7 +6,7 @@ Returns the estimated transaction fee for the rippled server the RippleAPI insta
### Parameters ### Parameters
This method has no parameters. <%- renderSchema('input/get-fee.json') %>
### Return Value ### Return Value

View File

@@ -14,9 +14,9 @@ This method returns a promise that resolves with an object with the following st
<%- renderSchema('output/get-orderbook.json') %> <%- renderSchema('output/get-orderbook.json') %>
### New in ripple-lib 0.22.0 and higher ### Raw order data
The response includes a `data` property containing the raw order data. This may include `owner_funds`, `Flags`, and other fields. (Requires ripple-lib 0.22.0 or higher.) The response includes a `data` property containing the raw order data. This may include `owner_funds`, `Flags`, and other fields.
For details, see the rippled method [book_offers](https://ripple.com/build/rippled-apis/#book-offers). For details, see the rippled method [book_offers](https://ripple.com/build/rippled-apis/#book-offers).

View File

@@ -0,0 +1,27 @@
## hasNextPage
`hasNextPage(currentResponse): boolean`
Returns `true` when there are more pages available.
When there are more results than contained in the response, the response includes a `marker` field. You can use this convenience method, or check for `marker` yourself.
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
### Return Value
This method returns `true` if `currentResponse` includes a `marker`.
### Example
```javascript
return api.request('ledger_data', {
ledger_index: 'validated'
}).then(response => {
/* Do something useful with response */
if (api.hasNextPage(response)) {
/* There are more pages available */
}
}).catch(console.error);
```

View File

@@ -4,6 +4,10 @@
<% include basictypes.md.ejs %> <% include basictypes.md.ejs %>
<% include transactions.md.ejs %> <% include transactions.md.ejs %>
<% include specifications.md.ejs %> <% include specifications.md.ejs %>
<% include rippledAPIs.md.ejs %>
<% include request.md.ejs %>
<% include hasNextPage.md.ejs %>
<% include requestNextPage.md.ejs %>
<% include methods.md.ejs %> <% include methods.md.ejs %>
<% include connect.md.ejs %> <% include connect.md.ejs %>
<% include disconnect.md.ejs %> <% include disconnect.md.ejs %>

View File

@@ -1,6 +1,6 @@
# Introduction # Introduction
RippleAPI 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.
Using RippleAPI, you can: Using RippleAPI, you can:
* [Query transactions from the XRP Ledger history](#gettransaction) * [Query transactions from the XRP Ledger history](#gettransaction)
@@ -8,5 +8,3 @@ Using RippleAPI, you can:
* [Submit](#submit) transactions to the XRP Ledger, including [Payments](#payment), [Orders](#order), [Settings changes](#settings), and [other types](#transaction-types) * [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) * [Generate a new XRP Ledger Address](#generateaddress)
* ... and [much more](#api-methods). * ... and [much more](#api-methods).
RippleAPI only provides access to *validated*, *immutable* transaction data.

View File

@@ -22,6 +22,8 @@ All "prepare*" methods have the same return type.
```javascript ```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'; const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
// Buy 10.10 USD (of the specified issuer) for 2.0 XRP (2000000 drops), fill or kill.
const order = <%- importFile('test/fixtures/requests/prepare-order.json') %>; const order = <%- importFile('test/fixtures/requests/prepare-order.json') %>;
return api.prepareOrder(address, order) return api.prepareOrder(address, order)
.then(prepared => {/* ... */}); .then(prepared => {/* ... */});

27
docs/src/request.md.ejs Normal file
View File

@@ -0,0 +1,27 @@
## request
`request(command: string, options: object): Promise<object>`
Returns the response from invoking the specified command, with the specified options, on the connected rippled server.
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).
### Return Value
This method returns a promise that resolves with the response from rippled.
### Example
```javascript
// Replace 'ledger' with your desired rippled command
return api.request('ledger', {
ledger_index: 'validated'
}).then(response => {
/* Do something useful with response */
console.log(JSON.stringify(response, null, 2))
}).catch(console.error);
```
<%- renderFixture('responses/ledger.json') %>

View File

@@ -0,0 +1,29 @@
## requestNextPage
`requestNextPage(command: string, params: object = {}, currentResponse: object): Promise<object>`
Requests the next page of data.
You can use this convenience method, or include `currentResponse.marker` in `params` yourself, when using `request`.
See [Markers and Pagination](https://ripple.com/build/rippled-apis/#markers-and-pagination).
### Return Value
This method returns a promise that resolves with the next page of data from rippled.
If the response does not have a next page, the promise will reject with `new errors.NotFoundError('response does not have a next page')`.
### Example
```javascript
const command = 'ledger_data'
const params = {
ledger_index: 'validated'
}
return api.request(command, params).then(response => {
return api.requestNextPage(command, params, response)
}).then(response_page_2 => {
/* Do something useful with second page of response */
}).catch(console.error);
```

View File

@@ -0,0 +1,66 @@
# 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).
* 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://ripple.com/build/rippled-apis/#specifying-currency-amounts). 1 XRP = 1000000 drops.
## Listening to streams
The `rippled` server can push updates to your client when various events happen. Refer to [Subscriptions in the `rippled` API docs](https://ripple.com/build/rippled-apis/#subscriptions) for details.
Note that the `streams` parameter for generic streams takes an array. For example, to subscribe to the `validations` stream, use `{ streams: [ 'validations' ] }`.
The string names of some generic streams to subscribe to are in the table below. (Refer to `rippled` for an up-to-date list of streams.)
Type | Description
---- | -----------
`server` | Sends a message whenever the status of the `rippled` server (for example, network connectivity) changes.
`ledger` | Sends a message whenever the consensus process declares a new validated ledger.
`transactions` | Sends a message whenever a transaction is included in a closed ledger.
`transactions_proposed` | Sends a message whenever a transaction is included in a closed ledger, as well as some transactions that have not yet been included in a validated ledger and may never be. Not all proposed transactions appear before validation. Even some transactions that don't succeed are included in validated ledgers because they take the anti-spam transaction fee.
`validations` | Sends a message whenever the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
`manifests` | Sends a message whenever the server receives a manifest.
`peer_status` | (Admin-only) Information about connected peer `rippled` servers, especially with regards to the consensus process.
When you subscribe to a stream, you must also listen to the relevant message type(s). Some of the available message types are in the table below. (Refer to `rippled` for an up-to-date list of message types.)
Type | Description
---- | -----------
`ledgerClosed` | Sent by the `ledger` stream when the consensus process declares a new fully validated ledger. The message identifies the ledger and provides some information about its contents.
`validationReceived` | Sent by the `validations` stream when the server receives a validation message, also called a validation vote, regardless of whether the server trusts the validator.
`manifestReceived` | Sent by the `manifests` stream when the server receives a manifest.
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://ripple.com/build/rippled-apis/#transaction-streams) for details.
`peerStatusChange` | (Admin-only) Reports a large amount of information on the activities of other `rippled` servers to which the server is connected.
To register your listener function, use `connection.on(type, handler)`.
Here is an example of listening for transactions on given account(s):
```
const account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // Replace with the account you want notifications for
api.connect().then(() => { // Omit this if you are already connected
// 'transaction' can be replaced with the relevant `type` from the table above
api.connection.on('transaction', (event) => {
// Do something useful with `event`
console.log(JSON.stringify(event, null, 2))
})
api.request('subscribe', {
accounts: [ account ]
}).then(response => {
if (response.status === 'success') {
console.log('Successfully subscribed')
}
}).catch(error => {
// Handle `error`
})
})
```
The subscription ends when you unsubscribe or the WebSocket connection is closed.
For full details, see [rippled Subscriptions](https://ripple.com/build/rippled-apis/#subscriptions).

View File

@@ -53,7 +53,7 @@ Transaction instructions indicate how to execute a transaction, complementary wi
<%- renderSchema("objects/instructions.json") %> <%- renderSchema("objects/instructions.json") %>
We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare*" method. We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
## Transaction ID ## Transaction ID

View File

@@ -1,6 +1,6 @@
{ {
"name": "ripple-lib", "name": "ripple-lib",
"version": "0.22.0", "version": "1.0.0-beta.3",
"license": "ISC", "license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser", "description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [ "files": [
@@ -26,7 +26,7 @@
"ripple-binary-codec": "^0.1.13", "ripple-binary-codec": "^0.1.13",
"ripple-hashes": "^0.3.1", "ripple-hashes": "^0.3.1",
"ripple-keypairs": "^0.10.1", "ripple-keypairs": "^0.10.1",
"ripple-lib-transactionparser": "^0.6.2", "ripple-lib-transactionparser": "0.7.1",
"ws": "^3.3.1" "ws": "^3.3.1"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,12 +1,9 @@
import * as _ from 'lodash'
import {EventEmitter} from 'events' import {EventEmitter} from 'events'
import {Connection, errors, validate} from './common' import {Connection, errors, validate, xrpToDrops, dropsToXrp} from './common'
import { import {
connect, connect,
disconnect, disconnect,
isConnected, isConnected,
getServerInfo,
getFee,
getLedgerVersion, getLedgerVersion,
formatLedgerClose formatLedgerClose
} from './server/server' } from './server/server'
@@ -53,18 +50,21 @@ import {
BookOffersRequest, BookOffersResponse, BookOffersRequest, BookOffersResponse,
GatewayBalancesRequest, GatewayBalancesResponse, GatewayBalancesRequest, GatewayBalancesResponse,
LedgerRequest, LedgerResponse, LedgerRequest, LedgerResponse,
LedgerEntryRequest, LedgerEntryResponse LedgerEntryRequest, LedgerEntryResponse,
ServerInfoRequest, ServerInfoResponse
} from './common/types/commands' } from './common/types/commands'
import RangeSet from './common/rangeset' import RangeSet from './common/rangeset'
import * as ledgerUtils from './ledger/utils' import * as ledgerUtils from './ledger/utils'
import * as schemaValidator from './common/schema-validator' import * as schemaValidator from './common/schema-validator'
import {getServerInfo, getFee} from './common/serverinfo'
import {clamp} from './ledger/utils' import {clamp} from './ledger/utils'
export type APIOptions = { export type APIOptions = {
server?: string, server?: string,
feeCushion?: number, feeCushion?: number,
maxFeeXRP?: string,
trace?: boolean, trace?: boolean,
proxy?: string, proxy?: string,
timeout?: number timeout?: number
@@ -87,25 +87,13 @@ function getCollectKeyFromCommand(command: string): string|undefined {
} }
} }
// prevent access to non-validated ledger versions
export class RestrictedConnection extends Connection {
request(request: any, timeout?: number) {
const ledger_index = request.ledger_index
if (ledger_index !== undefined && ledger_index !== 'validated') {
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
return Promise.reject(new errors.LedgerVersionError(
`ledgerVersion ${ledger_index} is greater than server\'s ` +
`most recent validated ledger: ${this._ledgerVersion}`))
}
}
return super.request(request, timeout)
}
}
class RippleAPI extends EventEmitter { class RippleAPI extends EventEmitter {
_feeCushion: number _feeCushion: number
connection: RestrictedConnection _maxFeeXRP: string
// New in > 0.21.0
// non-validated ledger versions are allowed, and passed to rippled as-is.
connection: Connection
// these are exposed only for use by unit tests; they are not part of the API. // these are exposed only for use by unit tests; they are not part of the API.
static _PRIVATE = { static _PRIVATE = {
@@ -119,9 +107,10 @@ class RippleAPI extends EventEmitter {
super() super()
validate.apiOptions(options) validate.apiOptions(options)
this._feeCushion = options.feeCushion || 1.2 this._feeCushion = options.feeCushion || 1.2
this._maxFeeXRP = options.maxFeeXRP || '2'
const serverURL = options.server const serverURL = options.server
if (serverURL !== undefined) { if (serverURL !== undefined) {
this.connection = new RestrictedConnection(serverURL, options) this.connection = new Connection(serverURL, options)
this.connection.on('ledgerClosed', message => { this.connection.on('ledgerClosed', message => {
this.emit('ledger', formatLedgerClose(message)) this.emit('ledger', formatLedgerClose(message))
}) })
@@ -137,14 +126,14 @@ class RippleAPI extends EventEmitter {
} else { } else {
// use null object pattern to provide better error message if user // use null object pattern to provide better error message if user
// tries to call a method that requires a connection // tries to call a method that requires a connection
this.connection = new RestrictedConnection(null, options) this.connection = new Connection(null, options)
} }
} }
async _request(command: 'account_info', params: AccountInfoRequest): async request(command: 'account_info', params: AccountInfoRequest):
Promise<AccountInfoResponse> Promise<AccountInfoResponse>
async _request(command: 'account_lines', params: AccountLinesRequest): async request(command: 'account_lines', params: AccountLinesRequest):
Promise<AccountLinesResponse> Promise<AccountLinesResponse>
/** /**
@@ -152,33 +141,64 @@ class RippleAPI extends EventEmitter {
* For an account's trust lines and balances, * For an account's trust lines and balances,
* see `getTrustlines` and `getBalances`. * see `getTrustlines` and `getBalances`.
*/ */
async _request(command: 'account_objects', params: AccountObjectsRequest): async request(command: 'account_objects', params: AccountObjectsRequest):
Promise<AccountObjectsResponse> Promise<AccountObjectsResponse>
async _request(command: 'account_offers', params: AccountOffersRequest): async request(command: 'account_offers', params: AccountOffersRequest):
Promise<AccountOffersResponse> Promise<AccountOffersResponse>
async _request(command: 'book_offers', params: BookOffersRequest): async request(command: 'book_offers', params: BookOffersRequest):
Promise<BookOffersResponse> Promise<BookOffersResponse>
async _request(command: 'gateway_balances', params: GatewayBalancesRequest): async request(command: 'gateway_balances', params: GatewayBalancesRequest):
Promise<GatewayBalancesResponse> Promise<GatewayBalancesResponse>
async _request(command: 'ledger', params: LedgerRequest): async request(command: 'ledger', params: LedgerRequest):
Promise<LedgerResponse> Promise<LedgerResponse>
async _request(command: 'ledger_entry', params: LedgerEntryRequest): async request(command: 'ledger_entry', params: LedgerEntryRequest):
Promise<LedgerEntryResponse> Promise<LedgerEntryResponse>
async request(command: 'server_info', params?: ServerInfoRequest):
Promise<ServerInfoResponse>
async request(command: string, params: object):
Promise<object>
/** /**
* Makes a request to the API with the given command and * Makes a request to the API with the given command and
* additional request body parameters. * additional request body parameters.
*
* NOTE: This command is under development.
*/ */
async _request(command: string, params: object = {}) { async request(command: string, params: object = {}): Promise<object> {
return this.connection.request({ return this.connection.request({
...params, ...params,
command command
}) })
} }
/**
* Returns true if there are more pages of data.
*
* When there are more results than contained in the response, the response
* includes a `marker` field.
*
* See https://ripple.com/build/rippled-apis/#markers-and-pagination
*/
hasNextPage<T extends {marker?: string}>(currentResponse: T): boolean {
return !!currentResponse.marker
}
async requestNextPage<T extends {marker?: string}>(
command: string,
params: object = {},
currentResponse: T
): Promise<object> {
if (!currentResponse.marker) {
return Promise.reject(
new errors.NotFoundError('response does not have a next page')
)
}
const nextPageParams = Object.assign({}, params, {
marker: currentResponse.marker
})
return this.request(command, nextPageParams)
}
/** /**
* Makes multiple paged requests to the API to return a given number of * Makes multiple paged requests to the API to return a given number of
* resources. _requestAll() will make multiple requests until the `limit` * resources. _requestAll() will make multiple requests until the `limit`
@@ -188,8 +208,9 @@ class RippleAPI extends EventEmitter {
* If the command is unknown, an additional `collect` property is required to * If the command is unknown, an additional `collect` property is required to
* know which response key contains the array of resources. * know which response key contains the array of resources.
* *
* NOTE: This command is under development and should not yet be relied * NOTE: This command is used by existing methods and is not recommended for
* on by external consumers. * general use. Instead, use rippled's built-in pagination and make multiple
* requests as needed.
*/ */
async _requestAll(command: 'account_offers', params: AccountOffersRequest): async _requestAll(command: 'account_offers', params: AccountOffersRequest):
Promise<AccountOffersResponse[]> Promise<AccountOffersResponse[]>
@@ -222,12 +243,9 @@ class RippleAPI extends EventEmitter {
limit: countRemaining, limit: countRemaining,
marker marker
} }
// NOTE: We have to generalize the `this._request()` function signature const singleResult = await this.request(command, repeatProps)
// here until we add support for unknown commands (since command is some
// unknown string).
const singleResult = await (<Function>this._request)(command, repeatProps)
const collectedData = singleResult[collectKey] const collectedData = singleResult[collectKey]
marker = singleResult.marker marker = singleResult['marker']
results.push(singleResult) results.push(singleResult)
// Make sure we handle when no data (not even an empty array) is returned. // Make sure we handle when no data (not even an empty array) is returned.
const isExpectedFormat = Array.isArray(collectedData) const isExpectedFormat = Array.isArray(collectedData)
@@ -285,6 +303,9 @@ class RippleAPI extends EventEmitter {
signPaymentChannelClaim = signPaymentChannelClaim signPaymentChannelClaim = signPaymentChannelClaim
verifyPaymentChannelClaim = verifyPaymentChannelClaim verifyPaymentChannelClaim = verifyPaymentChannelClaim
errors = errors errors = errors
xrpToDrops = xrpToDrops
dropsToXrp = dropsToXrp
} }
export { export {

View File

@@ -7,12 +7,6 @@ import {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError, TimeoutError, ResponseFormatError, ConnectionError,
RippledNotInitializedError} from './errors' RippledNotInitializedError} from './errors'
function isStreamMessageType(type) {
return type === 'ledgerClosed' ||
type === 'transaction' ||
type === 'path_find'
}
export interface ConnectionOptions { export interface ConnectionOptions {
trace?: boolean, trace?: boolean,
proxy?: string proxy?: string
@@ -90,19 +84,20 @@ class Connection extends EventEmitter {
const data = JSON.parse(message) const data = JSON.parse(message)
if (data.type === 'response') { if (data.type === 'response') {
if (!(Number.isInteger(data.id) && data.id >= 0)) { if (!(Number.isInteger(data.id) && data.id >= 0)) {
throw new ResponseFormatError('valid id not found in response') throw new ResponseFormatError('valid id not found in response', data)
} }
return [data.id.toString(), data] return [data.id.toString(), data]
} else if (isStreamMessageType(data.type)) {
if (data.type === 'ledgerClosed') {
this._updateLedgerVersions(data)
this._updateFees(data)
}
return [data.type, data]
} else if (data.type === undefined && data.error) { } else if (data.type === undefined && data.error) {
return ['error', data.error, data.error_message, data] // e.g. slowDown return ['error', data.error, data.error_message, data] // e.g. slowDown
} }
throw new ResponseFormatError('unrecognized message type: ' + data.type)
// Possible `data.type` values include 'ledgerClosed',
// 'transaction', 'path_find', and many others.
if (data.type === 'ledgerClosed') {
this._updateLedgerVersions(data)
this._updateFees(data)
}
return [data.type, data]
} }
_onMessage(message) { _onMessage(message) {
@@ -246,7 +241,7 @@ class Connection extends EventEmitter {
_onOpenError(reject, error) { _onOpenError(reject, error) {
this._onOpenErrorBound = null this._onOpenErrorBound = null
this._unbindOnUnxpectedClose() this._unbindOnUnxpectedClose()
reject(new NotConnectedError(error && error.message)) reject(new NotConnectedError(error.message, error))
} }
_createWebSocket(): WebSocket { _createWebSocket(): WebSocket {
@@ -404,7 +399,7 @@ class Connection extends EventEmitter {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._ws.send(message, undefined, error => { this._ws.send(message, undefined, error => {
if (error) { if (error) {
reject(new DisconnectedError(error.message)) reject(new DisconnectedError(error.message, error))
} else { } else {
resolve() resolve()
} }
@@ -427,7 +422,7 @@ class Connection extends EventEmitter {
function onDisconnect() { function onDisconnect() {
clearTimeout(timer) clearTimeout(timer)
self.removeAllListeners(eventName) self.removeAllListeners(eventName)
reject(new DisconnectedError()) reject(new DisconnectedError('websocket was closed'))
} }
function cleanup() { function cleanup() {
@@ -450,12 +445,12 @@ class Connection extends EventEmitter {
this.once(eventName, response => { this.once(eventName, response => {
if (response.status === 'error') { if (response.status === 'error') {
_reject(new RippledError(response.error)) _reject(new RippledError(response.error, response))
} else if (response.status === 'success') { } else if (response.status === 'success') {
_resolve(response.result) _resolve(response.result)
} else { } else {
_reject(new ResponseFormatError( _reject(new ResponseFormatError(
'unrecognized status: ' + response.status)) 'unrecognized status: ' + response.status, response))
} }
}) })

View File

@@ -12,6 +12,10 @@
"minimum": 1, "minimum": 1,
"description": "Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`." "description": "Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`."
}, },
"maxFeeXRP": {
"type": "string",
"description": "Maximum fee to use with transactions, in XRP. Must be a string-encoded number. Defaults to `'2'`."
},
"server": { "server": {
"type": "string", "type": "string",
"description": "URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.", "description": "URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.",

View File

@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getFeeParameters",
"description": "Parameters for getFee",
"type": "object",
"properties": {
"cushion": {
"type": "number",
"description": "The fee is the product of the base fee, the `load_factor`, and this cushion. Default is provided by the `RippleAPI` constructor's `feeCushion`."
}
},
"additionalProperties": false
}

View File

@@ -9,11 +9,11 @@
"$ref": "value" "$ref": "value"
}, },
"currency": { "currency": {
"description": "The three-character code or hexadecimal string used to denote currencies", "description": "The three-character code or hexadecimal string used to denote currencies, or \"drops\" for the smallest unit of XRP.",
"$ref": "currency" "$ref": "currency"
}, },
"counterparty": { "counterparty": {
"description": "The Ripple address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\")", "description": "The Ripple address of the account that owes or is owed the funds (omitted if `currency` is \"XRP\" or \"drops\")",
"$ref": "address" "$ref": "address"
} }
}, },
@@ -24,7 +24,7 @@
"properties": { "properties": {
"currency": { "currency": {
"not": { "not": {
"enum": ["XRP"] "enum": ["XRP", "drops"]
} }
} }
}, },
@@ -33,7 +33,7 @@
{ {
"properties": { "properties": {
"currency": { "currency": {
"enum": ["XRP"] "enum": ["XRP", "drops"]
} }
}, },
"not": { "not": {

View File

@@ -4,5 +4,5 @@
"description": "The three-character code or hexadecimal string used to denote currencies", "description": "The three-character code or hexadecimal string used to denote currencies",
"type": "string", "type": "string",
"link": "currency", "link": "currency",
"pattern": "^([a-zA-Z0-9<>(){}[\\]|?!@#$%^&*]{3}|[A-F0-9]{40})$" "pattern": "^([a-zA-Z0-9<>(){}[\\]|?!@#$%^&*]{3}|[A-F0-9]{40}|drops)$"
} }

View File

@@ -14,7 +14,7 @@
"$ref": "value" "$ref": "value"
}, },
"maxFee": { "maxFee": {
"description": "The maximum fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.", "description": "Deprecated: Use `maxFeeXRP` in the RippleAPI constructor instead. The maximum fee to pay for this transaction. If this exceeds `maxFeeXRP`, `maxFeeXRP` will be used instead. See [Transaction Fees](#transaction-fees) for more information.",
"$ref": "value" "$ref": "value"
}, },
"maxLedgerVersion": { "maxLedgerVersion": {

View File

@@ -93,7 +93,9 @@
"minItems": 1, "minItems": 1,
"maxItems": 8 "maxItems": 8
} }
} },
"required": ["threshold", "weights"],
"additionalProperties": false
}, },
"transferRate": { "transferRate": {
"description": " The fee to charge when users transfer this accounts 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 accounts 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.",

View File

@@ -25,7 +25,7 @@
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "array", "type": "array",
"description": "Key is the ripple address; value is an array of signed amounts representing changes of balances for that address.", "description": "Key is the XRP Ledger address; value is an array of signed amounts representing changes of balances for that address.",
"items": {"$ref": "balance"} "items": {"$ref": "balance"}
} }
}, },
@@ -33,10 +33,14 @@
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
"type": "array", "type": "array",
"description": "Key is the maker's ripple address; value is an array of changes", "description": "Key is the maker's XRP Ledger address; value is an array of changes",
"items": {"$ref": "orderChange"} "items": {"$ref": "orderChange"}
} }
}, },
"channelChanges": {
"type": "object",
"description": "Properties reflecting the details of the payment channel."
},
"ledgerVersion": { "ledgerVersion": {
"$ref": "ledgerVersion", "$ref": "ledgerVersion",
"description": "The ledger version that the transaction was validated in." "description": "The ledger version that the transaction was validated in."

View File

@@ -1,7 +1,7 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import {convertKeysFromSnakeCaseToCamelCase} from './utils' import {convertKeysFromSnakeCaseToCamelCase} from './utils'
import Connection from './connection'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import {RippleAPI} from '../index'
export type GetServerInfoResponse = { export type GetServerInfoResponse = {
buildVersion: string, buildVersion: string,
@@ -39,8 +39,8 @@ function renameKeys(object, mapping) {
}) })
} }
function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> { function getServerInfo(this: RippleAPI): Promise<GetServerInfoResponse> {
return connection.request({command: 'server_info'}).then(response => { return this.request('server_info').then(response => {
const info = convertKeysFromSnakeCaseToCamelCase(response.info) const info = convertKeysFromSnakeCaseToCamelCase(response.info)
renameKeys(info, {hostid: 'hostID'}) renameKeys(info, {hostid: 'hostID'})
if (info.validatedLedger) { if (info.validatedLedger) {
@@ -61,18 +61,25 @@ function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
}) })
} }
function computeFeeFromServerInfo(cushion: number, async function getFee(
serverInfo: GetServerInfoResponse this: RippleAPI,
): string { cushion?: number
return (new BigNumber(serverInfo.validatedLedger.baseFeeXRP)). ): Promise<string> {
times(serverInfo.loadFactor). if (cushion === undefined) {
times(cushion).toString() cushion = this._feeCushion
} }
if (cushion === undefined) {
cushion = 1.2
}
function getFee(connection: Connection, cushion: number): Promise<string> { const serverInfo = (await this.request('server_info')).info
return getServerInfo(connection).then(serverInfo => { const baseFeeXrp = new BigNumber(serverInfo.validated_ledger.base_fee_xrp)
return computeFeeFromServerInfo(cushion, serverInfo) let fee = baseFeeXrp.times(serverInfo.load_factor).times(cushion)
})
// Cap fee to `this._maxFeeXRP`
fee = BigNumber.min(fee, this._maxFeeXRP)
// Round fee to 6 decimal places
return (new BigNumber(fee.toFormat(6))).toString(10)
} }
export { export {

View File

@@ -6,3 +6,4 @@ export * from './book_offers'
export * from './gateway_balances' export * from './gateway_balances'
export * from './ledger' export * from './ledger'
export * from './ledger_entry' export * from './ledger_entry'
export * from './server_info'

View File

@@ -0,0 +1,51 @@
export interface ServerInfoRequest {
id?: number
}
export interface ServerInfoResponse {
info: {
amendment_blocked?: boolean,
build_version: string,
closed_ledger?: LedgerInfo,
complete_ledgers: string,
hostid: string,
io_latency_ms: number,
last_close: {
converge_time_s: number,
proposers: number
},
load?: {
job_types: {
job_type: string,
per_second: number,
in_progress: number
}[],
threads: number
},
load_factor: number,
load_factor_local?: number,
load_factor_net?: number,
load_factor_cluster?: number,
load_factor_fee_escalation?: number,
load_factor_fee_queue?: number,
load_factor_server?: number,
peers: number,
pubkey_node: string,
pubkey_validator: string,
server_state: string,
state_accounting: any,
uptime: number,
validated_ledger?: LedgerInfo,
validation_quorum: number,
validator_list_expires: string
},
}
export interface LedgerInfo {
age: number,
base_fee_xrp: number,
hash: string,
reserve_base_xrp: number,
reserve_inc_xrp: number,
seq: number,
}

View File

@@ -1,8 +1,8 @@
import * as _ from 'lodash' import * as _ from 'lodash'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
const {deriveKeypair} = require('ripple-keypairs') import {deriveKeypair} from 'ripple-keypairs'
import {Amount, RippledAmount} from './types/objects' import {Amount, RippledAmount} from './types/objects'
import {ValidationError} from './errors'
function isValidSecret(secret: string): boolean { function isValidSecret(secret: string): boolean {
try { try {
@@ -13,18 +13,86 @@ function isValidSecret(secret: string): boolean {
} }
} }
function dropsToXrp(drops: string): string { function dropsToXrp(drops: string | BigNumber): string {
return (new BigNumber(drops)).dividedBy(1000000.0).toString() 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]*$).`)
} else if (drops === '.') {
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
` should be a BigNumber or string-encoded number.`)
}
}
// Converting to BigNumber and then back to string should remove any
// decimal point followed by zeros, e.g. '1.00'.
// Important: specify base 10 to avoid exponential notation, e.g. '1e-7'.
drops = (new BigNumber(drops)).toString(10)
// drops are only whole units
if (drops.includes('.')) {
throw new ValidationError(`dropsToXrp: value '${drops}' has` +
` too many decimal places.`)
}
// This should never happen; the value has already been
// validated above. This just ensures BigNumber did not do
// something unexpected.
if (!drops.match(/^-?[0-9]+$/)) {
throw new ValidationError(`dropsToXrp: failed sanity check -` +
` value '${drops}',` +
` does not match (^-?[0-9]+$).`)
}
return (new BigNumber(drops)).dividedBy(1000000.0).toString(10)
} }
function xrpToDrops(xrp: string): string { function xrpToDrops(xrp: string | BigNumber): string {
return (new BigNumber(xrp)).times(1000000.0).floor().toString() 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]*$).`)
} else if (xrp === '.') {
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
` should be a BigNumber or string-encoded number.`)
}
}
// Important: specify base 10 to avoid exponential notation, e.g. '1e-7'.
xrp = (new BigNumber(xrp)).toString(10)
// This should never happen; the value has already been
// validated above. This just ensures BigNumber did not do
// something unexpected.
if (!xrp.match(/^-?[0-9.]+$/)) {
throw new ValidationError(`xrpToDrops: failed sanity check -` +
` value '${xrp}',` +
` does not match (^-?[0-9.]+$).`)
}
const components = xrp.split('.')
if (components.length > 2) {
throw new ValidationError(`xrpToDrops: failed sanity check -` +
` value '${xrp}' has` +
` too many decimal points.`)
}
const fraction = components[1] || '0'
if (fraction.length > 6) {
throw new ValidationError(`xrpToDrops: value '${xrp}' has` +
` too many decimal places.`)
}
return (new BigNumber(xrp)).times(1000000.0).floor().toString(10)
} }
function toRippledAmount(amount: Amount): RippledAmount { function toRippledAmount(amount: Amount): RippledAmount {
if (amount.currency === 'XRP') { if (amount.currency === 'XRP') {
return xrpToDrops(amount.value) return xrpToDrops(amount.value)
} }
if (amount.currency === 'drops') {
return amount.value
}
return { return {
currency: amount.currency, currency: amount.currency,
issuer: amount.counterparty ? amount.counterparty : issuer: amount.counterparty ? amount.counterparty :

View File

@@ -35,7 +35,7 @@ export default async function getAccountInfo(
// 1. Validate // 1. Validate
validate.getAccountInfo({address, options}) validate.getAccountInfo({address, options})
// 2. Make Request // 2. Make Request
const response = await this._request('account_info', { const response = await this.request('account_info', {
account: address, account: address,
ledger_index: options.ledgerVersion || 'validated' ledger_index: options.ledgerVersion || 'validated'
}) })

View File

@@ -14,7 +14,7 @@ export default async function getAccountObjects(
// through to rippled. rippled validates requests. // through to rippled. rippled validates requests.
// Make Request // Make Request
const response = await this._request('account_objects', removeUndefined({ const response = await this.request('account_objects', removeUndefined({
account: address, account: address,
type: options.type, type: options.type,
ledger_hash: options.ledgerHash, ledger_hash: options.ledgerHash,

View File

@@ -54,7 +54,7 @@ async function getBalanceSheet(
validate.getBalanceSheet({address, options}) validate.getBalanceSheet({address, options})
options = await ensureLedgerVersion.call(this, options) options = await ensureLedgerVersion.call(this, options)
// 2. Make Request // 2. Make Request
const response = await this._request('gateway_balances', { const response = await this.request('gateway_balances', {
account: address, account: address,
strict: true, strict: true,
hotwallet: options.excludeAddresses, hotwallet: options.excludeAddresses,

View File

@@ -15,7 +15,7 @@ async function getLedger(
// 1. Validate // 1. Validate
validate.getLedger({options}) validate.getLedger({options})
// 2. Make Request // 2. Make Request
const response = await this._request('ledger', { const response = await this.request('ledger', {
ledger_index: options.ledgerVersion || 'validated', ledger_index: options.ledgerVersion || 'validated',
expand: options.includeAllData, expand: options.includeAllData,
transactions: options.includeTransactions, transactions: options.includeTransactions,

View File

@@ -103,6 +103,8 @@ function parseOutcome(tx: any): any|undefined {
} }
const balanceChanges = transactionParser.parseBalanceChanges(metadata) const balanceChanges = transactionParser.parseBalanceChanges(metadata)
const orderbookChanges = transactionParser.parseOrderbookChanges(metadata) const orderbookChanges = transactionParser.parseOrderbookChanges(metadata)
const channelChanges = transactionParser.parseChannelChanges(metadata)
removeEmptyCounterpartyInBalanceChanges(balanceChanges) removeEmptyCounterpartyInBalanceChanges(balanceChanges)
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) removeEmptyCounterpartyInOrderbookChanges(orderbookChanges)
@@ -112,6 +114,7 @@ function parseOutcome(tx: any): any|undefined {
fee: common.dropsToXrp(tx.Fee), fee: common.dropsToXrp(tx.Fee),
balanceChanges: balanceChanges, balanceChanges: balanceChanges,
orderbookChanges: orderbookChanges, orderbookChanges: orderbookChanges,
channelChanges: channelChanges,
ledgerVersion: tx.ledger_index, ledgerVersion: tx.ledger_index,
indexInLedger: tx.meta.TransactionIndex, indexInLedger: tx.meta.TransactionIndex,
deliveredAmount: parseDeliveredAmount(tx) deliveredAmount: parseDeliveredAmount(tx)

View File

@@ -23,7 +23,7 @@ async function getPaymentChannel(
// 1. Validate // 1. Validate
validate.getPaymentChannel({id}) validate.getPaymentChannel({id})
// 2. Make Request // 2. Make Request
const response = await this._request('ledger_entry', { const response = await this.request('ledger_entry', {
index: id, index: id,
binary: false, binary: false,
ledger_index: 'validated' ledger_index: 'validated'

View File

@@ -33,7 +33,7 @@ async function getSettings(
// 1. Validate // 1. Validate
validate.getSettings({address, options}) validate.getSettings({address, options})
// 2. Make Request // 2. Make Request
const response = await this._request('account_info', { const response = await this.request('account_info', {
account: address, account: address,
ledger_index: options.ledgerVersion || 'validated', ledger_index: options.ledgerVersion || 'validated',
signer_lists: true signer_lists: true

View File

@@ -1,5 +1,4 @@
import * as common from '../common' import * as common from '../common'
import {GetServerInfoResponse} from '../common/serverinfo'
function isConnected(): boolean { function isConnected(): boolean {
return this.connection.isConnected() return this.connection.isConnected()
@@ -17,15 +16,6 @@ function disconnect(): Promise<void> {
return this.connection.disconnect() return this.connection.disconnect()
} }
function getServerInfo(): Promise<GetServerInfoResponse> {
return common.serverInfo.getServerInfo(this.connection)
}
function getFee(): Promise<string> {
const cushion = this._feeCushion || 1.2
return common.serverInfo.getFee(this.connection, cushion)
}
function formatLedgerClose(ledgerClose: any): Object { function formatLedgerClose(ledgerClose: any): Object {
return { return {
baseFeeXRP: common.dropsToXrp(ledgerClose.fee_base), baseFeeXRP: common.dropsToXrp(ledgerClose.fee_base),
@@ -43,8 +33,6 @@ export {
connect, connect,
disconnect, disconnect,
isConnected, isConnected,
getServerInfo,
getFee,
getLedgerVersion, getLedgerVersion,
formatLedgerClose formatLedgerClose
} }

View File

@@ -3,6 +3,9 @@ import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec') import binary = require('ripple-binary-codec')
import {computeBinaryTransactionHash} from 'ripple-hashes' import {computeBinaryTransactionHash} from 'ripple-hashes'
import {SignOptions, KeyPair} from './types' import {SignOptions, KeyPair} from './types'
import {BigNumber} from 'bignumber.js'
import {xrpToDrops} from '../common'
import {RippleAPI} from '../api'
const validate = utils.common.validate const validate = utils.common.validate
function computeSignature(tx: Object, privateKey: string, signAs?: string) { function computeSignature(tx: Object, privateKey: string, signAs?: string) {
@@ -13,6 +16,7 @@ function computeSignature(tx: Object, privateKey: string, signAs?: string) {
} }
function signWithKeypair( function signWithKeypair(
api: RippleAPI,
txJSON: string, txJSON: string,
keypair: KeyPair, keypair: KeyPair,
options: SignOptions = { options: SignOptions = {
@@ -28,6 +32,15 @@ function signWithKeypair(
) )
} }
const fee = new BigNumber(tx.Fee)
const maxFeeDrops = xrpToDrops(api._maxFeeXRP)
if (fee.greaterThan(maxFeeDrops)) {
throw new utils.common.errors.ValidationError(
`"Fee" should not exceed "${maxFeeDrops}". ` +
'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.'
)
}
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey tx.SigningPubKey = options.signAs ? '' : keypair.publicKey
if (options.signAs) { if (options.signAs) {
@@ -49,6 +62,7 @@ function signWithKeypair(
} }
function sign( function sign(
this: RippleAPI,
txJSON: string, txJSON: string,
secret?: any, secret?: any,
options?: SignOptions, options?: SignOptions,
@@ -58,9 +72,18 @@ function sign(
// we can't validate that the secret matches the account because // we can't validate that the secret matches the account because
// the secret could correspond to the regular key // the secret could correspond to the regular key
validate.sign({txJSON, secret}) validate.sign({txJSON, secret})
return signWithKeypair(txJSON, keypairs.deriveKeypair(secret), options) return signWithKeypair(
this,
txJSON,
keypairs.deriveKeypair(secret),
options
)
} else { } else {
return signWithKeypair(txJSON, keypair ? keypair : secret, options) return signWithKeypair(
this,
txJSON,
keypair ? keypair : secret,
options)
} }
} }

View File

@@ -12,6 +12,7 @@ import {ApiMemo} from './utils'
export type Instructions = { export type Instructions = {
sequence?: number, sequence?: number,
fee?: string, fee?: string,
// @deprecated
maxFee?: string, maxFee?: string,
maxLedgerVersion?: number, maxLedgerVersion?: number,
maxLedgerVersionOffset?: number, maxLedgerVersionOffset?: number,

View File

@@ -4,6 +4,7 @@ import {Memo} from '../common/types/objects'
const txFlags = common.txFlags const txFlags = common.txFlags
import {Instructions, Prepare} from './types' import {Instructions, Prepare} from './types'
import {RippleAPI} from '../api' import {RippleAPI} from '../api'
import {ValidationError} from '../common/errors'
export type ApiMemo = { export type ApiMemo = {
MemoData?: string, MemoData?: string,
@@ -63,11 +64,18 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
const multiplier = instructions.signersCount === undefined ? 1 : const multiplier = instructions.signersCount === undefined ? 1 :
instructions.signersCount + 1 instructions.signersCount + 1
if (instructions.fee !== undefined) { if (instructions.fee !== undefined) {
const fee = new BigNumber(instructions.fee)
if (fee.greaterThan(api._maxFeeXRP)) {
const errorMessage = `Fee of ${fee.toString(10)} XRP exceeds ` +
`max of ${api._maxFeeXRP} XRP. To use this fee, increase ` +
'`maxFeeXRP` in the RippleAPI constructor.'
throw new ValidationError(errorMessage)
}
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier) txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
return Promise.resolve(txJSON) return Promise.resolve(txJSON)
} }
const cushion = api._feeCushion const cushion = api._feeCushion
return common.serverInfo.getFee(api.connection, cushion).then(fee => { return api.getFee(cushion).then(fee => {
return api.connection.getFeeRef().then(feeRef => { return api.connection.getFeeRef().then(feeRef => {
const extraFee = const extraFee =
(txJSON.TransactionType !== 'EscrowFinish' || (txJSON.TransactionType !== 'EscrowFinish' ||
@@ -75,13 +83,12 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
(cushion * feeRef * (32 + Math.floor( (cushion * feeRef * (32 + Math.floor(
new Buffer(txJSON.Fulfillment, 'hex').length / 16))) new Buffer(txJSON.Fulfillment, 'hex').length / 16)))
const feeDrops = common.xrpToDrops(fee) const feeDrops = common.xrpToDrops(fee)
if (instructions.maxFee !== undefined) { const maxFeeXRP = instructions.maxFee ?
const maxFeeDrops = common.xrpToDrops(instructions.maxFee) BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
const normalFee = scaleValue(feeDrops, multiplier, extraFee) const maxFeeDrops = common.xrpToDrops(maxFeeXRP)
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString() const normalFee = scaleValue(feeDrops, multiplier, extraFee)
} else { txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
txJSON.Fee = scaleValue(feeDrops, multiplier, extraFee)
}
return txJSON return txJSON
}) })
}) })

View File

@@ -15,6 +15,7 @@ const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer'); const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
const schemaValidator = RippleAPI._PRIVATE.schemaValidator; const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const binary = require('ripple-binary-codec'); const binary = require('ripple-binary-codec');
const BigNumber = require('bignumber.js')
assert.options.strict = true; assert.options.strict = true;
// how long before each test case times out // how long before each test case times out
@@ -51,6 +52,273 @@ describe('RippleAPI', function () {
assert.strictEqual(error.inspect(), '[RippleError(mess, { data: 1 })]'); assert.strictEqual(error.inspect(), '[RippleError(mess, { data: 1 })]');
}); });
describe('xrpToDrops', function () {
it('works with a typical amount', function () {
const drops = this.api.xrpToDrops('2')
assert.strictEqual(drops, '2000000', '2 XRP equals 2 million drops')
})
it('works with fractions', function () {
let drops = this.api.xrpToDrops('3.456789')
assert.strictEqual(drops, '3456789', '3.456789 XRP equals 3,456,789 drops')
drops = this.api.xrpToDrops('3.400000')
assert.strictEqual(drops, '3400000', '3.400000 XRP equals 3,400,000 drops')
drops = this.api.xrpToDrops('0.000001')
assert.strictEqual(drops, '1', '0.000001 XRP equals 1 drop')
drops = this.api.xrpToDrops('0.0000010')
assert.strictEqual(drops, '1', '0.0000010 XRP equals 1 drop')
})
it('works with zero', function () {
let drops = this.api.xrpToDrops('0')
assert.strictEqual(drops, '0', '0 XRP equals 0 drops')
// negative zero is equivalent to zero
drops = this.api.xrpToDrops('-0')
assert.strictEqual(drops, '0', '-0 XRP equals 0 drops')
drops = this.api.xrpToDrops('0.000000')
assert.strictEqual(drops, '0', '0.000000 XRP equals 0 drops')
drops = this.api.xrpToDrops('0.0000000')
assert.strictEqual(drops, '0', '0.0000000 XRP equals 0 drops')
})
it('works with a negative value', function () {
const drops = this.api.xrpToDrops('-2')
assert.strictEqual(drops, '-2000000', '-2 XRP equals -2 million drops')
})
it('works with a value ending with a decimal point', function () {
let drops = this.api.xrpToDrops('2.')
assert.strictEqual(drops, '2000000', '2. XRP equals 2000000 drops')
drops = this.api.xrpToDrops('-2.')
assert.strictEqual(drops, '-2000000', '-2. XRP equals -2000000 drops')
})
it('works with BigNumber objects', function () {
let drops = this.api.xrpToDrops(new BigNumber(2))
assert.strictEqual(drops, '2000000', '(BigNumber) 2 XRP equals 2 million drops')
drops = this.api.xrpToDrops(new BigNumber(-2))
assert.strictEqual(drops, '-2000000', '(BigNumber) -2 XRP equals -2 million drops')
})
it('works with a number', function() {
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
let drops = this.api.xrpToDrops(2)
assert.strictEqual(drops, '2000000', '(number) 2 XRP equals 2 million drops')
drops = this.api.xrpToDrops(-2)
assert.strictEqual(drops, '-2000000', '(number) -2 XRP equals -2 million drops')
})
it('throws with an amount with too many decimal places', function () {
assert.throws(() => {
this.api.xrpToDrops('1.1234567')
}, /has too many decimal places/)
assert.throws(() => {
this.api.xrpToDrops('0.0000001')
}, /has too many decimal places/)
})
it('throws with an invalid value', function () {
assert.throws(() => {
this.api.xrpToDrops('FOO')
}, /invalid value/)
assert.throws(() => {
this.api.xrpToDrops('1e-7')
}, /invalid value/)
assert.throws(() => {
this.api.xrpToDrops('2,0')
}, /invalid value/)
assert.throws(() => {
this.api.xrpToDrops('.')
}, /xrpToDrops\: invalid value '\.', should be a BigNumber or string-encoded number\./)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
this.api.xrpToDrops('1.0.0')
}, /xrpToDrops:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
assert.throws(() => {
this.api.xrpToDrops('...')
}, /xrpToDrops:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
})
})
describe('dropsToXrp', function () {
it('works with a typical amount', function () {
const xrp = this.api.dropsToXrp('2000000')
assert.strictEqual(xrp, '2', '2 million drops equals 2 XRP')
})
it('works with fractions', function () {
let xrp = this.api.dropsToXrp('3456789')
assert.strictEqual(xrp, '3.456789', '3,456,789 drops equals 3.456789 XRP')
xrp = this.api.dropsToXrp('3400000')
assert.strictEqual(xrp, '3.4', '3,400,000 drops equals 3.4 XRP')
xrp = this.api.dropsToXrp('1')
assert.strictEqual(xrp, '0.000001', '1 drop equals 0.000001 XRP')
xrp = this.api.dropsToXrp('1.0')
assert.strictEqual(xrp, '0.000001', '1.0 drops equals 0.000001 XRP')
xrp = this.api.dropsToXrp('1.00')
assert.strictEqual(xrp, '0.000001', '1.00 drops equals 0.000001 XRP')
})
it('works with zero', function () {
let xrp = this.api.dropsToXrp('0')
assert.strictEqual(xrp, '0', '0 drops equals 0 XRP')
// negative zero is equivalent to zero
xrp = this.api.dropsToXrp('-0')
assert.strictEqual(xrp, '0', '-0 drops equals 0 XRP')
xrp = this.api.dropsToXrp('0.00')
assert.strictEqual(xrp, '0', '0.00 drops equals 0 XRP')
xrp = this.api.dropsToXrp('000000000')
assert.strictEqual(xrp, '0', '000000000 drops equals 0 XRP')
})
it('works with a negative value', function () {
const xrp = this.api.dropsToXrp('-2000000')
assert.strictEqual(xrp, '-2', '-2 million drops equals -2 XRP')
})
it('works with a value ending with a decimal point', function () {
let xrp = this.api.dropsToXrp('2000000.')
assert.strictEqual(xrp, '2', '2000000. drops equals 2 XRP')
xrp = this.api.dropsToXrp('-2000000.')
assert.strictEqual(xrp, '-2', '-2000000. drops equals -2 XRP')
})
it('works with BigNumber objects', function () {
let xrp = this.api.dropsToXrp(new BigNumber(2000000))
assert.strictEqual(xrp, '2', '(BigNumber) 2 million drops equals 2 XRP')
xrp = this.api.dropsToXrp(new BigNumber(-2000000))
assert.strictEqual(xrp, '-2', '(BigNumber) -2 million drops equals -2 XRP')
xrp = this.api.dropsToXrp(new BigNumber(2345678))
assert.strictEqual(xrp, '2.345678', '(BigNumber) 2,345,678 drops equals 2.345678 XRP')
xrp = this.api.dropsToXrp(new BigNumber(-2345678))
assert.strictEqual(xrp, '-2.345678', '(BigNumber) -2,345,678 drops equals -2.345678 XRP')
})
it('works with a number', function() {
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
let xrp = this.api.dropsToXrp(2000000)
assert.strictEqual(xrp, '2', '(number) 2 million drops equals 2 XRP')
xrp = this.api.dropsToXrp(-2000000)
assert.strictEqual(xrp, '-2', '(number) -2 million drops equals -2 XRP')
})
it('throws with an amount with too many decimal places', function () {
assert.throws(() => {
this.api.dropsToXrp('1.2')
}, /has too many decimal places/)
assert.throws(() => {
this.api.dropsToXrp('0.10')
}, /has too many decimal places/)
})
it('throws with an invalid value', function () {
assert.throws(() => {
this.api.dropsToXrp('FOO')
}, /invalid value/)
assert.throws(() => {
this.api.dropsToXrp('1e-7')
}, /invalid value/)
assert.throws(() => {
this.api.dropsToXrp('2,0')
}, /invalid value/)
assert.throws(() => {
this.api.dropsToXrp('.')
}, /dropsToXrp\: invalid value '\.', should be a BigNumber or string-encoded number\./)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
this.api.dropsToXrp('1.0.0')
}, /dropsToXrp:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
assert.throws(() => {
this.api.dropsToXrp('...')
}, /dropsToXrp:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
})
})
describe('pagination', function () {
describe('hasNextPage', function () {
it('returns true when there is another page', function () {
return this.api.request('ledger_data').then(response => {
assert(this.api.hasNextPage(response));
}
);
});
it('returns false when there are no more pages', function () {
return this.api.request('ledger_data').then(response => {
return this.api.requestNextPage('ledger_data', {}, response);
}).then(response => {
assert(!this.api.hasNextPage(response));
});
});
});
describe('requestNextPage', function () {
it('requests the next page', function () {
return this.api.request('ledger_data').then(response => {
return this.api.requestNextPage('ledger_data', {}, response);
}).then(response => {
assert.equal(response.state[0].index, '000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D731')
});
});
it('rejects when there are no more pages', function () {
return this.api.request('ledger_data').then(response => {
return this.api.requestNextPage('ledger_data', {}, response);
}).then(response => {
assert(!this.api.hasNextPage(response))
return this.api.requestNextPage('ledger_data', {}, response);
}).then(() => {
assert(false, 'Should reject');
}).catch(error => {
assert(error instanceof Error);
assert.equal(error.message, 'response does not have a next page')
});
});
});
});
describe('preparePayment', function () { describe('preparePayment', function () {
it('normal', function () { it('normal', function () {
@@ -124,6 +392,37 @@ describe('RippleAPI', function () {
instructions).then(_.partial(checkResult, instructions).then(_.partial(checkResult,
responses.preparePayment.minAmount, 'prepare')); responses.preparePayment.minAmount, 'prepare'));
}); });
it('preparePayment - throws when fee exceeds 2 XRP', function () {
const localInstructions = _.defaults({
fee: '2.1'
}, instructions);
assert.throws(() => {
this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions)
}, /Fee of 2\.1 XRP exceeds max of 2 XRP\. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor\./)
});
it('preparePayment - allows fee exceeding 2 XRP when maxFeeXRP is higher', function () {
this.api._maxFeeXRP = '2.2'
const localInstructions = _.defaults({
fee: '2.1'
}, instructions);
const expectedResponse = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2100000\",\"Sequence\":23}",
"instructions": {
"fee": "2.1",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
return this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions).then(
_.partial(checkResult, expectedResponse, 'prepare'));
});
}); });
it('prepareOrder - buy order', function () { it('prepareOrder - buy order', function () {
@@ -262,12 +561,26 @@ describe('RippleAPI', function () {
}); });
it('prepareSettings - set signers', function () { it('prepareSettings - set signers', function () {
const settings = requests.prepareSettings.signers; const settings = requests.prepareSettings.signers.normal;
return this.api.prepareSettings(address, settings, instructions).then( return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.signers, _.partial(checkResult, responses.prepareSettings.signers,
'prepare')); 'prepare'));
}); });
it('prepareSettings - signers no threshold', function () {
const settings = requests.prepareSettings.signers.noThreshold;
assert.throws(() => {
this.api.prepareSettings(address, settings, instructions);
}, this.api.errors.ValidationError);
});
it('prepareSettings - signers no weights', function () {
const settings = requests.prepareSettings.signers.noWeights;
assert.throws(() => {
this.api.prepareSettings(address, settings, instructions);
}, this.api.errors.ValidationError);
});
it('prepareSettings - fee for multisign', function () { it('prepareSettings - fee for multisign', function () {
const localInstructions = _.defaults({ const localInstructions = _.defaults({
signersCount: 4 signersCount: 4
@@ -555,6 +868,62 @@ describe('RippleAPI', function () {
assert.deepEqual(signature, responses.sign.signAs); assert.deepEqual(signature, responses.sign.signAs);
}); });
it('sign - throws when Fee exceeds maxFeeXRP (in drops)', function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
"instructions": {
"fee": "2.01",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
assert.throws(() => {
this.api.sign(request.txJSON, secret)
}, /Fee" should not exceed "2000000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./)
});
it('sign - throws when Fee exceeds maxFeeXRP (in drops) - custom maxFeeXRP', function () {
this.api._maxFeeXRP = '1.9'
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
"instructions": {
"fee": "2.01",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
assert.throws(() => {
this.api.sign(request.txJSON, secret)
}, /Fee" should not exceed "1900000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./)
});
it('sign - permits fee exceeding 2000000 drops when maxFeeXRP is higher than 2 XRP', function () {
this.api._maxFeeXRP = '2.1'
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}",
"instructions": {
"fee": "2.01",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
const result = this.api.sign(request.txJSON, secret)
const expectedResponse = {
signedTransaction: "12000322800000002400000017201B008695536840000000001EAB90732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8744630440220384FBB48EEE7B0E58BD89294A609F9407C51FBE8FA08A4B305B22E9A7489D66602200152315EFE752DA381E74493419871550D206AC6503841DA5F8C30E35D9E3892770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304",
id: "A1586D6AF7B0821E7075E12A0132D9EB50BC1874A0749441201497F7561795FB"
}
assert.deepEqual(result, expectedResponse)
schemaValidator.schemaValidate('sign', result)
});
it('submit', function () { it('submit', function () {
return this.api.submit(responses.sign.normal.signedTransaction).then( return this.api.submit(responses.sign.normal.signedTransaction).then(
_.partial(checkResult, responses.submit, 'submit')); _.partial(checkResult, responses.submit, 'submit'));
@@ -1213,7 +1582,7 @@ describe('RippleAPI', function () {
}); });
it('request account_objects', function () { it('request account_objects', function () {
return this.api._request('account_objects', { return this.api.request('account_objects', {
account: address account: address
}).then(response => }).then(response =>
checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response)); checkResult(responses.getAccountObjects, 'AccountObjectsResponse', response));
@@ -1221,7 +1590,7 @@ describe('RippleAPI', function () {
it('request account_objects - invalid options', function () { it('request account_objects - invalid options', function () {
// Intentionally no local validation of these options // Intentionally no local validation of these options
return this.api._request('account_objects', { return this.api.request('account_objects', {
account: address, account: address,
invalid: 'options' invalid: 'options'
}).then(response => }).then(response =>
@@ -1395,6 +1764,96 @@ describe('RippleAPI', function () {
}); });
}); });
it('getFee - high load_factor', function () {
this.api.connection._send(JSON.stringify({
command: 'config',
data: { highLoadFactor: true }
}));
return this.api.getFee().then(fee => {
assert.strictEqual(fee, '2');
});
});
it('fee - default maxFee of 2 XRP', function () {
this.api._feeCushion = 1000000;
const expectedResponse = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}",
"instructions": {
"fee": "2",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
return this.api.preparePayment(
address, requests.preparePayment.normal, instructions).then(
_.partial(checkResult, expectedResponse, 'prepare'));
});
it('fee - capped to maxFeeXRP when maxFee exceeds maxFeeXRP', function () {
this.api._feeCushion = 1000000
this.api._maxFeeXRP = '3'
const localInstructions = _.defaults({
maxFee: '4'
}, instructions);
const expectedResponse = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"3000000\",\"Sequence\":23}",
"instructions": {
"fee": "3",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
return this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions).then(
_.partial(checkResult, expectedResponse, 'prepare'));
});
it('fee - capped to maxFee', function () {
this.api._feeCushion = 1000000
this.api._maxFeeXRP = '5'
const localInstructions = _.defaults({
maxFee: '4'
}, instructions);
const expectedResponse = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"4000000\",\"Sequence\":23}",
"instructions": {
"fee": "4",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
return this.api.preparePayment(
address, requests.preparePayment.normal, localInstructions).then(
_.partial(checkResult, expectedResponse, 'prepare'));
});
it('fee - calculated fee does not use more than 6 decimal places', function () {
this.api.connection._send(JSON.stringify({
command: 'config',
data: { loadFactor: 5407.96875 }
}));
const expectedResponse = {
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"64896\",\"Sequence\":23}",
"instructions": {
"fee": "0.064896",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
return this.api.preparePayment(
address, requests.preparePayment.normal, instructions).then(
_.partial(checkResult, expectedResponse, 'prepare'));
});
it('disconnect & isConnected', function () { it('disconnect & isConnected', function () {
assert.strictEqual(this.api.isConnected(), true); assert.strictEqual(this.api.isConnected(), true);
return this.api.disconnect().then(() => { return this.api.disconnect().then(() => {
@@ -1528,12 +1987,12 @@ describe('RippleAPI', function () {
_.partial(checkResult, responses.getLedger.header, 'getLedger')); _.partial(checkResult, responses.getLedger.header, 'getLedger'));
}); });
// New in > 0.21.0
// future ledger versions are allowed, and passed to rippled as-is.
it('getLedger - future ledger version', function () { it('getLedger - future ledger version', function () {
return this.api.getLedger({ ledgerVersion: 14661789 }).then(() => { return this.api.getLedger({ ledgerVersion: 14661789 }).then(response => {
assert(false, 'Should throw LedgerVersionError'); assert(response)
}).catch(error => { })
assert(error instanceof this.api.errors.LedgerVersionError);
});
}); });
it('getLedger - with state as hashes', function () { it('getLedger - with state as hashes', function () {

View File

@@ -381,7 +381,7 @@ describe('Connection', function() {
})); }));
}); });
it('propagate error message', function(done) { it('propagates error message', function(done) {
this.api.on('error', (errorCode, errorMessage, data) => { this.api.on('error', (errorCode, errorMessage, data) => {
assert.strictEqual(errorCode, 'slowDown'); assert.strictEqual(errorCode, 'slowDown');
assert.strictEqual(errorMessage, 'slow down'); assert.strictEqual(errorMessage, 'slow down');
@@ -393,11 +393,24 @@ describe('Connection', function() {
})); }));
}); });
it('propagates RippledError data', function(done) {
this.api.request('subscribe', {streams: 'validations'}).catch(error => {
assert.strictEqual(error.name, 'RippledError')
assert.strictEqual(error.message, 'invalidParams')
assert.strictEqual(error.data.error_code, 31)
assert.strictEqual(error.data.error_message, 'Invalid parameters.')
assert.deepEqual(error.data.request, { command: 'subscribe', id: 0, streams: 'validations' })
assert.strictEqual(error.data.status, 'error')
assert.strictEqual(error.data.type, 'response')
done()
})
});
it('unrecognized message type', function(done) { it('unrecognized message type', function(done) {
this.api.on('error', (errorCode, errorMessage, message) => { // This enables us to automatically support any
assert.strictEqual(errorCode, 'badMessage'); // new messages added by rippled in the future.
assert.strictEqual(errorMessage, 'unrecognized message type: unknown'); this.api.connection.on('unknown', (event) => {
assert.strictEqual(message, '{"type":"unknown"}'); assert.deepEqual(event, {type: 'unknown'})
done(); done();
}); });
@@ -414,8 +427,8 @@ describe('Connection', function() {
}); });
it('should throw RippledNotInitializedError if server does not have ' + it('should throw RippledNotInitializedError if server does not have ' +
'validated ledgers', 'validated ledgers', function() {
function() {
this.timeout(3000); this.timeout(3000);
this.api.connection._send(JSON.stringify({ this.api.connection._send(JSON.stringify({
@@ -439,13 +452,13 @@ describe('Connection', function() {
this.api.on('error', error => { this.api.on('error', error => {
done(error || new Error('Should not emit error.')); done(error || new Error('Should not emit error.'));
}); });
let disconncedCount = 0; let disconnectedCount = 0;
this.api.on('connected', () => { this.api.on('connected', () => {
done(disconncedCount !== 1 ? done(disconnectedCount !== 1 ?
new Error('Wrong number of disconnects') : undefined); new Error('Wrong number of disconnects') : undefined);
}); });
this.api.on('disconnected', () => { this.api.on('disconnected', () => {
disconncedCount++; disconnectedCount++;
}); });
this.api.connection._send(JSON.stringify({ this.api.connection._send(JSON.stringify({

View File

@@ -22,7 +22,11 @@ module.exports = {
}, },
prepareSettings: { prepareSettings: {
domain: require('./prepare-settings'), domain: require('./prepare-settings'),
signers: require('./prepare-settings-signers') signers: {
normal: require('./prepare-settings-signers'),
noThreshold: require('./prepare-settings-signers-no-threshold'),
noWeights: require('./prepare-settings-signers-no-weights')
}
}, },
prepareEscrowCreation: { prepareEscrowCreation: {
normal: require('./prepare-escrow-creation'), normal: require('./prepare-escrow-creation'),

View File

@@ -1,7 +1,7 @@
{ {
"amount": { "amount": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
}, },
"checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334" "checkID": "838766BA2B995C00744175F69A1B11E32C3DBC40E64801A4056FCBD657F57334"
} }

View File

@@ -1,7 +1,7 @@
{ {
"destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", "destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW",
"sendMax": { "sendMax": {
"currency": "XRP", "currency": "drops",
"value": "1" "value": "1000000"
} }
} }

View File

@@ -4,7 +4,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -4,7 +4,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -3,7 +3,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -14,7 +14,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -6,9 +6,9 @@
"value": "10.1" "value": "10.1"
}, },
"totalPrice": { "totalPrice": {
"currency": "XRP", "currency": "drops",
"value": "2" "value": "2000000"
}, },
"passive": true, "passive": false,
"fillOrKill": true "fillOrKill": true
} }

View File

@@ -18,7 +18,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
], ],

View File

@@ -18,7 +18,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
], ],

View File

@@ -0,0 +1,18 @@
{
"signers": {
"weights": [
{
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"weight": 1
},
{
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"weight": 1
},
{
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J",
"weight": 1
}
]
}
}

View File

@@ -0,0 +1,5 @@
{
"signers": {
"threshold": 2
}
}

View File

@@ -3,7 +3,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -9,7 +9,7 @@
"memos": [ "memos": [
{ {
"type": "test", "type": "test",
"format": "plain/text", "format": "text/plain",
"data": "texted data" "data": "texted data"
} }
] ]

View File

@@ -23,6 +23,16 @@
}, },
"orderbookChanges": {}, "orderbookChanges": {},
"ledgerVersion": 786310, "ledgerVersion": 786310,
"indexInLedger": 0 "indexInLedger": 0,
"channelChanges": {
"status": "modified",
"channelId": "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1",
"source": "rfAuMkVuZQnQhvdMcUKg4ndBb9SPDhzNmK",
"destination": "rBmNDZ7vbTCwakKXsX3pDAwDdQuxM7yBRa",
"channelBalanceChangeDrops": "801000",
"channelAmountDrops": "1000000",
"channelBalanceDrops": "801000",
"previousTxnId": "0E9CA3AB1053FC0C1CBAA75F636FE1EC92F118C7056BBEF5D63E4C116458A16D"
}
} }
} }

View File

@@ -24,6 +24,14 @@
}, },
"orderbookChanges": {}, "orderbookChanges": {},
"ledgerVersion": 786309, "ledgerVersion": 786309,
"indexInLedger": 0 "indexInLedger": 0,
"channelChanges": {
"status": "created",
"channelId": "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1",
"source": "rfAuMkVuZQnQhvdMcUKg4ndBb9SPDhzNmK",
"destination": "rBmNDZ7vbTCwakKXsX3pDAwDdQuxM7yBRa",
"channelAmountDrops": "1000000",
"channelBalanceDrops": "0"
}
} }
} }

View File

@@ -21,6 +21,16 @@
}, },
"orderbookChanges": {}, "orderbookChanges": {},
"ledgerVersion": 786310, "ledgerVersion": 786310,
"indexInLedger": 1 "indexInLedger": 1,
"channelChanges": {
"status": "modified",
"channelId": "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1",
"source": "rfAuMkVuZQnQhvdMcUKg4ndBb9SPDhzNmK",
"destination": "rBmNDZ7vbTCwakKXsX3pDAwDdQuxM7yBRa",
"channelAmountChangeDrops": "1000000",
"channelAmountDrops": "2000000",
"channelBalanceDrops": "801000",
"previousTxnId": "81B9ECAE7195EB6E8034AEDF44D8415A7A803E14513FDBB34FA984AB37D59563"
}
} }
} }

23
test/fixtures/responses/ledger.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"ledger": {
"accepted": true,
"account_hash": "F9E9653EA76EA0AEA58AC98A8E19EDCEC8299C2940519A190674FFAED3639A1F",
"close_flags": 0,
"close_time": 577999430,
"close_time_human": "2018-Apr-25 19:23:50",
"close_time_resolution": 10,
"closed": true,
"hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_index": "38217406",
"parent_close_time": 577999422,
"parent_hash": "B8B364C63EB9E13FDB89CB729FEF833089B8438CBEB8FC41744CB667209221B3",
"seqNum": "38217406",
"totalCoins": "99992286058637091",
"total_coins": "99992286058637091",
"transaction_hash": "5BDD3D2780C28FB2C91C3404BD8ED04786B764B1E18CF319888EDE2C09834726"
},
"ledger_hash": "450E5CB0A39495839DA9CD9A0FED74BD71CBB929423A907ADC00F14FC7E7F920",
"ledger_index": 38217406,
"validated": true
}

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"EscrowCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"EscrowCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"EscrowFinish\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"EscrowFinish\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Owner\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":1234,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"OfferCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":23,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"OfferCancel\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"OfferSequence\":23,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10.1\"},\"TakerPays\":\"2000000\",\"Flags\":2148139008,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23,\"OfferSequence\":12345}", "txJSON": "{\"TransactionType\":\"OfferCreate\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TakerGets\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10.1\"},\"TakerPays\":\"2000000\",\"Flags\":2148139008,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23,\"OfferSequence\":12345}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"60\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"60\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.00006", "fee": "0.00006",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8819954,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,4 +1,4 @@
{ {
"signedTransaction": "12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402202FBF6A6F74DFDA17C7341D532B66141206BC71A147C08DBDA6A950AA9A1741DC022055859A39F2486A46487F8DA261E3D80B4FDD26178A716A929F26377D1BEC7E43770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304F9EA7C04746573747D0B74657874656420646174617E0A706C61696E2F74657874E1F1", "signedTransaction": "12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874463044022047E8275DD2D9796E1A749DD8613B512AB3F5800A4B4738F97CD6904701E185D9022062736A12F5AD9135D188EF20DA3995D8C8F3823FB52B9659E6B74DFF797DADFD770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304F9EA7C04746573747D0B74657874656420646174617E0A746578742F706C61696EE1F1",
"id": "4755D26FAC39E3E477870D4E03CC6783DDDF967FFBE240606755D3D03702FC16" "id": "00731CDA974C125217CAD6DFA8D733BDBD35F3F722D5FE4710634A3FD7A0339C"
} }

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -1,5 +1,5 @@
{ {
"txJSON": "{\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10000\"},\"Flags\":2149711872,\"QualityIn\":910000000,\"QualityOut\":870000000,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", "txJSON": "{\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10000\"},\"Flags\":2149711872,\"QualityIn\":910000000,\"QualityOut\":870000000,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"746578742F706C61696E\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": { "instructions": {
"fee": "0.000012", "fee": "0.000012",
"sequence": 23, "sequence": 23,

View File

@@ -16,6 +16,7 @@ module.exports = {
}, },
empty: require('./empty'), empty: require('./empty'),
subscribe: require('./subscribe'), subscribe: require('./subscribe'),
subscribe_error: require('./subscribe_error'),
unsubscribe: require('./unsubscribe'), unsubscribe: require('./unsubscribe'),
account_objects: { account_objects: {
normal: require('./account-objects'), normal: require('./account-objects'),
@@ -37,6 +38,10 @@ module.exports = {
usd_xrp: require('./book-offers-usd-xrp'), usd_xrp: require('./book-offers-usd-xrp'),
xrp_usd: require('./book-offers-xrp-usd') xrp_usd: require('./book-offers-xrp-usd')
}, },
ledger_data: {
first_page: require('./ledger-data-first-page'),
last_page: require('./ledger-data-last-page')
},
ledger_entry: { ledger_entry: {
error: require('./ledger-entry-error') error: require('./ledger-entry-error')
}, },

View File

@@ -0,0 +1,40 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"ledger_hash":
"102A6E70FFB18C18E97BB56E3047B0E45EA1BCC90BFCCB8CBB0D07BF0E2AB449",
"ledger_index": 38202000,
"marker":
"000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D730",
"state": [
{
"Flags": 0,
"Indexes": [
"B32769DB3BE790E959A96CF37A62414479E3EB20A5AEC7156B2BF8FD816DBFF8"
],
"LedgerEntryType": "DirectoryNode",
"Owner": "rwt5iiE1mRbBgNhH6spU4nKgHcE7xK9joN",
"RootIndex":
"0005C961C890079D3C4CC8317F9735D388C3CE3D9BCDC152D3C9A7C08F508D1B",
"index":
"0005C961C890079D3C4CC8317F9735D388C3CE3D9BCDC152D3C9A7C08F508D1B"
},
{
"Account": "rpzpyUjdWKmz7yyMvirk3abcaNvSPmDpJn",
"Balance": "91508000",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID":
"F62A5A5EC92DE4E52663B9C7B44A2B76DAB1371737C83A5A81127CBDA84DFE9E",
"PreviousTxnLgrSeq": 35672898,
"Sequence": 1,
"index":
"000B6A1287DB6174F61B1BF987E630CF41DA2A2131CFEB6C5C8143A8F539E9D1"
}
],
"validated": true
}
}

View File

@@ -0,0 +1,47 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"ledger_hash":
"102A6E70FFB18C18E97BB56E3047B0E45EA1BCC90BFCCB8CBB0D07BF0E2AB449",
"ledger_index": 38202000,
"state": [
{
"Account": "rN3rdDNhQidDuzTFU1ArXWr89B4JG9xZ99",
"Balance": "249222644",
"Flags": 0,
"LedgerEntryType": "AccountRoot",
"OwnerCount": 0,
"PreviousTxnID":
"9A6EEBB6055E2C768BCA3B89B458A5D14A931449443053D9A1A9256F79D590DC",
"PreviousTxnLgrSeq": 35891744,
"Sequence": 1,
"index":
"000B714B790C3C79FEE00D17C4DEB436B375466F29679447BA64F265FD63D731"
},
{
"Account": "rLNNqGs2jJKQcg2CuoACuwkJ1ssga9LTYT",
"BookDirectory":
"6FA9AF02AF19345DC187747EF07CDABECA37CB6DCFFB045E5A08D0CF885B163B",
"BookNode": "0000000000000000",
"Flags": 0,
"LedgerEntryType": "Offer",
"OwnerNode": "0000000000000000",
"PreviousTxnID":
"5D3E557E7C08FA90EF9EE144165855B3823BD24319F28BDD81E23C3573398C44",
"PreviousTxnLgrSeq": 38040457,
"Sequence": 9,
"TakerGets": {
"currency": "CNY",
"issuer": "rPT74sUcTBTQhkHVD54WGncoqXEAMYbmH7",
"value": "322.4"
},
"TakerPays": "80000000",
"index":
"0011C33FA959278D478E7A3811D7DBB9E43E1768E12538CD54B028E5E7DA96E5"
}
],
"validated": true
}
}

View File

@@ -0,0 +1,13 @@
{
"id": 0,
"status": "error",
"type": "response",
"error": "invalidParams",
"error_code": 31,
"error_message": "Invalid parameters.",
"request": {
"command": "subscribe",
"id": 0,
"streams": "validations"
}
}

View File

@@ -152,7 +152,39 @@ module.exports = function createMockRippled(port) {
mock.on('request_server_info', function (request, conn) { mock.on('request_server_info', function (request, conn) {
assert.strictEqual(request.command, 'server_info'); assert.strictEqual(request.command, 'server_info');
if (conn.config.returnErrorOnServerInfo) { if (conn.config.highLoadFactor || conn.config.loadFactor) {
const response = {
"id": 0,
"status": "success",
"type": "response",
"result": {
"info": {
"build_version": "0.24.0-rc1",
"complete_ledgers": "32570-6595042",
"hostid": "ARTS",
"io_latency_ms": 1,
"last_close": {
"converge_time_s": 2.007,
"proposers": 4
},
"load_factor": conn.config.loadFactor || 4294967296,
"peers": 53,
"pubkey_node": "n94wWvFUmaKGYrKUGgpv1DyYgDeXRGdACkNQaSe7zJiy5Znio7UC",
"server_state": "full",
"validated_ledger": {
"age": 5,
"base_fee_xrp": 0.00001,
"hash": "4482DEE5362332F54A4036ED57EE1767C9F33CF7CE5A6670355C16CECE381D46",
"reserve_base_xrp": 20,
"reserve_inc_xrp": 5,
"seq": 6595042
},
"validation_quorum": 3
}
}
}
conn.send(createResponse(request, response));
} else if (conn.config.returnErrorOnServerInfo) {
conn.send(createResponse(request, fixtures.server_info.error)); conn.send(createResponse(request, fixtures.server_info.error));
} else if (conn.config.disconnectOnServerInfo) { } else if (conn.config.disconnectOnServerInfo) {
conn.close(); conn.close();
@@ -168,7 +200,9 @@ module.exports = function createMockRippled(port) {
mock.on('request_subscribe', function (request, conn) { mock.on('request_subscribe', function (request, conn) {
assert.strictEqual(request.command, 'subscribe'); assert.strictEqual(request.command, 'subscribe');
if (mock.config.returnEmptySubscribeRequest) { if (request && request.streams === 'validations') {
conn.send(createResponse(request, fixtures.subscribe_error))
} else if (mock.config.returnEmptySubscribeRequest) {
mock.config.returnEmptySubscribeRequest--; mock.config.returnEmptySubscribeRequest--;
conn.send(createResponse(request, fixtures.empty)); conn.send(createResponse(request, fixtures.empty));
} else if (request.accounts) { } else if (request.accounts) {
@@ -237,6 +271,15 @@ module.exports = function createMockRippled(port) {
} }
}); });
mock.on('request_ledger_data', function (request, conn) {
assert.strictEqual(request.command, 'ledger_data');
if (request.marker) {
conn.send(createResponse(request, fixtures.ledger_data.last_page));
} else {
conn.send(createResponse(request, fixtures.ledger_data.first_page));
}
});
mock.on('request_ledger_entry', function (request, conn) { mock.on('request_ledger_entry', function (request, conn) {
assert.strictEqual(request.command, 'ledger_entry'); assert.strictEqual(request.command, 'ledger_entry');
if (request.index === if (request.index ===

View File

@@ -4164,9 +4164,9 @@ ripple-keypairs@^0.10.1:
hash.js "^1.0.3" hash.js "^1.0.3"
ripple-address-codec "^2.0.1" ripple-address-codec "^2.0.1"
ripple-lib-transactionparser@^0.6.2: ripple-lib-transactionparser@0.7.1:
version "0.6.2" version "0.7.1"
resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.2.tgz#eb117834816cab3398445a74ec3cacec95b6b5fa" resolved "https://registry.yarnpkg.com/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.7.1.tgz#5ececb1e03d65d05605343f4b9dbb76d1089145b"
dependencies: dependencies:
bignumber.js "^4.1.0" bignumber.js "^4.1.0"
lodash "^4.17.4" lodash "^4.17.4"