Compare commits

...

63 Commits

Author SHA1 Message Date
Elliot Lee
f785605db8 Release 1.0.1 2018-09-27 19:01:38 -07:00
Elliot Lee
01ad30ab07 Fix typos in ledger_entries.ts 2018-09-19 14:24:02 -07:00
Cory Perkins
e08d507462 Add remaining LedgerEntry types (#943)
* Replaced the union with 'any' in the LedgerEntry type
* Added DepositPreauthLedgerEntry (new in rippled 1.1.0)
2018-09-19 14:21:52 -07:00
wudanjs
4c23bd5ad3 Include memos when parsing trustlines (#949) 2018-09-19 14:17:38 -07:00
Scott M Sunarto
b15abd5376 Update server regex to accommodate UDS (#944) 2018-09-18 11:47:46 -07:00
Elliot Lee
79971f906b Update release notes 2018-09-11 18:45:52 -07:00
Elliot Lee
9eec98778f Expose validation methods (#932)
Merge branch 'movitto-expose_validation_methods' into develop
2018-09-11 18:33:55 -07:00
Elliot Lee
dcd0e14142 Add test of deriveKeypair with ed25519 secret 2018-09-11 18:33:07 -07:00
Elliot Lee
cfcbc9aab7 Update checksums for 1.0.0 2018-09-10 14:52:14 -07:00
Mo Morsi
a80de5658a Move deriveKeypair and deriveAddress to offline module 2018-09-09 09:39:25 -04:00
Mo Morsi
34215eb309 Add docs for newly exposed API methods 2018-09-09 09:39:25 -04:00
Mo Morsi
9458005d7f Add tests for newly exposed API methods 2018-09-09 09:39:25 -04:00
Mo Morsi
5f36df0172 Expose deriveKeypair, deriveAddress, isValidAddress, and isValidSecret to the public API 2018-09-09 09:39:25 -04:00
Elliot Lee
3ff4929a49 Fix PendingLedgerVersionError message and export FormattedTransactionType (#941) 2018-09-04 18:31:24 -07:00
Elliot Lee
4bea69d647 Release 1.0.0 2018-08-30 14:30:57 -07:00
Elliot Lee
09541dae86 docs: rippled APIs use issuer 2018-08-30 14:12:45 -07:00
Elliot Lee
bcbcc53c87 No `--coverage' option anymore with nyc
See https://github.com/istanbuljs/nyc
2018-08-30 13:41:52 -07:00
Elliot Lee
76f120bec9 Merge branch 'getLedger-includeRawTransaction-2' into develop 2018-08-30 13:30:18 -07:00
Elliot Lee
f8bf28876d if statements must be braced 2018-08-30 13:29:50 -07:00
wilsonianb
b03795df09 Add hidden computeTreeHashes option 2018-08-30 13:29:50 -07:00
Elliot Lee
dbe20d6574 getLedger: include raw transaction with each transaction
computeLedgerHash: support new getLedger response by using
rawTransaction

BREAKING CHANGES:

* Remove the `rawTransactions` field and replace it with a new `rawTransaction` field in each transaction.
* Rename the `metaData` field (in each raw transaction) to `meta` (for consistency with rippled's `tx` method).
* Add `ledger_index` to each raw transaction.
2018-08-30 13:28:52 -07:00
Elliot Lee
0550fab73e Update escrowCreation error message and docs (#934)
Fix #933
2018-08-29 19:03:33 -07:00
Elliot Lee
3f2d9d198e computeLedgerHash - add requireRawTransactions option 2018-08-28 17:31:12 -07:00
Elliot Lee
b9c953fce6 Fix getPaths (#930)
* getPaths:
  * Filter paths correctly
  * Use correct value when XRP is the destination currency
2018-08-23 17:37:02 -07:00
Elliot Lee
181cfd69de Prevent 'amount' from being misinterpreted (#924)
The 'amount' field should almost never be used.
With partial payments, the field can show an amount that is
significantly less than the amount that the transaction actually
delivered. This change sets amount to 0 XRP when it may be misleading.

This change omits the `amount` when parsing payment transactions.
See `HISTORY.md` for recommended alternatives.
2018-08-23 16:17:23 -07:00
Elliot Lee
569766b8f8 Rename json schemas (#931)
Rename files for consistency with their titles:

* Rename amount-base to amountbase
* Rename tx-hash to transaction-hash
* Rename id to transactionHash
* Rename objects/settings.json to objects/settings-plus-memos.json
* Rename ledgerversion to ledger-version
2018-08-22 14:41:41 -07:00
Elliot Lee
53a232ebdc Update docs 2018-08-14 20:05:50 -07:00
Elliot Lee
4c9a2ff538 Improve Order docs. Fix #776 2018-08-14 19:27:50 -07:00
Elliot Lee
dc623cd049 Release 1.0.0-beta.5 2018-08-11 01:38:36 -07:00
Elliot Lee
7cd517268b Fix error TS2307: Cannot find module 2018-08-11 01:35:47 -07:00
Elliot Lee
2438295e70 Release 1.0.0-beta.4 2018-08-10 14:58:19 -07:00
Elliot Lee
f5e1a4a588 Revert "Expose validation methods in public api"
This reverts commit 9e9a0a7d9b.

We should move methods like deriveKeypair and deriveAddress
off of the schemaValidator object.
2018-08-10 14:54:01 -07:00
Mo Morsi
9e9a0a7d9b Expose validation methods in public api
Updated to fix tests
2018-08-06 16:09:04 -07:00
Elliot Lee
1c68283d1e ES6: omit property value since it matches the variable name 2018-07-28 00:42:37 -07:00
Elliot Lee
28796d37cb Update TypeScript to 2.9.2 2018-07-28 00:18:58 -07:00
Elliot Lee
067bc48d4e Add prepareTransaction() (#898)
* Export iso8601ToRippleTime(), txFlags, convertStringToHex()

* Fix a bug that caused fees exceeding 999 to throw an error

e.g. '1,000' would not be recognized as a valid number.
To use 6 decimal places, toFixed should be used instead of toFormat.
2018-07-26 12:24:29 -07:00
Fred K. Schott
b94698df0b Update some API methods to use api.request() internally (#896)
* use any instead of object
2018-07-26 12:23:07 -07:00
Elliot Lee
4f40b5cb6d Update README.md - How to build ripple-lib (#837) 2018-07-25 12:09:55 -07:00
Elliot Lee
14704eee6b Add more tests of getFee (#897) 2018-07-25 01:53:27 -07:00
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
Elliot Lee
823d93b86c Add checksums for 0.22.0 2018-05-10 15:26:43 -07:00
Elliot Lee
7a42db99c9 Bump version to 0.22.0 2018-05-10 00:33:20 -07:00
Elliot Lee
f28ec27614 getOrderbook - return raw order data (#886)
Fix #799
2018-05-08 15:05:41 -07:00
Elliot Lee
a36e23ebfa Release 0.21.0 2018-04-11 21:33:44 -07:00
Elliot Lee
e978ef1888 Add getAccountObjects (#881)
Squashed commit of the following:

commit 361ead8cbbbe4fa25ecba614f8f11930ff679996
commit 5ff26d7d2defbbaaa7c50d6b3b5b74bf30be19ef
commit 97f5dfc86d4730082fd016197b0c025e499912e3
commit d48654098601f2a19484d9bbae7c65786e3c5dd4
commit 4790401123e7836f6bea8d03111bce60dcf95114
commit 57512f7fc000689bb8224f33173ba91221f27281
commit e75a7e95b11368b26c40e8e6e7b583d978475e95
2018-04-11 14:37:37 -07:00
Chris Yuen
9af3968508 Upgrade https-proxy-agent to version 2 (#883) 2018-04-10 12:59:24 -07:00
143 changed files with 6752 additions and 1164 deletions

2
.nvmrc
View File

@@ -1 +1 @@
v6
v8

View File

@@ -1,5 +1,228 @@
# ripple-lib Release History
## 1.0.1 (2018-09-27)
+ Add address/secret/key validation and derivation methods ([#932](https://github.com/ripple/ripple-lib/pull/932))
+ `isValidAddress(address: string) : boolean`: Checks if the specified string contains a valid address.
+ `isValidSecret(secret: string): boolean`: Checks if the specified string contains a valid secret.
+ `deriveKeypair(seed: string): {privateKey: string, publicKey: string}`: Derive a public and private key from a seed.
+ `deriveAddress(publicKey: string): string`: Derive an XRP Ledger address from a public key.
+ To derive an address from a secret:
1. Derive the public key from the secret.
2. Derive the address from the public key.
+ Example: `const address = api.deriveAddress(api.deriveKeypair(secret).publicKey)`
+ Update server regex to accommodate UDS (#944)
+ Include memos when parsing trustlines (#949)
+ Add remaining LedgerEntry types (#943)
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
9b6408641ce83659afcd5765c256c35829a4fcb4c3244dc9ca6bf27c871a45c4 ripple-1.0.1-debug.js
7ab2b69fe59c2d4a74638116e2ba3b387155eb2d23e48a01bbf7beb72911f898 ripple-1.0.1-min.js
8bb4dcad9ce25a27003b1d73d71ddf41b8a5af02ece4ebbfeaff4aeb91f3b8c4 ripple-1.0.1.js
```
## 1.0.0 (2018-08-30)
We are pleased to announce the release of `ripple-lib` version 1.0.0.
This version features a range of changes and improvements that make the library
more capable and flexible. It includes new methods for accessing rippled APIs,
including subscriptions.
When using this version with `rippled` for online functionality, we recommend
using `rippled` version 1.0.1 or later.
Here is a summary of the changes since `ripple-lib` version 0.22.0, which was
the last non-beta version.
### New Features
+ [Add `request()`, `hasNextPage()`, and `requestNextPage()` for accessing `rippled`
APIs](https://github.com/ripple/ripple-lib/blob/09541dae86bc859bf5928ac65b2645dfaaf7f8b1/docs/index.md#rippled-apis).
+ Add `prepareTransaction()` for preparing raw `txJSON`.
+ XRP amounts can be specified in drops. Also, `xrpToDrops()` and `dropsToXrp()`
are available to make conversions.
+ `getTransaction` responses can include a new `channelChanges` property that
describes the details of a payment channel.
### Data Validation and Errors
+ [Amounts in drops and XRP are checked for
validity](https://github.com/ripple/ripple-lib/blob/develop/HISTORY.md#100-beta1-2018-05-24).
+ [A maximum fee is now
imposed](https://github.com/ripple/ripple-lib/blob/develop/HISTORY.md#100-beta2-2018-06-08). Exceeding it causes a `ValidationError` to be
thrown.
+ Errors are improved and more data validation was added.
+ Bug fix: `getPaths` now filters paths correctly and works correctly when the
destination currency is XRP.
### Breaking Changes
The following changes were introduced in 1.0.0.
+ `getTransaction()` and `getTransactions()`
+ The `specification.destination.amount` field has been removed from the parsed transaction response.
+ To determine the amount that a transaction delivered, use `outcome.deliveredAmount`.
+ If you require the provisional requested `Amount` from the original transaction:
+ Use `getTransaction`'s `includeRawTransaction` option, or
+ Use `getTransactions`'s `includeRawTransactions` option, or
+ Use the rippled APIs directly with `request`. For example, call the API methods `tx`, `account_tx`, etc.
+ `getLedger()` response object
+ The `rawTransactions` field has been removed (for consistency with `getTransaction()` and `getTransactions()`).
+ Instead, within each `transaction`, use the new `rawTransaction` JSON string.
+ The `metaData` field has been renamed to `meta` for consistency with rippled's `tx` method.
+ `ledger_index` has been added to each raw transaction.
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
06e5efcb6846ad45dedfd85cfa2ef4bdeb608b15ccbfb60b872c995d97342426 ripple-1.0.0-debug.js
cdb26b928a89ce228c727d1ff966df266eb46b2f76bd94f81cbeb0a9d75febf0 ripple-1.0.0-min.js
f74ee804e8a945a994e4e3901a0a3eb52292fbdcbff61ed30cefb8ffbcba50c3 ripple-1.0.0.js
```
## 1.0.0-beta.5 (2018-08-11)
+ [Fix a TypeScript error by importing the `Prepare` type](https://github.com/ripple/ripple-lib/commit/7cd517268bda5fe74b91dad02fedf8b51b7eae9b)
## 1.0.0-beta.4 (2018-08-10)
+ [Add `prepareTransaction()`](https://github.com/ripple/ripple-lib/pull/898)
+ Internal improvements and cleanup
## 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)
+ [`getOrderbook` - return raw order data](https://github.com/ripple/ripple-lib/pull/886). The full `BookOffer` data is now provided under `data`.
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
33f71b55c4adec4452826e44fe7809377364df04222b60f0fce01e7de2daff33 ripple-0.22.0-debug.js
63232888a4ea77065e8e8eb8fdaa8ebfe3a785428fe935e2667c1ea54c837f29 ripple-0.22.0-min.js
ab98026fabe296bd938297c48cb58e01dfdbe90f3c66c9617d6a3e1efd4c6b93 ripple-0.22.0.js
```
## 0.21.0 (2018-04-11)
+ [Upgrade https-proxy-agent](https://github.com/ripple/ripple-lib/pull/883)
+ [Add getAccountObjects](https://github.com/ripple/ripple-lib/pull/881)
The SHA-256 checksums for the browser version of this release can be found
below.
```
% shasum -a 256 *
3ab52209ad4a80393c8c08ef3f4aa9cfb47bc76c0ede2ee9fa7f5ca180ba4d67 ripple-0.21.0-debug.js
3b1efccded347bed5f64757098a1ea6a513bb8932d922d00af47cd24e001dc14 ripple-0.21.0-min.js
db08e5a3eab1f659b4c803543374398004d950ba720adc4b9a7658817cb5c94b ripple-0.21.0.js
```
## 0.20.0 (2018-04-09)
+ [Add support for using a keypair with sign()](https://github.com/ripple/ripple-lib/pull/769)

View File

@@ -26,13 +26,41 @@ Install `ripple-lib`:
$ yarn add ripple-lib
```
Then see the [documentation](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md) and [code samples](https://github.com/ripple/ripple-lib/tree/develop/docs/samples)
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).
## Running tests
### 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)
## Development
To build the library for Node.js:
```
$ yarn compile
```
The TypeScript compiler will [output](./tsconfig.json#L7) the resulting JS files in `./dist/npm/`.
To build the library for the browser:
```
$ yarn build
```
Gulp will [output](./Gulpfile.js) the resulting JS files in `./build/`.
For more details, see the `scripts` in `package.json`.
## Running Tests
1. Clone the repository
2. `cd` into the repository and install dependencies with `yarn install`
3. `yarn test` or `yarn test --coverage` (`istanbul` will create coverage reports in `coverage/lcov-report/`)
3. `yarn test`
## Generating Documentation

File diff suppressed because it is too large Load Diff

View File

@@ -19,14 +19,13 @@ Currencies are represented as either 3-character currency codes or 40-character
## 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.
**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`.
## Amount
Example amount:
Example 100.00 USD amount:
```json
{
@@ -36,15 +35,16 @@ Example amount:
}
```
Example XRP amount:
Example 3.0 XRP amount, in drops:
```json
{
"currency": "XRP",
"value": "2000"
"currency": "drops",
"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.
@@ -52,4 +52,4 @@ A *lax lax amount* allows either or both the counterparty and value to be omitte
A *balance* is an amount than can have a negative value.
<%- renderSchema('objects/amount-base.json') %>
<%- renderSchema('objects/amountbase.json') %>

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.
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`:

View File

@@ -0,0 +1,19 @@
## deriveAddress
`deriveAddress(publicKey: string): string`
Derive an XRP Ledger address from a public key.
### Parameters
This method takes one parameter, the public key from which to derive the address.
### Return Value
This method returns a string corresponding to the address derived from the public key.
### Example
```javascript
var address = api.deriveAddress(public_key);
```

View File

@@ -0,0 +1,21 @@
## deriveKeypair
`deriveKeypair(seed: string): {privateKey: string, publicKey: string}`
Derive a public and private key from a seed.
### Parameters
This method takes one parameter, the seed from which to derive the public and private key.
### Return Value
This method returns an object containing the public and private components of the keypair corresponding to the seed.
### Example
```javascript
var keypair = api.deriveKeypair(seed)
var public_key = keypair.publicKey;
var private_key = keypair.privateKey;
```

View File

@@ -0,0 +1,33 @@
## getAccountObjects
`getAccountObjects(address: string, options: object): Promise<AccountObjectsResponse>`
Returns objects owned by an account. For an account's trust lines and balances, see `getTrustlines` and `getBalances`.
### Parameters
<%- renderSchema('input/get-account-objects.json') %>
### Return Value
This method returns a promise that resolves with an object with the following structure:
<%- renderSchema('output/get-account-objects.json') %>
The types of objects that may be returned include:
* Offer objects for orders that are currently live, unfunded, or expired but not yet removed.
* RippleState objects for trust lines where this account's side is not in the default state.
* A SignerList object if the account has multi-signing enabled.
* Escrow objects for held payments that have not yet been executed or canceled.
* PayChannel objects for open payment channels.
* Check objects for pending checks.
### Example
```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
return api.getAccountObjects(address: address).then(objects =>
{/* ... */});
```
<%- renderFixture('responses/get-account-objects.json') %>

View File

@@ -4,13 +4,15 @@
Returns the estimated transaction fee for the rippled server the RippleAPI instance is connected to.
This will use the [feeCushion parameter](#parameters) provided to the RippleAPI constructor, or the default value of `1.2`.
### Parameters
This method has no parameters.
<%- renderSchema('input/get-fee.json') %>
### Return Value
This method returns a promise that resolves with a string encoded floating point value representing the estimated fee to submit a transaction, expressed in XRP.
This method returns a promise that resolves with a string-encoded floating point value representing the estimated fee to submit a transaction, expressed in XRP.
### Example
@@ -19,5 +21,5 @@ return api.getFee().then(fee => {/* ... */});
```
```json
"0.012"
"0.000012"
```

View File

@@ -14,6 +14,12 @@ This method returns a promise that resolves with an object with the following st
<%- renderSchema('output/get-orderbook.json') %>
### Raw order data
(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).
### Example
```javascript

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 transactions.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 connect.md.ejs %>
<% include disconnect.md.ejs %>
@@ -21,6 +25,7 @@
<% include getOrderbook.md.ejs %>
<% include getSettings.md.ejs %>
<% include getAccountInfo.md.ejs %>
<% include getAccountObjects.md.ejs %>
<% include getPaymentChannel.md.ejs %>
<% include getLedger.md.ejs %>
<% include preparePayment.md.ejs %>
@@ -41,6 +46,10 @@
<% include combine.md.ejs %>
<% include submit.md.ejs %>
<% include generateAddress.md.ejs %>
<% include isValidAddress.md.ejs %>
<% include isValidSecret.md.ejs %>
<% include deriveKeypair.md.ejs %>
<% include deriveAddress.md.ejs %>
<% include signPaymentChannelClaim.md.ejs %>
<% include verifyPaymentChannelClaim.md.ejs %>
<% include computeLedgerHash.md.ejs %>

View File

@@ -1,6 +1,6 @@
# 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:
* [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)
* [Generate a new XRP Ledger Address](#generateaddress)
* ... and [much more](#api-methods).
RippleAPI only provides access to *validated*, *immutable* transaction data.

View File

@@ -0,0 +1,19 @@
## isValidAddress
`isValidAddress(address: string): boolean`
Checks if the specified string contains a valid address.
### Parameters
This method takes one parameter, the address to validate.
### Return Value
This method returns `true` if the address is valid and `false` if it is not.
### Example
```javascript
return api.isValidAddress("address")
```

View File

@@ -0,0 +1,19 @@
## isValidSecret
`isValidSecret(secret: string): boolean`
Checks if the specified string contains a valid secret.
### Parameters
This method takes one parameter, the secret which to validate.
### Return Value
This method returns `true` if the secret is valid and `false` if it is not.
### Example
```javascript
return api.isValidSecret("secret")
```

View File

@@ -8,6 +8,12 @@ Prepare an escrow creation transaction. The prepared transaction must subsequent
<%- renderSchema('input/prepare-escrow-creation.json') %>
This is a convenience method for generating the EscrowCreate JSON used by rippled, so the same restrictions apply.
Field mapping: `allowCancelAfter` is equivalent to rippled's `CancelAfter`; `allowExecuteAfter` is equivalent to `FinishAfter`. At the `allowCancelAfter` time, the escrow is considered expired. This means that the funds can only be returned to the sender. At the `allowExecuteAfter` time, the escrow is permitted to be released to the recipient (if the `condition` is fulfilled).
Note that `allowCancelAfter` must be chronologically later than `allowExecuteAfter`.
### Return Value
This method returns a promise that resolves with an object with the following structure:

View File

@@ -22,6 +22,8 @@ All "prepare*" methods have the same return type.
```javascript
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') %>;
return api.prepareOrder(address, order)
.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,69 @@
# 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://developers.ripple.com/basic-data-types.html#specifying-currency-amounts).
* [Specify timestamps as the number of seconds since the "Ripple Epoch"](https://developers.ripple.com/basic-data-types.html#specifying-time).
* Instead of `counterparty`, use `issuer`.
## 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://developers.ripple.com/subscription-methods.html) 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

@@ -28,6 +28,8 @@ See [Transaction Types](#transaction-types) for a description.
<%- renderSchema('specifications/order.json') %>
The following invalid flag combination causes a `ValidationError`: `immediateOrCancel` and `fillOrKill`. These fields are mutually exclusive, and cannot both be set at the same time.
### Example
<%- renderFixture('requests/prepare-order.json') %>

View File

@@ -53,7 +53,7 @@ Transaction instructions indicate how to execute a transaction, complementary wi
<%- 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

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.20.0",
"version": "1.0.1",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -19,14 +19,14 @@
"@types/lodash": "^4.14.85",
"@types/ws": "^3.2.0",
"bignumber.js": "^4.1.0",
"https-proxy-agent": "^1.0.0",
"https-proxy-agent": "2.2.1",
"jsonschema": "1.2.2",
"lodash": "^4.17.4",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.1.13",
"ripple-hashes": "^0.3.1",
"ripple-keypairs": "^0.10.1",
"ripple-lib-transactionparser": "^0.6.2",
"ripple-lib-transactionparser": "0.7.1",
"ws": "^3.3.1"
},
"devDependencies": {
@@ -53,7 +53,7 @@
"ts-node": "^3.3.0",
"tslint": "^5.8.0",
"tslint-eslint-rules": "^4.1.1",
"typescript": "^2.6.1",
"typescript": "2.9.2",
"uglifyjs-webpack-plugin": "^1.1.4",
"webpack": "^3.10.0",
"yargs": "^8.0.2"

View File

@@ -1,12 +1,17 @@
import * as _ from 'lodash'
import {EventEmitter} from 'events'
import {Connection, errors, validate} from './common'
import {
Connection,
errors,
validate,
xrpToDrops,
dropsToXrp,
iso8601ToRippleTime,
txFlags
} from './common'
import {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion,
formatLedgerClose
} from './server/server'
@@ -20,6 +25,7 @@ import getOrders from './ledger/orders'
import getOrderbook from './ledger/orderbook'
import getSettings from './ledger/settings'
import getAccountInfo from './ledger/accountinfo'
import getAccountObjects from './ledger/accountobjects'
import getPaymentChannel from './ledger/payment-channel'
import preparePayment from './transaction/payment'
import prepareTrustline from './transaction/trustline'
@@ -39,30 +45,37 @@ import sign from './transaction/sign'
import combine from './transaction/combine'
import submit from './transaction/submit'
import {generateAddressAPI} from './offline/generate-address'
import {deriveKeypair, deriveAddress} from './offline/derive'
import computeLedgerHash from './offline/ledgerhash'
import signPaymentChannelClaim from './offline/sign-payment-channel-claim'
import verifyPaymentChannelClaim from './offline/verify-payment-channel-claim'
import getLedger from './ledger/ledger'
import {
AccountObjectsRequest, AccountObjectsResponse,
AccountOffersRequest, AccountOffersResponse,
AccountInfoRequest, AccountInfoResponse,
AccountLinesRequest, AccountLinesResponse,
BookOffersRequest, BookOffersResponse,
GatewayBalancesRequest, GatewayBalancesResponse,
LedgerRequest, LedgerResponse,
LedgerEntryRequest, LedgerEntryResponse
LedgerEntryRequest, LedgerEntryResponse,
ServerInfoRequest, ServerInfoResponse
} from './common/types/commands'
import RangeSet from './common/rangeset'
import * as ledgerUtils from './ledger/utils'
import * as transactionUtils from './transaction/utils'
import * as schemaValidator from './common/schema-validator'
import {getServerInfo, getFee} from './common/serverinfo'
import {clamp} from './ledger/utils'
import {Instructions, Prepare} from './transaction/types'
export type APIOptions = {
server?: string,
feeCushion?: number,
maxFeeXRP?: string,
trace?: boolean,
proxy?: string,
timeout?: number
@@ -85,29 +98,17 @@ 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 {
_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.
static _PRIVATE = {
validate: validate,
validate,
RangeSet,
ledgerUtils,
schemaValidator
@@ -117,9 +118,10 @@ class RippleAPI extends EventEmitter {
super()
validate.apiOptions(options)
this._feeCushion = options.feeCushion || 1.2
this._maxFeeXRP = options.maxFeeXRP || '2'
const serverURL = options.server
if (serverURL !== undefined) {
this.connection = new RestrictedConnection(serverURL, options)
this.connection = new Connection(serverURL, options)
this.connection.on('ledgerClosed', message => {
this.emit('ledger', formatLedgerClose(message))
})
@@ -135,49 +137,103 @@ class RippleAPI extends EventEmitter {
} else {
// use null object pattern to provide better error message if user
// tries to call a method that requires a connection
this.connection = new RestrictedConnection(null, options)
this.connection = new Connection(null, options)
}
}
/**
* Makes a simple request to the API with the given command and any
* Makes a request to the API with the given command and
* additional request body parameters.
*
* NOTE: This command is under development and should not yet be relied
* on by external consumers.
*/
async _request(command: 'account_info', params: AccountInfoRequest):
async request(command: 'account_info', params: AccountInfoRequest):
Promise<AccountInfoResponse>
async _request(command: 'account_lines', params: AccountLinesRequest):
async request(command: 'account_lines', params: AccountLinesRequest):
Promise<AccountLinesResponse>
async _request(command: 'account_offers', params: AccountOffersRequest):
async request(command: 'account_objects', params: AccountObjectsRequest):
Promise<AccountObjectsResponse>
async request(command: 'account_offers', params: AccountOffersRequest):
Promise<AccountOffersResponse>
async _request(command: 'book_offers', params: BookOffersRequest):
async request(command: 'book_offers', params: BookOffersRequest):
Promise<BookOffersResponse>
async _request(command: 'gateway_balances', params: GatewayBalancesRequest):
async request(command: 'gateway_balances', params: GatewayBalancesRequest):
Promise<GatewayBalancesResponse>
async _request(command: 'ledger', params: LedgerRequest):
async request(command: 'ledger', params: LedgerRequest):
Promise<LedgerResponse>
async _request(command: 'ledger_entry', params: LedgerEntryRequest):
async request(command: 'ledger_entry', params: LedgerEntryRequest):
Promise<LedgerEntryResponse>
async _request(command: string, params: any = {}) {
async request(command: 'server_info', params?: ServerInfoRequest):
Promise<ServerInfoResponse>
async request(command: string, params: any):
Promise<any>
async request(command: string, params: any = {}): Promise<any> {
return this.connection.request({
...params,
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)
}
/**
* Prepare a transaction.
*
* You can later submit the transaction with `submit()`.
*/
async prepareTransaction(txJSON: object, instructions: Instructions = {}):
Promise<Prepare> {
return transactionUtils.prepareTransaction(txJSON, this, instructions)
}
/**
* Convert a string to hex.
*
* This can be used to generate `MemoData`, `MemoType`, and `MemoFormat`.
*
* @param string string to convert to hex
*/
convertStringToHex(string: string): string {
return transactionUtils.convertStringToHex(string)
}
/**
* 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`
* number of resources is reached (if no `limit` is provided, a single request
* will be made).
*
* If the command is unknown, an additional `collect` property is required to
* know which response key contains the array of resources.
*
* NOTE: This command is under development and should not yet be relied
* on by external consumers.
* NOTE: This command is used by existing methods and is not recommended for
* general use. Instead, use rippled's built-in pagination and make multiple
* requests as needed.
*/
async _requestAll(command: 'account_offers', params: AccountOffersRequest):
Promise<AccountOffersResponse[]>
@@ -210,12 +266,9 @@ class RippleAPI extends EventEmitter {
limit: countRemaining,
marker
}
// NOTE: We have to generalize the `this._request()` function signature
// here until we add support for unknown commands (since command is some
// unknown string).
const singleResult = await (<Function>this._request)(command, repeatProps)
const singleResult = await this.request(command, repeatProps)
const collectedData = singleResult[collectKey]
marker = singleResult.marker
marker = singleResult['marker']
results.push(singleResult)
// Make sure we handle when no data (not even an empty array) is returned.
const isExpectedFormat = Array.isArray(collectedData)
@@ -246,6 +299,7 @@ class RippleAPI extends EventEmitter {
getOrderbook = getOrderbook
getSettings = getSettings
getAccountInfo = getAccountInfo
getAccountObjects = getAccountObjects
getPaymentChannel = getPaymentChannel
getLedger = getLedger
@@ -268,10 +322,20 @@ class RippleAPI extends EventEmitter {
submit = submit
generateAddress = generateAddressAPI
deriveKeypair = deriveKeypair
deriveAddress = deriveAddress
computeLedgerHash = computeLedgerHash
signPaymentChannelClaim = signPaymentChannelClaim
verifyPaymentChannelClaim = verifyPaymentChannelClaim
errors = errors
xrpToDrops = xrpToDrops
dropsToXrp = dropsToXrp
iso8601ToRippleTime = iso8601ToRippleTime
txFlags = txFlags
isValidAddress = schemaValidator.isValidAddress
isValidSecret = schemaValidator.isValidSecret
}
export {

View File

@@ -7,12 +7,6 @@ import {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError,
RippledNotInitializedError} from './errors'
function isStreamMessageType(type) {
return type === 'ledgerClosed' ||
type === 'transaction' ||
type === 'path_find'
}
export interface ConnectionOptions {
trace?: boolean,
proxy?: string
@@ -90,19 +84,20 @@ class Connection extends EventEmitter {
const data = JSON.parse(message)
if (data.type === 'response') {
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]
} 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) {
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) {
@@ -246,7 +241,7 @@ class Connection extends EventEmitter {
_onOpenError(reject, error) {
this._onOpenErrorBound = null
this._unbindOnUnxpectedClose()
reject(new NotConnectedError(error && error.message))
reject(new NotConnectedError(error.message, error))
}
_createWebSocket(): WebSocket {
@@ -404,7 +399,7 @@ class Connection extends EventEmitter {
return new Promise((resolve, reject) => {
this._ws.send(message, undefined, error => {
if (error) {
reject(new DisconnectedError(error.message))
reject(new DisconnectedError(error.message, error))
} else {
resolve()
}
@@ -427,7 +422,7 @@ class Connection extends EventEmitter {
function onDisconnect() {
clearTimeout(timer)
self.removeAllListeners(eventName)
reject(new DisconnectedError())
reject(new DisconnectedError('websocket was closed'))
}
function cleanup() {
@@ -450,12 +445,12 @@ class Connection extends EventEmitter {
this.once(eventName, response => {
if (response.status === 'error') {
_reject(new RippledError(response.error))
_reject(new RippledError(response.error, response))
} else if (response.status === 'success') {
_resolve(response.result)
} else {
_reject(new ResponseFormatError(
'unrecognized status: ' + response.status))
'unrecognized status: ' + response.status, response))
}
})

View File

@@ -70,7 +70,7 @@ class MissingLedgerHistoryError extends RippleError {
class PendingLedgerVersionError extends RippleError {
constructor(message?: string) {
super(message || 'maxLedgerVersion is greater than server\'s most recent ' +
super(message || 'maxLedgerVersion is greater than server\'s most recent' +
' validated ledger')
}
}

View File

@@ -9,13 +9,13 @@ function loadSchemas() {
// listed explicitly for webpack (instead of scanning schemas directory)
const schemas = [
require('./schemas/objects/tx-json.json'),
require('./schemas/objects/tx-type.json'),
require('./schemas/objects/transaction-type.json'),
require('./schemas/objects/hash128.json'),
require('./schemas/objects/hash256.json'),
require('./schemas/objects/sequence.json'),
require('./schemas/objects/signature.json'),
require('./schemas/objects/issue.json'),
require('./schemas/objects/ledgerversion.json'),
require('./schemas/objects/ledger-version.json'),
require('./schemas/objects/max-adjustment.json'),
require('./schemas/objects/memo.json'),
require('./schemas/objects/memos.json'),
@@ -31,21 +31,23 @@ function loadSchemas() {
require('./schemas/objects/min-adjustment.json'),
require('./schemas/objects/source-exact-adjustment.json'),
require('./schemas/objects/destination-exact-adjustment.json'),
require('./schemas/objects/tx-hash.json'),
require('./schemas/objects/destination-address-tag.json'),
require('./schemas/objects/transaction-hash.json'),
require('./schemas/objects/address.json'),
require('./schemas/objects/adjustment.json'),
require('./schemas/objects/quality.json'),
require('./schemas/objects/amount.json'),
require('./schemas/objects/amount-base.json'),
require('./schemas/objects/amountbase.json'),
require('./schemas/objects/balance.json'),
require('./schemas/objects/blob.json'),
require('./schemas/objects/currency.json'),
require('./schemas/objects/signed-value.json'),
require('./schemas/objects/orderbook.json'),
require('./schemas/objects/instructions.json'),
require('./schemas/objects/settings.json'),
require('./schemas/objects/settings-plus-memos.json'),
require('./schemas/specifications/settings.json'),
require('./schemas/specifications/payment.json'),
require('./schemas/specifications/get-payment.json'),
require('./schemas/specifications/escrow-cancellation.json'),
require('./schemas/specifications/order-cancellation.json'),
require('./schemas/specifications/order.json'),
@@ -61,6 +63,7 @@ function loadSchemas() {
require('./schemas/output/sign.json'),
require('./schemas/output/submit.json'),
require('./schemas/output/get-account-info.json'),
require('./schemas/output/get-account-objects.json'),
require('./schemas/output/get-balances.json'),
require('./schemas/output/get-balance-sheet.json'),
require('./schemas/output/get-ledger.json'),
@@ -90,6 +93,7 @@ function loadSchemas() {
require('./schemas/input/api-options.json'),
require('./schemas/input/get-settings.json'),
require('./schemas/input/get-account-info.json'),
require('./schemas/input/get-account-objects.json'),
require('./schemas/input/get-transaction.json'),
require('./schemas/input/get-transactions.json'),
require('./schemas/input/get-trustlines.json'),
@@ -156,5 +160,6 @@ function schemaValidate(schemaName: string, object: any): void {
export {
schemaValidate,
isValidSecret
isValidSecret,
isValidAddress
}

View File

@@ -12,11 +12,15 @@
"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`."
},
"maxFeeXRP": {
"type": "string",
"description": "Maximum fee to use with transactions, in XRP. Must be a string-encoded number. Defaults to `'2'`."
},
"server": {
"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://`, `ws://`, `wss+unix://`, or `ws+unix://`.",
"format": "uri",
"pattern": "^wss?://"
"pattern": "^(wss?|wss?\\+unix)://"
},
"proxy": {
"format": "uri",

View File

@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getAccountObjectsOptions",
"description": "Request options for getAccountObjects",
"type": "object",
"properties": {
"address": {
"$ref": "address",
"description": "The address of the account to get the account objects of."
},
"options": {
"description": "Options that affect what to return.",
"properties": {
"type": {
"type": "string",
"enum": [
"check",
"escrow",
"offer",
"payment_channel",
"signer_list",
"state"
],
"description":
"(Optional) Filter results to include only this type of ledger object. The valid types are: `check`, `escrow`, `offer`, `payment_channel`, `signer_list`, and `state` (trust line)."
},
"ledgerHash": {
"type": "string",
"description":
"(Optional) A 20-byte hex string for the ledger version to use."
},
"ledgerIndex": {
"oneOf": [
{
"$ref": "ledgerVersion"
},
{
"type": "string"
}
],
"description":
"(Optional) The sequence number of the ledger to use, or a shortcut string to choose a ledger automatically."
},
"limit": {
"type": "integer",
"minimum": 1,
"description":
"(Optional) The maximum number of objects to include in the results."
}
},
"additionalProperties": false
}
},
"required": ["address"],
"additionalProperties": false
}

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

@@ -4,9 +4,9 @@
"description": "Parameters for getTransaction",
"type": "object",
"properties": {
"id": {"$ref": "id"},
"id": {"$ref": "transactionHash"},
"options": {
"description": "Options to limit the ledger versions to search.",
"description": "Options to limit the ledger versions to search and/or to include raw transaction data.",
"properties": {
"minLedgerVersion": {
"$ref": "ledgerVersion",
@@ -15,6 +15,9 @@
"maxLedgerVersion": {
"$ref": "ledgerVersion",
"description": "The highest ledger version to search"
},
"includeRawTransaction": {
"description": "Include raw transaction data. For advanced users; exercise caution when interpreting this data. "
}
},
"additionalProperties": false

View File

@@ -49,6 +49,9 @@
"items": {"$ref": "transactionType"},
"description": "Only return transactions of the specified [Transaction Types](#transaction-types)."
},
"includeRawTransactions": {
"description": "Include raw transaction data. For advanced users; exercise caution when interpreting this data. "
},
"binary": {
"type": "boolean",
"description": "If true, the transactions will be sent from the server in a condensed binary format rather than JSON."

View File

@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "amount",
"link": "amount",
"description": "An Amount on the Ripple Protocol",
"description": "An Amount on the XRP Ledger",
"allOf": [
{"$ref": "amountbase"},
{"required": ["value"]}

View File

@@ -9,11 +9,11 @@
"$ref": "value"
},
"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"
},
"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"
}
},
@@ -24,7 +24,7 @@
"properties": {
"currency": {
"not": {
"enum": ["XRP"]
"enum": ["XRP", "drops"]
}
}
},
@@ -33,7 +33,7 @@
{
"properties": {
"currency": {
"enum": ["XRP"]
"enum": ["XRP", "drops"]
}
},
"not": {

View File

@@ -4,5 +4,5 @@
"description": "The three-character code or hexadecimal string used to denote currencies",
"type": "string",
"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

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "destinationAddressTag",
"description": "A destination address and optional tag, with no amount included. When parsing an incoming transaction, the original specification's amount is hidden to prevent misinterpretation. For the amount that the transaction delivered, see `outcome.deliveredAmount`.",
"type": "object",
"properties": {
"address": {
"$ref": "address",
"description": "The address to receive at."
},
"tag": {"$ref": "tag"}
},
"required": ["address"],
"additionalProperties": false
}

View File

@@ -9,7 +9,7 @@
},
"amount": {
"$ref": "laxAmount",
"description": "An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with destination.minAmount)."
"description": "An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)"
},
"tag": {"$ref": "tag"}
},

View File

@@ -14,7 +14,7 @@
"$ref": "value"
},
"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"
},
"maxLedgerVersion": {

View File

@@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ledgerVersion",
"description": "A ledger version number",
"oneOf": [
{
"type": "integer",
"minimum": 1
},
{
"type": "string",
"enum": ["validated", "closed", "current"]
}
]
}

View File

@@ -1,7 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "ledgerVersion",
"description": "A ledger version number",
"type": "integer",
"minimum": 1
}

View File

@@ -93,7 +93,9 @@
"minItems": 1,
"maxItems": 8
}
}
},
"required": ["threshold", "weights"],
"additionalProperties": false
},
"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.",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "id",
"title": "transactionHash",
"link": "transaction-id",
"description": "A hash of a transaction used to identify the transaction, represented in hexadecimal.",
"type": "string",

View File

@@ -0,0 +1,48 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "AccountObjectsResponse",
"description": "Response format for account_objects",
"type": "object",
"properties": {
"account": {
"$ref": "address",
"description":
"Unique address of the account this request corresponds to."
},
"account_objects": {
"type": "array",
"items": {
"type": "object"
},
"description":
"Array of objects owned by this account. Each object is in its raw ledger format."
},
"ledger_hash": {
"type": "string",
"description":
"(May be omitted) The identifying hash of the ledger that was used to generate this response."
},
"ledger_index": {
"$ref": "ledgerVersion",
"description":
"(May be omitted) The sequence number of the ledger that was used to generate this response."
},
"ledger_current_index": {
"$ref": "ledgerVersion",
"description":
"(May be omitted) The sequence number of the ledger that was used to generate this response."
},
"limit": {
"type": "integer",
"description":
"(May be omitted) The limit that was used in this request, if any."
},
"validated": {
"type": "boolean",
"description":
"If included and set to true, the information in this request comes from a validated ledger version. Otherwise, the information is subject to change."
}
},
"required": ["account", "account_objects"],
"additionalProperties": false
}

View File

@@ -55,15 +55,11 @@
"description": "A transaction in the same format as the return value of [getTransaction](#gettransaction)."
}
},
"rawTransactions": {
"type": "string",
"description": "A JSON string containing rippled format transaction JSON for all transactions that were validated in this ledger."
},
"transactionHashes": {
"description": "An array of hashes of all transactions that were validated in this ledger.",
"type": "array",
"items": {
"$ref": "id"
"$ref": "transactionHash"
}
},
"rawState": {

View File

@@ -1,20 +1,21 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getTransaction",
"description": "getTransaction response",
"link": "gettransaction",
"properties": {
"type": {
"$ref": "transactionType"
},
"specification": {
"description": "A specification that would produce the same outcome as this transaction. The structure of the specification depends on the value of the `type` field (see [Transaction Types](#transaction-types) for details). *Note:* This is **not** necessarily the same as the original specification."
"description": "A specification that would produce the same outcome as this transaction. *Exception:* For payment transactions, this omits the `destination.amount` field, to prevent misunderstanding. The structure of the specification depends on the value of the `type` field (see [Transaction Types](#transaction-types) for details). *Note:* This is **not** necessarily the same as the original specification."
},
"outcome": {
"$ref": "outcome",
"description": "The outcome of the transaction (what effects it had)."
},
"id": {
"$ref": "id",
"$ref": "transactionHash",
"description": "A hash of the transaction that can be used to identify it."
},
"address": {
@@ -24,6 +25,10 @@
"sequence": {
"$ref": "sequence",
"description": "The account sequence number of the transaction for the account that initiated it."
},
"rawTransaction": {
"description": "The raw transaction data as a JSON string. For advanced users only; exercise caution when interpreting this data.",
"type": "string"
}
},
"required": [
@@ -44,7 +49,7 @@
]
},
"specification": {
"$ref": "payment"
"$ref": "getPayment"
}
}
},

View File

@@ -45,9 +45,14 @@
},
"required": ["fundedAmount", "priceOfFundedAmount"],
"additionalProperties": false
},
"data": {
"description": "The raw order data. This may include `owner_funds`, `Flags`, and other fields.",
"type": "object",
"additionalProperties": true
}
},
"required": ["specification", "properties"],
"required": ["specification", "properties", "data"],
"additionalProperties": false
}
}

View File

@@ -25,7 +25,7 @@
"type": "object",
"additionalProperties": {
"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"}
}
},
@@ -33,10 +33,14 @@
"type": "object",
"additionalProperties": {
"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"}
}
},
"channelChanges": {
"type": "object",
"description": "Properties reflecting the details of the payment channel."
},
"ledgerVersion": {
"$ref": "ledgerVersion",
"description": "The ledger version that the transaction was validated in."

View File

@@ -9,7 +9,7 @@
"description": "The signed transaction represented as an uppercase hexadecimal string."
},
"id": {
"$ref": "id",
"$ref": "transactionHash",
"description": "The [Transaction ID](#transaction-id) of the signed transaction."
}
},

View File

@@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "getPayment",
"description": "A specification of a payment in a response for getTransaction or getTransactions.",
"type": "object",
"properties": {
"source": {
"$ref": "sourceAdjustment",
"description": "The source of the funds to be sent."
},
"destination": {
"$ref": "destinationAddressTag",
"description": "The destination of the funds to be sent. Since this is a payment response, the amount is not shown here. For the amount that the transaction delivered, see `outcome.deliveredAmount`."
},
"paths": {
"type": "string",
"description": "The paths of trustlines and orders to use in executing the payment."
},
"memos": {"$ref": "memos"},
"invoiceID": {
"description": "A 256-bit hash that can be used to identify a particular payment.",
"$ref": "hash256"
},
"allowPartialPayment": {
"description": "If true, this payment should proceed even if the whole amount cannot be delivered due to a lack of liquidity or a lack of funds in the source account.",
"type": "boolean"
},
"noDirectRipple": {
"description": "If true and paths are specified, the sender would like the XRP Ledger to disregard any direct paths from the source account to the destination account. This may be used to take advantage of an arbitrage opportunity or by gateways wishing to issue balances from a hot wallet to a user who has mistakenly set a trustline directly to the hot wallet.",
"type": "boolean"
},
"limitQuality": {
"description": "Only take paths where all the conversions have an input:output ratio that is equal or better than the ratio of destination.amount:source.maxAmount.",
"type": "boolean"
}
},
"required": ["source", "destination"],
"additionalProperties": false
}

View File

@@ -19,11 +19,11 @@
},
"immediateOrCancel": {
"type": "boolean",
"description": "Treat the offer as an [Immediate or Cancel order](http://en.wikipedia.org/wiki/Immediate_or_cancel). If enabled, the offer will never become a ledger node: it only attempts to match existing offers in the ledger."
"description": "Treat the offer as an [Immediate or Cancel order](http://en.wikipedia.org/wiki/Immediate_or_cancel). If enabled, the offer will never become a ledger node: it only attempts to match existing offers in the ledger. This cannot be used with `fillOrKill`."
},
"fillOrKill": {
"type": "boolean",
"description": "Treat the offer as a [Fill or Kill order](http://en.wikipedia.org/wiki/Fill_or_kill). Only attempt to match existing offers in the ledger, and only do so if the entire quantity can be exchanged."
"description": "Treat the offer as a [Fill or Kill order](http://en.wikipedia.org/wiki/Fill_or_kill). Only attempt to match existing offers in the ledger, and only do so if the entire quantity can be exchanged. This cannot be used with `immediateOrCancel`."
},
"passive": {
"description": "If enabled, the offer will not consume offers that exactly match it, and instead becomes an Offer node in the ledger. It will still consume offers that cross it.",

View File

@@ -1,7 +1,7 @@
import * as _ from 'lodash'
import {convertKeysFromSnakeCaseToCamelCase} from './utils'
import Connection from './connection'
import BigNumber from 'bignumber.js'
import {RippleAPI} from '../index'
export type GetServerInfoResponse = {
buildVersion: string,
@@ -39,8 +39,8 @@ function renameKeys(object, mapping) {
})
}
function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
return connection.request({command: 'server_info'}).then(response => {
function getServerInfo(this: RippleAPI): Promise<GetServerInfoResponse> {
return this.request('server_info').then(response => {
const info = convertKeysFromSnakeCaseToCamelCase(response.info)
renameKeys(info, {hostid: 'hostID'})
if (info.validatedLedger) {
@@ -61,18 +61,27 @@ function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
})
}
function computeFeeFromServerInfo(cushion: number,
serverInfo: GetServerInfoResponse
): string {
return (new BigNumber(serverInfo.validatedLedger.baseFeeXRP)).
times(serverInfo.loadFactor).
times(cushion).toString()
}
// This is a public API that can be called directly.
// This is not used by the `prepare*` methods. See `src/transaction/utils.ts`
async function getFee(
this: RippleAPI,
cushion?: number
): Promise<string> {
if (cushion === undefined) {
cushion = this._feeCushion
}
if (cushion === undefined) {
cushion = 1.2
}
function getFee(connection: Connection, cushion: number): Promise<string> {
return getServerInfo(connection).then(serverInfo => {
return computeFeeFromServerInfo(cushion, serverInfo)
})
const serverInfo = (await this.request('server_info')).info
const baseFeeXrp = new BigNumber(serverInfo.validated_ledger.base_fee_xrp)
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.toFixed(6))).toString(10)
}
export {

View File

@@ -0,0 +1,72 @@
import {CheckLedgerEntry} from '../objects'
export interface GetAccountObjectsOptions {
type?: string | (
'check' |
'escrow' |
'offer' |
'payment_channel' |
'signer_list' |
'state'
),
ledgerHash?: string,
ledgerIndex?: number | ('validated' | 'closed' | 'current'),
limit?: number,
marker?: string
}
export interface AccountObjectsRequest {
account: string,
// (Optional) Filter results to include only this type of ledger object.
type?: string | (
'check' |
'escrow' |
'offer' |
'payment_channel' |
'signer_list' |
'state'
),
// (Optional) A 20-byte hex string for the ledger version to use.
ledger_hash?: string,
// (Optional) The sequence number of the ledger to use,
// or a shortcut string to choose a ledger automatically.
ledger_index?: number | ('validated' | 'closed' | 'current'),
limit?: number,
marker?: string
}
export interface AccountObjectsResponse {
account: string,
// Array of objects owned by this account.
account_objects: CheckLedgerEntry | object,
// (May be omitted) The identifying hash of the ledger
// that was used to generate this response.
ledger_hash?: string,
// (May be omitted) The sequence number of the ledger version
// that was used to generate this response.
ledger_index?: number,
// (May be omitted) The sequence number of the current in-progress ledger
// version that was used to generate this response.
ledger_current_index?: number,
// The limit that was used in this request, if any.
limit?: number,
// Server-defined value indicating the response is paginated. Pass this
// to the next call to resume where this call left off. Omitted when there
// are no additional pages after this one.
marker?: string,
// If true, this information comes from a ledger version
// that has been validated by consensus.
validated?: boolean
}

View File

@@ -1,7 +1,9 @@
export * from './account_info'
export * from './account_lines'
export * from './account_objects'
export * from './account_offers'
export * from './book_offers'
export * from './gateway_balances'
export * from './ledger'
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,4 +1,130 @@
import {SignerEntry} from './index'
import {Amount, RippledAmount} from './amounts'
export interface AccountRootLedgerEntry {
LedgerEntryType: 'AccountRoot',
Account: string,
Balance: string,
Flags: number,
OwnerCount: number,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
Sequence: number,
AccountTxnID?: string,
Domain?: string,
EmailHash?: string,
MessageKey?: string
RegularKey?: string,
TickSize?: number,
TransferRate?: number,
WalletLocator?: string, // DEPRECATED
WalletSize?: number // DEPRECATED
}
export interface AmendmentsLedgerEntry {
LedgerEntryType: 'Amendments',
Amendments?: string[],
Majorities?: any[],
Flags: 0
}
export interface CheckLedgerEntry {
LedgerEntryType: 'Check',
Account: string,
Destination, string,
Flags: 0,
OwnerNode: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
SendMax: string | object,
Sequence: number,
DestinationNode: string,
DestinationTag: number,
Expiration: number,
InvoiceID: string,
SourceTag: number
}
export interface DepositPreauthLedgerEntry {
LedgerEntryType: 'DepositPreauth',
Account: string,
Authorize: string,
OwnerNode: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number
}
export interface DirectoryNodeLedgerEntry {
LedgerEntryType: 'DirectoryNode',
Flags: number,
RootIndex: string,
Indexes: string[],
IndexNext?: number,
IndexPrevious?: number
}
export interface OfferDirectoryNodeLedgerEntry
extends DirectoryNodeLedgerEntry {
TakerPaysCurrency: string,
TakerPaysIssuer: string,
TakerGetsCurrency: string,
TakerGetsIssuer: string,
ExchangeRate?: number // DEPRECATED
}
export interface OwnerDirectoryNodeLedgerEntry
extends DirectoryNodeLedgerEntry {
Owner: string,
}
export interface EscrowLedgerEntry {
LedgerEntryType: 'Escrow',
Account: string,
Destination: string,
Amount: string,
Condition?: string,
CancelAfter?: number,
FinishAfter?: number,
Flags: number,
SourceTag?: number,
DestinationTag?: number,
OwnerNode: string,
DestinationNode?: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number
}
export interface FeeSettingsLedgerEntry {
LedgerEntryType: 'FeeSettings',
BaseFee: string,
ReferenceFeeUnits: number,
ReserveBase: number,
ReserveIncrement: number,
Flags: number
}
export interface LedgerHashesLedgerEntry {
LedgerEntryType: 'LedgerHashes',
Hashes: string[],
Flags: number,
FirstLedgerSequence?: number, // DEPRECATED
LastLedgerSequence?: number
}
export interface OfferLedgerEntry {
LedgerEntryType: 'Offer',
Flags: number,
Account: string,
Sequence: number,
TakerPays: RippledAmount,
TakerGets: RippledAmount,
BookDirectory: string,
BookNode: string,
OwnerNode: string,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
Expiration?: number
}
export interface PayChannelLedgerEntry {
LedgerEntryType: 'PayChannel',
@@ -19,22 +145,20 @@ export interface PayChannelLedgerEntry {
index: string
}
export interface AccountRootLedgerEntry {
LedgerEntryType: 'AccountRoot',
Account: string,
export interface RippleStateLedgerEntry {
LedgerEntryType: 'RippleState',
Flags: number,
Sequence: number,
Balance: string,
OwnerCount: number,
Balance: Amount,
LowLimit: Amount,
HighLimit: Amount,
PreviousTxnID: string,
PreviousTxnLgrSeq: number,
AccountTxnID?: string,
RegularKey?: string,
EmailHash?: string,
MessageKey?: string
TickSize?: number,
TransferRate?: number,
Domain?: string
LowNode?: string,
HighNode?: string,
LowQualityIn?: number,
LowQualityOut?: number,
HighQualityIn?: number,
HighQualityOut?: number
}
export interface SignerListLedgerEntry {
@@ -47,10 +171,19 @@ export interface SignerListLedgerEntry {
PreviousTxnLgrSeq: number
}
// TODO: Add the other ledger entry types, then remove the `any` fallback
// see https://ripple.com/build/ledger-format/#ledger-object-types
export type LedgerEntry =
PayChannelLedgerEntry |
AccountRootLedgerEntry |
SignerListLedgerEntry |
any
AmendmentsLedgerEntry |
CheckLedgerEntry |
DepositPreauthLedgerEntry |
DirectoryNodeLedgerEntry |
OfferDirectoryNodeLedgerEntry |
OwnerDirectoryNodeLedgerEntry |
EscrowLedgerEntry |
FeeSettingsLedgerEntry |
LedgerHashesLedgerEntry |
OfferLedgerEntry |
PayChannelLedgerEntry |
RippleStateLedgerEntry |
SignerListLedgerEntry

View File

@@ -1,8 +1,8 @@
import * as _ from 'lodash'
import BigNumber from 'bignumber.js'
const {deriveKeypair} = require('ripple-keypairs')
import {deriveKeypair} from 'ripple-keypairs'
import {Amount, RippledAmount} from './types/objects'
import {ValidationError} from './errors'
function isValidSecret(secret: string): boolean {
try {
@@ -13,18 +13,86 @@ function isValidSecret(secret: string): boolean {
}
}
function dropsToXrp(drops: string): string {
return (new BigNumber(drops)).dividedBy(1000000.0).toString()
function dropsToXrp(drops: string | BigNumber): string {
if (typeof drops === 'string') {
if (!drops.match(/^-?[0-9]*\.?[0-9]*$/)) {
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
} 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 {
return (new BigNumber(xrp)).times(1000000.0).floor().toString()
function xrpToDrops(xrp: string | BigNumber): string {
if (typeof xrp === 'string') {
if (!xrp.match(/^-?[0-9]*\.?[0-9]*$/)) {
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
} 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 {
if (amount.currency === 'XRP') {
return xrpToDrops(amount.value)
}
if (amount.currency === 'drops') {
return amount.value
}
return {
currency: amount.currency,
issuer: amount.counterparty ? amount.counterparty :

View File

@@ -1,5 +1,8 @@
export {RippleAPI} from './api'
export {
FormattedTransactionType
} from './transaction/types'
// Broadcast api is experimental
export {RippleAPIBroadcast} from './broadcast'

View File

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

View File

@@ -0,0 +1,27 @@
import {removeUndefined} from '../common'
import {RippleAPI} from '../api'
import {
GetAccountObjectsOptions,
AccountObjectsResponse
} from '../common/types/commands/account_objects'
export default async function getAccountObjects(
this: RippleAPI,
address: string,
options: GetAccountObjectsOptions = {}
): Promise<AccountObjectsResponse> {
// Don't validate the options so that new types can be passed
// through to rippled. rippled validates requests.
// Make Request
const response = await this.request('account_objects', removeUndefined({
account: address,
type: options.type,
ledger_hash: options.ledgerHash,
ledger_index: options.ledgerIndex,
limit: options.limit,
marker: options.marker
}))
// Return Response
return response
}

View File

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

View File

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

View File

@@ -73,7 +73,7 @@ async function makeRequest(
ledger_index: options.ledgerVersion || 'validated',
limit: options.limit,
taker
})
})
}

View File

@@ -18,18 +18,18 @@ export type FormattedLedger = {
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}
function parseTransactionWrapper(ledgerVersion, tx) {
// renames metaData to meta and adds ledger_index
const transaction = _.assign({}, _.omit(tx, 'metaData'), {
meta: tx.metaData,
ledger_index: ledgerVersion
})
const result = parseTransaction(transaction)
const result = parseTransaction(transaction, true)
if (!result.outcome.ledgerVersion) {
result.outcome.ledgerVersion = ledgerVersion
}
@@ -45,8 +45,7 @@ function parseTransactions(transactions, ledgerVersion) {
}
return {
transactions: _.map(transactions,
_.partial(parseTransactionWrapper, ledgerVersion)),
rawTransactions: JSON.stringify(transactions)
_.partial(parseTransactionWrapper, ledgerVersion))
}
}
@@ -62,19 +61,20 @@ function parseState(state) {
export function parseLedger(ledger: Ledger): FormattedLedger {
const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10)
return removeUndefined(Object.assign({
stateHash: ledger.account_hash,
closeTime: rippleTimeToISO8601(ledger.close_time),
closeTimeResolution: ledger.close_time_resolution,
closeFlags: ledger.close_flags,
ledgerHash: ledger.hash || ledger.ledger_hash,
ledgerVersion: ledgerVersion,
parentLedgerHash: ledger.parent_hash,
parentCloseTime: rippleTimeToISO8601(ledger.parent_close_time),
totalDrops: ledger.total_coins || ledger.totalCoins,
transactionHash: ledger.transaction_hash
},
parseTransactions(ledger.transactions, ledgerVersion),
parseState(ledger.accountState)
return removeUndefined(Object.assign(
{
stateHash: ledger.account_hash,
closeTime: rippleTimeToISO8601(ledger.close_time),
closeTimeResolution: ledger.close_time_resolution,
closeFlags: ledger.close_flags,
ledgerHash: ledger.hash || ledger.ledger_hash,
ledgerVersion: ledgerVersion,
parentLedgerHash: ledger.parent_hash,
parentCloseTime: rippleTimeToISO8601(ledger.parent_close_time),
totalDrops: ledger.total_coins || ledger.totalCoins,
transactionHash: ledger.transaction_hash
},
parseTransactions(ledger.transactions, ledgerVersion),
parseState(ledger.accountState)
))
}

View File

@@ -17,15 +17,16 @@ export type FormattedOrderbookOrder = {
state?: {
fundedAmount: Amount,
priceOfFundedAmount: Amount
}
},
data: BookOffer
}
export function parseOrderbookOrder(
order: BookOffer
data: BookOffer
): FormattedOrderbookOrder {
const direction = (order.Flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.TakerGets)
const takerPaysAmount = parseAmount(order.TakerPays)
const direction = (data.Flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(data.TakerGets)
const takerPaysAmount = parseAmount(data.TakerPays)
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount
@@ -35,25 +36,25 @@ export function parseOrderbookOrder(
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
passive: ((order.Flags & orderFlags.Passive) !== 0) || undefined,
expirationTime: parseTimestamp(order.Expiration)
passive: ((data.Flags & orderFlags.Passive) !== 0) || undefined,
expirationTime: parseTimestamp(data.Expiration)
})
const properties = {
maker: order.Account,
sequence: order.Sequence,
makerExchangeRate: adjustQualityForXRP(order.quality,
maker: data.Account,
sequence: data.Sequence,
makerExchangeRate: adjustQualityForXRP(data.quality,
takerGetsAmount.currency, takerPaysAmount.currency)
}
const takerGetsFunded = order.taker_gets_funded ?
parseAmount(order.taker_gets_funded) : undefined
const takerPaysFunded = order.taker_pays_funded ?
parseAmount(order.taker_pays_funded) : undefined
const takerGetsFunded = data.taker_gets_funded ?
parseAmount(data.taker_gets_funded) : undefined
const takerPaysFunded = data.taker_pays_funded ?
parseAmount(data.taker_pays_funded) : undefined
const available = removeUndefined({
fundedAmount: takerGetsFunded,
priceOfFundedAmount: takerPaysFunded
})
const state = _.isEmpty(available) ? undefined : available
return removeUndefined({specification, properties, state})
return removeUndefined({specification, properties, state, data})
}

View File

@@ -17,6 +17,7 @@ function removeGenericCounterparty(amount, address) {
_.omit(amount, 'counterparty') : amount
}
// Payment specification
function parsePayment(tx: any): Object {
assert(tx.TransactionType === 'Payment')
@@ -27,10 +28,13 @@ function parsePayment(tx: any): Object {
tag: tx.SourceTag
}
const destination = {
const destination: {
address: string,
tag: number | undefined
} = {
address: tx.Destination,
amount: removeGenericCounterparty(parseAmount(tx.Amount), tx.Destination),
tag: tx.DestinationTag
// Notice that `amount` is omitted to prevent misinterpretation
}
return removeUndefined({

View File

@@ -42,7 +42,7 @@ function parseTransactionType(type) {
return mapping[type] || null
}
function parseTransaction(tx: any): any {
function parseTransaction(tx: any, includeRawTransaction: boolean): any {
const type = parseTransactionType(tx.TransactionType)
const mapping = {
'payment': parsePayment,
@@ -72,7 +72,8 @@ function parseTransaction(tx: any): any {
sequence: tx.Sequence,
id: tx.hash,
specification: removeUndefined(specification),
outcome: outcome ? removeUndefined(outcome) : undefined
outcome: outcome ? removeUndefined(outcome) : undefined,
rawTransaction: includeRawTransaction ? JSON.stringify(tx) : undefined
})
}

View File

@@ -1,5 +1,5 @@
import * as assert from 'assert'
import {parseQuality} from './utils'
import {parseQuality, parseMemos} from './utils'
import {txFlags, removeUndefined} from '../../common'
const flags = txFlags.TrustSet
@@ -20,6 +20,7 @@ function parseTrustline(tx: any): Object {
limit: tx.LimitAmount.value,
currency: tx.LimitAmount.currency,
counterparty: tx.LimitAmount.issuer,
memos: parseMemos(tx),
qualityIn: parseQuality(tx.QualityIn),
qualityOut: parseQuality(tx.QualityOut),
ripplingDisabled: parseFlag(

View File

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

View File

@@ -1,7 +1,13 @@
import * as _ from 'lodash'
import BigNumber from 'bignumber.js'
import {getXRPBalance, renameCounterpartyToIssuer} from './utils'
import {validate, toRippledAmount, errors} from '../common'
import {
validate,
toRippledAmount,
errors,
xrpToDrops,
dropsToXrp
} from '../common'
import {Connection} from '../common'
import parsePathfind from './parse/pathfind'
import {RippledAmount, Amount} from '../common/types/objects'
@@ -23,7 +29,11 @@ function addParams(request: PathFindRequest, result: RippledPathsResponse
function requestPathFind(connection: Connection, pathfind: PathFind
): Promise<RippledPathsResponse> {
const destinationAmount: Amount = _.assign(
{value: '-1'},
{
// This is converted back to drops by toRippledAmount()
value: pathfind.destination.amount.currency === 'XRP' ?
dropsToXrp('-1') : '-1'
},
pathfind.destination.amount
)
const request: PathFindRequest = {
@@ -95,13 +105,21 @@ function filterSourceFundsLowPaths(pathfind: PathFind,
): RippledPathsResponse {
if (pathfind.source.amount &&
pathfind.destination.amount.value === undefined && paths.alternatives) {
paths.alternatives = _.filter(paths.alternatives, alt =>
!!alt.source_amount &&
!!pathfind.source.amount &&
// TODO: Returns false when alt.source_amount is a string. Fix?
typeof alt.source_amount !== 'string' &&
new BigNumber(alt.source_amount.value).eq(pathfind.source.amount.value)
)
paths.alternatives = _.filter(paths.alternatives, alt => {
if (!alt.source_amount) {
return false
}
const pathfindSourceAmountValue = new BigNumber(
pathfind.source.amount.currency === 'XRP' ?
xrpToDrops(pathfind.source.amount.value) :
pathfind.source.amount.value)
const altSourceAmountValue = new BigNumber(
typeof alt.source_amount === 'string' ?
alt.source_amount :
alt.source_amount.value
)
return altSourceAmountValue.eq(pathfindSourceAmountValue)
})
}
return paths
}

View File

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

View File

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

View File

@@ -7,7 +7,8 @@ import {FormattedTransactionType} from '../transaction/types'
export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
maxLedgerVersion?: number,
includeRawTransaction?: boolean
}
type TransactionResponse = FormattedTransactionType & {
hash: string,
@@ -18,7 +19,7 @@ type TransactionResponse = FormattedTransactionType & {
function attachTransactionDate(connection: Connection, tx: any
): Promise<FormattedTransactionType> {
): Promise<TransactionResponse> {
if (tx.date) {
return Promise.resolve(tx)
}
@@ -83,31 +84,25 @@ function convertError(connection: Connection, options: TransactionOptions,
function formatResponse(options: TransactionOptions, tx: TransactionResponse
): FormattedTransactionType {
if (tx.validated !== true || !isTransactionInRange(tx, options)) {
throw new errors.NotFoundError('Transaction not found')
throw new errors.NotFoundError('Transaction not found')
}
return parseTransaction(tx)
return parseTransaction(tx, options.includeRawTransaction)
}
function getTransaction(id: string, options: TransactionOptions = {}
async function getTransaction(id: string, options: TransactionOptions = {}
): Promise<FormattedTransactionType> {
validate.getTransaction({id, options})
const request = {
command: 'tx',
transaction: id,
binary: false
const _options = await utils.ensureLedgerVersion.call(this, options)
try {
const tx = await this.request('tx', {
transaction: id,
binary: false
})
const txWithDate = await attachTransactionDate(this.connection, tx)
return formatResponse(_options, txWithDate)
} catch (error) {
throw (await convertError(this.connection, _options, error))
}
return utils.ensureLedgerVersion.call(this, options).then(_options => {
return this.connection.request(request).then((tx: TransactionResponse) =>
attachTransactionDate(this.connection, tx)
).then(_.partial(formatResponse, _options))
.catch(error => {
return convertError(this.connection, _options, error).then(_error => {
throw _error
})
})
})
}
export default getTransaction

View File

@@ -18,6 +18,7 @@ export type TransactionsOptions = {
initiated?: boolean,
counterparty?: string,
types?: Array<string>,
includeRawTransactions?: boolean,
binary?: boolean,
startTx?: FormattedTransactionType
}
@@ -35,11 +36,11 @@ function parseBinaryTransaction(transaction) {
}
}
function parseAccountTxTransaction(tx) {
function parseAccountTxTransaction(tx, includeRawTransaction: boolean) {
const _tx = tx.tx_blob ? parseBinaryTransaction(tx) : tx
// rippled uses a different response format for 'account_tx' than 'tx'
return parseTransaction(_.assign({}, _tx.tx,
{meta: _tx.meta, validated: _tx.validated}))
{meta: _tx.meta, validated: _tx.validated}), includeRawTransaction)
}
function counterpartyFilter(filters, tx: FormattedTransactionType) {
@@ -87,11 +88,13 @@ function orderFilter(
function formatPartialResponse(address: string,
options: TransactionsOptions, data
) {
const parse = tx =>
parseAccountTxTransaction(tx, options.includeRawTransactions)
return {
marker: data.marker,
results: data.transactions
.filter(tx => tx.validated)
.map(parseAccountTxTransaction)
.map(parse)
.filter(_.partial(transactionFilter, address, options))
.filter(_.partial(orderFilter, options))
}

7
src/offline/derive.ts Normal file
View File

@@ -0,0 +1,7 @@
import {deriveKeypair, deriveAddress} from 'ripple-keypairs'
export {
deriveKeypair,
deriveAddress
}

View File

@@ -25,13 +25,33 @@ function hashLedgerHeader(ledgerHeader) {
return hashes.computeLedgerHash(header)
}
function computeTransactionHash(ledger, version) {
if (ledger.rawTransactions === undefined) {
function computeTransactionHash(ledger, version,
options: ComputeLedgerHashOptions) {
let transactions: any[]
if (ledger.rawTransactions) {
transactions = JSON.parse(ledger.rawTransactions)
} else if (ledger.transactions) {
try {
transactions = ledger.transactions.map(tx =>
JSON.parse(tx.rawTransaction))
} catch (e) {
if (e.toString() === 'SyntaxError: Unexpected' +
' token u in JSON at position 0') {
// one or more of the `tx.rawTransaction`s is undefined
throw new common.errors.ValidationError('ledger'
+ ' is missing raw transactions')
}
}
} else {
if (options.computeTreeHashes) {
throw new common.errors.ValidationError('transactions'
+ ' property is missing from the ledger')
}
return ledger.transactionHash
}
const transactions: any[] = JSON.parse(ledger.rawTransactions)
const txs = _.map(transactions, tx => {
const mergeTx = _.assign({}, _.omit(tx, 'tx'), tx.tx || {})
// rename `meta` back to `metaData`
const renameMeta = _.assign({}, _.omit(mergeTx, 'meta'),
tx.meta ? {metaData: tx.meta} : {})
return renameMeta
@@ -40,13 +60,21 @@ function computeTransactionHash(ledger, version) {
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
+ ' does not match computed hash of transactions')
+ ' does not match computed hash of transactions', {
transactionHashInHeader: ledger.transactionHash,
computedHashOfTransactions: transactionHash
})
}
return transactionHash
}
function computeStateHash(ledger, version) {
function computeStateHash(ledger, version,
options: ComputeLedgerHashOptions) {
if (ledger.rawState === undefined) {
if (options.computeTreeHashes) {
throw new common.errors.ValidationError('rawState'
+ ' property is missing from the ledger')
}
return ledger.stateHash
}
const state = JSON.parse(ledger.rawState)
@@ -60,11 +88,16 @@ function computeStateHash(ledger, version) {
const sLCF_SHAMapV2 = 0x02
function computeLedgerHash(ledger: any): string {
export type ComputeLedgerHashOptions = {
computeTreeHashes?: boolean
}
function computeLedgerHash(ledger: any,
options: ComputeLedgerHashOptions = {}): string {
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
const subhashes = {
transactionHash: computeTransactionHash(ledger, version),
stateHash: computeStateHash(ledger, version)
transactionHash: computeTransactionHash(ledger, version, options),
stateHash: computeStateHash(ledger, version, options)
}
return hashLedgerHeader(_.assign({}, ledger, subhashes))
}

View File

@@ -1,5 +1,4 @@
import * as common from '../common'
import {GetServerInfoResponse} from '../common/serverinfo'
function isConnected(): boolean {
return this.connection.isConnected()
@@ -17,15 +16,6 @@ function disconnect(): Promise<void> {
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 {
return {
baseFeeXRP: common.dropsToXrp(ledgerClose.fee_base),
@@ -43,8 +33,6 @@ export {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion,
formatLedgerClose
}

View File

@@ -46,8 +46,8 @@ function createEscrowCreationTransaction(account: string,
}
if (Boolean(payment.allowCancelAfter) && Boolean(payment.allowExecuteAfter) &&
txJSON.CancelAfter <= txJSON.FinishAfter) {
throw new ValidationError('"CancelAfter" must be after "FinishAfter" for'
+ ' EscrowCreate')
throw new ValidationError('prepareEscrowCreation: ' +
'"allowCancelAfter" must be after "allowExecuteAfter"')
}
return txJSON
}

View File

@@ -7,6 +7,7 @@ const ValidationError = utils.common.errors.ValidationError
import {Instructions, Prepare} from './types'
import {Amount, Adjustment, MaxAdjustment,
MinAdjustment, Memo} from '../common/types/objects'
import {xrpToDrops} from '../common'
export interface Payment {
@@ -32,12 +33,12 @@ export interface Payment {
function isMaxAdjustment(
source: Adjustment | MaxAdjustment): source is MaxAdjustment {
return (source as MaxAdjustment).maxAmount !== undefined
return (source as MaxAdjustment).maxAmount !== undefined
}
function isMinAdjustment(
destination: Adjustment | MinAdjustment): destination is MinAdjustment {
return (destination as MinAdjustment).minAmount !== undefined
return (destination as MinAdjustment).minAmount !== undefined
}
function isXRPToXRPPayment(payment: Payment): boolean {
@@ -50,7 +51,7 @@ function isXRPToXRPPayment(payment: Payment): boolean {
}
function isIOUWithoutCounterparty(amount: Amount): boolean {
return amount && amount.currency !== 'XRP'
return amount && amount.currency !== 'XRP' && amount.currency !== 'drops'
&& amount.counterparty === undefined
}
@@ -72,7 +73,14 @@ function applyAnyCounterpartyEncoding(payment: Payment): void {
function createMaximalAmount(amount: Amount): Amount {
const maxXRPValue = '100000000000'
const maxIOUValue = '9999999999999999e80'
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue
let maxValue
if (amount.currency === 'XRP') {
maxValue = maxXRPValue
} else if (amount.currency === 'drops') {
maxValue = xrpToDrops(maxXRPValue)
} else {
maxValue = maxIOUValue
}
return _.assign({}, amount, {value: maxValue})
}

View File

@@ -3,6 +3,9 @@ import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import {computeBinaryTransactionHash} from 'ripple-hashes'
import {SignOptions, KeyPair} from './types'
import {BigNumber} from 'bignumber.js'
import {xrpToDrops} from '../common'
import {RippleAPI} from '../api'
const validate = utils.common.validate
function computeSignature(tx: Object, privateKey: string, signAs?: string) {
@@ -13,6 +16,7 @@ function computeSignature(tx: Object, privateKey: string, signAs?: string) {
}
function signWithKeypair(
api: RippleAPI,
txJSON: string,
keypair: KeyPair,
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
if (options.signAs) {
@@ -49,6 +62,7 @@ function signWithKeypair(
}
function sign(
this: RippleAPI,
txJSON: string,
secret?: any,
options?: SignOptions,
@@ -58,9 +72,18 @@ function sign(
// we can't validate that the secret matches the account because
// the secret could correspond to the regular key
validate.sign({txJSON, secret})
return signWithKeypair(txJSON, keypairs.deriveKeypair(secret), options)
return signWithKeypair(
this,
txJSON,
keypairs.deriveKeypair(secret),
options
)
} else {
return signWithKeypair(txJSON, keypair ? keypair : secret, options)
return signWithKeypair(
this,
txJSON,
keypair ? keypair : secret,
options)
}
}

View File

@@ -1,7 +1,12 @@
import * as _ from 'lodash'
import * as utils from './utils'
import {validate} from '../common'
import {Submit} from './types'
import {RippleAPI} from '..'
export interface FormattedSubmitResponse {
resultCode: string,
resultMessage: string
}
function isImmediateRejection(engineResult: string): boolean {
// note: "tel" errors mean the local server refused to process the
@@ -13,7 +18,7 @@ function isImmediateRejection(engineResult: string): boolean {
return _.startsWith(engineResult, 'tem')
}
function formatSubmitResponse(response) {
function formatSubmitResponse(response): FormattedSubmitResponse {
const data = {
resultCode: response.engine_result,
resultMessage: response.engine_result_message
@@ -24,14 +29,15 @@ function formatSubmitResponse(response) {
return data
}
function submit(signedTransaction: string): Promise<Submit> {
async function submit(
this: RippleAPI, signedTransaction: string
): Promise<FormattedSubmitResponse> {
// 1. Validate
validate.submit({signedTransaction})
const request = {
command: 'submit',
tx_blob: signedTransaction
}
return this.connection.request(request).then(formatSubmitResponse)
// 2. Make Request
const response = await this.request('submit', {tx_blob: signedTransaction})
// 3. Return Formatted Response
return formatSubmitResponse(response)
}
export default submit

View File

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

View File

@@ -4,6 +4,7 @@ import {Memo} from '../common/types/objects'
const txFlags = common.txFlags
import {Instructions, Prepare} from './types'
import {RippleAPI} from '../api'
import {ValidationError} from '../common/errors'
export type ApiMemo = {
MemoData?: string,
@@ -63,11 +64,18 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
const multiplier = instructions.signersCount === undefined ? 1 :
instructions.signersCount + 1
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)
return Promise.resolve(txJSON)
}
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 => {
const extraFee =
(txJSON.TransactionType !== 'EscrowFinish' ||
@@ -75,31 +83,27 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
(cushion * feeRef * (32 + Math.floor(
new Buffer(txJSON.Fulfillment, 'hex').length / 16)))
const feeDrops = common.xrpToDrops(fee)
if (instructions.maxFee !== undefined) {
const maxFeeDrops = common.xrpToDrops(instructions.maxFee)
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString()
} else {
txJSON.Fee = scaleValue(feeDrops, multiplier, extraFee)
}
const maxFeeXRP = instructions.maxFee ?
BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
const maxFeeDrops = common.xrpToDrops(maxFeeXRP)
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
return txJSON
})
})
}
function prepareSequence(): Promise<Object> {
async function prepareSequence(): Promise<Object> {
if (instructions.sequence !== undefined) {
txJSON.Sequence = instructions.sequence
return Promise.resolve(txJSON)
}
const request = {
command: 'account_info',
account: account
}
return api.connection.request(request).then(response => {
txJSON.Sequence = response.account_data.Sequence
return txJSON
const response = await api.request('account_info', {
account: account as string
})
txJSON.Sequence = response.account_data.Sequence
return txJSON
}
return Promise.all([

File diff suppressed because it is too large Load Diff

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) => {
assert.strictEqual(errorCode, 'slowDown');
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) {
this.api.on('error', (errorCode, errorMessage, message) => {
assert.strictEqual(errorCode, 'badMessage');
assert.strictEqual(errorMessage, 'unrecognized message type: unknown');
assert.strictEqual(message, '{"type":"unknown"}');
// This enables us to automatically support any
// new messages added by rippled in the future.
this.api.connection.on('unknown', (event) => {
assert.deepEqual(event, {type: 'unknown'})
done();
});
@@ -414,8 +427,8 @@ describe('Connection', function() {
});
it('should throw RippledNotInitializedError if server does not have ' +
'validated ledgers',
function() {
'validated ledgers', function() {
this.timeout(3000);
this.api.connection._send(JSON.stringify({
@@ -439,13 +452,13 @@ describe('Connection', function() {
this.api.on('error', error => {
done(error || new Error('Should not emit error.'));
});
let disconncedCount = 0;
let disconnectedCount = 0;
this.api.on('connected', () => {
done(disconncedCount !== 1 ?
done(disconnectedCount !== 1 ?
new Error('Wrong number of disconnects') : undefined);
});
this.api.on('disconnected', () => {
disconncedCount++;
disconnectedCount++;
});
this.api.connection._send(JSON.stringify({

View File

@@ -22,7 +22,11 @@ module.exports = {
},
prepareSettings: {
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: {
normal: require('./prepare-escrow-creation'),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,12 +18,11 @@
"memos": [
{
"type": "test",
"format": "plain/text",
"format": "text/plain",
"data": "texted data"
}
],
"invoiceID": "A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A",
"noDirectRipple": true,
"limitQuality": true,
"paths": "[[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"type\":49,\"type_hex\":\"0000000000000031\"},{\"currency\":\"LTC\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"type\":48,\"type_hex\":\"0000000000000030\"},{\"account\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"type\":49,\"type_hex\":\"0000000000000031\"}]]"
"limitQuality": true
}

View File

@@ -3,8 +3,7 @@
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "0.01",
"currency": "XRP",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
"currency": "XRP"
}
},
"destination": {

View File

@@ -18,7 +18,7 @@
"memos": [
{
"type": "test",
"format": "plain/text",
"format": "text/plain",
"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
}
}

Some files were not shown because too many files have changed in this diff Show More