mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-13 17:15:49 +00:00
Compare commits
167 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97f9812876 | ||
|
|
dcc50e1b36 | ||
|
|
ae8824fb11 | ||
|
|
337ab2993b | ||
|
|
a4782764dd | ||
|
|
0a4c28f799 | ||
|
|
1470a0d234 | ||
|
|
bc19db9ddd | ||
|
|
d96b0d3986 | ||
|
|
a5c35586f7 | ||
|
|
a54655c0ff | ||
|
|
2681e81d6b | ||
|
|
2a0234e5ce | ||
|
|
6b326a6efd | ||
|
|
48d2bf849f | ||
|
|
4ab808b6de | ||
|
|
e5496e84a6 | ||
|
|
83b5c7f678 | ||
|
|
e6c9617e01 | ||
|
|
6c5fcc3dc6 | ||
|
|
2931bb2863 | ||
|
|
d745b128e3 | ||
|
|
73b952ec0d | ||
|
|
be961fb9a7 | ||
|
|
851d84bde8 | ||
|
|
854c4ebfdd | ||
|
|
a77448f7c0 | ||
|
|
0dc33f3d88 | ||
|
|
aaff0257b0 | ||
|
|
3557a57bbd | ||
|
|
051d23edff | ||
|
|
ccb91c1268 | ||
|
|
6c1c0eee59 | ||
|
|
f1c1c7033a | ||
|
|
bc352c4cf0 | ||
|
|
1980fa9fa4 | ||
|
|
6cabb2e935 | ||
|
|
05411527ee | ||
|
|
3c13da66b3 | ||
|
|
5d6af09508 | ||
|
|
15bf721d24 | ||
|
|
14351c9512 | ||
|
|
3b13de5310 | ||
|
|
f92eff2df8 | ||
|
|
a65ac5f8f0 | ||
|
|
0e36a1c505 | ||
|
|
f5bed635e0 | ||
|
|
905ab9f2e4 | ||
|
|
547b63b891 | ||
|
|
c26ddb497e | ||
|
|
2e81cfb56f | ||
|
|
337cb6574a | ||
|
|
abcb6bfecb | ||
|
|
797fda3363 | ||
|
|
bd920ee5bb | ||
|
|
2720970e1f | ||
|
|
b7a12d4bbb | ||
|
|
b4f6135b96 | ||
|
|
7d65bf4641 | ||
|
|
656c81a72c | ||
|
|
bfd0374ef6 | ||
|
|
bf1a772e40 | ||
|
|
8ede100594 | ||
|
|
927f1f6d9a | ||
|
|
0be4c6f233 | ||
|
|
912eea5037 | ||
|
|
06a029b89c | ||
|
|
df708a77d2 | ||
|
|
31232ad50c | ||
|
|
7abaf61e11 | ||
|
|
903a6e31b8 | ||
|
|
2eb5898e8b | ||
|
|
dc2bc0291b | ||
|
|
1357f7eeb4 | ||
|
|
b0cb0a759b | ||
|
|
19d0ca6bfc | ||
|
|
26d03fe2a5 | ||
|
|
dfa61df40a | ||
|
|
d154cced14 | ||
|
|
80b96d9bc9 | ||
|
|
8fa30f71eb | ||
|
|
804094b1ce | ||
|
|
9caf077b58 | ||
|
|
1a5ba06ca3 | ||
|
|
657cad9ffd | ||
|
|
a338a936db | ||
|
|
226e10bca2 | ||
|
|
3a5a989011 | ||
|
|
c9720ef061 | ||
|
|
b6927f178f | ||
|
|
6b40e4fe9d | ||
|
|
59ec56db4c | ||
|
|
a8119d678a | ||
|
|
8e38e313b2 | ||
|
|
b7b75d78ae | ||
|
|
824efb6b59 | ||
|
|
c151ca202c | ||
|
|
b9a64c92e7 | ||
|
|
bcaa06721a | ||
|
|
06227ef12b | ||
|
|
c17827e030 | ||
|
|
97ca0f0b21 | ||
|
|
e4e6419e50 | ||
|
|
fd8c883cf4 | ||
|
|
6b66a59673 | ||
|
|
46177338c2 | ||
|
|
6fcff9b106 | ||
|
|
208f5f6c5c | ||
|
|
de3e2a9867 | ||
|
|
40eea3c659 | ||
|
|
4f9b6b9186 | ||
|
|
4a848ec527 | ||
|
|
10414e169c | ||
|
|
1a6c68d028 | ||
|
|
9156734ced | ||
|
|
0dfe3ff4ac | ||
|
|
3a8c7f02cc | ||
|
|
e03b192fcc | ||
|
|
cf40bd2c30 | ||
|
|
4082e88416 | ||
|
|
27abc10d93 | ||
|
|
d6474d71f2 | ||
|
|
d57603e854 | ||
|
|
ac92584678 | ||
|
|
c234be0a8c | ||
|
|
7c6b8398cf | ||
|
|
7de677c953 | ||
|
|
aa23f44555 | ||
|
|
901d75a1eb | ||
|
|
aa95286810 | ||
|
|
f6b3f661d6 | ||
|
|
0850d85791 | ||
|
|
c564400ac4 | ||
|
|
94ab545ffe | ||
|
|
e10df203b7 | ||
|
|
eea20a6eab | ||
|
|
5f208801ee | ||
|
|
0a22697e5d | ||
|
|
30cf4f0b00 | ||
|
|
e4bb88a725 | ||
|
|
e3822e6bc3 | ||
|
|
20d3be0d1d | ||
|
|
1785863686 | ||
|
|
ea4ced3cc1 | ||
|
|
149008d18b | ||
|
|
55a21d2eec | ||
|
|
c7491e631a | ||
|
|
468a205e36 | ||
|
|
bebe951a57 | ||
|
|
85a8ab32ef | ||
|
|
34ddbe170c | ||
|
|
e9846eb249 | ||
|
|
7cc418ac93 | ||
|
|
69532a4f23 | ||
|
|
f59419d96f | ||
|
|
7f288d0555 | ||
|
|
53afa8c276 | ||
|
|
22f4dd2f75 | ||
|
|
0989152024 | ||
|
|
f74809d361 | ||
|
|
9724cf7776 | ||
|
|
909e5438a8 | ||
|
|
4022a59705 | ||
|
|
d7d26a3ae1 | ||
|
|
66db127245 | ||
|
|
932be02e9e | ||
|
|
fc524894c6 |
@@ -1,6 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 8
|
||||
- 10
|
||||
- 12
|
||||
- 13
|
||||
|
||||
@@ -10,9 +10,19 @@ These sites are independent of Ripple and have not been authorized, endorsed, sp
|
||||
|
||||
Warning: Use at your own risk.
|
||||
|
||||
## Exchanges
|
||||
|
||||
- **[The World Exchange](https://www.theworldexchange.net/)**
|
||||
|
||||
Trade, issue, and send directly on the XRP Ledger. A user interface for the XRPL's decentralized exchange.
|
||||
|
||||
- **[Bitso](https://bitso.com/)**
|
||||
|
||||
Exchange allowing clients to buy and sell XRP, based in Mexico.
|
||||
|
||||
## Data and visualizations
|
||||
|
||||
- **[xrp1ntel - XRP Intelligence](https://xrp1ntel.com/)**
|
||||
- **[xrpintel - XRP Intelligence](https://xrpintel.com/)**
|
||||
|
||||
Monitor the XRP Network in real time and explore historical statistics.
|
||||
|
||||
@@ -39,27 +49,25 @@ Warning: Use at your own risk.
|
||||
- **[XRP Scan - XRP Ledger explorer](https://xrpscan.com)**
|
||||
|
||||
XRP Ledger explorer, metrics and analytics.
|
||||
|
||||
- **[xrplorer](https://xrplorer.com)**
|
||||
|
||||
## Send and request payments
|
||||
XRP Ledger explorer, API, metrics, and analytics using a graph database that is synchronized live with the XRPL.
|
||||
|
||||
- **[XRP Tip Bot](https://www.xrptipbot.com/)**
|
||||
- **[zerptracker](https://zerptracker.com)**
|
||||
|
||||
A bot that enables users on reddit, Twitter and Discord to send XRP to each other through reddit comments and Twitter tweets.
|
||||
|
||||
- **[XRP Text](https://xrptext.com/)**
|
||||
|
||||
Send XRP using SMS text messages.
|
||||
|
||||
- **[XRParrot](https://xrparrot.com/)** (uses `ripple-address-codec`)
|
||||
|
||||
Easy EUR (SEPA) to XRP transfer (currency conversion).
|
||||
|
||||
- **[XRP Payment](https://xrpayments.co/)** (xrpayments.co)
|
||||
|
||||
Tool for generating a XRP payment request URI in a QR code, with currency converter.
|
||||
Monitor the XRPL using powerful JSONPath expressions, and receive notifications via email, SMS, webhooks, and more.
|
||||
|
||||
## Wallets and wallet tools
|
||||
|
||||
- **[XUMM](https://xumm.app/)**
|
||||
|
||||
Users can use the xumm application to track their accounts, balances and transactions. The true power of xumm is the platform available for developers.
|
||||
|
||||
- **[Xpring Wallet](https://xpring.io)** (uses `ripple-keypairs`)
|
||||
|
||||
Non-custodial XRP wallet.
|
||||
|
||||
- **[XRP Toolkit](https://www.xrptoolkit.com)**
|
||||
|
||||
A web interface to the XRP Ledger, supporting both hardware and software wallets.
|
||||
@@ -88,11 +96,29 @@ Warning: Use at your own risk.
|
||||
|
||||
Recover a 24 word mnemonic if one word is wrong or one word is missing.
|
||||
|
||||
## Send and request payments
|
||||
|
||||
- **[XRP Tip Bot](https://www.xrptipbot.com/)**
|
||||
|
||||
A bot that enables users on reddit, Twitter and Discord to send XRP to each other through reddit comments and Twitter tweets.
|
||||
|
||||
- **[XRP Text](https://xrptext.com/)**
|
||||
|
||||
Send XRP using SMS text messages.
|
||||
|
||||
- **[XRParrot](https://xrparrot.com/)** (uses `ripple-address-codec`)
|
||||
|
||||
Easy EUR (SEPA) to XRP transfer (currency conversion).
|
||||
|
||||
- **[XRP Payment](https://xrpayments.co/)** (xrpayments.co)
|
||||
|
||||
Tool for generating a XRP payment request URI in a QR code, with currency converter.
|
||||
|
||||
## Development tools
|
||||
|
||||
- **[XRP Test Net Faucet](https://developers.ripple.com/xrp-test-net-faucet.html)**
|
||||
- **[XRP Faucets for Testnet and Devnet](https://xrpl.org/xrp-testnet-faucet.html)**
|
||||
|
||||
Get some test funds for development on the test network. The faucet was built using `ripple-lib`.
|
||||
Get some test funds for development on the test network. The faucet uses `ripple-lib`.
|
||||
|
||||
## Code samples and libraries
|
||||
|
||||
|
||||
110
HISTORY.md
110
HISTORY.md
@@ -1,5 +1,113 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
Subscribe to [the **ripple-lib-announce** mailing list](https://groups.google.com/forum/#!forum/ripple-lib-announce) for release announcements. We recommend that ripple-lib users stay up-to-date with the latest stable release.
|
||||
|
||||
## 1.8.2 (2020-10-23)
|
||||
|
||||
* Bug fixes
|
||||
* Browser compatibility: Use ripple-binary-codec 0.2.x to prevent browser issues (#1321)
|
||||
* Memory leak: Clear awaiting response promises to prevent memory leak (#1302)
|
||||
* Feature: getSettings() - allow ledgerVersion to be 'validated', 'closed', or 'current' (#1298)
|
||||
* Docs: Update APPLICATIONS.md
|
||||
|
||||
The SHA-256 checksums for the browser version of this release can be found below.
|
||||
```
|
||||
% shasum -a 256 *
|
||||
ba760c36028b8a3ce267386e188a422890dfb1b03bc87c852a4c2034ea9bac2e ripple-latest-min.js
|
||||
7e5281eb9623602284b9f11564f0f3a36cfde305f2c2f7a32e0d29a04913c817 ripple-latest.js
|
||||
```
|
||||
|
||||
## 1.8.1 (2020-09-25)
|
||||
|
||||
* Internal
|
||||
* Bump elliptic to 6.5.3 (this patches a potential security issue, although we do not believe that the issue affects ripple-lib)
|
||||
* Bump ripple-binary-codec to 1.0.2
|
||||
* Bump lodash to 4.17.19
|
||||
|
||||
The SHA-256 checksums for the browser version of this release can be found below.
|
||||
```
|
||||
% shasum -a 256 *
|
||||
0895f349944fa11bb1976b2c350c0eb143dfd09abbfc7c2be33aed5c2a4b9ee8 ripple-latest-min.js
|
||||
7c00188a28f9d295d8e66aa08b340294d2fe49f635d154fb0df049ae8572c195 ripple-latest.js
|
||||
```
|
||||
|
||||
## 1.8.0 (2020-07-06)
|
||||
|
||||
* [Document `request('submit', ...)` method](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md#submit), which includes additional useful information in the response
|
||||
* Update [list of applications using ripple-lib](https://github.com/ripple/ripple-lib/blob/1.7.0/APPLICATIONS.md)
|
||||
|
||||
## 1.7.1 (2020-05-26)
|
||||
|
||||
* Fix preparePayment when using source.amount/destination.minAmount (#1295) (Fix #1237) (Thanks to @leobel)
|
||||
* Docs
|
||||
* Fix generateXAddress example (#1286)
|
||||
* Update Transaction Streams link (#1278)
|
||||
* Dependencies
|
||||
* Update assert-diff, mocha, webpack-bundle-analyzer, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, @types/ws, @types/node, ws, ts-node, eventemitter2
|
||||
|
||||
## 1.7.0 (2020-04-28)
|
||||
|
||||
* Export hashing functions (#1275)
|
||||
* Add failHard (fail_hard) option in `submit` method (#1029)
|
||||
* Add type for parseAccountFlags (#1258)
|
||||
* Add api.connection.getReserveBase() (#1259)
|
||||
* Travis: remove node 8 (#1257)
|
||||
* Dependencies
|
||||
* Update ripple-address-codec, @types/ws, @types/lodash, https-proxy-agent
|
||||
* Update devDependencies: eventemitter2, nyc, ejs, @types/node, webpack, ts-node, prettier, @typescript-eslint/eslint-plugin
|
||||
|
||||
## 1.6.5 (2020-03-23)
|
||||
|
||||
* APPLICATIONS.md: Add xrplorer.com
|
||||
* Internal: Fix typos
|
||||
* Dependencies
|
||||
* Update @types/ws, @types/node, @typescript-eslint/eslint-plugin, @types/mocha, webpack, typescript, mocha, assert-diff
|
||||
* Remove mocha-junit-reporter
|
||||
|
||||
## 1.6.4 (2020-02-18)
|
||||
|
||||
* Fix generateXAddress() and generateAddress() with no entropy (#1211, #1209)
|
||||
* Internal
|
||||
* Improve unit tests
|
||||
* Dependencies
|
||||
* Update webpack-cli, @types/node, webpack, @typescript-eslint/eslint-plugin,
|
||||
typescript, ripple-keypairs
|
||||
|
||||
## 1.6.3 (2020-02-05)
|
||||
|
||||
* Update ripple-keypairs to 1.0.0
|
||||
* Bug fix: Assign event listener to socket close event on open before attempting post-open logic (#1186)
|
||||
* Protects against possible unhandled rejection in disconnect
|
||||
* Adds the Connection `_ws.close` event listener post `_ws.open` before executing any post `_ws.open` logic, i.e. `Connection._subscribeToLedger`
|
||||
* This prevents a reconnection error loop that occurs if `Connection._ws` is never cleaned up by the unreachable `_ws.close` event listener
|
||||
* Also ensures that a possible disconnect() promise rejection is not unhandled if any `_ws.open` logic in `Connection.connect()` throws
|
||||
* Dependencies
|
||||
* Update mocha-junit-reporter, @types/node, mocha, @typescript-eslint/eslint-plugin, ripple-address-codec
|
||||
|
||||
## 1.6.2 (2020-01-17)
|
||||
|
||||
* Bug fix: Catch possible error in reconnect() on _heartbeat(), emit reconnect error (#1179)
|
||||
* Docs: Fix whitespace
|
||||
* Dependencies
|
||||
* Update @typescript-eslint/eslint-plugin, ts-node, @types/node, @types/ws, typescript
|
||||
|
||||
## 1.6.1 (2020-01-14)
|
||||
|
||||
> **Update Note:** In this release we refactored the internal connection logic of ripple-lib to improve resiliency across dropped messages and reconnects. The external interface remains the same, with zero changes to public methods/properties. However, If you experience any problems around connections, requests, and request retries, please [file an issue]( https://github.com/ripple/ripple-lib/issues/new) with the repo (and be sure to test against v1.6.0 to confirm that the problem was introduced in this version).
|
||||
|
||||
* Documentation
|
||||
* Document message type 'path_find' (#1121) (thanks @r0bertz)
|
||||
* Improve documentation for address generation; functions that can be called offline; generateXAddress() (#1158, #1159, #1169) (thanks @hbergren)
|
||||
* Add [Security Policy](https://github.com/ripple/ripple-lib/blob/develop/SECURITY.md)
|
||||
* Bug fixes
|
||||
* Types: Fix AccountObjectsResponse structure (#1127) (thanks @you21979)
|
||||
* In some cases, ripple-lib would fail to wait for the connection to be ready (#1119)
|
||||
* Dependencies
|
||||
* Update docs dependencies, ejs and doctoc (#1153)
|
||||
* Update eslint, ripple-lib-transactionparser, typescript, nyc, ws, @types/node, ripple-binary-codec, mocha, mocha-junit-reporter
|
||||
* Internal
|
||||
* Add LedgerHistory to Connection (#1119): Moved ledger logic into its own Ledger class
|
||||
|
||||
## 1.6.0 (2020-01-06)
|
||||
|
||||
* Add support for AccountDelete (#1120)
|
||||
@@ -447,7 +555,7 @@ f28921f57a133678dcb3cb54c497626bd76b1f953d22d61f3ddca31c8947d552 ripple-1.1.0-m
|
||||
The SHA-256 checksums for the browser version of this release can be found
|
||||
below.
|
||||
```
|
||||
% shasum -a 256 *
|
||||
% shasum -a 256 *
|
||||
2556fe17296e127ed44e7066e90a6175e2b164f00ca3c1aa7b1c554f31c688dd ripple-1.0.2-debug.js
|
||||
e0342ea21eac32a1024c62034fba09c6f26dd3e7371b23ea1e153e03135cd590 ripple-1.0.2-min.js
|
||||
c7286c517497d018d02d09257e81172b61d36c8b9885a077af68e8133c3b3b9b ripple-1.0.2.js
|
||||
|
||||
10
README.md
10
README.md
@@ -6,7 +6,13 @@ A JavaScript/TypeScript API for interacting with the XRP Ledger
|
||||
|
||||
This is the recommended library for integrating a JavaScript/TypeScript app with the XRP Ledger, especially if you intend to use advanced functionality such as IOUs, payment paths, the decentralized exchange, account settings, payment channels, escrows, multi-signing, and more.
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications](APPLICATIONS.md) that use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
## [➡️ Reference Documentation](https://xrpl.org/rippleapi-reference.html)
|
||||
|
||||
See the full reference documentation on the XRP Ledger Dev Portal.
|
||||
|
||||
## [➡️ Applications and Projects](APPLICATIONS.md)
|
||||
|
||||
What is ripple-lib used for? The applications on the list linked above use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
|
||||
### Features
|
||||
|
||||
@@ -67,7 +73,7 @@ For details, see the `scripts` in `package.json`.
|
||||
|
||||
### Linting
|
||||
|
||||
Run `yarn lint` to lint the code with `tslint`.
|
||||
Run `yarn lint` to lint the code with `eslint`.
|
||||
|
||||
## Generating Documentation
|
||||
|
||||
|
||||
29
SECURITY.md
Normal file
29
SECURITY.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This table shows which versions of ripple-lib are currently supported with security updates:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ---------------------- |
|
||||
| 1.x | :white_check_mark: Yes |
|
||||
| 0.x | :x: No |
|
||||
|
||||
## Responsible disclosure security policy
|
||||
|
||||
The responsible disclosure of vulnerabilities helps to protect users of the project. Vulnerabilities are first triaged in a private manner, and only publicly disclosed after a reasonable time period that allows patching the vulnerability and provides an upgrade path for users.
|
||||
|
||||
When contacting us directly via email, we will do our best to respond in a reasonable time to resolve the issue. Do not disclose the vulnerability until it has been patched and users have been given time to upgrade.
|
||||
|
||||
We kindly ask you to refrain from malicious acts that put our users, the project, or any of the project’s team members at risk.
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Security is a top priority. But no matter how much effort we put into security, there can still be vulnerabilities present.
|
||||
|
||||
If you discover a security vulnerability, please use the following means of communications to report it to us:
|
||||
|
||||
- Report the security issue to bugs@ripple.com
|
||||
- [Ripple Bug Bounty](https://ripple.com/bug-bounty/)
|
||||
|
||||
Your efforts to responsibly disclose your findings are sincerely appreciated and will be taken into account to acknowledge your contributions.
|
||||
147
docs/index.md
147
docs/index.md
@@ -39,6 +39,7 @@
|
||||
- [hasNextPage](#hasnextpage)
|
||||
- [requestNextPage](#requestnextpage)
|
||||
- [Static Methods](#static-methods)
|
||||
- [computeBinaryTransactionHash](#computebinarytransactionhash)
|
||||
- [renameCounterpartyToIssuer](#renamecounterpartytoissuer)
|
||||
- [formatBidsAndAsks](#formatbidsandasks)
|
||||
- [API Methods](#api-methods)
|
||||
@@ -221,6 +222,7 @@ Methods that depend on the state of the XRP Ledger are unavailable in offline mo
|
||||
* [prepareEscrowExecution](#prepareescrowexecution)
|
||||
* [sign](#sign)
|
||||
* [generateAddress](#generateaddress)
|
||||
* [generateXAddress](#generatexaddress)
|
||||
* [computeLedgerHash](#computeledgerhash)
|
||||
|
||||
# Basic Types
|
||||
@@ -832,15 +834,16 @@ 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.
|
||||
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://xrpl.org/subscribe.html#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.
|
||||
`path_find` | Asynchronous follow-up response to the currently open path\_find request. See [rippled path\_find method](https://xrpl.org/path_find.html) for details.
|
||||
|
||||
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
|
||||
api.connect().then(() => {
|
||||
|
||||
// 'transaction' can be replaced with the relevant `type` from the table above
|
||||
api.connection.on('transaction', (event) => {
|
||||
@@ -852,9 +855,7 @@ api.connect().then(() => { // Omit this if you are already connected
|
||||
api.request('subscribe', {
|
||||
accounts: [ account ]
|
||||
}).then(response => {
|
||||
if (response.status === 'success') {
|
||||
console.log('Successfully subscribed')
|
||||
}
|
||||
console.log('Successfully subscribed')
|
||||
}).catch(error => {
|
||||
// Handle `error`
|
||||
})
|
||||
@@ -980,6 +981,40 @@ return api.request(command, params).then(response => {
|
||||
|
||||
# Static Methods
|
||||
|
||||
ripple-lib features a number of static methods that you can access directly on the `RippleAPI` object. The most commonly-used one is `computeBinaryTransactionHash`, described below. For the full list, see the [XRP Ledger Hashes README](https://github.com/ripple/ripple-lib/blob/develop/src/common/hashes/README.md).
|
||||
|
||||
## computeBinaryTransactionHash
|
||||
|
||||
`computeBinaryTransactionHash(txBlobHex: string): string`
|
||||
|
||||
Returns the hash (id) of a binary transaction blob.
|
||||
|
||||
This is a static method on the `RippleAPI` class.
|
||||
|
||||
### Parameters
|
||||
|
||||
This method takes one parameter, a string containing a binary transaction in hex.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a string representing the transaction's id (hash).
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signed_blob = '120000228000000024000B2E5A201B0066374B61400000003B9ACA0068400000000000000C732102356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC74473045022100B3721EEB1ED6DFF29FB8B209E2DE6B54A0A6E44D52D926342F3D334BE98F08640220367A74107AD5DEAEFA3AB2984C161FC23F30B2704BB5CC984358BA262177A4568114F667B0CA50CC7709A220B0561B85E53A48461FA883142B71D8B09B4EE8DAA68FB936C23E3A974713BDAC'
|
||||
if (typeof signed_blob === 'string' && signed_blob.match(/^[A-F0-9]+$/)) {
|
||||
const id = RippleAPI.computeBinaryTransactionHash(signed_blob)
|
||||
console.log('Transaction hash:', id')
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Transaction hash: 80C5E11E1A21A626759D6CB944B33DBAAC66BD704A289C86E330B847904F5C13
|
||||
```
|
||||
|
||||
[RunKit Example: computeBinaryTransactionHash](https://runkit.com/intelliot/computebinarytransactionhash-example)
|
||||
|
||||
## renameCounterpartyToIssuer
|
||||
|
||||
`renameCounterpartyToIssuer(issue: {currency: string, counterparty: address}): {currency: string, issuer: address}`
|
||||
@@ -5589,66 +5624,87 @@ return api.combine(signedTransactions);
|
||||
|
||||
## submit
|
||||
|
||||
`submit(signedTransaction: string): Promise<object>`
|
||||
`request('submit', {tx_blob: string, fail_hard: boolean}): Promise<object>`
|
||||
|
||||
Submits a signed transaction. The transaction is not guaranteed to succeed; it must be verified with [getTransaction](#gettransaction).
|
||||
The `submit` method applies a transaction and sends it to the network to be confirmed and included in future ledgers.
|
||||
|
||||
This method takes a signed, serialized transaction as a binary blob, and submits it to the network as-is. Since signed transaction objects are immutable, no part of the transaction can be modified or automatically filled in after submission.
|
||||
|
||||
To send a transaction as robustly as possible, you should construct and sign it in advance, persist it somewhere that you can access even after a power outage, then `submit` it as a `tx_blob`. After submission, monitor the network with the `tx` method to see if the transaction was successfully applied; if a restart or other problem occurs, you can safely re-submit the `tx_blob` transaction: it won't be applied twice since it has the same sequence number as the old transaction.
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
signedTransaction | string | A signed transaction as returned by [sign](#sign).
|
||||
| `Field` | Type | Description |
|
||||
|:------------|:--------|:-----------------------------------------------------|
|
||||
| `tx_blob` | String | Hex representation of the signed transaction to submit. This can be a multi-signed transaction. |
|
||||
| `fail_hard` | Boolean | (Optional, defaults to false) If true, and the transaction fails locally, do not retry or relay the transaction to other servers |
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
When successful, this method returns an object containing the following fields:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
resultCode | string | Deprecated: Use `engine_result` instead.
|
||||
resultMessage | string | Deprecated: Use `engine_result_message` instead.
|
||||
engine_result | string | Code indicating the preliminary result of the transaction, for example `tesSUCCESS`. [List of transaction responses](https://developers.ripple.com/transaction-results.html)
|
||||
engine_result_code | integer | Numeric code indicating the preliminary result of the transaction, directly correlated to `engine_result`
|
||||
engine_result_message | string | Human-readable explanation of the transaction's preliminary result.
|
||||
tx_blob | string | The complete transaction in hex string format.
|
||||
tx_json | [tx-json](https://developers.ripple.com/transaction-formats.html) | The complete transaction in JSON format.
|
||||
| `Field` | Type | Description |
|
||||
|:------------------------|:--------|:-----------------------------------------|
|
||||
| `engine_result` | String | Text [result code](https://xrpl.org/transaction-results.html) indicating the preliminary result of the transaction, for example `tesSUCCESS` |
|
||||
| `engine_result_code` | Integer | Numeric version of the [result code](https://xrpl.org/transaction-results.html). **Not recommended.** |
|
||||
| `engine_result_message` | String | Human-readable explanation of the transaction's preliminary result |
|
||||
| `tx_blob` | String | The complete transaction in hex string format |
|
||||
| `tx_json` | Object | The complete transaction in JSON format |
|
||||
| `accepted` | Boolean | The value `true` indicates that the transaction was applied, queued, broadcast, or kept for later. The value `false` indicates that none of those happened, so the transaction cannot possibly succeed as long as you do not submit it again and have not already submitted it another time. [New in: rippled 1.5.0] |
|
||||
| `account_sequence_available` | Number | The next [Sequence Number](https://xrpl.org/basic-data-types.html#account-sequence) available for the sending account after all pending and [queued](https://xrpl.org/transaction-queue.html) transactions. [New in: rippled 1.5.0] |
|
||||
| `account_sequence_next` | number | The next [Sequence Number](https://xrpl.org/basic-data-types.html#account-sequence) for the sending account after all transactions that have been provisionally applied, but not transactions in the [queue](https://xrpl.org/transaction-queue.html). [New in: rippled 1.5.0] |
|
||||
| `applied` | Boolean | The value `true` indicates that this transaction was applied to the open ledger. In this case, the transaction is likely, but not guaranteed, to be validated in the next ledger version. [New in: rippled 1.5.0] |
|
||||
| `broadcast` | Boolean | The value `true` indicates this transaction was broadcast to peer servers in the peer-to-peer XRP Ledger network. (Note: if the server has no peers, such as in [stand-alone mode](https://xrpl.org/rippled-server-modes.html#reasons-to-run-a-rippled-server-in-stand-alone-mode), the server uses the value `true` for cases where it _would_ have broadcast the transaction.) The value `false` indicates the transaction was not broadcast to any other servers. [New in: rippled 1.5.0] |
|
||||
| `kept` | Boolean | The value `true` indicates that the transaction was kept to be retried later. [New in: rippled 1.5.0] |
|
||||
| `queued` | Boolean | The value `true` indicates the transaction was put in the [Transaction Queue](https://xrpl.org/transaction-queue.html), which means it is likely to be included in a future ledger version. [New in: rippled 1.5.0] |
|
||||
| `open_ledger_cost` | String | The current [open ledger cost](https://xrpl.org/transaction-cost.html#open-ledger-cost) before processing this transaction. Transactions with a lower cost are likely to be [queued](https://xrpl.org/transaction-queue.html). [New in: rippled 1.5.0] |
|
||||
| `validated_ledger_index` | Integer | The [ledger index](https://xrpl.org/basic-data-types.html#ledger-index) of the newest validated ledger at the time of submission. This provides a lower bound on the ledger versions that the transaction can appear in as a result of this request. (The transaction could only have been validated in this ledger version or earlier if it had already been submitted before.) |
|
||||
|
||||
Note: Many situations can prevent a transaction from processing successfully, such as a lack of trust lines connecting the two accounts in a payment, or changes in the state of the ledger since the time the transaction was constructed. Even if nothing is wrong, it may take several seconds to close and validate the ledger version that includes the transaction. Do not consider the transaction's results final until they appear in a validated ledger version.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signedTransaction = '12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100BDE09A1F6670403F341C21A77CF35BA47E45CDE974096E1AA5FC39811D8269E702203D60291B9A27F1DCABA9CF5DED307B4F23223E0B6F156991DB601DFB9C41CE1C770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304';
|
||||
return api.submit(signedTransaction)
|
||||
.then(result => {/* ... */});
|
||||
const signedTransaction = '12000022800000002400000007201B007008BC61400000000754D4C068400000000000000C732103E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F824832874473045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22811492F80A3F3849DBB5714A4F2C691CE7D47BEED58083141266204CFBC657E65D9B4D30301FF98644693935';
|
||||
const failHard = false;
|
||||
const result = await api.request('submit', {
|
||||
tx_blob: signedTransaction,
|
||||
fail_hard: failHard // optional
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"resultCode": "tesSUCCESS",
|
||||
"resultMessage": "The transaction was applied. Only final in a validated ledger.",
|
||||
"accepted": true,
|
||||
"account_sequence_available": 8,
|
||||
"account_sequence_next": 8,
|
||||
"applied": true,
|
||||
"broadcast": true,
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
|
||||
"kept": true,
|
||||
"open_ledger_cost": "10",
|
||||
"queued": false,
|
||||
"tx_blob": "12000022800000002400000007201B007008BC61400000000754D4C068400000000000000C732103E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F824832874473045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22811492F80A3F3849DBB5714A4F2C691CE7D47BEED58083141266204CFBC657E65D9B4D30301FF98644693935",
|
||||
"tx_json": {
|
||||
"Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"Amount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"value": "1"
|
||||
},
|
||||
"Destination": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||
"Fee": "10000",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 360,
|
||||
"SigningPubKey": "03AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB",
|
||||
"TransactionType": "Payment",
|
||||
"TxnSignature": "304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F8580",
|
||||
"hash": "4D5D90890F8D49519E4151938601EF3D0B30B16CD6A519D9C99102C9FA77F7E0"
|
||||
}
|
||||
"Account": "rNQao3Z1irwRjKWSs8heL4a8WKLPKfLrXs",
|
||||
"Amount": "123000000",
|
||||
"Destination": "rpgHWJdXkSvvzikdJCpuMzU7zWnuqsJRCZ",
|
||||
"Fee": "12",
|
||||
"Flags": 2147483648,
|
||||
"LastLedgerSequence": 7342268,
|
||||
"Sequence": 7,
|
||||
"SigningPubKey": "03E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F8248328",
|
||||
"TransactionType": "Payment",
|
||||
"TxnSignature": "3045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22",
|
||||
"hash": "FE8D68D7FF5805EB07AF15A1ADF07FB5463CCD2C6C0A15981EB3D26A02E1551C"
|
||||
},
|
||||
"validated_ledger_index": 7341775
|
||||
}
|
||||
```
|
||||
|
||||
(In ripple-lib 1.8.0, [the old `submit` method](https://github.com/ripple/ripple-lib/blob/1.7.0/docs/index.md#submit) was deprecated.)
|
||||
|
||||
## generateXAddress
|
||||
|
||||
@@ -5662,7 +5718,8 @@ Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed. Must be an array of length 16 with values from 0-255 (16 bytes of entropy)
|
||||
*options.* includeClassicAddress | boolean | *Optional* Specifies whether the classic address should also be included in the returned payload.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
### Return Value
|
||||
@@ -5677,7 +5734,7 @@ secret | secret string | The secret corresponding to the address.
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
return api.generateXAddress();
|
||||
```
|
||||
|
||||
|
||||
@@ -5703,7 +5760,7 @@ Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
options | object | *Optional* Options to control how the address and secret are generated.
|
||||
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
|
||||
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed. Must be an array of length 16 with values from 0-255 (16 bytes of entropy)
|
||||
*options.* includeClassicAddress | boolean | *Optional* If `true`, return the classic address, in addition to the X-address.
|
||||
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ This method returns an object with the following structure:
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
return api.generateAddress();
|
||||
return api.generateXAddress();
|
||||
```
|
||||
|
||||
<%- renderFixture('responses/generate-x-address.json') %>
|
||||
|
||||
@@ -1,69 +1,69 @@
|
||||
<% include introduction.md.ejs %>
|
||||
<% include boilerplate.md.ejs %>
|
||||
<% include offline.md.ejs %>
|
||||
<% 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('introduction.md.ejs') %>
|
||||
<%- include('boilerplate.md.ejs') %>
|
||||
<%- include('offline.md.ejs') %>
|
||||
<%- 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 staticMethods.md.ejs %>
|
||||
<% include renameCounterpartyToIssuer.md.ejs %>
|
||||
<% include formatBidsAndAsks.md.ejs %>
|
||||
<%- include('staticMethods.md.ejs') %>
|
||||
<%- include('renameCounterpartyToIssuer.md.ejs') %>
|
||||
<%- include('formatBidsAndAsks.md.ejs') %>
|
||||
|
||||
<% include methods.md.ejs %>
|
||||
<% include connect.md.ejs %>
|
||||
<% include disconnect.md.ejs %>
|
||||
<% include isConnected.md.ejs %>
|
||||
<% include getServerInfo.md.ejs %>
|
||||
<% include getFee.md.ejs %>
|
||||
<% include getLedgerVersion.md.ejs %>
|
||||
<% include getTransaction.md.ejs %>
|
||||
<% include getTransactions.md.ejs %>
|
||||
<% include getTrustlines.md.ejs %>
|
||||
<% include getBalances.md.ejs %>
|
||||
<% include getBalanceSheet.md.ejs %>
|
||||
<% include getPaths.md.ejs %>
|
||||
<% include getOrders.md.ejs %>
|
||||
<% 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 parseAccountFlags.md.ejs %>
|
||||
<% include prepareTransaction.md.ejs %>
|
||||
<% include preparePayment.md.ejs %>
|
||||
<% include prepareTrustline.md.ejs %>
|
||||
<% include prepareOrder.md.ejs %>
|
||||
<% include prepareOrderCancellation.md.ejs %>
|
||||
<% include prepareSettings.md.ejs %>
|
||||
<% include prepareEscrowCreation.md.ejs %>
|
||||
<% include prepareEscrowCancellation.md.ejs %>
|
||||
<% include prepareEscrowExecution.md.ejs %>
|
||||
<% include preparePaymentChannelCreate.md.ejs %>
|
||||
<% include preparePaymentChannelClaim.md.ejs %>
|
||||
<% include preparePaymentChannelFund.md.ejs %>
|
||||
<% include prepareCheckCreate.md.ejs %>
|
||||
<% include prepareCheckCancel.md.ejs %>
|
||||
<% include prepareCheckCash.md.ejs %>
|
||||
<% include sign.md.ejs %>
|
||||
<% include combine.md.ejs %>
|
||||
<% include submit.md.ejs %>
|
||||
<% include generateXAddress.md.ejs %>
|
||||
<% include generateAddress.md.ejs %>
|
||||
<% include isValidAddress.md.ejs %>
|
||||
<% include isValidSecret.md.ejs %>
|
||||
<% include deriveKeypair.md.ejs %>
|
||||
<% include deriveAddress.md.ejs %>
|
||||
<% include signPaymentChannelClaim.md.ejs %>
|
||||
<% include verifyPaymentChannelClaim.md.ejs %>
|
||||
<% include computeLedgerHash.md.ejs %>
|
||||
<% include xrpToDropsAndDropsToXrp.md.ejs %>
|
||||
<% include iso8601ToRippleTime.md.ejs %>
|
||||
<% include rippleTimeToISO8601.md.ejs %>
|
||||
<% include txFlags.md.ejs %>
|
||||
<% include schemaValidator.md.ejs %>
|
||||
<% include events.md.ejs %>
|
||||
<%- include('methods.md.ejs') %>
|
||||
<%- include('connect.md.ejs') %>
|
||||
<%- include('disconnect.md.ejs') %>
|
||||
<%- include('isConnected.md.ejs') %>
|
||||
<%- include('getServerInfo.md.ejs') %>
|
||||
<%- include('getFee.md.ejs') %>
|
||||
<%- include('getLedgerVersion.md.ejs') %>
|
||||
<%- include('getTransaction.md.ejs') %>
|
||||
<%- include('getTransactions.md.ejs') %>
|
||||
<%- include('getTrustlines.md.ejs') %>
|
||||
<%- include('getBalances.md.ejs') %>
|
||||
<%- include('getBalanceSheet.md.ejs') %>
|
||||
<%- include('getPaths.md.ejs') %>
|
||||
<%- include('getOrders.md.ejs') %>
|
||||
<%- 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('parseAccountFlags.md.ejs') %>
|
||||
<%- include('prepareTransaction.md.ejs') %>
|
||||
<%- include('preparePayment.md.ejs') %>
|
||||
<%- include('prepareTrustline.md.ejs') %>
|
||||
<%- include('prepareOrder.md.ejs') %>
|
||||
<%- include('prepareOrderCancellation.md.ejs') %>
|
||||
<%- include('prepareSettings.md.ejs') %>
|
||||
<%- include('prepareEscrowCreation.md.ejs') %>
|
||||
<%- include('prepareEscrowCancellation.md.ejs') %>
|
||||
<%- include('prepareEscrowExecution.md.ejs') %>
|
||||
<%- include('preparePaymentChannelCreate.md.ejs') %>
|
||||
<%- include('preparePaymentChannelClaim.md.ejs') %>
|
||||
<%- include('preparePaymentChannelFund.md.ejs') %>
|
||||
<%- include('prepareCheckCreate.md.ejs') %>
|
||||
<%- include('prepareCheckCancel.md.ejs') %>
|
||||
<%- include('prepareCheckCash.md.ejs') %>
|
||||
<%- include('sign.md.ejs') %>
|
||||
<%- include('combine.md.ejs') %>
|
||||
<%- include('submit.md.ejs') %>
|
||||
<%- include('generateXAddress.md.ejs') %>
|
||||
<%- include('generateAddress.md.ejs') %>
|
||||
<%- include('isValidAddress.md.ejs') %>
|
||||
<%- include('isValidSecret.md.ejs') %>
|
||||
<%- include('deriveKeypair.md.ejs') %>
|
||||
<%- include('deriveAddress.md.ejs') %>
|
||||
<%- include('signPaymentChannelClaim.md.ejs') %>
|
||||
<%- include('verifyPaymentChannelClaim.md.ejs') %>
|
||||
<%- include('computeLedgerHash.md.ejs') %>
|
||||
<%- include('xrpToDropsAndDropsToXrp.md.ejs') %>
|
||||
<%- include('iso8601ToRippleTime.md.ejs') %>
|
||||
<%- include('rippleTimeToISO8601.md.ejs') %>
|
||||
<%- include('txFlags.md.ejs') %>
|
||||
<%- include('schemaValidator.md.ejs') %>
|
||||
<%- include('events.md.ejs') %>
|
||||
|
||||
@@ -23,4 +23,5 @@ Methods that depend on the state of the XRP Ledger are unavailable in offline mo
|
||||
* [prepareEscrowExecution](#prepareescrowexecution)
|
||||
* [sign](#sign)
|
||||
* [generateAddress](#generateaddress)
|
||||
* [generateXAddress](#generatexaddress)
|
||||
* [computeLedgerHash](#computeledgerhash)
|
||||
|
||||
@@ -37,15 +37,16 @@ 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.
|
||||
`transaction` | Sent by many subscriptions including `transactions`, `transactions_proposed`, `accounts`, `accounts_proposed`, and `book` (Order Book). See [Transaction Streams](https://xrpl.org/subscribe.html#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.
|
||||
`path_find` | Asynchronous follow-up response to the currently open path\_find request. See [rippled path\_find method](https://xrpl.org/path_find.html) for details.
|
||||
|
||||
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
|
||||
api.connect().then(() => {
|
||||
|
||||
// 'transaction' can be replaced with the relevant `type` from the table above
|
||||
api.connection.on('transaction', (event) => {
|
||||
@@ -57,9 +58,7 @@ api.connect().then(() => { // Omit this if you are already connected
|
||||
api.request('subscribe', {
|
||||
accounts: [ account ]
|
||||
}).then(response => {
|
||||
if (response.status === 'success') {
|
||||
console.log('Successfully subscribed')
|
||||
}
|
||||
console.log('Successfully subscribed')
|
||||
}).catch(error => {
|
||||
// Handle `error`
|
||||
})
|
||||
|
||||
@@ -1 +1,35 @@
|
||||
# Static Methods
|
||||
|
||||
ripple-lib features a number of static methods that you can access directly on the `RippleAPI` object. The most commonly-used one is `computeBinaryTransactionHash`, described below. For the full list, see the [XRP Ledger Hashes README](https://github.com/ripple/ripple-lib/blob/develop/src/common/hashes/README.md).
|
||||
|
||||
## computeBinaryTransactionHash
|
||||
|
||||
`computeBinaryTransactionHash(txBlobHex: string): string`
|
||||
|
||||
Returns the hash (id) of a binary transaction blob.
|
||||
|
||||
This is a static method on the `RippleAPI` class.
|
||||
|
||||
### Parameters
|
||||
|
||||
This method takes one parameter, a string containing a binary transaction in hex.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a string representing the transaction's id (hash).
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signed_blob = '120000228000000024000B2E5A201B0066374B61400000003B9ACA0068400000000000000C732102356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC74473045022100B3721EEB1ED6DFF29FB8B209E2DE6B54A0A6E44D52D926342F3D334BE98F08640220367A74107AD5DEAEFA3AB2984C161FC23F30B2704BB5CC984358BA262177A4568114F667B0CA50CC7709A220B0561B85E53A48461FA883142B71D8B09B4EE8DAA68FB936C23E3A974713BDAC'
|
||||
if (typeof signed_blob === 'string' && signed_blob.match(/^[A-F0-9]+$/)) {
|
||||
const id = RippleAPI.computeBinaryTransactionHash(signed_blob)
|
||||
console.log('Transaction hash:', id')
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Transaction hash: 80C5E11E1A21A626759D6CB944B33DBAAC66BD704A289C86E330B847904F5C13
|
||||
```
|
||||
|
||||
[RunKit Example: computeBinaryTransactionHash](https://runkit.com/intelliot/computebinarytransactionhash-example)
|
||||
|
||||
@@ -1,25 +1,83 @@
|
||||
## submit
|
||||
|
||||
`submit(signedTransaction: string): Promise<object>`
|
||||
`request('submit', {tx_blob: string, fail_hard: boolean}): Promise<object>`
|
||||
|
||||
Submits a signed transaction. The transaction is not guaranteed to succeed; it must be verified with [getTransaction](#gettransaction).
|
||||
The `submit` method applies a transaction and sends it to the network to be confirmed and included in future ledgers.
|
||||
|
||||
This method takes a signed, serialized transaction as a binary blob, and submits it to the network as-is. Since signed transaction objects are immutable, no part of the transaction can be modified or automatically filled in after submission.
|
||||
|
||||
To send a transaction as robustly as possible, you should construct and sign it in advance, persist it somewhere that you can access even after a power outage, then `submit` it as a `tx_blob`. After submission, monitor the network with the `tx` method to see if the transaction was successfully applied; if a restart or other problem occurs, you can safely re-submit the `tx_blob` transaction: it won't be applied twice since it has the same sequence number as the old transaction.
|
||||
|
||||
### Parameters
|
||||
|
||||
<%- renderSchema('input/submit.json') %>
|
||||
| `Field` | Type | Description |
|
||||
|:------------|:--------|:-----------------------------------------------------|
|
||||
| `tx_blob` | String | Hex representation of the signed transaction to submit. This can be a multi-signed transaction. |
|
||||
| `fail_hard` | Boolean | (Optional, defaults to false) If true, and the transaction fails locally, do not retry or relay the transaction to other servers |
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns an object with the following structure:
|
||||
When successful, this method returns an object containing the following fields:
|
||||
|
||||
<%- renderSchema('output/submit.json') %>
|
||||
| `Field` | Type | Description |
|
||||
|:------------------------|:--------|:-----------------------------------------|
|
||||
| `engine_result` | String | Text [result code](https://xrpl.org/transaction-results.html) indicating the preliminary result of the transaction, for example `tesSUCCESS` |
|
||||
| `engine_result_code` | Integer | Numeric version of the [result code](https://xrpl.org/transaction-results.html). **Not recommended.** |
|
||||
| `engine_result_message` | String | Human-readable explanation of the transaction's preliminary result |
|
||||
| `tx_blob` | String | The complete transaction in hex string format |
|
||||
| `tx_json` | Object | The complete transaction in JSON format |
|
||||
| `accepted` | Boolean | The value `true` indicates that the transaction was applied, queued, broadcast, or kept for later. The value `false` indicates that none of those happened, so the transaction cannot possibly succeed as long as you do not submit it again and have not already submitted it another time. [New in: rippled 1.5.0] |
|
||||
| `account_sequence_available` | Number | The next [Sequence Number](https://xrpl.org/basic-data-types.html#account-sequence) available for the sending account after all pending and [queued](https://xrpl.org/transaction-queue.html) transactions. [New in: rippled 1.5.0] |
|
||||
| `account_sequence_next` | number | The next [Sequence Number](https://xrpl.org/basic-data-types.html#account-sequence) for the sending account after all transactions that have been provisionally applied, but not transactions in the [queue](https://xrpl.org/transaction-queue.html). [New in: rippled 1.5.0] |
|
||||
| `applied` | Boolean | The value `true` indicates that this transaction was applied to the open ledger. In this case, the transaction is likely, but not guaranteed, to be validated in the next ledger version. [New in: rippled 1.5.0] |
|
||||
| `broadcast` | Boolean | The value `true` indicates this transaction was broadcast to peer servers in the peer-to-peer XRP Ledger network. (Note: if the server has no peers, such as in [stand-alone mode](https://xrpl.org/rippled-server-modes.html#reasons-to-run-a-rippled-server-in-stand-alone-mode), the server uses the value `true` for cases where it _would_ have broadcast the transaction.) The value `false` indicates the transaction was not broadcast to any other servers. [New in: rippled 1.5.0] |
|
||||
| `kept` | Boolean | The value `true` indicates that the transaction was kept to be retried later. [New in: rippled 1.5.0] |
|
||||
| `queued` | Boolean | The value `true` indicates the transaction was put in the [Transaction Queue](https://xrpl.org/transaction-queue.html), which means it is likely to be included in a future ledger version. [New in: rippled 1.5.0] |
|
||||
| `open_ledger_cost` | String | The current [open ledger cost](https://xrpl.org/transaction-cost.html#open-ledger-cost) before processing this transaction. Transactions with a lower cost are likely to be [queued](https://xrpl.org/transaction-queue.html). [New in: rippled 1.5.0] |
|
||||
| `validated_ledger_index` | Integer | The [ledger index](https://xrpl.org/basic-data-types.html#ledger-index) of the newest validated ledger at the time of submission. This provides a lower bound on the ledger versions that the transaction can appear in as a result of this request. (The transaction could only have been validated in this ledger version or earlier if it had already been submitted before.) |
|
||||
|
||||
Note: Many situations can prevent a transaction from processing successfully, such as a lack of trust lines connecting the two accounts in a payment, or changes in the state of the ledger since the time the transaction was constructed. Even if nothing is wrong, it may take several seconds to close and validate the ledger version that includes the transaction. Do not consider the transaction's results final until they appear in a validated ledger version.
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signedTransaction = '12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100BDE09A1F6670403F341C21A77CF35BA47E45CDE974096E1AA5FC39811D8269E702203D60291B9A27F1DCABA9CF5DED307B4F23223E0B6F156991DB601DFB9C41CE1C770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304';
|
||||
return api.submit(signedTransaction)
|
||||
.then(result => {/* ... */});
|
||||
const signedTransaction = '12000022800000002400000007201B007008BC61400000000754D4C068400000000000000C732103E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F824832874473045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22811492F80A3F3849DBB5714A4F2C691CE7D47BEED58083141266204CFBC657E65D9B4D30301FF98644693935';
|
||||
const failHard = false;
|
||||
const result = await api.request('submit', {
|
||||
tx_blob: signedTransaction,
|
||||
fail_hard: failHard // optional
|
||||
});
|
||||
```
|
||||
|
||||
<%- renderFixture('responses/submit.json') %>
|
||||
```json
|
||||
{
|
||||
"accepted": true,
|
||||
"account_sequence_available": 8,
|
||||
"account_sequence_next": 8,
|
||||
"applied": true,
|
||||
"broadcast": true,
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"kept": true,
|
||||
"open_ledger_cost": "10",
|
||||
"queued": false,
|
||||
"tx_blob": "12000022800000002400000007201B007008BC61400000000754D4C068400000000000000C732103E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F824832874473045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22811492F80A3F3849DBB5714A4F2C691CE7D47BEED58083141266204CFBC657E65D9B4D30301FF98644693935",
|
||||
"tx_json": {
|
||||
"Account": "rNQao3Z1irwRjKWSs8heL4a8WKLPKfLrXs",
|
||||
"Amount": "123000000",
|
||||
"Destination": "rpgHWJdXkSvvzikdJCpuMzU7zWnuqsJRCZ",
|
||||
"Fee": "12",
|
||||
"Flags": 2147483648,
|
||||
"LastLedgerSequence": 7342268,
|
||||
"Sequence": 7,
|
||||
"SigningPubKey": "03E8110048477E60F292DEDA67CF518511E70A15E1E3771B3C024026E1F8248328",
|
||||
"TransactionType": "Payment",
|
||||
"TxnSignature": "3045022100D659C836C24FF346A87054E463078D805B19EFE9F10348FD4C6ED6C3F3C4D750022060BE0BFD5E2C4963A1B0E0F21D5BA800969863BA486F71E75C08D76D77C45B22",
|
||||
"hash": "FE8D68D7FF5805EB07AF15A1ADF07FB5463CCD2C6C0A15981EB3D26A02E1551C"
|
||||
},
|
||||
"validated_ledger_index": 7341775
|
||||
}
|
||||
```
|
||||
|
||||
(In ripple-lib 1.8.0, [the old `submit` method](https://github.com/ripple/ripple-lib/blob/1.7.0/docs/index.md#submit) was deprecated.)
|
||||
|
||||
37
package.json
37
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.6.0",
|
||||
"version": "1.8.2",
|
||||
"license": "ISC",
|
||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -21,36 +21,35 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^6.0.3",
|
||||
"@types/ws": "^7.2.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"https-proxy-agent": "^4.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ripple-address-codec": "^4.0.0",
|
||||
"ripple-binary-codec": "^0.2.5",
|
||||
"ripple-keypairs": "^0.11.0",
|
||||
"ripple-lib-transactionparser": "0.8.1",
|
||||
"ripple-address-codec": "^4.1.1",
|
||||
"ripple-binary-codec": "^0.2.7",
|
||||
"ripple-keypairs": "^1.0.0",
|
||||
"ripple-lib-transactionparser": "0.8.2",
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/node": "^13.1.1",
|
||||
"@types/mocha": "^7.0.1",
|
||||
"@types/node": "^14.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.3.3",
|
||||
"@typescript-eslint/parser": "^2.3.3",
|
||||
"assert-diff": "^2.0.3",
|
||||
"doctoc": "^0.15.0",
|
||||
"ejs": "^2.3.4",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"assert-diff": "^3.0.0",
|
||||
"doctoc": "^1.4.0",
|
||||
"ejs": "^3.0.1",
|
||||
"eslint": "^6.5.1",
|
||||
"eventemitter2": "^6.0.0",
|
||||
"json-schema-to-markdown-table": "^0.4.0",
|
||||
"mocha": "6.2.2",
|
||||
"mocha-junit-reporter": "^1.9.1",
|
||||
"nyc": "^14.1.1",
|
||||
"prettier": "^1.19.1",
|
||||
"mocha": "^7.1.1",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^2.0.5",
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "^3.6.4",
|
||||
"webpack": "^4.41.2",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,10 @@ lint() {
|
||||
|
||||
unittest() {
|
||||
# test "src"
|
||||
mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
|
||||
|
||||
# TODO: replace/upgrade mocha-junit-reporter
|
||||
#mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
|
||||
|
||||
yarn test --coverage
|
||||
#yarn run coveralls
|
||||
|
||||
|
||||
49
src/api.ts
49
src/api.ts
@@ -89,6 +89,19 @@ import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
import {ConnectionUserOptions} from './common/connection'
|
||||
import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec'
|
||||
import {
|
||||
computeBinaryTransactionHash,
|
||||
computeTransactionHash,
|
||||
computeBinaryTransactionSigningHash,
|
||||
computeAccountLedgerObjectID,
|
||||
computeSignerListLedgerObjectID,
|
||||
computeOrderID,
|
||||
computeTrustlineHash,
|
||||
computeTransactionTreeHash,
|
||||
computeStateTreeHash,
|
||||
computeEscrowHash,
|
||||
computePaymentChannelHash
|
||||
} from './common/hashes'
|
||||
|
||||
export interface APIOptions extends ConnectionUserOptions {
|
||||
server?: string
|
||||
@@ -153,10 +166,10 @@ class RippleAPI extends EventEmitter {
|
||||
})
|
||||
this.connection.on('disconnected', code => {
|
||||
let finalCode = code
|
||||
// This is a backwards-compatible fix for this change in the ws library:
|
||||
// https://github.com/websockets/ws/issues/1257
|
||||
// 1005: This is a backwards-compatible fix for this change in the ws library: https://github.com/websockets/ws/issues/1257
|
||||
// 4000: Connection uses a 4000 code internally to indicate a manual disconnect/close
|
||||
// TODO: Remove in next major, breaking version
|
||||
if (finalCode === 1005) {
|
||||
if (finalCode === 1005 || finalCode === 4000) {
|
||||
finalCode = 1000
|
||||
}
|
||||
this.emit('disconnected', finalCode)
|
||||
@@ -252,7 +265,7 @@ class RippleAPI extends EventEmitter {
|
||||
/**
|
||||
* Prepare a transaction.
|
||||
*
|
||||
* You can later submit the transaction with `submit()`.
|
||||
* You can later submit the transaction with a `submit` request.
|
||||
*/
|
||||
async prepareTransaction(
|
||||
txJSON: TransactionJSON,
|
||||
@@ -385,7 +398,8 @@ class RippleAPI extends EventEmitter {
|
||||
prepareSettings = prepareSettings
|
||||
sign = sign
|
||||
combine = combine
|
||||
submit = submit
|
||||
|
||||
submit = submit // @deprecated Use api.request('submit', { tx_blob: signedTransaction }) instead
|
||||
|
||||
deriveKeypair = deriveKeypair
|
||||
deriveAddress = deriveAddress
|
||||
@@ -402,6 +416,31 @@ class RippleAPI extends EventEmitter {
|
||||
static isValidXAddress = isValidXAddress
|
||||
static isValidClassicAddress = isValidClassicAddress
|
||||
|
||||
/**
|
||||
* Static methods that replace functionality from the now-deprecated ripple-hashes library
|
||||
*/
|
||||
// Compute the hash of a binary transaction blob.
|
||||
static computeBinaryTransactionHash = computeBinaryTransactionHash // (txBlobHex: string): string
|
||||
// Compute the hash of a transaction in txJSON format.
|
||||
static computeTransactionHash = computeTransactionHash // (txJSON: any): string
|
||||
static computeBinaryTransactionSigningHash = computeBinaryTransactionSigningHash // (txBlobHex: string): string
|
||||
// Compute the hash of an account, given the account's classic address (starting with `r`).
|
||||
static computeAccountLedgerObjectID = computeAccountLedgerObjectID // (address: string): string
|
||||
// Compute the hash (ID) of an account's SignerList.
|
||||
static computeSignerListLedgerObjectID = computeSignerListLedgerObjectID // (address: string): string
|
||||
// Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.
|
||||
static computeOrderID = computeOrderID // (address: string, sequence: number): string
|
||||
// Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code.
|
||||
static computeTrustlineHash = computeTrustlineHash // (address1: string, address2: string, currency: string): string
|
||||
static computeTransactionTreeHash = computeTransactionTreeHash // (transactions: any[]): string
|
||||
static computeStateTreeHash = computeStateTreeHash // (entries: any[]): string
|
||||
// Compute the hash of a ledger.
|
||||
static computeLedgerHash = computeLedgerHash // (ledgerHeader): string
|
||||
// Compute the hash of an escrow, given the owner's classic address (starting with `r`) and the account sequence number of the `EscrowCreate` escrow transaction.
|
||||
static computeEscrowHash = computeEscrowHash // (address, sequence): string
|
||||
// Compute the hash of a payment channel, given the owner's classic address (starting with `r`), the classic address of the destination, and the account sequence number of the `PaymentChannelCreate` payment channel transaction.
|
||||
static computePaymentChannelHash = computePaymentChannelHash // (address, dstAddress, sequence): string
|
||||
|
||||
xrpToDrops = xrpToDrops
|
||||
dropsToXrp = dropsToXrp
|
||||
rippleTimeToISO8601 = rippleTimeToISO8601
|
||||
|
||||
44
src/common/backoff.ts
Normal file
44
src/common/backoff.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Original code based on "backo" - https://github.com/segmentio/backo
|
||||
* MIT License - Copyright 2014 Segment.io
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Back off strategy that increases exponentially. Useful with repeated
|
||||
* setTimeout calls over a network (where the destination may be down).
|
||||
*/
|
||||
export class ExponentialBackoff {
|
||||
private readonly ms: number
|
||||
private readonly max: number
|
||||
private readonly factor: number = 2
|
||||
private readonly jitter: number = 0
|
||||
attempts: number = 0
|
||||
|
||||
constructor(opts: {min?: number; max?: number} = {}) {
|
||||
this.ms = opts.min || 100
|
||||
this.max = opts.max || 10000
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the backoff duration.
|
||||
*/
|
||||
duration() {
|
||||
var ms = this.ms * Math.pow(this.factor, this.attempts++)
|
||||
if (this.jitter) {
|
||||
var rand = Math.random()
|
||||
var deviation = Math.floor(rand * this.jitter * ms)
|
||||
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation
|
||||
}
|
||||
return Math.min(ms, this.max) | 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the number of attempts.
|
||||
*/
|
||||
reset() {
|
||||
this.attempts = 0
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,18 @@ const AccountFlags = {
|
||||
defaultRipple: accountRootFlags.DefaultRipple
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
passwordSpent?: boolean
|
||||
requireDestinationTag?: boolean
|
||||
requireAuthorization?: boolean
|
||||
depositAuth?: boolean
|
||||
disallowIncomingXRP?: boolean
|
||||
disableMasterKey?: boolean
|
||||
noFreeze?: boolean
|
||||
globalFreeze?: boolean
|
||||
defaultRipple?: boolean
|
||||
}
|
||||
|
||||
const AccountFlagIndices = {
|
||||
requireDestinationTag: txFlagIndices.AccountSet.asfRequireDest,
|
||||
requireAuthorization: txFlagIndices.AccountSet.asfRequireAuth,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Methods to hash XRP Ledger objects
|
||||
|
||||
## Methods
|
||||
## Computing a transaction hash (ID)
|
||||
|
||||
### computeBinaryTransactionHash = (txBlobHex: string): string
|
||||
|
||||
@@ -12,19 +12,35 @@ Compute the hash of a binary transaction blob.
|
||||
|
||||
Compute the hash of a transaction in txJSON format.
|
||||
|
||||
## [Hash Prefixes](https://xrpl.org/basic-data-types.html#hash-prefixes)
|
||||
|
||||
In many cases, the XRP Ledger prefixes an object's binary data with a 4-byte code before calculating its hash, so that objects of different types have different hashes even if the binary data is the same. The existing 4-byte codes are structured as 3 alphabetic characters, encoded as ASCII, followed by a zero byte.
|
||||
|
||||
Some types of hashes appear in API requests and responses. Others are only calculated as the first step of signing a certain type of data, or calculating a higher-level hash. Some of following methods internally use some of the 4-byte hash prefixes in order to calculate the appropriate hash.
|
||||
|
||||
### computeBinaryTransactionSigningHash = (txBlobHex: string): string
|
||||
|
||||
### computeTransactionSigningHash = (txJSON: any): string
|
||||
In order to single-sign a transaction, you must perform these steps:
|
||||
|
||||
### computeAccountHash = (address: string): string
|
||||
1. Assuming the transaction is in JSON format (txJSON), `encode` the transaction in the XRP Ledger's binary format.
|
||||
2. Hash the data with the appropriate prefix (`0x53545800` if single-signing, or `0x534D5400` if multi-signing).
|
||||
3. After signing, you must re-serialize the transaction with the `TxnSignature` field included.
|
||||
|
||||
The `computeBinaryTransactionSigningHash` helps with step 2, automatically using the `0x53545800` prefix needed for single-signing a transaction.
|
||||
|
||||
For details, see [Serialization Format](https://xrpl.org/serialization.html).
|
||||
|
||||
_Removed:_ `computeTransactionSigningHash`, which took txJSON as a parameter. It was part of the deprecated ripple-hashes library. If you have txJSON, `encode` it with [ripple-binary-codec](https://github.com/ripple/ripple-binary-codec) first. Example: `return computeBinaryTransactionSigningHash(encode(txJSON))`
|
||||
|
||||
### computeAccountLedgerObjectID = (address: string): string
|
||||
|
||||
Compute the hash of an account, given the account's classic address (starting with `r`).
|
||||
|
||||
### computeSignerListHash = (address: string): string
|
||||
### computeSignerListLedgerObjectID = (address: string): string
|
||||
|
||||
Compute the hash of an account's SignerList.
|
||||
|
||||
### computeOrderHash = (address: string, sequence: number): string
|
||||
### computeOrderID = (address: string, sequence: number): string
|
||||
|
||||
Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.
|
||||
|
||||
|
||||
@@ -71,6 +71,14 @@ export const computeTransactionHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionHash(encode(txJSON))
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the given binary transaction data with the single-signing prefix.
|
||||
*
|
||||
* See [Serialization Format](https://xrpl.org/serialization.html)
|
||||
*
|
||||
* @param txBlobHex The binary transaction blob as a hexadecimal string
|
||||
* @returns {string} The hash to sign
|
||||
*/
|
||||
export const computeBinaryTransactionSigningHash = (
|
||||
txBlobHex: string
|
||||
): string => {
|
||||
@@ -78,21 +86,56 @@ export const computeBinaryTransactionSigningHash = (
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionSigningHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionSigningHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeAccountHash = (address: string): string => {
|
||||
/**
|
||||
* Compute Account Ledger Object ID
|
||||
*
|
||||
* All objects in a ledger's state tree have a unique ID.
|
||||
* The Account Ledger Object ID is derived by hashing the
|
||||
* address with a namespace identifier. This ensures every
|
||||
* ID is unique.
|
||||
*
|
||||
* See [Ledger Object IDs](https://xrpl.org/ledger-object-ids.html)
|
||||
*
|
||||
* @param address The classic account address
|
||||
* @returns {string} The Ledger Object ID for the account
|
||||
*/
|
||||
export const computeAccountLedgerObjectID = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
|
||||
}
|
||||
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
/**
|
||||
* [SignerList ID Format](https://xrpl.org/signerlist.html#signerlist-id-format)
|
||||
*
|
||||
* The ID of a SignerList object is the SHA-512Half of the following values, concatenated in order:
|
||||
* * The RippleState space key (0x0053)
|
||||
* * The AccountID of the owner of the SignerList
|
||||
* * The SignerListID (currently always 0)
|
||||
*
|
||||
* This method computes a SignerList Ledger Object ID.
|
||||
*
|
||||
* @param address The classic account address of the SignerList owner (starting with r)
|
||||
* @return {string} The ID of the account's SignerList object
|
||||
*/
|
||||
export const computeSignerListLedgerObjectID = (address: string): string => {
|
||||
return sha512Half(
|
||||
ledgerSpaceHex('signerList') + addressToHex(address) + '00000000'
|
||||
) // uint32(0) signer list index
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
/**
|
||||
* [Offer ID Format](https://xrpl.org/offer.html#offer-id-format)
|
||||
*
|
||||
* The ID of a Offer object is the SHA-512Half of the following values, concatenated in order:
|
||||
* * The Offer space key (0x006F)
|
||||
* * The AccountID of the account placing the offer
|
||||
* * The Sequence number of the OfferCreate transaction that created the offer
|
||||
*
|
||||
* This method computes an Offer ID (aka Order ID).
|
||||
*
|
||||
* @param address The classic account address of the SignerList owner (starting with r)
|
||||
* @returns {string} The ID of the account's Offer object
|
||||
*/
|
||||
export const computeOrderID = (address: string, sequence: number): string => {
|
||||
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
|
||||
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
* XRP Ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* The XRP Ledger is a key-value store. In order to avoid name collisions,
|
||||
* names are partitioned into namespaces.
|
||||
*
|
||||
* Each namespace is just a single character prefix.
|
||||
*
|
||||
* See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100)
|
||||
*/
|
||||
export default {
|
||||
account: 'a',
|
||||
@@ -16,9 +18,12 @@ export default {
|
||||
bookDir: 'B', // Directory of order books.
|
||||
contract: 'c',
|
||||
skipList: 's',
|
||||
escrow: 'u',
|
||||
amendment: 'f',
|
||||
feeSettings: 'e',
|
||||
ticket: 'T',
|
||||
signerList: 'S',
|
||||
escrow: 'u',
|
||||
paychan: 'x'
|
||||
paychan: 'x',
|
||||
check: 'C',
|
||||
depositPreauth: 'p'
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@ export {
|
||||
iso8601ToRippleTime,
|
||||
rippleTimeToISO8601
|
||||
} from './utils'
|
||||
export {default as Connection} from './connection'
|
||||
export {Connection} from './connection'
|
||||
export {txFlags} from './txflags'
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"description": "The entropy to use to generate the seed."
|
||||
"description": "The entropy to use to generate the seed. Must be an array of length 16 with values from 0-255 (16 bytes of entropy)"
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"description": "The entropy to use to generate the seed."
|
||||
"description": "The entropy to use to generate the seed. Must be an array of length 16 with values from 0-255 (16 bytes of entropy)"
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
@@ -24,6 +24,10 @@
|
||||
"test": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
|
||||
},
|
||||
"includeClassicAddress": {
|
||||
"type": "boolean",
|
||||
"description": "Specifies whether the classic address should also be included in the returned payload."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"signedTransaction": {
|
||||
"$ref": "blob",
|
||||
"description": "A signed transaction as returned by [sign](#sign)."
|
||||
},
|
||||
"failHard": {
|
||||
"type": "boolean",
|
||||
"description": "If `true`, and the transaction fails locally, do not retry or relay the transaction to other servers. Defaults to `false`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export interface SignerEntry {
|
||||
Account: string
|
||||
SignerWeight: number
|
||||
SignerEntry: {
|
||||
Account: string
|
||||
SignerWeight: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,18 @@ import {validate, constants, ensureClassicAddress} from '../common'
|
||||
import {FormattedSettings} from '../common/types/objects'
|
||||
import {AccountInfoResponse} from '../common/types/commands'
|
||||
import {RippleAPI} from '..'
|
||||
import {Settings} from '../common/constants'
|
||||
|
||||
const AccountFlags = constants.AccountFlags
|
||||
|
||||
export type SettingsOptions = {
|
||||
ledgerVersion?: number
|
||||
ledgerVersion?: number | 'validated' | 'closed' | 'current'
|
||||
}
|
||||
|
||||
export function parseAccountFlags(
|
||||
value: number,
|
||||
options: {excludeFalse?: boolean} = {}
|
||||
) {
|
||||
): Settings {
|
||||
const settings = {}
|
||||
for (const flagName in AccountFlags) {
|
||||
if (value & AccountFlags[flagName]) {
|
||||
|
||||
@@ -28,7 +28,16 @@ export interface GenerateAddressOptions {
|
||||
function generateAddressAPI(options: GenerateAddressOptions): GeneratedAddress {
|
||||
validate.generateAddress({options})
|
||||
try {
|
||||
const secret = keypairs.generateSeed(options)
|
||||
const generateSeedOptions: {
|
||||
entropy?: Uint8Array
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519'
|
||||
} = {
|
||||
algorithm: options.algorithm
|
||||
}
|
||||
if (options.entropy) {
|
||||
generateSeedOptions.entropy = Uint8Array.from(options.entropy)
|
||||
}
|
||||
const secret = keypairs.generateSeed(generateSeedOptions)
|
||||
const keypair = keypairs.deriveKeypair(secret)
|
||||
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
||||
const returnValue: any = {
|
||||
|
||||
@@ -7,7 +7,7 @@ function verifyPaymentChannelClaim(
|
||||
amount: string,
|
||||
signature: string,
|
||||
publicKey: string
|
||||
): string {
|
||||
): boolean {
|
||||
validate.verifyPaymentChannelClaim({channel, amount, signature, publicKey})
|
||||
|
||||
const signingData = binary.encodeForSigningClaim({
|
||||
|
||||
@@ -9,12 +9,14 @@ function getLedgerVersion(this: RippleAPI): Promise<number> {
|
||||
return this.connection.getLedgerVersion()
|
||||
}
|
||||
|
||||
function connect(this: RippleAPI): Promise<void> {
|
||||
async function connect(this: RippleAPI): Promise<void> {
|
||||
return this.connection.connect()
|
||||
}
|
||||
|
||||
function disconnect(this: RippleAPI): Promise<void> {
|
||||
return this.connection.disconnect()
|
||||
async function disconnect(this: RippleAPI): Promise<void> {
|
||||
// backwards compatibility: connection.disconnect() can return a number, but
|
||||
// this method returns nothing. SO we await but don't return any result.
|
||||
await this.connection.disconnect()
|
||||
}
|
||||
|
||||
function formatLedgerClose(ledgerClose: any): object {
|
||||
|
||||
@@ -87,7 +87,12 @@ function applyAnyCounterpartyEncoding(payment: Payment): void {
|
||||
|
||||
function createMaximalAmount(amount: Amount): Amount {
|
||||
const maxXRPValue = '100000000000'
|
||||
const maxIOUValue = '9999999999999999e80'
|
||||
|
||||
// Equivalent to '9999999999999999e80' but we cannot use that because sign()
|
||||
// now checks that the encoded representation exactly matches the transaction
|
||||
// as it was originally provided.
|
||||
const maxIOUValue = '999999999999999900000000000000000000000000000000000000000000000000000000000000000000000000000000'
|
||||
|
||||
let maxValue
|
||||
if (amount.currency === 'XRP') {
|
||||
maxValue = maxXRPValue
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// Deprecated - use api.request instead:
|
||||
// const response = await api.request('submit', {
|
||||
// tx_blob: signedTransaction,
|
||||
// fail_hard: failHard
|
||||
// });
|
||||
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
import {validate} from '../common'
|
||||
@@ -36,14 +42,19 @@ function formatSubmitResponse(response): FormattedSubmitResponse {
|
||||
return data
|
||||
}
|
||||
|
||||
// @deprecated Use api.request('submit', { tx_blob: signedTransaction }) instead
|
||||
async function submit(
|
||||
this: RippleAPI,
|
||||
signedTransaction: string
|
||||
signedTransaction: string,
|
||||
failHard?: boolean
|
||||
): Promise<FormattedSubmitResponse> {
|
||||
// 1. Validate
|
||||
validate.submit({signedTransaction})
|
||||
// 2. Make Request
|
||||
const response = await this.request('submit', {tx_blob: signedTransaction})
|
||||
const response = await this.request('submit', {
|
||||
tx_blob: signedTransaction,
|
||||
...(failHard ? {fail_hard: failHard} : {})
|
||||
})
|
||||
// 3. Return Formatted Response
|
||||
return formatSubmitResponse(response)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import {TestSuite} from '../../utils'
|
||||
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
|
||||
const {generateAddress: RESPONSE_FIXTURES} = responses
|
||||
|
||||
/**
|
||||
@@ -9,22 +10,231 @@ const {generateAddress: RESPONSE_FIXTURES} = responses
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateAddress': async (api, address) => {
|
||||
function random(): number[] {
|
||||
'generateAddress': async api => {
|
||||
// GIVEN entropy of all zeros
|
||||
function random() {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
// WHEN generating an address
|
||||
api.generateAddress({entropy: random()}),
|
||||
|
||||
// THEN we get the expected return value
|
||||
RESPONSE_FIXTURES
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress invalid': async (api, address) => {
|
||||
'generateAddress invalid entropy': async api => {
|
||||
assert.throws(() => {
|
||||
// GIVEN entropy of 1 byte
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
api.generateAddress({entropy: random()})
|
||||
|
||||
// THEN an UnexpectedError is thrown
|
||||
// because 16 bytes of entropy are required
|
||||
}, api.errors.UnexpectedError)
|
||||
},
|
||||
|
||||
'generateAddress with no options object': async api => {
|
||||
// GIVEN no options
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress()
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
|
||||
},
|
||||
|
||||
'generateAddress with empty options object': async api => {
|
||||
// GIVEN an empty options object
|
||||
const options = {}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1`': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's' (not 'sEd')
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
assert.notStrictEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`secp256k1 secret ${account.secret} must not start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519`': async api => {
|
||||
// GIVEN we want to use 'ed25519'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 'sEd'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`Ed25519 secret ${account.secret} must start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
const response = Object.assign({}, responses.generateAddress, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'TVG3TcCD58BD6MZqsNuTihdrhZwR8SzvYS8U87zvHsAcNw4'
|
||||
})
|
||||
assert.deepEqual(account, response)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
||||
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress for test network use': async api => {
|
||||
// GIVEN we want an address for test network use
|
||||
const options: GenerateAddressOptions = {test: true}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with xAddress starting with 'T' and a secret starting with 's'
|
||||
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
assert.deepEqual(
|
||||
account.xAddress.slice(0, 1),
|
||||
'T',
|
||||
'Test addresses start with T'
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import {TestSuite} from '../../utils'
|
||||
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
@@ -8,22 +9,231 @@ import {TestSuite} from '../../utils'
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateXAddress': async (api, address) => {
|
||||
'generateXAddress': async api => {
|
||||
// GIVEN entropy of all zeros
|
||||
function random() {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
// WHEN generating an X-address
|
||||
api.generateXAddress({entropy: random()}),
|
||||
|
||||
// THEN we get the expected return value
|
||||
responses.generateXAddress
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress invalid': async (api, address) => {
|
||||
'generateXAddress invalid entropy': async api => {
|
||||
assert.throws(() => {
|
||||
// GIVEN entropy of 1 byte
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
api.generateXAddress({entropy: random()})
|
||||
|
||||
// THEN an UnexpectedError is thrown
|
||||
// because 16 bytes of entropy are required
|
||||
}, api.errors.UnexpectedError)
|
||||
},
|
||||
|
||||
'generateXAddress with no options object': async api => {
|
||||
// GIVEN no options
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress()
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert(account.secret.startsWith('s'), 'Secrets start with s')
|
||||
},
|
||||
|
||||
'generateXAddress with empty options object': async api => {
|
||||
// GIVEN an empty options object
|
||||
const options = {}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert(account.secret.startsWith('s'), 'Secrets start with s')
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1`': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
assert.notStrictEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`secp256k1 secret ${account.secret} must not start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519`': async api => {
|
||||
// GIVEN we want to use 'ed25519'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 'sEd'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`Ed25519 secret ${account.secret} must start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateXAddress)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
const response = Object.assign({}, responses.generateAddress, {
|
||||
xAddress: 'TVG3TcCD58BD6MZqsNuTihdrhZwR8SzvYS8U87zvHsAcNw4'
|
||||
})
|
||||
assert.deepEqual(account, response)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress for test network use': async api => {
|
||||
// GIVEN we want an X-address for test network use
|
||||
const options: GenerateAddressOptions = {test: true}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with xAddress starting with 'T' and a secret starting with 's'
|
||||
assert.deepEqual(
|
||||
account.xAddress.slice(0, 1),
|
||||
'T',
|
||||
'Test X-addresses start with T'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ export default <TestSuite>{
|
||||
},
|
||||
|
||||
'getFee - high load_factor': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {highLoadFactor: true}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {highLoadFactor: true}
|
||||
})
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '2')
|
||||
},
|
||||
@@ -33,12 +31,10 @@ export default <TestSuite>{
|
||||
// Ensure that overriding with high maxFeeXRP of '51540' causes no errors.
|
||||
// (fee will actually be 51539.607552)
|
||||
api._maxFeeXRP = '51540'
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {highLoadFactor: true}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {highLoadFactor: true}
|
||||
})
|
||||
const fee = await api.getFee()
|
||||
assert.strictEqual(fee, '51539.607552')
|
||||
},
|
||||
|
||||
@@ -14,12 +14,10 @@ export default <TestSuite>{
|
||||
},
|
||||
|
||||
'error': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {returnErrorOnServerInfo: true}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {returnErrorOnServerInfo: true}
|
||||
})
|
||||
try {
|
||||
await api.getServerInfo()
|
||||
throw new Error('Should throw NetworkError')
|
||||
@@ -31,12 +29,10 @@ export default <TestSuite>{
|
||||
},
|
||||
|
||||
'no validated ledger': async (api, address) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {serverInfoWithoutValidated: true}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {serverInfoWithoutValidated: true}
|
||||
})
|
||||
const serverInfo = await api.getServerInfo()
|
||||
assert.strictEqual(serverInfo.networkLedger, 'waiting')
|
||||
},
|
||||
|
||||
@@ -355,7 +355,8 @@ export default <TestSuite>{
|
||||
},
|
||||
|
||||
'AccountDelete': async (api, address) => {
|
||||
const hash = 'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
|
||||
const hash =
|
||||
'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
|
||||
@@ -2,6 +2,11 @@ import {assertResultMatch, TestSuite, assertRejects} from '../../utils'
|
||||
import responses from '../../fixtures/responses'
|
||||
import requests from '../../fixtures/requests'
|
||||
import {ValidationError} from 'ripple-api/common/errors'
|
||||
import binary from 'ripple-binary-codec'
|
||||
import assert from 'assert-diff'
|
||||
import {RippleAPI} from 'ripple-api'
|
||||
|
||||
const {schemaValidator} = RippleAPI._PRIVATE
|
||||
const instructionsWithMaxLedgerVersionOffset = {maxLedgerVersionOffset: 100}
|
||||
const {preparePayment: REQUEST_FIXTURES} = requests
|
||||
const {preparePayment: RESPONSE_FIXTURES} = responses
|
||||
@@ -288,6 +293,55 @@ export default <TestSuite>{
|
||||
assertResultMatch(response, RESPONSE_FIXTURES.noCounterparty, 'prepare')
|
||||
},
|
||||
|
||||
'preparePayment with source.amount/destination.minAmount can be signed': async (api, address) => {
|
||||
// See also: 'sign succeeds with source.amount/destination.minAmount'
|
||||
|
||||
const localInstructions = {
|
||||
...instructionsWithMaxLedgerVersionOffset,
|
||||
sequence: 23
|
||||
}
|
||||
const response = await api.preparePayment(
|
||||
address,
|
||||
{
|
||||
"source": {
|
||||
address,
|
||||
"amount": {
|
||||
"currency": "GBP",
|
||||
"value": "0.1",
|
||||
"counterparty": "rpat5TmYjDsnFSStmgTumFgXCM9eqsWPro"
|
||||
}
|
||||
},
|
||||
"destination": {
|
||||
"address": "rEX4LtGJubaUcMWCJULcy4NVxGT9ZEMVRq",
|
||||
"minAmount": {
|
||||
"currency": "USD",
|
||||
"value": "0.1248548562296331",
|
||||
"counterparty": "rMaa8VLBTjwTJWA2kSme4Sqgphhr6Lr6FH"
|
||||
}
|
||||
}
|
||||
},
|
||||
localInstructions
|
||||
)
|
||||
|
||||
// Important: check that the prepared transaction can actually be signed
|
||||
// https://github.com/ripple/ripple-lib/issues/1237#issuecomment-631670946
|
||||
|
||||
const secret = 'shotKgaEotpcYsshSE39vmSnBDRim'
|
||||
const result = api.sign(response.txJSON, secret)
|
||||
const expectedResult = {
|
||||
signedTransaction:
|
||||
'12000022800200002400000017201B0086955361EC6386F26FC0FFFF0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A68400000000000000C69D4438D7EA4C6800000000000000000000000000047425000000000000C155FFE99C8C91F67083CEFFDB69EBFE76348CA6AD4446F8C5D8A5E0B0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A7321022B05847086686F9D0499B13136B94AD4323EE1B67D4C429ECC987AB35ACFA34574473045022100D9634523D8E232D4A7807A71856023D82AC928FA29848571B820867898413B5F022041AC00EC1F81A26A6504EBF844A38CC3204694EF2CC1A97A87632721631F93DA81145E7B112523F68D2F5E879DB4EAC51C6698A6930483149F500E50C2F016CA01945E5A1E5846B61EF2D376',
|
||||
id: '1C558AA9B926C24FB6BBD6950B2DB1350A83F9F12E4385208867907019761A2D'
|
||||
}
|
||||
const decoded = binary.decode(result.signedTransaction)
|
||||
assert(
|
||||
decoded.Flags === 2147614720,
|
||||
`Flags = ${decoded.Flags}, should be 2147614720`
|
||||
)
|
||||
assert.deepEqual(result, expectedResult)
|
||||
schemaValidator.schemaValidate('sign', result)
|
||||
},
|
||||
|
||||
'destination.minAmount': async (api, address) => {
|
||||
const response = await api.preparePayment(
|
||||
address,
|
||||
@@ -416,12 +470,10 @@ export default <TestSuite>{
|
||||
api,
|
||||
address
|
||||
) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {loadFactor: 5407.96875}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {loadFactor: 5407.96875}
|
||||
})
|
||||
const expectedResponse = {
|
||||
txJSON:
|
||||
'{"Flags":2147483648,"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Amount":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"SendMax":{"value":"0.01","currency":"USD","issuer":"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"},"LastLedgerSequence":8820051,"Fee":"64896","Sequence":23}',
|
||||
|
||||
@@ -1050,12 +1050,10 @@ export default <TestSuite>{
|
||||
api,
|
||||
address
|
||||
) => {
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {loadFactor: 5407.96875}
|
||||
})
|
||||
)
|
||||
api.connection.request({
|
||||
command: 'config',
|
||||
data: {loadFactor: 5407.96875}
|
||||
})
|
||||
|
||||
const expectedResponse = {
|
||||
txJSON:
|
||||
|
||||
@@ -4,6 +4,7 @@ import binary from 'ripple-binary-codec'
|
||||
import requests from '../../fixtures/requests'
|
||||
import responses from '../../fixtures/responses'
|
||||
import {TestSuite} from '../../utils'
|
||||
|
||||
const {schemaValidator} = RippleAPI._PRIVATE
|
||||
const {sign: REQUEST_FIXTURES} = requests
|
||||
const {sign: RESPONSE_FIXTURES} = responses
|
||||
@@ -145,6 +146,27 @@ export default <TestSuite>{
|
||||
schemaValidator.schemaValidate('sign', result)
|
||||
},
|
||||
|
||||
'sign succeeds with source.amount/destination.minAmount': async (api, address) => {
|
||||
// See also: 'preparePayment with source.amount/destination.minAmount'
|
||||
|
||||
const txJSON =
|
||||
'{"TransactionType":"Payment","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Destination":"rEX4LtGJubaUcMWCJULcy4NVxGT9ZEMVRq","Amount":{"currency":"USD","issuer":"rMaa8VLBTjwTJWA2kSme4Sqgphhr6Lr6FH","value":"999999999999999900000000000000000000000000000000000000000000000000000000000000000000000000000000"},"Flags":2147614720,"SendMax":{"currency":"GBP","issuer":"rpat5TmYjDsnFSStmgTumFgXCM9eqsWPro","value":"0.1"},"DeliverMin":{"currency":"USD","issuer":"rMaa8VLBTjwTJWA2kSme4Sqgphhr6Lr6FH","value":"0.1248548562296331"},"Sequence":23,"LastLedgerSequence":8820051,"Fee":"12"}'
|
||||
const secret = 'shotKgaEotpcYsshSE39vmSnBDRim'
|
||||
const result = api.sign(txJSON, secret)
|
||||
const expectedResult = {
|
||||
signedTransaction:
|
||||
'12000022800200002400000017201B0086955361EC6386F26FC0FFFF0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A68400000000000000C69D4438D7EA4C6800000000000000000000000000047425000000000000C155FFE99C8C91F67083CEFFDB69EBFE76348CA6AD4446F8C5D8A5E0B0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A7321022B05847086686F9D0499B13136B94AD4323EE1B67D4C429ECC987AB35ACFA34574473045022100D9634523D8E232D4A7807A71856023D82AC928FA29848571B820867898413B5F022041AC00EC1F81A26A6504EBF844A38CC3204694EF2CC1A97A87632721631F93DA81145E7B112523F68D2F5E879DB4EAC51C6698A6930483149F500E50C2F016CA01945E5A1E5846B61EF2D376',
|
||||
id: '1C558AA9B926C24FB6BBD6950B2DB1350A83F9F12E4385208867907019761A2D'
|
||||
}
|
||||
const decoded = binary.decode(result.signedTransaction)
|
||||
assert(
|
||||
decoded.Flags === 2147614720,
|
||||
`Flags = ${decoded.Flags}, should be 2147614720`
|
||||
)
|
||||
assert.deepEqual(result, expectedResult)
|
||||
schemaValidator.schemaValidate('sign', result)
|
||||
},
|
||||
|
||||
'throws when encoded tx does not match decoded tx - prepared payment': async (
|
||||
api,
|
||||
address
|
||||
|
||||
37
test/backoff-test.ts
Normal file
37
test/backoff-test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import assert from 'assert-diff'
|
||||
import {ExponentialBackoff} from '../src/common/backoff'
|
||||
|
||||
describe('ExponentialBackoff', function() {
|
||||
it('duration() return value starts with the min value', function() {
|
||||
// default: 100ms
|
||||
assert(new ExponentialBackoff().duration(), 100)
|
||||
assert(new ExponentialBackoff({min: 100}).duration(), 100)
|
||||
assert(new ExponentialBackoff({min: 123}).duration(), 123)
|
||||
})
|
||||
|
||||
it('duration() return value increases when called multiple times', function() {
|
||||
const backoff = new ExponentialBackoff({min: 100, max: 1000})
|
||||
assert.strictEqual(backoff.duration(), 100)
|
||||
assert.strictEqual(backoff.duration(), 200)
|
||||
assert.strictEqual(backoff.duration(), 400)
|
||||
assert.strictEqual(backoff.duration(), 800)
|
||||
})
|
||||
|
||||
it('duration() never returns greater than the max value', function() {
|
||||
const backoff = new ExponentialBackoff({min: 300, max: 1000})
|
||||
assert.strictEqual(backoff.duration(), 300)
|
||||
assert.strictEqual(backoff.duration(), 600)
|
||||
assert.strictEqual(backoff.duration(), 1000)
|
||||
assert.strictEqual(backoff.duration(), 1000)
|
||||
})
|
||||
|
||||
it('reset() will reset the duration() value', function() {
|
||||
const backoff = new ExponentialBackoff({min: 100, max: 1000})
|
||||
assert.strictEqual(backoff.duration(), 100)
|
||||
assert.strictEqual(backoff.duration(), 200)
|
||||
assert.strictEqual(backoff.duration(), 400)
|
||||
backoff.reset()
|
||||
assert.strictEqual(backoff.duration(), 100)
|
||||
assert.strictEqual(backoff.duration(), 200)
|
||||
})
|
||||
})
|
||||
@@ -4,6 +4,7 @@ import setupAPI from './setup-api'
|
||||
import responses from './fixtures/responses'
|
||||
import ledgerClosed from './fixtures/rippled/ledger-close.json'
|
||||
import {RippleAPI} from 'ripple-api'
|
||||
import {ignoreWebSocketDisconnect} from './utils'
|
||||
const schemaValidator = RippleAPI._PRIVATE.schemaValidator
|
||||
|
||||
const TIMEOUT = 20000
|
||||
@@ -43,12 +44,12 @@ describe('RippleAPIBroadcast', function() {
|
||||
ledgerNext.ledger_index++
|
||||
|
||||
this.api._apis.forEach(api =>
|
||||
api.connection._send(
|
||||
JSON.stringify({
|
||||
api.connection
|
||||
.request({
|
||||
command: 'echo',
|
||||
data: ledgerNext
|
||||
})
|
||||
)
|
||||
.catch(ignoreWebSocketDisconnect)
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -63,11 +64,11 @@ describe('RippleAPIBroadcast', function() {
|
||||
assert.strictEqual(info, 'info')
|
||||
done()
|
||||
})
|
||||
this.api._apis[1].connection._send(
|
||||
JSON.stringify({
|
||||
this.api._apis[1].connection
|
||||
.request({
|
||||
command: 'echo',
|
||||
data: {error: 'type', error_message: 'info'}
|
||||
})
|
||||
)
|
||||
.catch(ignoreWebSocketDisconnect)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import assert from 'assert-diff'
|
||||
import setupAPI from './setup-api'
|
||||
import {RippleAPI} from 'ripple-api'
|
||||
import ledgerClose from './fixtures/rippled/ledger-close.json'
|
||||
import {ignoreWebSocketDisconnect} from './utils'
|
||||
const utils = RippleAPI._PRIVATE.ledgerUtils
|
||||
|
||||
const TIMEOUT = 200000 // how long before each test case times out
|
||||
@@ -35,11 +36,12 @@ describe('Connection', function() {
|
||||
})
|
||||
|
||||
describe('trace', () => {
|
||||
const message1 = '{"type": "transaction"}'
|
||||
const message2 = '{"type": "path_find"}'
|
||||
const mockedRequestData = {mocked: 'request'}
|
||||
const mockedResponse = JSON.stringify({mocked: 'response', id: 0})
|
||||
const expectedMessages = [
|
||||
['send', message1],
|
||||
['receive', message2]
|
||||
// We add the ID here, since it's not a part of the user-provided request.
|
||||
['send', JSON.stringify({...mockedRequestData, id: 0})],
|
||||
['receive', mockedResponse]
|
||||
]
|
||||
const originalConsoleLog = console.log
|
||||
|
||||
@@ -52,8 +54,8 @@ describe('Connection', function() {
|
||||
console.log = (id, message) => messages.push([id, message])
|
||||
const connection: any = new utils.common.Connection('url', {trace: false})
|
||||
connection._ws = {send: function() {}}
|
||||
connection._send(message1)
|
||||
connection._onMessage(message2)
|
||||
connection.request(mockedRequestData)
|
||||
connection._onMessage(mockedResponse)
|
||||
assert.deepEqual(messages, [])
|
||||
})
|
||||
|
||||
@@ -62,8 +64,8 @@ describe('Connection', function() {
|
||||
console.log = (id, message) => messages.push([id, message])
|
||||
const connection: any = new utils.common.Connection('url', {trace: true})
|
||||
connection._ws = {send: function() {}}
|
||||
connection._send(message1)
|
||||
connection._onMessage(message2)
|
||||
connection.request(mockedRequestData)
|
||||
connection._onMessage(mockedResponse)
|
||||
assert.deepEqual(messages, expectedMessages)
|
||||
})
|
||||
|
||||
@@ -73,12 +75,32 @@ describe('Connection', function() {
|
||||
trace: (id, message) => messages.push([id, message])
|
||||
})
|
||||
connection._ws = {send: function() {}}
|
||||
connection._send(message1)
|
||||
connection._onMessage(message2)
|
||||
connection.request(mockedRequestData)
|
||||
connection._onMessage(mockedResponse)
|
||||
assert.deepEqual(messages, expectedMessages)
|
||||
})
|
||||
})
|
||||
|
||||
it('ledger methods work as expected', async function() {
|
||||
assert.strictEqual(await this.api.connection.getLedgerVersion(), 8819951)
|
||||
assert.strictEqual(
|
||||
await this.api.connection.hasLedgerVersion(8819951),
|
||||
true
|
||||
)
|
||||
assert.strictEqual(
|
||||
await this.api.connection.hasLedgerVersions(8819951, undefined),
|
||||
true
|
||||
)
|
||||
// It would be nice to test a better range, but the mocked ledger only supports this single number
|
||||
assert.strictEqual(
|
||||
await this.api.connection.hasLedgerVersions(8819951, 8819951),
|
||||
true
|
||||
)
|
||||
assert.strictEqual(await this.api.connection.getFeeBase(), 10)
|
||||
assert.strictEqual(await this.api.connection.getFeeRef(), 10)
|
||||
assert.strictEqual(await this.api.connection.getReserveBase(), 20000000) // 20 XRP
|
||||
})
|
||||
|
||||
it('with proxy', function(done) {
|
||||
if (isBrowser) {
|
||||
done()
|
||||
@@ -154,13 +176,11 @@ describe('Connection', function() {
|
||||
})
|
||||
})
|
||||
|
||||
it('DisconnectedError', function() {
|
||||
this.api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'config',
|
||||
data: {disconnectOnServerInfo: true}
|
||||
})
|
||||
)
|
||||
it('DisconnectedError', async function() {
|
||||
await this.api.connection.request({
|
||||
command: 'config',
|
||||
data: {disconnectOnServerInfo: true}
|
||||
})
|
||||
return this.api
|
||||
.getServerInfo()
|
||||
.then(() => {
|
||||
@@ -172,12 +192,12 @@ describe('Connection', function() {
|
||||
})
|
||||
|
||||
it('TimeoutError', function() {
|
||||
this.api.connection._send = function() {
|
||||
return Promise.resolve({})
|
||||
this.api.connection._ws.send = function(message, options, callback) {
|
||||
callback(null)
|
||||
}
|
||||
const request = {command: 'server_info'}
|
||||
return this.api.connection
|
||||
.request(request, 1)
|
||||
.request(request, 10)
|
||||
.then(() => {
|
||||
assert(false, 'Should throw TimeoutError')
|
||||
})
|
||||
@@ -204,44 +224,33 @@ describe('Connection', function() {
|
||||
it('DisconnectedError on initial _onOpen send', async function() {
|
||||
// _onOpen previously could throw PromiseRejectionHandledWarning: Promise rejection was handled asynchronously
|
||||
// do not rely on the api.setup hook to test this as it bypasses the case, disconnect api connection first
|
||||
await this.api.disconnect();
|
||||
await this.api.disconnect()
|
||||
|
||||
// stub _onOpen to only run logic relevant to test case
|
||||
this.api.connection._onOpen = () => {
|
||||
// overload websocket send on open when _ws exists
|
||||
this.api.connection._ws.send = function(data, options, cb) {
|
||||
// recent ws throws this error instead of calling back
|
||||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
|
||||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)')
|
||||
}
|
||||
const request = {command: 'subscribe', streams: ['ledger']};
|
||||
return this.api.connection.request(request);
|
||||
const request = {command: 'subscribe', streams: ['ledger']}
|
||||
return this.api.connection.request(request)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.api.connect();
|
||||
await this.api.connect()
|
||||
} catch (error) {
|
||||
assert(error instanceof this.api.errors.DisconnectedError);
|
||||
assert.strictEqual(error.message, 'WebSocket is not open: readyState 0 (CONNECTING)');
|
||||
assert(error instanceof this.api.errors.DisconnectedError)
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'WebSocket is not open: readyState 0 (CONNECTING)'
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it('ResponseFormatError', function() {
|
||||
this.api.connection._send = function(message) {
|
||||
const parsed = JSON.parse(message)
|
||||
setTimeout(() => {
|
||||
this._ws.emit(
|
||||
'message',
|
||||
JSON.stringify({
|
||||
id: parsed.id,
|
||||
type: 'response',
|
||||
status: 'unrecognized'
|
||||
})
|
||||
)
|
||||
}, 2)
|
||||
return new Promise(() => {})
|
||||
}
|
||||
return this.api
|
||||
.getServerInfo()
|
||||
.request('test_command', {data: {unrecognizedResponse: true}})
|
||||
.then(() => {
|
||||
assert(false, 'Should throw ResponseFormatError')
|
||||
})
|
||||
@@ -250,34 +259,16 @@ describe('Connection', function() {
|
||||
})
|
||||
})
|
||||
|
||||
it('reconnect on unexpected close ', function(done) {
|
||||
it('reconnect on unexpected close', function(done) {
|
||||
this.api.connection.on('connected', () => {
|
||||
done()
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.api.connection._ws.close()
|
||||
}, 1)
|
||||
})
|
||||
|
||||
describe('reconnection test', function() {
|
||||
beforeEach(function() {
|
||||
this.api.connection.__workingUrl = this.api.connection._url
|
||||
this.api.connection.__doReturnBad = function() {
|
||||
this._url = this.__badUrl
|
||||
const self = this
|
||||
function onReconnect(num) {
|
||||
if (num >= 2) {
|
||||
self._url = self.__workingUrl
|
||||
self.removeListener('reconnecting', onReconnect)
|
||||
}
|
||||
}
|
||||
this.on('reconnecting', onReconnect)
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(function() {})
|
||||
|
||||
it('reconnect on several unexpected close', function(done) {
|
||||
if (isBrowser) {
|
||||
const phantomTest = /PhantomJS/
|
||||
@@ -289,15 +280,13 @@ describe('Connection', function() {
|
||||
}
|
||||
this.timeout(70001)
|
||||
const self = this
|
||||
self.api.connection.__badUrl = 'ws://testripple.circleci.com:129'
|
||||
function breakConnection() {
|
||||
self.api.connection.__doReturnBad()
|
||||
self.api.connection._send(
|
||||
JSON.stringify({
|
||||
self.api.connection
|
||||
.request({
|
||||
command: 'test_command',
|
||||
data: {disconnectIn: 10}
|
||||
})
|
||||
)
|
||||
.catch(ignoreWebSocketDisconnect)
|
||||
}
|
||||
|
||||
let connectsCount = 0
|
||||
@@ -328,11 +317,11 @@ describe('Connection', function() {
|
||||
' instead)'
|
||||
)
|
||||
)
|
||||
} else if (reconnectsCount !== num * 2) {
|
||||
} else if (reconnectsCount !== num) {
|
||||
done(
|
||||
new Error(
|
||||
'reconnectsCount must be equal to ' +
|
||||
num * 2 +
|
||||
num +
|
||||
' (got ' +
|
||||
reconnectsCount +
|
||||
' instead)'
|
||||
@@ -370,6 +359,36 @@ describe('Connection', function() {
|
||||
// Hook up a listener for the reconnect event
|
||||
this.api.connection.on('reconnect', () => done())
|
||||
// Trigger a heartbeat
|
||||
this.api.connection._heartbeat().catch(error => {
|
||||
/* ignore - test expects heartbeat failure */
|
||||
})
|
||||
})
|
||||
|
||||
it('heartbeat failure and reconnect failure', function(done) {
|
||||
if (isBrowser) {
|
||||
const phantomTest = /PhantomJS/
|
||||
if (phantomTest.test(navigator.userAgent)) {
|
||||
// inside PhantomJS this one just hangs, so skip as not very relevant
|
||||
done()
|
||||
return
|
||||
}
|
||||
}
|
||||
// Set the heartbeat to less than the 1 second ping response
|
||||
this.api.connection._config.timeout = 500
|
||||
// Drop the test runner timeout, since this should be a quick test
|
||||
this.timeout(5000)
|
||||
// fail on reconnect/connection
|
||||
this.api.connection.reconnect = async () => {
|
||||
throw new Error('error on reconnect')
|
||||
}
|
||||
// Hook up a listener for the reconnect error event
|
||||
this.api.on('error', (error, message) => {
|
||||
if (error === 'reconnect' && message === 'error on reconnect') {
|
||||
return done()
|
||||
}
|
||||
return done(new Error('Expected error on reconnect'))
|
||||
})
|
||||
// Trigger a heartbeat
|
||||
this.api.connection._heartbeat()
|
||||
})
|
||||
|
||||
@@ -382,19 +401,19 @@ describe('Connection', function() {
|
||||
})
|
||||
|
||||
it('should emit disconnected event with code 1006 (CLOSE_ABNORMAL)', function(done) {
|
||||
this.api.once('error', error => {
|
||||
this.api.connection.once('error', error => {
|
||||
done(new Error('should not throw error, got ' + String(error)))
|
||||
})
|
||||
this.api.once('disconnected', code => {
|
||||
this.api.connection.once('disconnected', code => {
|
||||
assert.strictEqual(code, 1006)
|
||||
done()
|
||||
})
|
||||
this.api.connection._send(
|
||||
JSON.stringify({
|
||||
this.api.connection
|
||||
.request({
|
||||
command: 'test_command',
|
||||
data: {disconnectIn: 10}
|
||||
})
|
||||
)
|
||||
.catch(ignoreWebSocketDisconnect)
|
||||
})
|
||||
|
||||
it('should emit connected event on after reconnect', function(done) {
|
||||
@@ -455,7 +474,8 @@ describe('Connection', function() {
|
||||
this.api.connection.on('path_find', () => {
|
||||
pathFindCount++
|
||||
})
|
||||
this.api.connection.on('1', () => {
|
||||
this.api.connection.on('response', message => {
|
||||
assert.strictEqual(message.id, 1)
|
||||
assert.strictEqual(transactionCount, 1)
|
||||
assert.strictEqual(pathFindCount, 1)
|
||||
done()
|
||||
@@ -550,15 +570,13 @@ describe('Connection', function() {
|
||||
it(
|
||||
'should throw RippledNotInitializedError if server does not have ' +
|
||||
'validated ledgers',
|
||||
function() {
|
||||
async function() {
|
||||
this.timeout(3000)
|
||||
|
||||
this.api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'global_config',
|
||||
data: {returnEmptySubscribeRequest: 1}
|
||||
})
|
||||
)
|
||||
await this.api.connection.request({
|
||||
command: 'global_config',
|
||||
data: {returnEmptySubscribeRequest: 1}
|
||||
})
|
||||
|
||||
const api = new RippleAPI({server: this.api.connection._url})
|
||||
return api.connect().then(
|
||||
@@ -576,9 +594,26 @@ describe('Connection', function() {
|
||||
}
|
||||
)
|
||||
|
||||
it('should clean up websocket connection if error after websocket is opened', async function() {
|
||||
await this.api.disconnect()
|
||||
// fail on connection
|
||||
this.api.connection._subscribeToLedger = async () => {
|
||||
throw new Error('error on _subscribeToLedger')
|
||||
}
|
||||
try {
|
||||
await this.api.connect()
|
||||
throw new Error('expected connect() to reject, but it resolved')
|
||||
} catch (err) {
|
||||
assert(err.message === 'error on _subscribeToLedger')
|
||||
// _ws.close event listener should have cleaned up the socket when disconnect _ws.close is run on connection error
|
||||
// do not fail on connection anymore
|
||||
this.api.connection._subscribeToLedger = async () => {}
|
||||
await this.api.connection.reconnect()
|
||||
}
|
||||
})
|
||||
|
||||
it('should try to reconnect on empty subscribe response on reconnect', function(done) {
|
||||
this.timeout(23000)
|
||||
|
||||
this.api.on('error', error => {
|
||||
done(error || new Error('Should not emit error.'))
|
||||
})
|
||||
@@ -593,19 +628,9 @@ describe('Connection', function() {
|
||||
this.api.on('disconnected', () => {
|
||||
disconnectedCount++
|
||||
})
|
||||
|
||||
this.api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'global_config',
|
||||
data: {returnEmptySubscribeRequest: 3}
|
||||
})
|
||||
)
|
||||
|
||||
this.api.connection._send(
|
||||
JSON.stringify({
|
||||
command: 'test_command',
|
||||
data: {disconnectIn: 10}
|
||||
})
|
||||
)
|
||||
this.api.connection.request({
|
||||
command: 'test_command',
|
||||
data: {disconnectIn: 5}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"4.93463759481038\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
|
||||
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"999999999999999900000000000000000000000000000000000000000000000000000000000000000000000000000000\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"4.93463759481038\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
|
||||
"instructions": {
|
||||
"fee": "0.000012",
|
||||
"sequence": 23,
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Ledger', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
||||
var expectedEntryHash =
|
||||
'2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8'
|
||||
var actualEntryHash = hashes.computeAccountHash(account)
|
||||
var actualEntryHash = hashes.computeAccountLedgerObjectID(account)
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
@@ -105,18 +105,18 @@ describe('Ledger', function() {
|
||||
var sequence = 137
|
||||
var expectedEntryHash =
|
||||
'03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3'
|
||||
var actualEntryHash = hashes.computeOrderHash(account, sequence)
|
||||
var actualEntryHash = hashes.computeOrderID(account, sequence)
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeSignerListHash', function() {
|
||||
describe('computeSignerListLedgerObjectID', function() {
|
||||
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
||||
var expectedEntryHash =
|
||||
'778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF'
|
||||
var actualEntryHash = hashes.computeSignerListHash(account)
|
||||
var actualEntryHash = hashes.computeSignerListLedgerObjectID(account)
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Connection from '../../src/common/connection'
|
||||
import {Connection} from '../../src/common/connection'
|
||||
|
||||
const request1 = {
|
||||
command: 'server_info'
|
||||
|
||||
@@ -119,12 +119,26 @@ export function createMockRippled(port) {
|
||||
mock.on('request_config', function(request, conn) {
|
||||
assert.strictEqual(request.command, 'config')
|
||||
conn.config = _.assign(conn.config, request.data)
|
||||
conn.send(
|
||||
createResponse(request, {
|
||||
status: 'success',
|
||||
type: 'response',
|
||||
result: {}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
mock.on('request_test_command', function(request, conn) {
|
||||
assert.strictEqual(request.command, 'test_command')
|
||||
if (request.data.disconnectIn) {
|
||||
setTimeout(conn.terminate.bind(conn), request.data.disconnectIn)
|
||||
conn.send(
|
||||
createResponse(request, {
|
||||
status: 'success',
|
||||
type: 'response',
|
||||
result: {}
|
||||
})
|
||||
)
|
||||
} else if (request.data.openOnOtherPort) {
|
||||
getFreePort().then(newPort => {
|
||||
createMockRippled(newPort)
|
||||
@@ -145,12 +159,27 @@ export function createMockRippled(port) {
|
||||
}, request.data.closeServerAndReopen)
|
||||
})
|
||||
}, 10)
|
||||
} else if (request.data.unrecognizedResponse) {
|
||||
conn.send(
|
||||
createResponse(request, {
|
||||
status: 'unrecognized',
|
||||
type: 'response',
|
||||
result: {}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
mock.on('request_global_config', function(request, conn) {
|
||||
assert.strictEqual(request.command, 'global_config')
|
||||
mock.config = _.assign(conn.config, request.data)
|
||||
conn.send(
|
||||
createResponse(request, {
|
||||
status: 'success',
|
||||
type: 'response',
|
||||
result: {}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
mock.on('request_echo', function(request, conn) {
|
||||
|
||||
@@ -32,21 +32,33 @@ describe('RippleAPI [Test Runner]', function() {
|
||||
// Run all the tests:
|
||||
for (const {name: methodName, tests, config} of allTestSuites) {
|
||||
describe(`api.${methodName}`, () => {
|
||||
// Run each test with the original-style address.
|
||||
describe(`[Original Address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
// Run each test that does not use an address.
|
||||
for (const [testName, fn] of tests) {
|
||||
if (fn.length === 1) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT)
|
||||
})
|
||||
}
|
||||
}
|
||||
// Run each test with a classic address.
|
||||
describe(`[Classic Address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
if (fn.length === 2) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Run each test with the newer, x-address style.
|
||||
// Run each test with an X-address.
|
||||
if (!config.skipXAddress) {
|
||||
describe(`[X-address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT_X)
|
||||
})
|
||||
if (fn.length === 2) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT_X)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -139,3 +139,15 @@ export function loadTestSuites(): LoadedTestSuite[] {
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore WebSocket DisconnectErrors. Useful for making requests where we don't
|
||||
* care about the response and plan to teardown the test before the response
|
||||
* has come back.
|
||||
*/
|
||||
export function ignoreWebSocketDisconnect(error: Error): void {
|
||||
if (error.message === 'websocket was closed') {
|
||||
return
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"moduleResolution": "node",
|
||||
|
||||
"declaration": true,
|
||||
"declarationMap": true /* Added 2019-04-13 */,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
|
||||
Reference in New Issue
Block a user