mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 21:15:47 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8278dc5b5b | ||
|
|
c90d486454 | ||
|
|
35f9b7ec8d | ||
|
|
69e621af86 | ||
|
|
0e92e696d4 | ||
|
|
0b1445bfe9 | ||
|
|
db2d7ba1f5 | ||
|
|
d82703f41b | ||
|
|
8213861ab7 | ||
|
|
a842c380cf | ||
|
|
5bf6f1849a | ||
|
|
bfe4877f73 | ||
|
|
63dcddf6f4 | ||
|
|
68735ddb35 | ||
|
|
1fd9ca7ef2 | ||
|
|
2445004333 | ||
|
|
dc148bf954 | ||
|
|
f3c34bd75a | ||
|
|
5419e67dbc | ||
|
|
8d37da0952 | ||
|
|
a8075d98df |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
# .gitignore
|
||||
|
||||
# Ignore package locks other than Yarn.
|
||||
package-lock.json
|
||||
npm-shrinkwrap.json
|
||||
|
||||
# Ignore vim swap files.
|
||||
*.swp
|
||||
|
||||
|
||||
119
APPLICATIONS.md
Normal file
119
APPLICATIONS.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Applications using ripple-lib (RippleAPI)
|
||||
|
||||
A curated list of some of the projects and apps that leverage `ripple-lib` in some way.
|
||||
|
||||
**Have one to add?** Please edit this file and open a PR!
|
||||
|
||||
## Notice (disclaimer)
|
||||
|
||||
These sites are independent of Ripple and have not been authorized, endorsed, sponsored or otherwise approved by Ripple or its affiliates.
|
||||
|
||||
Warning: Use at your own risk.
|
||||
|
||||
## Data and visualizations
|
||||
|
||||
- **[Wipple - XRP Intelligence](https://wipple.devnull.network/)**
|
||||
|
||||
Monitor the XRP Network in real time and explore historical statistics.
|
||||
|
||||
- **[XRP Charts](https://xrpcharts.ripple.com/)** (xrpcharts.ripple.com)
|
||||
|
||||
XRP Charts provides information based on public data, including trade volume, top markets, metrics, transactions, and more.
|
||||
|
||||
- **[Ripple Live](https://gatehub.net/live)** (gatehub.net/live)
|
||||
|
||||
Visualize XRP network transactions.
|
||||
|
||||
- **[XRPL Dev. Dashboard](https://xrp.fans/)** (xrp.fans)
|
||||
|
||||
Debugging dashboard for `rippled-ws-client-pool`, transaction and query explorer, and transaction signing and submission tool.
|
||||
|
||||
- **[XRP Value](http://xrpvalue.com/)**
|
||||
|
||||
Real-time XRP price, trades, and orderbook data from the XRP Ledger.
|
||||
|
||||
- **[Bithomp - XRPL validators](https://bithomp.com/validators)**
|
||||
|
||||
List of XRPL validators, nodes, and testnet validators.
|
||||
|
||||
## 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.
|
||||
|
||||
## Wallets and wallet tools
|
||||
|
||||
- **[Toast Wallet](https://toastwallet.com/)**
|
||||
|
||||
A free, open source XRP Wallet for iOS, Android, Windows, Mac and Linux.
|
||||
|
||||
- **[Toastify Ledger](https://github.com/WietseWind/toastify-ledger)** (uses `ripple-keypairs`)
|
||||
|
||||
Add a Regular Key to a mnemonic XRP Wallet (e.g. Ledger Nano S) to use the account with a Family Seed (secret).
|
||||
|
||||
- **[Bithomp-submit](https://github.com/Bithomp/bithomp-submit)** (GitHub)
|
||||
|
||||
A tool to submit an offline-signed XRPL transaction.
|
||||
|
||||
- **[Kyte](https://kyteapp.co/)** (kyteapp.co) ([Source](https://github.com/WietseWind/Zerp-Wallet)) (Deprecated)
|
||||
|
||||
Web-based XRP wallet.
|
||||
|
||||
- **[XRP Vanity Address Generator](https://github.com/WietseWind/xrp-vanity-generator)** (Node.js)
|
||||
|
||||
A vanity address is a wallet address containing a few characters you like at the beginning or the end of the wallet address.
|
||||
|
||||
- **[XRP Account Mnemonic Recovery](https://github.com/WietseWind/xrp-mnemonic-recovery)** (uses `ripple-keypairs`)
|
||||
|
||||
Recover a 24 word mnemonic if one word is wrong or one word is missing.
|
||||
|
||||
## Development tools
|
||||
|
||||
- **[XRP Test Net Faucet](https://developers.ripple.com/xrp-test-net-faucet.html)**
|
||||
|
||||
Get some test funds for development on the test network. The faucet was built using `ripple-lib`.
|
||||
|
||||
## Code samples and libraries
|
||||
|
||||
- **[ilp-plugin-xrp-paychan](https://github.com/interledgerjs/ilp-plugin-xrp-paychan)**
|
||||
|
||||
Send ILP payments using XRP and payment channels (PayChan).
|
||||
|
||||
- **[RunKit: WietseWind](https://runkit.com/wietsewind/)**
|
||||
|
||||
XRP Ledger code samples for Node.js.
|
||||
|
||||
- **[GitHub Gist: WietseWind](https://gist.github.com/WietseWind)**
|
||||
|
||||
XRP Ledger code samples for Node.js and the web (mostly).
|
||||
|
||||
- **[rippled-ws-client-sign](https://github.com/WietseWind/rippled-ws-client-sign)**
|
||||
|
||||
Sign transactions, with support for MultiSign.
|
||||
|
||||
- **[ILP-enabled power switch](https://xrpcommunity.blog/raspberry-pi-interledger-xp-powerswitch-howto/)** ([video](https://www.youtube.com/watch?v=c-eS0HQUuJg)) (uses [`moneyd-uplink-xrp`](https://github.com/interledgerjs/moneyd-uplink-xrp))
|
||||
|
||||
For about $30 in parts (Raspberry Pi, 3.3V Relay board and a few wires) you can build your own power switch that will switch on if a streaming ILP payment comes in. When the payment stream stops, the power turns off.
|
||||
|
||||
## Related apps that do not appear to use ripple-lib
|
||||
|
||||
- **[XRP Stats](https://ledger.exposed/)** (ledger.exposed)
|
||||
|
||||
Rich list, live ledger stats and XRP distribution. Visualize escrows and flow of funds.
|
||||
|
||||
- **[XRP Vanity](https://xrpvanity.com/)** (xrpvanity.com)
|
||||
|
||||
Custom XRP addresses for sale, delivered by SetRegularKey.
|
||||
160
HISTORY.md
160
HISTORY.md
@@ -1,5 +1,165 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
## 1.2.1 (2019-03-23)
|
||||
|
||||
* Update `ripple-binary-codec` to 0.2.1 to support `tecKILLED`
|
||||
|
||||
The SHA-256 checksums for the browser version of this release can be found
|
||||
below.
|
||||
|
||||
```
|
||||
% shasum -a 256 *
|
||||
531c2a8f4bf6d6b5bd4afe6a40b6a68a77179a343902cfa4210d7e35b5697af0 ripple-1.2.1-debug.js
|
||||
201ee99922b16b7e32afb5317ef4bb9facc23b20c272bb5c4ed7010f5d996cab ripple-1.2.1-min.js
|
||||
c1b984581299bf00e0e3c8ac4e62eadfc9b190bd78a2458a76e59ceb56046148 ripple-1.2.1.js
|
||||
```
|
||||
|
||||
## 1.2.0 (2019-03-19)
|
||||
|
||||
This release:
|
||||
|
||||
* changes the way you handle errors for the `prepare*` methods.
|
||||
* improves the `message` field of `RippledError`s.
|
||||
* allows `Sequence` to be set in the transaction JSON provided to
|
||||
`prepareTransaction`.
|
||||
|
||||
For details, continue reading:
|
||||
|
||||
### [BREAKING CHANGE] `prepare*` methods reject the Promise on error
|
||||
|
||||
The `prepare*` methods now always reject the Promise when an error occurs, instead of throwing.
|
||||
|
||||
Previously, the methods would synchronously throw on validation errors, despite being asynchronous methods that return Promises.
|
||||
|
||||
In other words, to handle errors in the past, you would need to use a try/catch block:
|
||||
|
||||
```
|
||||
// OBSOLETE - no need for try/catch anymore
|
||||
try {
|
||||
api.preparePayment(address, payment, instructions).then(prepared => {
|
||||
res.send(prepared.txJSON);
|
||||
}).catch(error => {
|
||||
// Handle asynchronous error
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle synchronous error
|
||||
}
|
||||
```
|
||||
|
||||
Now, you can rely on the Promise's `catch` handler, which is called with the error when the Promise is rejected:
|
||||
|
||||
```
|
||||
api.preparePayment(address, payment, instructions).then(prepared => {
|
||||
res.send(prepared.txJSON);
|
||||
}).catch(error => {
|
||||
// Handle error
|
||||
});
|
||||
```
|
||||
|
||||
This applies to:
|
||||
* preparePayment
|
||||
* prepareTrustline
|
||||
* prepareOrder
|
||||
* prepareOrderCancellation
|
||||
* prepareSettings
|
||||
* prepareEscrowCreation
|
||||
* prepareEscrowExecution
|
||||
* prepareCheckCreate
|
||||
* prepareCheckCash
|
||||
* prepareCheckCancel
|
||||
* preparePaymentChannelCreate
|
||||
* preparePaymentChannelClaim
|
||||
* preparePaymentChannelFund
|
||||
|
||||
### Improved `RippledError` `message`
|
||||
|
||||
Previously, `RippledErrors` (errors from rippled) used rippled's `error` field as the `message`.
|
||||
|
||||
Now, the `error_message` field is used as the `message`.
|
||||
|
||||
This helps to surface the specific cause of an error.
|
||||
|
||||
For example, before:
|
||||
```
|
||||
[RippledError(invalidParams, { error: 'invalidParams',
|
||||
error_code: 31,
|
||||
error_message: 'Missing field \'account\'.',
|
||||
id: 3,
|
||||
request: { command: 'account_info', id: 3 },
|
||||
status: 'error',
|
||||
type: 'response' })]
|
||||
```
|
||||
|
||||
After:
|
||||
```
|
||||
[RippledError(Missing field 'account'., { error: 'invalidParams',
|
||||
error_code: 31,
|
||||
error_message: 'Missing field \'account\'.',
|
||||
id: 3,
|
||||
request: { command: 'account_info', id: 3 },
|
||||
status: 'error',
|
||||
type: 'response' })]
|
||||
```
|
||||
|
||||
In this case, you can see at a glance that `account` is the missing field.
|
||||
|
||||
The `error` field is still available in `errorObject.data.error`.
|
||||
|
||||
When `error_message` is not set (as with e.g. error 'entryNotFound'), the `error` field is used as the `message`.
|
||||
|
||||
### [BUG FIX] `prepareTransaction` does not overwrite the `Sequence` field
|
||||
|
||||
The `prepareTransaction` method now allows `Sequence` to be set in the Transaction JSON object, instead of overwriting it with the account's expected sequence based on the state of the ledger.
|
||||
|
||||
Previously, you had to use the `sequence` field in the `instructions` object to manually set a transaction's sequence number.
|
||||
|
||||
### New in rippled 1.2.1
|
||||
|
||||
As this is the first release of ripple-lib following the release of rippled 1.2.1, we would like to highlight the following API improvements:
|
||||
|
||||
1. The [`delivered_amount` field](https://developers.ripple.com/partial-payments.html#the-delivered-amount-field) has been added to the `ledger` method, and to transaction subscriptions.
|
||||
|
||||
api.getLedger({includeTransactions: true, includeAllData: true, ledgerVersion: 17718771}).then(...)
|
||||
|
||||
You can also call `ledger` directly:
|
||||
|
||||
request('ledger', {...}).then(...)
|
||||
|
||||
2. [Support for Ed25519 seeds encoded using ripple-lib](https://github.com/ripple/rippled/pull/2734)
|
||||
|
||||
You have access to these improvements when you use a rippled server running version 1.2.1 or later. At the time of writing, we recommend using rippled version **1.2.2** or later.
|
||||
|
||||
The SHA-256 checksums for the browser version of this release can be found
|
||||
below.
|
||||
```
|
||||
% shasum -a 256 *
|
||||
13021fe3efbdd59faf68597b0b18204b39847b285cca82f84c737e3d19922cc2 ripple-1.2.0-debug.js
|
||||
0070225e731afd8c2c0a0976111ebf326c19a96ee1549368de9f016abdd53d2f ripple-1.2.0-min.js
|
||||
d440268397c03ad5137a3294e53a07b959ef93cd23b1990d6f82621c4776ba9f ripple-1.2.0.js
|
||||
```
|
||||
|
||||
## 1.1.2 (2018-12-12)
|
||||
|
||||
+ Update `submit` response (#978)
|
||||
+ Includes the full object returned by rippled, while keeping the existing
|
||||
fields for backward compatibility
|
||||
+ Add `getLedger` option for ledger hash (#980)
|
||||
+ Use the `ledgerHash` option to get a specific ledger by hash
|
||||
|
||||
Thanks to @alexchiriac for the contributions in this release.
|
||||
|
||||
When using `ripple-lib` with `rippled`, we recommend using `rippled` version
|
||||
1.1.2 or later.
|
||||
|
||||
The SHA-256 checksums for the browser version of this release can be found
|
||||
below.
|
||||
```
|
||||
% shasum -a 256 *
|
||||
e6cc52395d0c3e205263777ba2e528e50f4d1f84bb4b16763a3bf7f5fcc290f5 ripple-1.1.2-debug.js
|
||||
82df879bc2970e0e4fd161975a99448b4859b0cde751d8ea34e9f51d672090b9 ripple-1.1.2-min.js
|
||||
12f56330dc71bba8ac3004025cbc9698413a0c619df302dda105b31228a67319 ripple-1.1.2.js
|
||||
```
|
||||
|
||||
## 1.1.1 (2018-11-27)
|
||||
|
||||
+ Fix `getOrderbook` offer sorting (#970)
|
||||
|
||||
12
README.md
12
README.md
@@ -7,8 +7,8 @@ A JavaScript API for interacting with the XRP Ledger
|
||||
### Features
|
||||
|
||||
+ Connect to a `rippled` server from Node.js or a web browser
|
||||
+ Issue [rippled API](https://ripple.com/build/rippled-apis/) requests
|
||||
+ Listen to events on the XRP Ledger (transaction, ledger, etc.)
|
||||
+ Helpers for creating requests and parsing responses for the [rippled API](https://developers.ripple.com/rippled-api.html)
|
||||
+ Listen to events on the XRP Ledger (transactions, ledger, validations, etc.)
|
||||
+ Sign and submit transactions to the XRP Ledger
|
||||
+ Type definitions for TypeScript
|
||||
|
||||
@@ -30,6 +30,8 @@ $ yarn add ripple-lib
|
||||
|
||||
Then see the [documentation](https://github.com/ripple/ripple-lib/blob/develop/docs/index.md) and [code samples](https://github.com/ripple/ripple-lib/tree/develop/docs/samples).
|
||||
|
||||
**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!
|
||||
|
||||
### Mailing Lists
|
||||
|
||||
We have a low-traffic mailing list for announcements of new ripple-lib releases. (About 1 email per week)
|
||||
@@ -56,7 +58,7 @@ $ yarn build
|
||||
|
||||
Gulp will [output](./Gulpfile.js) the resulting JS files in `./build/`.
|
||||
|
||||
For more details, see the `scripts` in `package.json`.
|
||||
For details, see the `scripts` in `package.json`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
@@ -64,12 +66,12 @@ For more details, see the `scripts` in `package.json`.
|
||||
2. `cd` into the repository and install dependencies with `yarn install`
|
||||
3. `yarn test`
|
||||
|
||||
Also, run `yarn lint` to lint the code with `tslint`.
|
||||
|
||||
## Generating Documentation
|
||||
|
||||
The continuous integration tests require that the documentation stays up-to-date. If you make changes to the JSON schemas, fixtures, or documentation sources, you must update the documentation by running `yarn run docgen`.
|
||||
|
||||
`npm` may be used instead of `yarn` in the commands above.
|
||||
|
||||
## More Information
|
||||
|
||||
+ [Ripple Developer Center](https://ripple.com/build/)
|
||||
|
||||
@@ -293,7 +293,7 @@ Type | Description
|
||||
[escrowCancellation](#escrow-cancellation) | An `escrowCancellation` transaction unlocks the funds in an escrow and sends them back to the creator of the escrow, but it will only work after the escrow expires.
|
||||
[escrowExecution](#escrow-execution) | An `escrowExecution` transaction unlocks the funds in an escrow and sends them to the destination of the escrow, but it will only work if the cryptographic condition is provided.
|
||||
[checkCreate](#check-create) | A `checkCreate` transaction creates a check on the ledger, which is a deferred payment that can be cashed by its intended destination.
|
||||
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unreedemed Check, removing it from the ledger without sending any money.
|
||||
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unredeemed Check, removing it from the ledger without sending any money.
|
||||
[checkCash](#check-cash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
|
||||
[paymentChannelCreate](#payment-channel-create) | A `paymentChannelCreate` transaction opens a payment channel between two addresses with XRP set aside for asynchronous payments.
|
||||
[paymentChannelFund](#payment-channel-fund) | A `paymentChannelFund` transaction adds XRP to a payment channel and optionally sets a new expiration for the channel.
|
||||
@@ -339,7 +339,7 @@ maxLedgerVersionOffset | integer | *Optional* Offset from current validated ledg
|
||||
sequence | [sequence](#account-sequence-number) | *Optional* The initiating account's sequence number for this transaction.
|
||||
signersCount | integer | *Optional* Number of signers that will be signing this transaction.
|
||||
|
||||
We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
|
||||
We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
|
||||
|
||||
## Transaction ID
|
||||
|
||||
@@ -1494,7 +1494,7 @@ sequence | [sequence](#account-sequence-number) | The account sequence number of
|
||||
type | [transactionType](#transaction-types) | The type of the transaction.
|
||||
specification | object | A specification that would produce the same outcome as this transaction. *Exception:* For payment transactions, this omits the `destination.amount` field, to prevent misunderstanding. The structure of the specification depends on the value of the `type` field (see [Transaction Types](#transaction-types) for details). *Note:* This is **not** necessarily the same as the original specification.
|
||||
outcome | object | The outcome of the transaction (what effects it had).
|
||||
*outcome.* result | string | Result code returned by rippled. See [Transaction Results](https://ripple.com/build/transactions/#full-transaction-response-list) for a complete list.
|
||||
*outcome.* result | string | Result code returned by rippled. See [Transaction Results](https://developers.ripple.com/transaction-results.html) for a complete list.
|
||||
*outcome.* fee | [value](#value) | The XRP fee that was charged for the transaction.
|
||||
*outcome.balanceChanges.* \* | array\<[balance](#amount)\> | Key is the XRP Ledger address; value is an array of signed amounts representing changes of balances for that address.
|
||||
*outcome.orderbookChanges.* \* | array | Key is the maker's XRP Ledger address; value is an array of changes
|
||||
@@ -4387,6 +4387,7 @@ options | object | *Optional* Options affecting what ledger and how much data to
|
||||
*options.* includeAllData | boolean | *Optional* Include full transactions and/or state information if `includeTransactions` and/or `includeState` is set.
|
||||
*options.* includeState | boolean | *Optional* Return an array of hashes for all state data or an array of all state data in this ledger version, depending on whether `includeAllData` is set.
|
||||
*options.* includeTransactions | boolean | *Optional* Return an array of hashes for each transaction or an array of all transactions that were validated in this ledger version, depending on whether `includeAllData` is set.
|
||||
*options.* ledgerHash | string | *Optional* Get ledger data for this historical ledger hash.
|
||||
*options.* ledgerVersion | integer | *Optional* Get ledger data for this historical ledger version.
|
||||
*options.* ledgerVersion | string | *Optional* Get ledger data for this historical ledger version.
|
||||
|
||||
@@ -4474,7 +4475,7 @@ console.log(JSON.stringify(flags, null, 2))
|
||||
|
||||
## prepareTransaction
|
||||
|
||||
`prepareTransaction(address: string, transaction: object, instructions: object): Promise<object>`
|
||||
`prepareTransaction(transaction: object, instructions: object): Promise<object>`
|
||||
|
||||
Prepare a transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
|
||||
|
||||
@@ -4583,8 +4584,11 @@ const payment = {
|
||||
}
|
||||
}
|
||||
};
|
||||
return api.preparePayment(address, payment).then(prepared =>
|
||||
{/* ... */});
|
||||
return api.preparePayment(address, payment).then(prepared => {
|
||||
/* ... */
|
||||
}).catch(error => {
|
||||
/* ... as with all prepare* methods, use a Promise catch block to handle errors ... */
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -5481,8 +5485,13 @@ This method returns an object with the following structure:
|
||||
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
resultCode | string | The result code returned by rippled. [List of transaction responses](https://ripple.com/build/transactions/#full-transaction-response-list)
|
||||
resultMessage | string | Human-readable explanation of the status of the transaction.
|
||||
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.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -5496,7 +5505,27 @@ return api.submit(signedTransaction)
|
||||
```json
|
||||
{
|
||||
"resultCode": "tesSUCCESS",
|
||||
"resultMessage": "The transaction was applied. Only final in a validated ledger."
|
||||
"resultMessage": "The transaction was applied. Only final in a validated ledger.",
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -23,8 +23,11 @@ All "prepare*" methods have the same return type.
|
||||
```javascript
|
||||
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
|
||||
const payment = <%- importFile('test/fixtures/requests/prepare-payment.json') %>;
|
||||
return api.preparePayment(address, payment).then(prepared =>
|
||||
{/* ... */});
|
||||
return api.preparePayment(address, payment).then(prepared => {
|
||||
/* ... */
|
||||
}).catch(error => {
|
||||
/* ... as with all prepare* methods, use a Promise catch block to handle errors ... */
|
||||
})
|
||||
```
|
||||
|
||||
<%- renderFixture("responses/prepare-payment.json") %>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## prepareTransaction
|
||||
|
||||
`prepareTransaction(address: string, transaction: object, instructions: object): Promise<object>`
|
||||
`prepareTransaction(transaction: object, instructions: object): Promise<object>`
|
||||
|
||||
Prepare a transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ Type | Description
|
||||
[escrowCancellation](#escrow-cancellation) | An `escrowCancellation` transaction unlocks the funds in an escrow and sends them back to the creator of the escrow, but it will only work after the escrow expires.
|
||||
[escrowExecution](#escrow-execution) | An `escrowExecution` transaction unlocks the funds in an escrow and sends them to the destination of the escrow, but it will only work if the cryptographic condition is provided.
|
||||
[checkCreate](#check-create) | A `checkCreate` transaction creates a check on the ledger, which is a deferred payment that can be cashed by its intended destination.
|
||||
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unreedemed Check, removing it from the ledger without sending any money.
|
||||
[checkCancel](#check-cancel) | A `checkCancel` transaction cancels an unredeemed Check, removing it from the ledger without sending any money.
|
||||
[checkCash](#check-cash) | A `checkCash` transaction redeems a Check to receive up to the amount authorized by the corresponding `checkCreate` transaction. Only the `destination` address of a Check can cash it.
|
||||
[paymentChannelCreate](#payment-channel-create) | A `paymentChannelCreate` transaction opens a payment channel between two addresses with XRP set aside for asynchronous payments.
|
||||
[paymentChannelFund](#payment-channel-fund) | A `paymentChannelFund` transaction adds XRP to a payment channel and optionally sets a new expiration for the channel.
|
||||
@@ -53,7 +53,7 @@ Transaction instructions indicate how to execute a transaction, complementary wi
|
||||
|
||||
<%- renderSchema("objects/instructions.json") %>
|
||||
|
||||
We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
|
||||
We recommend that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeed in the future. It is impossible for a transaction to succeed after the XRP Ledger's consensus-validated ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare\*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare\*" method.
|
||||
|
||||
## Transaction ID
|
||||
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -23,8 +23,8 @@
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^2.0.1",
|
||||
"ripple-binary-codec": "0.2.0",
|
||||
"ripple-hashes": "^0.3.1",
|
||||
"ripple-binary-codec": "0.2.1",
|
||||
"ripple-hashes": "0.3.2",
|
||||
"ripple-keypairs": "^0.10.1",
|
||||
"ripple-lib-transactionparser": "0.7.1",
|
||||
"ws": "^3.3.1"
|
||||
@@ -32,19 +32,16 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^8.0.53",
|
||||
"assert-diff": "^1.0.1",
|
||||
"coveralls": "^2.13.1",
|
||||
"doctoc": "^0.15.0",
|
||||
"ejs": "^2.3.4",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"gulp": "^3.8.10",
|
||||
"gulp-bump": "^0.1.13",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"http-server": "^0.8.5",
|
||||
"jayson": "^1.2.2",
|
||||
"json-loader": "^0.5.2",
|
||||
"json-schema-to-markdown-table": "^0.4.0",
|
||||
"mocha": "^2.1.0",
|
||||
"mocha-in-sauce": "^0.0.1",
|
||||
"mocha": "6.0.2",
|
||||
"mocha-junit-reporter": "^1.9.1",
|
||||
"null-loader": "^0.1.1",
|
||||
"nyc": "^11.3.0",
|
||||
@@ -66,8 +63,7 @@
|
||||
"compile": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc",
|
||||
"watch": "tsc -w",
|
||||
"prepublish": "npm run clean && npm run compile && npm run build",
|
||||
"test": "nyc mocha",
|
||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||
"test": "nyc mocha --exit",
|
||||
"lint": "tslint -p ./",
|
||||
"perf": "./scripts/perf_test.sh",
|
||||
"start": "node scripts/http.js",
|
||||
|
||||
@@ -16,7 +16,7 @@ unittest() {
|
||||
# test "src"
|
||||
mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
|
||||
yarn test --coverage
|
||||
yarn run coveralls
|
||||
#yarn run coveralls
|
||||
|
||||
# test compiled version in "dist/npm"
|
||||
$(npm bin)/babel -D --optional runtime --ignore "**/node_modules/**" -d test-compiled/ test/
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
|
||||
const _ = require('lodash');
|
||||
const MochaSauce = require('mocha-in-sauce');
|
||||
|
||||
const testUrl = 'http://testripple.circleci.com:8080/test/saucerunner.html';
|
||||
|
||||
|
||||
function main() {
|
||||
// uncomment for more debug info
|
||||
// process.env.DEBUG = '*';
|
||||
|
||||
// configure
|
||||
const config = {
|
||||
name: 'RippleAPI',
|
||||
host: 'localhost',
|
||||
port: 4445,
|
||||
maxDuration: 180000,
|
||||
// the current build name (optional)
|
||||
build: Date.now(),
|
||||
url: testUrl,
|
||||
runSauceConnect: true
|
||||
};
|
||||
|
||||
if (process.env.CIRCLE_BUILD_NUM) {
|
||||
config.build = process.env.CIRCLE_BUILD_NUM;
|
||||
config.tags = [process.env.CIRCLE_BRANCH, process.env.CIRCLE_SHA1];
|
||||
config.tunnelIdentifier = process.env.CIRCLE_BUILD_NUM;
|
||||
}
|
||||
|
||||
const sauce = new MochaSauce(config);
|
||||
|
||||
sauce.concurrency(5);
|
||||
|
||||
// setup what browsers to test with
|
||||
sauce.browser({browserName: 'firefox', platform: 'Linux',
|
||||
version: '43'});
|
||||
sauce.browser({browserName: 'firefox', platform: 'Windows 8.1',
|
||||
version: '43'});
|
||||
sauce.browser({browserName: 'firefox', platform: 'OS X 10.11',
|
||||
version: '43'});
|
||||
sauce.browser({browserName: 'safari', platform: 'OS X 10.11',
|
||||
version: '9'});
|
||||
sauce.browser({browserName: 'safari', platform: 'OS X 10.10',
|
||||
version: '8'});
|
||||
sauce.browser({browserName: 'safari', platform: 'OS X 10.9',
|
||||
version: '7'});
|
||||
sauce.browser({browserName: 'chrome', platform: 'OS X 10.11',
|
||||
version: '47'});
|
||||
sauce.browser({browserName: 'chrome', platform: 'Linux',
|
||||
version: '47'});
|
||||
sauce.browser({browserName: 'chrome', platform: 'Windows 8.1',
|
||||
version: '47'});
|
||||
sauce.browser({browserName: 'internet explorer', platform: 'Windows 10',
|
||||
version: '11'});
|
||||
sauce.browser({browserName: 'MicrosoftEdge', platform: 'Windows 10',
|
||||
version: '20'});
|
||||
|
||||
sauce.on('init', function(browser) {
|
||||
console.log(' init : %s %s', browser.browserName, browser.platform);
|
||||
});
|
||||
|
||||
sauce.on('start', function(browser) {
|
||||
console.log(' start : %s %s', browser.browserName, browser.platform);
|
||||
});
|
||||
|
||||
sauce.on('end', function(browser, res) {
|
||||
console.log(' end : %s %s : %d failures', browser.browserName,
|
||||
browser.platform, res && res.failures);
|
||||
});
|
||||
|
||||
sauce.on('connected', sauceConnectProcess => {
|
||||
sauceConnectProcess.on('exit', function(code, /* signal */) {
|
||||
if (code > 0) {
|
||||
console.log('something wrong - exiting');
|
||||
process.exit();
|
||||
} else {
|
||||
console.log('normal tunnel exit');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
sauce.start(function(err, res) {
|
||||
let failure = false;
|
||||
if (err) {
|
||||
console.log('Error starting Sauce');
|
||||
console.error(err);
|
||||
process.exitCode = 2;
|
||||
} else {
|
||||
console.log('-------------- done --------------');
|
||||
failure = _.some(res, 'failures');
|
||||
console.log('Tests are failed:', failure);
|
||||
if (failure) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -71,7 +71,7 @@ import * as transactionUtils from './transaction/utils'
|
||||
import * as schemaValidator from './common/schema-validator'
|
||||
import {getServerInfo, getFee} from './common/serverinfo'
|
||||
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {Instructions, Prepare} from './transaction/types'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
|
||||
export type APIOptions = {
|
||||
server?: string,
|
||||
@@ -210,7 +210,7 @@ class RippleAPI extends EventEmitter {
|
||||
*
|
||||
* You can later submit the transaction with `submit()`.
|
||||
*/
|
||||
async prepareTransaction(txJSON: object, instructions: Instructions = {}):
|
||||
async prepareTransaction(txJSON: TransactionJSON, instructions: Instructions = {}):
|
||||
Promise<Prepare> {
|
||||
return transactionUtils.prepareTransaction(txJSON, this, instructions)
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ class Connection extends EventEmitter {
|
||||
options.agent = new HttpsProxyAgent(proxyOptions)
|
||||
}
|
||||
if (this._authorization !== undefined) {
|
||||
const base64 = new Buffer(this._authorization).toString('base64')
|
||||
const base64 = Buffer.from(this._authorization).toString('base64')
|
||||
options.headers = {Authorization: `Basic ${base64}`}
|
||||
}
|
||||
const optionsOverrides = _.omitBy({
|
||||
@@ -445,7 +445,7 @@ class Connection extends EventEmitter {
|
||||
|
||||
this.once(eventName, response => {
|
||||
if (response.status === 'error') {
|
||||
_reject(new RippledError(response.error, response))
|
||||
_reject(new RippledError(response.error_message || response.error, response))
|
||||
} else if (response.status === 'success') {
|
||||
_resolve(response.result)
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
"options": {
|
||||
"description": "Options affecting what ledger and how much data to return.",
|
||||
"properties": {
|
||||
"ledgerHash": {
|
||||
"type": "string",
|
||||
"description": "Get ledger data for this historical ledger hash."
|
||||
},
|
||||
"ledgerVersion": {
|
||||
"$ref": "ledgerVersion",
|
||||
"description": "Get ledger data for this historical ledger version."
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "tx",
|
||||
"link": "https://ripple.com/build/transactions/",
|
||||
"title": "tx-json",
|
||||
"link": "https://developers.ripple.com/transaction-formats.html",
|
||||
"description": "An object in rippled txJSON format",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Account": {"$ref": "address"}
|
||||
"Account": {"$ref": "address"},
|
||||
"TransactionType": {"type": "string"}
|
||||
},
|
||||
"required": ["Account"]
|
||||
"required": ["Account", "TransactionType"]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"properties": {
|
||||
"result": {
|
||||
"type": "string",
|
||||
"description": "Result code returned by rippled. See [Transaction Results](https://ripple.com/build/transactions/#full-transaction-response-list) for a complete list."
|
||||
"description": "Result code returned by rippled. See [Transaction Results](https://developers.ripple.com/transaction-results.html) for a complete list."
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
|
||||
@@ -5,13 +5,33 @@
|
||||
"properties": {
|
||||
"resultCode": {
|
||||
"type": "string",
|
||||
"description": "The result code returned by rippled. [List of transaction responses](https://ripple.com/build/transactions/#full-transaction-response-list)"
|
||||
"description": "Deprecated: Use `engine_result` instead."
|
||||
},
|
||||
"resultMessage": {
|
||||
"type": "string",
|
||||
"description": "Human-readable explanation of the status of the transaction."
|
||||
"description": "Deprecated: Use `engine_result_message` instead."
|
||||
},
|
||||
"engine_result": {
|
||||
"type": "string",
|
||||
"description": "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": {
|
||||
"type": "integer",
|
||||
"description": "Numeric code indicating the preliminary result of the transaction, directly correlated to `engine_result`"
|
||||
},
|
||||
"engine_result_message": {
|
||||
"type": "string",
|
||||
"description": "Human-readable explanation of the transaction's preliminary result."
|
||||
},
|
||||
"tx_blob": {
|
||||
"type": "string",
|
||||
"description": "The complete transaction in hex string format."
|
||||
},
|
||||
"tx_json": {
|
||||
"$ref": "tx-json",
|
||||
"description": "The complete transaction in JSON format."
|
||||
}
|
||||
},
|
||||
"required": ["resultCode", "resultMessage"],
|
||||
"required": ["resultCode", "resultMessage", "engine_result", "engine_result_code", "engine_result_message", "tx_blob", "tx_json"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
||||
@@ -18,5 +18,6 @@ export interface Ledger {
|
||||
hash?: string,
|
||||
close_flags?: number,
|
||||
parent_close_time?: number,
|
||||
accountState?: any[]
|
||||
accountState?: any[],
|
||||
validated?: boolean
|
||||
}
|
||||
|
||||
@@ -125,7 +125,6 @@ function removeUndefined<T extends object>(obj: T): T {
|
||||
/**
|
||||
* @param {Number} rpepoch (seconds since 1/1/2000 GMT)
|
||||
* @return {Number} ms since unix epoch
|
||||
*
|
||||
*/
|
||||
function rippleToUnixTimestamp(rpepoch: number): number {
|
||||
return (rpepoch + 0x386D4380) * 1000
|
||||
|
||||
@@ -124,3 +124,6 @@ _.partial(schemaValidate, 'api-options')
|
||||
|
||||
export const instructions =
|
||||
_.partial(schemaValidate, 'instructions')
|
||||
|
||||
export const tx_json =
|
||||
_.partial(schemaValidate, 'tx-json')
|
||||
|
||||
@@ -3,6 +3,7 @@ import {FormattedLedger, parseLedger} from './parse/ledger'
|
||||
import {RippleAPI} from '../api'
|
||||
|
||||
export type GetLedgerOptions = {
|
||||
ledgerHash?: string,
|
||||
ledgerVersion?: number,
|
||||
includeAllData?: boolean,
|
||||
includeTransactions?: boolean,
|
||||
@@ -16,6 +17,7 @@ async function getLedger(
|
||||
validate.getLedger({options})
|
||||
// 2. Make Request
|
||||
const response = await this.request('ledger', {
|
||||
ledger_hash: options.ledgerHash,
|
||||
ledger_index: options.ledgerVersion || 'validated',
|
||||
expand: options.includeAllData,
|
||||
transactions: options.includeTransactions,
|
||||
|
||||
@@ -5,7 +5,7 @@ const AccountFields = constants.AccountFields
|
||||
|
||||
function parseField(info, value) {
|
||||
if (info.encoding === 'hex' && !info.length) { // e.g. "domain"
|
||||
return new Buffer(value, 'hex').toString('ascii')
|
||||
return Buffer.from(value, 'hex').toString('ascii')
|
||||
}
|
||||
if (info.shift) {
|
||||
return (new BigNumber(value)).shift(-info.shift).toNumber()
|
||||
|
||||
@@ -59,6 +59,11 @@ function parseState(state) {
|
||||
return {rawState: JSON.stringify(state)}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Ledger} ledger must be a *closed* ledger with valid `close_time` and `parent_close_time`
|
||||
* @returns {FormattedLedger} formatted ledger
|
||||
* @throws RangeError: Invalid time value (rippleTimeToISO8601)
|
||||
*/
|
||||
export function parseLedger(ledger: Ledger): FormattedLedger {
|
||||
const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10)
|
||||
return removeUndefined(Object.assign(
|
||||
|
||||
@@ -4,6 +4,7 @@ import {PayChannelLedgerEntry} from '../../common/types/objects'
|
||||
|
||||
export type FormattedPaymentChannel = {
|
||||
account: string,
|
||||
amount: string,
|
||||
balance: string,
|
||||
publicKey: string,
|
||||
destination: string,
|
||||
|
||||
@@ -122,7 +122,7 @@ function parseOutcome(tx: any): any|undefined {
|
||||
}
|
||||
|
||||
function hexToString(hex: string): string|undefined {
|
||||
return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined
|
||||
return hex ? Buffer.from(hex, 'hex').toString('utf-8') : undefined
|
||||
}
|
||||
|
||||
function parseMemos(tx: any): Array<Memo>|undefined {
|
||||
|
||||
@@ -46,9 +46,7 @@ function requestPathFind(connection: Connection, pathfind: PathFind
|
||||
&& !request.destination_amount.issuer) {
|
||||
// Convert blank issuer to sender's address
|
||||
// (Ripple convention for 'any issuer')
|
||||
// https://ripple.com/build/transactions/
|
||||
// #special-issuer-values-for-sendmax-and-amount
|
||||
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
|
||||
// https://developers.ripple.com/payment.html#special-issuer-values-for-sendmax-and-amount
|
||||
request.destination_amount.issuer = request.destination_account
|
||||
}
|
||||
if (pathfind.source.currencies && pathfind.source.currencies.length > 0) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import parseTransaction from './parse/transaction'
|
||||
import {validate, errors} from '../common'
|
||||
import {Connection} from '../common'
|
||||
import {FormattedTransactionType} from '../transaction/types'
|
||||
import {RippledError} from '../common/errors'
|
||||
|
||||
export type TransactionOptions = {
|
||||
minLedgerVersion?: number,
|
||||
@@ -59,10 +60,16 @@ function isTransactionInRange(tx: any, options: TransactionOptions) {
|
||||
}
|
||||
|
||||
function convertError(connection: Connection, options: TransactionOptions,
|
||||
error: Error
|
||||
error: RippledError
|
||||
): Promise<Error> {
|
||||
const _error = (error.message === 'txnNotFound') ?
|
||||
new errors.NotFoundError('Transaction not found') : error
|
||||
let shouldUseNotFoundError = false
|
||||
if ((error.data && error.data.error === 'txnNotFound') || error.message === 'txnNotFound') {
|
||||
shouldUseNotFoundError = true
|
||||
}
|
||||
|
||||
// In the future, we should deprecate this error, instead passing through the one from rippled.
|
||||
const _error = shouldUseNotFoundError ? new errors.NotFoundError('Transaction not found') : error
|
||||
|
||||
if (_error instanceof errors.NotFoundError) {
|
||||
return utils.hasCompleteLedgerRange(connection, options.minLedgerVersion,
|
||||
options.maxLedgerVersion).then(hasCompleteLedgerRange => {
|
||||
|
||||
@@ -76,7 +76,7 @@ function signum(num) {
|
||||
* Order two rippled transactions based on their ledger_index.
|
||||
* If two transactions took place in the same ledger, sort
|
||||
* them based on TransactionIndex
|
||||
* See: https://ripple.com/build/transactions/
|
||||
* See: https://developers.ripple.com/transaction-metadata.html
|
||||
*/
|
||||
function compareTransactions(
|
||||
first: FormattedTransactionType, second: FormattedTransactionType
|
||||
|
||||
@@ -4,4 +4,3 @@ export {
|
||||
deriveKeypair,
|
||||
deriveAddress
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import * as utils from './utils'
|
||||
import {TransactionJSON, prepareTransaction} from './utils'
|
||||
import {validate} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
|
||||
export type CheckCancel = {
|
||||
export type CheckCancelParameters = {
|
||||
checkID: string
|
||||
}
|
||||
|
||||
function createCheckCancelTransaction(account: string,
|
||||
cancel: CheckCancel
|
||||
): object {
|
||||
cancel: CheckCancelParameters
|
||||
): TransactionJSON {
|
||||
const txJSON = {
|
||||
Account: account,
|
||||
TransactionType: 'CheckCancel',
|
||||
@@ -19,14 +19,18 @@ function createCheckCancelTransaction(account: string,
|
||||
}
|
||||
|
||||
function prepareCheckCancel(address: string,
|
||||
checkCancel: CheckCancel,
|
||||
checkCancel: CheckCancelParameters,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareCheckCancel(
|
||||
{address, checkCancel, instructions})
|
||||
const txJSON = createCheckCancelTransaction(
|
||||
address, checkCancel)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareCheckCancel(
|
||||
{address, checkCancel, instructions})
|
||||
const txJSON = createCheckCancelTransaction(
|
||||
address, checkCancel)
|
||||
return prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareCheckCancel
|
||||
|
||||
@@ -2,18 +2,18 @@ import * as utils from './utils'
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
const toRippledAmount = utils.common.toRippledAmount
|
||||
import {validate} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Amount} from '../common/types/objects'
|
||||
|
||||
export type CheckCash = {
|
||||
export type CheckCashParameters = {
|
||||
checkID: string,
|
||||
amount?: Amount,
|
||||
deliverMin?: Amount
|
||||
}
|
||||
|
||||
function createCheckCashTransaction(account: string,
|
||||
checkCash: CheckCash
|
||||
): object {
|
||||
checkCash: CheckCashParameters
|
||||
): TransactionJSON {
|
||||
if (checkCash.amount && checkCash.deliverMin) {
|
||||
throw new ValidationError('"amount" and "deliverMin" properties on '
|
||||
+ 'CheckCash are mutually exclusive')
|
||||
@@ -37,14 +37,18 @@ function createCheckCashTransaction(account: string,
|
||||
}
|
||||
|
||||
function prepareCheckCash(address: string,
|
||||
checkCash: CheckCash,
|
||||
checkCash: CheckCashParameters,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareCheckCash(
|
||||
{address, checkCash, instructions})
|
||||
const txJSON = createCheckCashTransaction(
|
||||
address, checkCash)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareCheckCash(
|
||||
{address, checkCash, instructions})
|
||||
const txJSON = createCheckCashTransaction(
|
||||
address, checkCash)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareCheckCash
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as utils from './utils'
|
||||
const toRippledAmount = utils.common.toRippledAmount
|
||||
import {validate, iso8601ToRippleTime} from '../common'
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Amount} from '../common/types/objects'
|
||||
|
||||
export type CheckCreate = {
|
||||
export type CheckCreateParameters = {
|
||||
destination: string,
|
||||
sendMax: Amount,
|
||||
destinationTag?: number,
|
||||
@@ -13,8 +13,8 @@ export type CheckCreate = {
|
||||
}
|
||||
|
||||
function createCheckCreateTransaction(account: string,
|
||||
check: CheckCreate
|
||||
): object {
|
||||
check: CheckCreateParameters
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
Account: account,
|
||||
TransactionType: 'CheckCreate',
|
||||
@@ -38,14 +38,18 @@ function createCheckCreateTransaction(account: string,
|
||||
}
|
||||
|
||||
function prepareCheckCreate(address: string,
|
||||
checkCreate: CheckCreate,
|
||||
checkCreate: CheckCreateParameters,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareCheckCreate(
|
||||
{address, checkCreate, instructions})
|
||||
const txJSON = createCheckCreateTransaction(
|
||||
address, checkCreate)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareCheckCreate(
|
||||
{address, checkCreate, instructions})
|
||||
const txJSON = createCheckCreateTransaction(
|
||||
address, checkCreate)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareCheckCreate
|
||||
|
||||
@@ -7,7 +7,7 @@ import {validate} from '../common'
|
||||
import {computeBinaryTransactionHash} from 'ripple-hashes'
|
||||
|
||||
function addressToBigNumber(address) {
|
||||
const hex = (new Buffer(decodeAddress(address))).toString('hex')
|
||||
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
return new BigNumber(hex, 16)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Memo} from '../common/types/objects'
|
||||
|
||||
export type EscrowCancellation = {
|
||||
owner: string,
|
||||
escrowSequence: number,
|
||||
|
||||
// TODO: This ripple-lib memo format should be deprecated in favor of rippled's format.
|
||||
// If necessary, expose a public method for converting between the two formats.
|
||||
memos?: Array<Memo>
|
||||
}
|
||||
|
||||
function createEscrowCancellationTransaction(account: string,
|
||||
payment: EscrowCancellation
|
||||
): object {
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
TransactionType: 'EscrowCancel',
|
||||
Account: account,
|
||||
@@ -20,7 +22,7 @@ function createEscrowCancellationTransaction(account: string,
|
||||
OfferSequence: payment.escrowSequence
|
||||
}
|
||||
if (payment.memos !== undefined) {
|
||||
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
|
||||
txJSON.Memos = payment.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
import {validate, iso8601ToRippleTime, xrpToDrops} from '../common'
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Memo} from '../common/types/objects'
|
||||
|
||||
export type EscrowCreation = {
|
||||
@@ -18,7 +17,7 @@ export type EscrowCreation = {
|
||||
|
||||
function createEscrowCreationTransaction(account: string,
|
||||
payment: EscrowCreation
|
||||
): object {
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
TransactionType: 'EscrowCreate',
|
||||
Account: account,
|
||||
@@ -42,7 +41,7 @@ function createEscrowCreationTransaction(account: string,
|
||||
txJSON.DestinationTag = payment.destinationTag
|
||||
}
|
||||
if (payment.memos !== undefined) {
|
||||
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
|
||||
txJSON.Memos = payment.memos.map(utils.convertMemo)
|
||||
}
|
||||
if (Boolean(payment.allowCancelAfter) && Boolean(payment.allowExecuteAfter) &&
|
||||
txJSON.CancelAfter <= txJSON.FinishAfter) {
|
||||
@@ -56,11 +55,15 @@ function prepareEscrowCreation(address: string,
|
||||
escrowCreation: EscrowCreation,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareEscrowCreation(
|
||||
{address, escrowCreation, instructions})
|
||||
const txJSON = createEscrowCreationTransaction(
|
||||
address, escrowCreation)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareEscrowCreation(
|
||||
{address, escrowCreation, instructions})
|
||||
const txJSON = createEscrowCreationTransaction(
|
||||
address, escrowCreation)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareEscrowCreation
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
@@ -15,7 +14,7 @@ export type EscrowExecution = {
|
||||
|
||||
function createEscrowExecutionTransaction(account: string,
|
||||
payment: EscrowExecution
|
||||
): object {
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: any = {
|
||||
TransactionType: 'EscrowFinish',
|
||||
Account: account,
|
||||
@@ -35,7 +34,7 @@ function createEscrowExecutionTransaction(account: string,
|
||||
txJSON.Fulfillment = payment.fulfillment
|
||||
}
|
||||
if (payment.memos !== undefined) {
|
||||
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
|
||||
txJSON.Memos = payment.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON
|
||||
}
|
||||
@@ -44,11 +43,15 @@ function prepareEscrowExecution(address: string,
|
||||
escrowExecution: EscrowExecution,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareEscrowExecution(
|
||||
{address, escrowExecution, instructions})
|
||||
const txJSON = createEscrowExecutionTransaction(
|
||||
address, escrowExecution)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareEscrowExecution(
|
||||
{address, escrowExecution, instructions})
|
||||
const txJSON = createEscrowExecutionTransaction(
|
||||
address, escrowExecution)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareEscrowExecution
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
const offerFlags = utils.common.txFlags.OfferCreate
|
||||
import {validate, iso8601ToRippleTime} from '../common'
|
||||
@@ -39,7 +38,7 @@ function createOrderTransaction(
|
||||
txJSON.OfferSequence = order.orderToReplace
|
||||
}
|
||||
if (order.memos !== undefined) {
|
||||
txJSON.Memos = _.map(order.memos, utils.convertMemo)
|
||||
txJSON.Memos = order.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON as OfferCreateTransaction
|
||||
}
|
||||
@@ -47,9 +46,13 @@ function createOrderTransaction(
|
||||
function prepareOrder(address: string, order: FormattedOrderSpecification,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareOrder({address, order, instructions})
|
||||
const txJSON = createOrderTransaction(address, order)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareOrder({address, order, instructions})
|
||||
const txJSON = createOrderTransaction(address, order)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareOrder
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
|
||||
function createOrderCancellationTransaction(account: string,
|
||||
orderCancellation: any
|
||||
): object {
|
||||
): TransactionJSON {
|
||||
const txJSON: any = {
|
||||
TransactionType: 'OfferCancel',
|
||||
Account: account,
|
||||
OfferSequence: orderCancellation.orderSequence
|
||||
}
|
||||
if (orderCancellation.memos !== undefined) {
|
||||
txJSON.Memos = _.map(orderCancellation.memos, utils.convertMemo)
|
||||
txJSON.Memos = orderCancellation.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON
|
||||
}
|
||||
@@ -20,9 +19,13 @@ function createOrderCancellationTransaction(account: string,
|
||||
function prepareOrderCancellation(address: string, orderCancellation: object,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareOrderCancellation({address, orderCancellation, instructions})
|
||||
const txJSON = createOrderCancellationTransaction(address, orderCancellation)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareOrderCancellation({address, orderCancellation, instructions})
|
||||
const txJSON = createOrderCancellationTransaction(address, orderCancellation)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareOrderCancellation
|
||||
|
||||
@@ -16,8 +16,8 @@ export type PaymentChannelClaim = {
|
||||
|
||||
function createPaymentChannelClaimTransaction(account: string,
|
||||
claim: PaymentChannelClaim
|
||||
): object {
|
||||
const txJSON: any = {
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: utils.TransactionJSON = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelClaim',
|
||||
Channel: claim.channel,
|
||||
@@ -62,11 +62,15 @@ function preparePaymentChannelClaim(address: string,
|
||||
paymentChannelClaim: PaymentChannelClaim,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.preparePaymentChannelClaim(
|
||||
{address, paymentChannelClaim, instructions})
|
||||
const txJSON = createPaymentChannelClaimTransaction(
|
||||
address, paymentChannelClaim)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.preparePaymentChannelClaim(
|
||||
{address, paymentChannelClaim, instructions})
|
||||
const txJSON = createPaymentChannelClaimTransaction(
|
||||
address, paymentChannelClaim)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default preparePaymentChannelClaim
|
||||
|
||||
@@ -14,7 +14,7 @@ export type PaymentChannelCreate = {
|
||||
|
||||
function createPaymentChannelCreateTransaction(account: string,
|
||||
paymentChannel: PaymentChannelCreate
|
||||
): object {
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: any = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelCreate',
|
||||
@@ -41,11 +41,15 @@ function preparePaymentChannelCreate(address: string,
|
||||
paymentChannelCreate: PaymentChannelCreate,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.preparePaymentChannelCreate(
|
||||
{address, paymentChannelCreate, instructions})
|
||||
const txJSON = createPaymentChannelCreateTransaction(
|
||||
address, paymentChannelCreate)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.preparePaymentChannelCreate(
|
||||
{address, paymentChannelCreate, instructions})
|
||||
const txJSON = createPaymentChannelCreateTransaction(
|
||||
address, paymentChannelCreate)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default preparePaymentChannelCreate
|
||||
|
||||
@@ -10,8 +10,8 @@ export type PaymentChannelFund = {
|
||||
|
||||
function createPaymentChannelFundTransaction(account: string,
|
||||
fund: PaymentChannelFund
|
||||
): object {
|
||||
const txJSON: any = {
|
||||
): utils.TransactionJSON {
|
||||
const txJSON: utils.TransactionJSON = {
|
||||
Account: account,
|
||||
TransactionType: 'PaymentChannelFund',
|
||||
Channel: fund.channel,
|
||||
@@ -29,11 +29,15 @@ function preparePaymentChannelFund(address: string,
|
||||
paymentChannelFund: PaymentChannelFund,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.preparePaymentChannelFund(
|
||||
{address, paymentChannelFund, instructions})
|
||||
const txJSON = createPaymentChannelFundTransaction(
|
||||
address, paymentChannelFund)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.preparePaymentChannelFund(
|
||||
{address, paymentChannelFund, instructions})
|
||||
const txJSON = createPaymentChannelFundTransaction(
|
||||
address, paymentChannelFund)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default preparePaymentChannelFund
|
||||
|
||||
@@ -4,7 +4,7 @@ const validate = utils.common.validate
|
||||
const toRippledAmount = utils.common.toRippledAmount
|
||||
const paymentFlags = utils.common.txFlags.Payment
|
||||
const ValidationError = utils.common.errors.ValidationError
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {Amount, Adjustment, MaxAdjustment,
|
||||
MinAdjustment, Memo} from '../common/types/objects'
|
||||
import {xrpToDrops} from '../common'
|
||||
@@ -59,9 +59,7 @@ function isIOUWithoutCounterparty(amount: Amount): boolean {
|
||||
function applyAnyCounterpartyEncoding(payment: Payment): void {
|
||||
// Convert blank counterparty to sender or receiver's address
|
||||
// (Ripple convention for 'any counterparty')
|
||||
// https://ripple.com/build/transactions/
|
||||
// #special-issuer-values-for-sendmax-and-amount
|
||||
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
|
||||
// https://developers.ripple.com/payment.html#special-issuer-values-for-sendmax-and-amount
|
||||
_.forEach([payment.source, payment.destination], adjustment => {
|
||||
_.forEach(['amount', 'minAmount', 'maxAmount'], key => {
|
||||
if (isIOUWithoutCounterparty(adjustment[key])) {
|
||||
@@ -86,7 +84,7 @@ function createMaximalAmount(amount: Amount): Amount {
|
||||
}
|
||||
|
||||
function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
): object {
|
||||
): TransactionJSON {
|
||||
const payment = _.cloneDeep(paymentArgument)
|
||||
applyAnyCounterpartyEncoding(payment)
|
||||
|
||||
@@ -172,9 +170,13 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
|
||||
function preparePayment(address: string, payment: Payment,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.preparePayment({address, payment, instructions})
|
||||
const txJSON = createPaymentTransaction(address, payment)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.preparePayment({address, payment, instructions})
|
||||
const txJSON = createPaymentTransaction(address, payment)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default preparePayment
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import * as _ from 'lodash'
|
||||
import * as assert from 'assert'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
const AccountFlagIndices = utils.common.constants.AccountFlagIndices
|
||||
const AccountFields = utils.common.constants.AccountFields
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, SettingsTransaction} from './types'
|
||||
import {FormattedSettings, WeightedSigner} from '../common/types/objects'
|
||||
|
||||
// Emptry string passed to setting will clear it
|
||||
const CLEAR_SETTING = null
|
||||
|
||||
function setTransactionFlags(txJSON: any, values: FormattedSettings) {
|
||||
function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSettings) {
|
||||
const keys = Object.keys(values)
|
||||
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
|
||||
const flagName = keys[0]
|
||||
@@ -26,7 +22,8 @@ function setTransactionFlags(txJSON: any, values: FormattedSettings) {
|
||||
}
|
||||
}
|
||||
|
||||
function setTransactionFields(txJSON: object, input: FormattedSettings) {
|
||||
// Sets `null` fields to their `default`.
|
||||
function setTransactionFields(txJSON: utils.TransactionJSON, input: FormattedSettings) {
|
||||
const fieldSchema = AccountFields
|
||||
for (const fieldName in fieldSchema) {
|
||||
const field = fieldSchema[fieldName]
|
||||
@@ -37,13 +34,13 @@ function setTransactionFields(txJSON: object, input: FormattedSettings) {
|
||||
}
|
||||
|
||||
// The value required to clear an account root field varies
|
||||
if (value === CLEAR_SETTING && field.hasOwnProperty('defaults')) {
|
||||
if (value === null && field.hasOwnProperty('defaults')) {
|
||||
value = field.defaults
|
||||
}
|
||||
|
||||
if (field.encoding === 'hex' && !field.length) {
|
||||
// This is currently only used for Domain field
|
||||
value = new Buffer(value, 'ascii').toString('hex').toUpperCase()
|
||||
value = Buffer.from(value, 'ascii').toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
txJSON[fieldName] = value
|
||||
@@ -63,7 +60,7 @@ function setTransactionFields(txJSON: object, input: FormattedSettings) {
|
||||
* are returned
|
||||
*/
|
||||
|
||||
function convertTransferRate(transferRate: number | string): number | string {
|
||||
function convertTransferRate(transferRate: number): number {
|
||||
return (new BigNumber(transferRate)).shift(9).toNumber()
|
||||
}
|
||||
|
||||
@@ -78,7 +75,7 @@ function formatSignerEntry(signer: WeightedSigner): object {
|
||||
|
||||
function createSettingsTransactionWithoutMemos(
|
||||
account: string, settings: FormattedSettings
|
||||
): any {
|
||||
): SettingsTransaction {
|
||||
if (settings.regularKey !== undefined) {
|
||||
const removeRegularKey = {
|
||||
TransactionType: 'SetRegularKey',
|
||||
@@ -87,7 +84,7 @@ function createSettingsTransactionWithoutMemos(
|
||||
if (settings.regularKey === null) {
|
||||
return removeRegularKey
|
||||
}
|
||||
return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey})
|
||||
return Object.assign({}, removeRegularKey, {RegularKey: settings.regularKey})
|
||||
}
|
||||
|
||||
if (settings.signers !== undefined) {
|
||||
@@ -95,17 +92,19 @@ function createSettingsTransactionWithoutMemos(
|
||||
TransactionType: 'SignerListSet',
|
||||
Account: account,
|
||||
SignerQuorum: settings.signers.threshold,
|
||||
SignerEntries: _.map(settings.signers.weights, formatSignerEntry)
|
||||
SignerEntries: settings.signers.weights.map(formatSignerEntry)
|
||||
}
|
||||
}
|
||||
|
||||
const txJSON: any = {
|
||||
const txJSON: SettingsTransaction = {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: account
|
||||
}
|
||||
|
||||
setTransactionFlags(txJSON, _.omit(settings, 'memos'))
|
||||
setTransactionFields(txJSON, settings)
|
||||
const settingsWithoutMemos = Object.assign({}, settings)
|
||||
delete settingsWithoutMemos.memos
|
||||
setTransactionFlags(txJSON, settingsWithoutMemos)
|
||||
setTransactionFields(txJSON, settings) // Sets `null` fields to their `default`.
|
||||
|
||||
if (txJSON.TransferRate !== undefined) {
|
||||
txJSON.TransferRate = convertTransferRate(txJSON.TransferRate)
|
||||
@@ -114,10 +113,10 @@ function createSettingsTransactionWithoutMemos(
|
||||
}
|
||||
|
||||
function createSettingsTransaction(account: string, settings: FormattedSettings
|
||||
): object {
|
||||
): SettingsTransaction {
|
||||
const txJSON = createSettingsTransactionWithoutMemos(account, settings)
|
||||
if (settings.memos !== undefined) {
|
||||
txJSON.Memos = _.map(settings.memos, utils.convertMemo)
|
||||
txJSON.Memos = settings.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON
|
||||
}
|
||||
@@ -125,9 +124,13 @@ function createSettingsTransaction(account: string, settings: FormattedSettings
|
||||
function prepareSettings(address: string, settings: FormattedSettings,
|
||||
instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareSettings({address, settings, instructions})
|
||||
const txJSON = createSettingsTransaction(address, settings)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareSettings({address, settings, instructions})
|
||||
const txJSON = createSettingsTransaction(address, settings)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareSettings
|
||||
|
||||
@@ -20,8 +20,15 @@ function isImmediateRejection(engineResult: string): boolean {
|
||||
|
||||
function formatSubmitResponse(response): FormattedSubmitResponse {
|
||||
const data = {
|
||||
// @deprecated
|
||||
resultCode: response.engine_result,
|
||||
resultMessage: response.engine_result_message
|
||||
// @deprecated
|
||||
resultMessage: response.engine_result_message,
|
||||
engine_result: response.engine_result,
|
||||
engine_result_code: response.engine_result_code,
|
||||
engine_result_message: response.engine_result_message,
|
||||
tx_blob: response.tx_blob,
|
||||
tx_json: response.tx_json
|
||||
}
|
||||
if (isImmediateRejection(response.engine_result)) {
|
||||
throw new utils.common.errors.RippledError('Submit failed', data)
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as _ from 'lodash'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as utils from './utils'
|
||||
const validate = utils.common.validate
|
||||
const trustlineFlags = utils.common.txFlags.TrustSet
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {Instructions, Prepare, TransactionJSON} from './types'
|
||||
import {
|
||||
FormattedTrustlineSpecification
|
||||
} from '../common/types/objects/trustlines'
|
||||
@@ -14,7 +13,7 @@ function convertQuality(quality) {
|
||||
|
||||
function createTrustlineTransaction(account: string,
|
||||
trustline: FormattedTrustlineSpecification
|
||||
): object {
|
||||
): TransactionJSON {
|
||||
const limit = {
|
||||
currency: trustline.currency,
|
||||
issuer: trustline.counterparty,
|
||||
@@ -45,7 +44,7 @@ function createTrustlineTransaction(account: string,
|
||||
trustlineFlags.SetFreeze : trustlineFlags.ClearFreeze
|
||||
}
|
||||
if (trustline.memos !== undefined) {
|
||||
txJSON.Memos = _.map(trustline.memos, utils.convertMemo)
|
||||
txJSON.Memos = trustline.memos.map(utils.convertMemo)
|
||||
}
|
||||
return txJSON
|
||||
}
|
||||
@@ -53,9 +52,13 @@ function createTrustlineTransaction(account: string,
|
||||
function prepareTrustline(address: string,
|
||||
trustline: FormattedTrustlineSpecification, instructions: Instructions = {}
|
||||
): Promise<Prepare> {
|
||||
validate.prepareTrustline({address, trustline, instructions})
|
||||
const txJSON = createTrustlineTransaction(address, trustline)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
try {
|
||||
validate.prepareTrustline({address, trustline, instructions})
|
||||
const txJSON = createTrustlineTransaction(address, trustline)
|
||||
return utils.prepareTransaction(txJSON, this, instructions)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export default prepareTrustline
|
||||
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
Memo,
|
||||
FormattedSettings
|
||||
} from '../common/types/objects'
|
||||
import {ApiMemo} from './utils'
|
||||
import {
|
||||
ApiMemo,
|
||||
TransactionJSON
|
||||
} from './utils'
|
||||
|
||||
export type TransactionJSON = TransactionJSON
|
||||
|
||||
export type Instructions = {
|
||||
sequence?: number,
|
||||
@@ -37,7 +42,7 @@ export type Submit = {
|
||||
txJson?: object
|
||||
}
|
||||
|
||||
export interface OfferCreateTransaction {
|
||||
export interface OfferCreateTransaction extends TransactionJSON {
|
||||
TransactionType: 'OfferCreate',
|
||||
Account: string,
|
||||
Fee: string,
|
||||
@@ -48,7 +53,11 @@ export interface OfferCreateTransaction {
|
||||
TakerPays: RippledAmount,
|
||||
Expiration?: number,
|
||||
OfferSequence?: number,
|
||||
Memos: {Memo: ApiMemo}[]
|
||||
Memos?: {Memo: ApiMemo}[]
|
||||
}
|
||||
|
||||
export interface SettingsTransaction extends TransactionJSON {
|
||||
TransferRate?: number
|
||||
}
|
||||
|
||||
export type KeyPair = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import * as common from '../common'
|
||||
import {Memo} from '../common/types/objects'
|
||||
import {Memo, RippledAmount} from '../common/types/objects'
|
||||
const txFlags = common.txFlags
|
||||
import {Instructions, Prepare} from './types'
|
||||
import {RippleAPI} from '../api'
|
||||
@@ -12,6 +12,15 @@ export type ApiMemo = {
|
||||
MemoFormat?: string
|
||||
}
|
||||
|
||||
export type TransactionJSON = {
|
||||
Account: string,
|
||||
TransactionType: string,
|
||||
Memos?: {Memo: ApiMemo}[],
|
||||
Flags?: number,
|
||||
Fulfillment?: string,
|
||||
[Field: string]: string | number | Array<any> | RippledAmount
|
||||
}
|
||||
|
||||
function formatPrepareResponse(txJSON: any): Prepare {
|
||||
const instructions = {
|
||||
fee: common.dropsToXrp(txJSON.Fee),
|
||||
@@ -37,10 +46,11 @@ function scaleValue(value, multiplier, extra = 0) {
|
||||
return (new BigNumber(value)).times(multiplier).plus(extra).toString()
|
||||
}
|
||||
|
||||
function prepareTransaction(txJSON: any, api: RippleAPI,
|
||||
function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
|
||||
instructions: Instructions
|
||||
): Promise<Prepare> {
|
||||
common.validate.instructions(instructions)
|
||||
common.validate.tx_json(txJSON)
|
||||
|
||||
const account = txJSON.Account
|
||||
setCanonicalFlag(txJSON)
|
||||
@@ -81,7 +91,7 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
|
||||
(txJSON.TransactionType !== 'EscrowFinish' ||
|
||||
txJSON.Fulfillment === undefined) ? 0 :
|
||||
(cushion * feeRef * (32 + Math.floor(
|
||||
new Buffer(txJSON.Fulfillment, 'hex').length / 16)))
|
||||
Buffer.from(txJSON.Fulfillment, 'hex').length / 16)))
|
||||
const feeDrops = common.xrpToDrops(fee)
|
||||
const maxFeeXRP = instructions.maxFee ?
|
||||
BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
|
||||
@@ -96,14 +106,28 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
|
||||
|
||||
async function prepareSequence(): Promise<object> {
|
||||
if (instructions.sequence !== undefined) {
|
||||
txJSON.Sequence = instructions.sequence
|
||||
if (txJSON.Sequence === undefined || instructions.sequence === txJSON.Sequence) {
|
||||
txJSON.Sequence = instructions.sequence
|
||||
return Promise.resolve(txJSON)
|
||||
} else {
|
||||
// Both txJSON.Sequence and instructions.sequence are defined, and they are NOT equal
|
||||
return Promise.reject(new ValidationError('`Sequence` in txJSON must match `sequence` in Instructions'))
|
||||
}
|
||||
}
|
||||
if (txJSON.Sequence !== undefined) {
|
||||
return Promise.resolve(txJSON)
|
||||
}
|
||||
const response = await api.request('account_info', {
|
||||
account: account as string
|
||||
})
|
||||
txJSON.Sequence = response.account_data.Sequence
|
||||
return txJSON
|
||||
|
||||
try {
|
||||
// Consider requesting from the 'current' ledger (instead of 'validated').
|
||||
const response = await api.request('account_info', {
|
||||
account
|
||||
})
|
||||
txJSON.Sequence = response.account_data.Sequence
|
||||
return Promise.resolve(txJSON)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
@@ -114,7 +138,7 @@ function prepareTransaction(txJSON: any, api: RippleAPI,
|
||||
}
|
||||
|
||||
function convertStringToHex(string: string): string {
|
||||
return new Buffer(string, 'utf8').toString('hex').toUpperCase()
|
||||
return Buffer.from(string, 'utf8').toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
function convertMemo(memo: Memo): {Memo: ApiMemo} {
|
||||
|
||||
790
test/api-test.js
790
test/api-test.js
@@ -33,7 +33,11 @@ function checkResult(expected, schemaName, response) {
|
||||
assert(response.txJSON);
|
||||
assert.deepEqual(JSON.parse(response.txJSON), JSON.parse(expected.txJSON));
|
||||
}
|
||||
assert.deepEqual(_.omit(response, 'txJSON'), _.omit(expected, 'txJSON'));
|
||||
if (expected.tx_json) {
|
||||
assert(response.tx_json);
|
||||
assert.deepEqual(response.tx_json, expected.tx_json);
|
||||
}
|
||||
assert.deepEqual(_.omit(response, 'txJSON'), _.omit(expected, 'txJSON'), _.omit(response, 'tx_json'), _.omit(response, 'tx_json'));
|
||||
if (schemaName) {
|
||||
schemaValidator.schemaValidate(schemaName, response);
|
||||
}
|
||||
@@ -367,6 +371,389 @@ describe('RippleAPI', function () {
|
||||
|
||||
});
|
||||
|
||||
describe('prepareTransaction - auto-fillable fields', function () {
|
||||
|
||||
it('does not overwrite Sequence in txJSON', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
Sequence: 100
|
||||
}
|
||||
|
||||
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
const expected = {
|
||||
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
||||
instructions: {
|
||||
fee: '0.000012',
|
||||
sequence: 100,
|
||||
maxLedgerVersion: 8820051
|
||||
}
|
||||
}
|
||||
return checkResult(expected, 'prepare', response)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not overwrite Sequence in Instructions', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012',
|
||||
sequence: 100
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
const expected = {
|
||||
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
||||
instructions: {
|
||||
fee: '0.000012',
|
||||
sequence: 100,
|
||||
maxLedgerVersion: 8820051
|
||||
}
|
||||
}
|
||||
return checkResult(expected, 'prepare', response)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not overwrite Sequence when same sequence is provided in both txJSON and Instructions', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012',
|
||||
sequence: 100
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
Sequence: 100
|
||||
}
|
||||
|
||||
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
const expected = {
|
||||
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":100}',
|
||||
instructions: {
|
||||
fee: '0.000012',
|
||||
sequence: 100,
|
||||
maxLedgerVersion: 8820051
|
||||
}
|
||||
}
|
||||
return checkResult(expected, 'prepare', response)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects Promise when Sequence in txJSON does not match sequence in Instructions', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012',
|
||||
sequence: 100
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
Sequence: 101
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, '`Sequence` in txJSON must match `sequence` in Instructions');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when the Sequence is capitalized in Instructions', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012',
|
||||
Sequence: 100 // Intentionally capitalized in this test, but the correct field would be `sequence`
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance additionalProperty "Sequence" exists in instance when not allowed');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when an unrecognized field is in Instructions', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012',
|
||||
foo: 'bar'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance additionalProperty "foo" exists in instance when not allowed');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects Promise when Account is missing', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
// assert.strictEqual(err.name, 'RippledError');
|
||||
// assert.strictEqual(err.message, 'Missing field \'account\'.');
|
||||
// assert.strictEqual(err.data.error, 'invalidParams');
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance requires property "Account"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when Account is not a string', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: 1234,
|
||||
TransactionType: 'DepositPreauth',
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when Account is invalid', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xkXXXX', // Invalid checksum
|
||||
TransactionType: 'DepositPreauth',
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.Account does not conform to the "address" format');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when Account is valid but non-existent on the ledger', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: 'rogvkYnY8SWjxkJNgU4ZRVfLeRyt5DR9i',
|
||||
TransactionType: 'DepositPreauth',
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'RippledError');
|
||||
assert.strictEqual(err.message, 'Account not found.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects Promise when TransactionType is missing', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: address,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
// If not caught by ripple-lib validation, the rippled error looks like:
|
||||
// { error: 'invalidTransaction',
|
||||
// error_exception: 'Field not found',
|
||||
// id: 4,
|
||||
// request:
|
||||
// { command: 'submit',
|
||||
// id: 4,
|
||||
// tx_blob: '24000000032B7735940068400000000000000C732102E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA97446304402206CE34A79A44AEF15786F23DB25C8420E739C167E66750C0B7999EE4BF74A93A1022052E077A6435548F0EE0C5FE2EAB1E5A56376BA360F924DA2E162CCA6C7CB30CB8114D51F9A17208CF113AF23B97ECD5FCD314FBAE52E' },
|
||||
// status: 'error',
|
||||
// type: 'response' }
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance requires property "TransactionType"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
// Note: This transaction will fail at the `sign` step:
|
||||
//
|
||||
// Error: DepositPreXXXX is not a valid name or ordinal for TransactionType
|
||||
//
|
||||
// at Function.from (ripple-binary-codec/distrib/npm/enums/index.js:43:15)
|
||||
it('prepares tx when TransactionType is invalid', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: address,
|
||||
TransactionType: 'DepositPreXXXX',
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
const expected = {
|
||||
txJSON: '{"TransactionType":"DepositPreXXXX","Account":"' + address + '","Authorize":"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
||||
instructions: {
|
||||
fee: '0.000012',
|
||||
sequence: 23,
|
||||
maxLedgerVersion: 8820051
|
||||
}
|
||||
}
|
||||
return checkResult(expected, 'prepare', response)
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects Promise when TransactionType is not a string', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: address,
|
||||
TransactionType: 1234,
|
||||
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo'
|
||||
}
|
||||
|
||||
try {
|
||||
this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.TransactionType is not of a type(s) string');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
}
|
||||
})
|
||||
|
||||
// Note: This transaction will fail at the `submit` step:
|
||||
//
|
||||
// [RippledError(Submit failed, { resultCode: 'temMALFORMED',
|
||||
// resultMessage: 'Malformed transaction.',
|
||||
// engine_result: 'temMALFORMED',
|
||||
// engine_result_code: -299,
|
||||
// engine_result_message: 'Malformed transaction.',
|
||||
// tx_blob:
|
||||
// '120013240000000468400000000000000C732102E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA97446304402201F0EF6A2DE7F96966F7082294D14F3EC1EF59C21E29443E5858A0120079357A302203CDB7FEBDEAAD93FF39CB589B55778CB80DC3979F96F27E828D5E659BEB26B7A8114D51F9A17208CF113AF23B97ECD5FCD314FBAE52E',
|
||||
// tx_json:
|
||||
// { Account: 'rLRt8bmZFBEeM5VMSxZy15k8KKJEs68W6C',
|
||||
// Fee: '12',
|
||||
// Sequence: 4,
|
||||
// SigningPubKey:
|
||||
// '02E1EA8199F570E7F997A7B34EDFDA0A7D8B38173A17450B121A2EB048FDD16CA9',
|
||||
// TransactionType: 'DepositPreauth',
|
||||
// TxnSignature:
|
||||
// '304402201F0EF6A2DE7F96966F7082294D14F3EC1EF59C21E29443E5858A0120079357A302203CDB7FEBDEAAD93FF39CB589B55778CB80DC3979F96F27E828D5E659BEB26B7A',
|
||||
// hash:
|
||||
// 'C181D470684311658852713DA81F8201062535C8DE2FF853F7DD9981BB85312F' } })]
|
||||
it('prepares tx when a required field is missing', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
}, instructions)
|
||||
|
||||
const txJSON = {
|
||||
Account: address,
|
||||
TransactionType: 'DepositPreauth',
|
||||
// Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo' // Normally required, intentionally removed
|
||||
}
|
||||
|
||||
return this.api.prepareTransaction(txJSON, localInstructions).then(response => {
|
||||
const expected = {
|
||||
txJSON: '{"TransactionType":"DepositPreauth","Account":"' + address + '","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
|
||||
instructions: {
|
||||
fee: '0.000012',
|
||||
sequence: 23,
|
||||
maxLedgerVersion: 8820051
|
||||
}
|
||||
}
|
||||
return checkResult(expected, 'prepare', response)
|
||||
})
|
||||
})
|
||||
|
||||
describe('preparePayment', function () {
|
||||
|
||||
it('normal', function () {
|
||||
@@ -515,23 +902,170 @@ describe('RippleAPI', function () {
|
||||
})
|
||||
});
|
||||
|
||||
it('preparePayment - XRP to XRP no partial', function () {
|
||||
assert.throws(() => {
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongPartial);
|
||||
}, /XRP to XRP payments cannot be partial payments/);
|
||||
});
|
||||
describe('errors', function () {
|
||||
|
||||
it('preparePayment - address must match payment.source.address', function (
|
||||
) {
|
||||
assert.throws(() => {
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongAddress);
|
||||
}, /address must match payment.source.address/);
|
||||
});
|
||||
const senderAddress = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
|
||||
const recipientAddress = 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo';
|
||||
|
||||
it('preparePayment - wrong amount', function () {
|
||||
assert.throws(() => {
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongAmount);
|
||||
}, this.api.errors.ValidationError);
|
||||
it('rejects promise and does not throw when payment object is invalid', function (done) {
|
||||
const payment = {
|
||||
source: {
|
||||
address: senderAddress,
|
||||
amount: { // instead of `maxAmount`
|
||||
value: '1000',
|
||||
currency: 'drops'
|
||||
}
|
||||
},
|
||||
destination: {
|
||||
address: recipientAddress,
|
||||
amount: {
|
||||
value: '1000',
|
||||
currency: 'drops'
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
||||
// See https://github.com/mochajs/mocha/issues/3097
|
||||
try {
|
||||
this.api.preparePayment(senderAddress, payment).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('rejects promise and does not throw when field is missing', function (done) {
|
||||
const payment = {
|
||||
source: {
|
||||
address: senderAddress
|
||||
// `maxAmount` missing
|
||||
},
|
||||
destination: {
|
||||
address: recipientAddress,
|
||||
amount: {
|
||||
value: '1000',
|
||||
currency: 'drops'
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
||||
// See https://github.com/mochajs/mocha/issues/3097
|
||||
try {
|
||||
this.api.preparePayment(senderAddress, payment).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.payment.source is not exactly one from <sourceExactAdjustment>,<maxAdjustment>');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('rejects promise and does not throw when fee exceeds maxFeeXRP', function (done) {
|
||||
const payment = {
|
||||
source: {
|
||||
address: senderAddress,
|
||||
maxAmount: {
|
||||
value: '1000',
|
||||
currency: 'drops'
|
||||
}
|
||||
},
|
||||
destination: {
|
||||
address: recipientAddress,
|
||||
amount: {
|
||||
value: '1000',
|
||||
currency: 'drops'
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cannot use `assert.rejects` because then the test passes (with UnhandledPromiseRejectionWarning) even when it should not.
|
||||
// See https://github.com/mochajs/mocha/issues/3097
|
||||
try {
|
||||
this.api.preparePayment(senderAddress, payment, {
|
||||
fee: '3' // XRP
|
||||
}).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'Fee of 3 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('preparePayment - XRP to XRP no partial', function (done) {
|
||||
try {
|
||||
// Cannot return promise because we want/expect it to reject.
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongPartial).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'XRP to XRP payments cannot be partial payments');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('preparePayment - address must match payment.source.address', function (done) {
|
||||
try {
|
||||
// Cannot return promise because we want/expect it to reject.
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongAddress).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'address must match payment.source.address');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('preparePayment - wrong amount', function (done) {
|
||||
try {
|
||||
// Cannot return promise because we want/expect it to reject.
|
||||
this.api.preparePayment(address, requests.preparePayment.wrongAmount).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'payment must specify either (source.maxAmount and destination.amount) or (source.amount and destination.minAmount)');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('preparePayment - throws when fee exceeds 2 XRP', function (done) {
|
||||
const localInstructions = _.defaults({
|
||||
fee: '2.1'
|
||||
}, instructions);
|
||||
|
||||
try {
|
||||
// Cannot return promise because we want/expect it to reject.
|
||||
this.api.preparePayment(
|
||||
address, requests.preparePayment.normal, localInstructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'Fee of 2.1 XRP exceeds max of 2 XRP. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
it('preparePayment with all options specified', function () {
|
||||
@@ -561,17 +1095,6 @@ describe('RippleAPI', function () {
|
||||
responses.preparePayment.minAmount, 'prepare'));
|
||||
});
|
||||
|
||||
it('preparePayment - throws when fee exceeds 2 XRP', function () {
|
||||
const localInstructions = _.defaults({
|
||||
fee: '2.1'
|
||||
}, instructions);
|
||||
|
||||
assert.throws(() => {
|
||||
this.api.preparePayment(
|
||||
address, requests.preparePayment.normal, localInstructions)
|
||||
}, /Fee of 2\.1 XRP exceeds max of 2 XRP\. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor\./)
|
||||
});
|
||||
|
||||
it('preparePayment - caps fee at 2 XRP by default', function () {
|
||||
this.api._feeCushion = 1000000;
|
||||
|
||||
@@ -629,6 +1152,22 @@ describe('RippleAPI', function () {
|
||||
_.partial(checkResult, responses.prepareOrder.sell, 'prepare'));
|
||||
});
|
||||
|
||||
it('prepareOrder - invalid', function (done) {
|
||||
const request = requests.prepareOrder.sell;
|
||||
delete request.direction; // Make invalid
|
||||
try {
|
||||
this.api.prepareOrder(address, request, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.order requires property "direction"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareOrderCancellation', function () {
|
||||
const request = requests.prepareOrderCancellation.simple;
|
||||
return this.api.prepareOrderCancellation(address, request, instructions)
|
||||
@@ -652,6 +1191,22 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareOrderCancellation - invalid', function (done) {
|
||||
const request = requests.prepareOrderCancellation.withMemos;
|
||||
delete request.orderSequence; // Make invalid
|
||||
try {
|
||||
this.api.prepareOrderCancellation(address, request).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.orderCancellation requires property "orderSequence"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareTrustline - simple', function () {
|
||||
return this.api.prepareTrustline(
|
||||
address, requests.prepareTrustline.simple, instructions).then(
|
||||
@@ -670,6 +1225,23 @@ describe('RippleAPI', function () {
|
||||
_.partial(checkResult, responses.prepareTrustline.complex, 'prepare'));
|
||||
});
|
||||
|
||||
it('prepareTrustline - invalid', function (done) {
|
||||
const trustline = requests.prepareTrustline.complex;
|
||||
delete trustline.limit; // Make invalid
|
||||
try {
|
||||
this.api.prepareTrustline(
|
||||
address, trustline, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.trustline requires property "limit"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareSettings', function () {
|
||||
return this.api.prepareSettings(
|
||||
address, requests.prepareSettings.domain, instructions).then(
|
||||
@@ -752,18 +1324,34 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - signers no threshold', function () {
|
||||
it('prepareSettings - signers no threshold', function (done) {
|
||||
const settings = requests.prepareSettings.signers.noThreshold;
|
||||
assert.throws(() => {
|
||||
this.api.prepareSettings(address, settings, instructions);
|
||||
}, this.api.errors.ValidationError);
|
||||
try {
|
||||
this.api.prepareSettings(address, settings, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.settings.signers requires property "threshold"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareSettings - signers no weights', function () {
|
||||
it('prepareSettings - signers no weights', function (done) {
|
||||
const settings = requests.prepareSettings.signers.noWeights;
|
||||
assert.throws(() => {
|
||||
this.api.prepareSettings(address, settings, instructions);
|
||||
}, this.api.errors.ValidationError);
|
||||
try {
|
||||
this.api.prepareSettings(address, settings, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.settings.signers requires property "weights"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareSettings - fee for multisign', function () {
|
||||
@@ -776,6 +1364,30 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareSettings - invalid', function (done) {
|
||||
// domain must be a string
|
||||
const settings = Object.assign({},
|
||||
requests.prepareSettings.domain,
|
||||
{domain: 123});
|
||||
|
||||
const localInstructions = _.defaults({
|
||||
signersCount: 4
|
||||
}, instructions);
|
||||
|
||||
try {
|
||||
this.api.prepareSettings(
|
||||
address, settings, localInstructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.settings.domain is not of a type(s) string');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareEscrowCreation', function () {
|
||||
const localInstructions = _.defaults({
|
||||
maxFee: '0.000012'
|
||||
@@ -794,6 +1406,23 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareEscrowCreation - invalid', function (done) {
|
||||
const escrow = Object.assign({}, requests.prepareEscrowCreation.full);
|
||||
delete escrow.amount; // Make invalid
|
||||
try {
|
||||
this.api.prepareEscrowCreation(
|
||||
address, escrow).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, 'instance.escrowCreation requires property "amount"');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareEscrowExecution', function () {
|
||||
return this.api.prepareEscrowExecution(
|
||||
address,
|
||||
@@ -812,18 +1441,34 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('prepareEscrowExecution - no condition', function () {
|
||||
assert.throws(() => {
|
||||
it('prepareEscrowExecution - no condition', function (done) {
|
||||
try {
|
||||
this.api.prepareEscrowExecution(address,
|
||||
requests.prepareEscrowExecution.noCondition, instructions);
|
||||
}, /"condition" and "fulfillment" fields on EscrowFinish must only be specified together./);
|
||||
requests.prepareEscrowExecution.noCondition, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareEscrowExecution - no fulfillment', function () {
|
||||
assert.throws(() => {
|
||||
it('prepareEscrowExecution - no fulfillment', function (done) {
|
||||
try {
|
||||
this.api.prepareEscrowExecution(address,
|
||||
requests.prepareEscrowExecution.noFulfillment, instructions);
|
||||
}, /"condition" and "fulfillment" fields on EscrowFinish must only be specified together./);
|
||||
requests.prepareEscrowExecution.noFulfillment, instructions).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, '"condition" and "fulfillment" fields on EscrowFinish must only be specified together.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('prepareEscrowCancellation', function () {
|
||||
@@ -1378,22 +2023,34 @@ describe('RippleAPI', function () {
|
||||
'prepare'));
|
||||
});
|
||||
|
||||
it('throws on preparePaymentChannelClaim with renew and close', function () {
|
||||
assert.throws(() => {
|
||||
it('rejects Promise on preparePaymentChannelClaim with renew and close', function (done) {
|
||||
try {
|
||||
this.api.preparePaymentChannelClaim(
|
||||
address, requests.preparePaymentChannelClaim.full).then(
|
||||
_.partial(checkResult, responses.preparePaymentChannelClaim.full,
|
||||
'prepare'));
|
||||
}, this.api.errors.ValidationError);
|
||||
address, requests.preparePaymentChannelClaim.full).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, '"renew" and "close" flags on PaymentChannelClaim are mutually exclusive');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('throws on preparePaymentChannelClaim with no signature', function () {
|
||||
assert.throws(() => {
|
||||
it('rejects Promise on preparePaymentChannelClaim with no signature', function (done) {
|
||||
try {
|
||||
this.api.preparePaymentChannelClaim(
|
||||
address, requests.preparePaymentChannelClaim.noSignature).then(
|
||||
_.partial(checkResult, responses.preparePaymentChannelClaim.noSignature,
|
||||
'prepare'));
|
||||
}, this.api.errors.ValidationError);
|
||||
address, requests.preparePaymentChannelClaim.noSignature).then(prepared => {
|
||||
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(prepared)));
|
||||
}).catch(err => {
|
||||
assert.strictEqual(err.name, 'ValidationError');
|
||||
assert.strictEqual(err.message, '"signature" and "publicKey" fields on PaymentChannelClaim must only be specified together.');
|
||||
done();
|
||||
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
|
||||
} catch (err) {
|
||||
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
|
||||
};
|
||||
});
|
||||
|
||||
it('sign', function () {
|
||||
@@ -1536,8 +2193,9 @@ describe('RippleAPI', function () {
|
||||
});
|
||||
|
||||
it('submit', function () {
|
||||
return this.api.submit(responses.sign.normal.signedTransaction).then(
|
||||
_.partial(checkResult, responses.submit, 'submit'));
|
||||
return this.api.submit(responses.sign.normal.signedTransaction).then(response => {
|
||||
checkResult(responses.submit, 'submit', response);
|
||||
});
|
||||
});
|
||||
|
||||
it('submit - failure', function () {
|
||||
@@ -1766,10 +2424,12 @@ describe('RippleAPI', function () {
|
||||
it('getTransaction - not validated', function () {
|
||||
const hash =
|
||||
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA10';
|
||||
return this.api.getTransaction(hash).then(() => {
|
||||
return this.api.getTransaction(hash).then((response) => {
|
||||
console.log(response);
|
||||
assert(false, 'Should throw NotFoundError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.NotFoundError);
|
||||
assert.equal(error.message, 'Transaction not found');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2085,7 +2745,8 @@ describe('RippleAPI', function () {
|
||||
start: hashes.NOTFOUND_TRANSACTION_HASH,
|
||||
counterparty: address
|
||||
};
|
||||
return this.api.getTransactions(address, options).then(() => {
|
||||
return this.api.getTransactions(address, options).then((response) => {
|
||||
console.log(response);
|
||||
assert(false, 'Should throw NotFoundError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.NotFoundError);
|
||||
@@ -2731,7 +3392,8 @@ describe('RippleAPI', function () {
|
||||
assert(false, 'Should throw entryNotFound');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.RippledError);
|
||||
assert(_.includes(error.message, 'entryNotFound'));
|
||||
assert.equal(error.message, 'entryNotFound');
|
||||
assert.equal(error.data.error, 'entryNotFound');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2762,7 +3424,8 @@ describe('RippleAPI', function () {
|
||||
assert(false, 'Should throw NetworkError');
|
||||
}).catch(error => {
|
||||
assert(error instanceof this.api.errors.RippledError);
|
||||
assert(_.includes(error.message, 'slowDown'));
|
||||
assert.equal(error.message, 'You are placing too much load on the server.');
|
||||
assert.equal(error.data.error, 'slowDown');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3085,6 +3748,11 @@ describe('RippleAPI', function () {
|
||||
_.partial(checkResult, responses.getLedger.header, 'getLedger'));
|
||||
});
|
||||
|
||||
it('getLedger - by hash', function () {
|
||||
return this.api.getLedger({ ledgerHash: '15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D' }).then(
|
||||
_.partial(checkResult, responses.getLedger.headerByHash, 'getLedger'));
|
||||
});
|
||||
|
||||
it('getLedger - future ledger version', function () {
|
||||
return this.api.getLedger({ ledgerVersion: 14661789 }).then(response => {
|
||||
assert(response)
|
||||
|
||||
@@ -396,7 +396,8 @@ describe('Connection', function() {
|
||||
it('propagates RippledError data', function(done) {
|
||||
this.api.request('subscribe', {streams: 'validations'}).catch(error => {
|
||||
assert.strictEqual(error.name, 'RippledError')
|
||||
assert.strictEqual(error.message, 'invalidParams')
|
||||
assert.strictEqual(error.data.error, 'invalidParams')
|
||||
assert.strictEqual(error.message, 'Invalid parameters.')
|
||||
assert.strictEqual(error.data.error_code, 31)
|
||||
assert.strictEqual(error.data.error_message, 'Invalid parameters.')
|
||||
assert.deepEqual(error.data.request, { command: 'subscribe', id: 0, streams: 'validations' })
|
||||
|
||||
12
test/fixtures/responses/get-ledger-by-hash.json
vendored
Normal file
12
test/fixtures/responses/get-ledger-by-hash.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"stateHash": "A155BFE86054BE654796EC449E7C374CD5CAA3789BA75D302E7F0F4CE470CCB3",
|
||||
"closeTime": "2018-12-07T11:10:30.000Z",
|
||||
"closeTimeResolution": 10,
|
||||
"closeFlags": 0,
|
||||
"ledgerHash": "15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D",
|
||||
"ledgerVersion": 14995338,
|
||||
"parentLedgerHash": "E0BC4F5FB8D9025087BE238664833DFA5658C9E7CE413B3B6F7DF4FFF1EDBF40",
|
||||
"parentCloseTime": "2018-12-07T11:10:22.000Z",
|
||||
"totalDrops": "99997114637345372",
|
||||
"transactionHash": "52C0B6604D2EF203710FEA24F4A3750A4F2BCD5C67D6EB5FB1B2DBAE9A14DCE8"
|
||||
}
|
||||
1
test/fixtures/responses/index.js
vendored
1
test/fixtures/responses/index.js
vendored
@@ -83,6 +83,7 @@ module.exports = {
|
||||
},
|
||||
getLedger: {
|
||||
header: require('./get-ledger'),
|
||||
headerByHash: require('./get-ledger-by-hash'),
|
||||
full: require('./get-ledger-full'),
|
||||
withSettingsTx: require('./get-ledger-with-settings-tx'),
|
||||
withStateAsHashes: require('./get-ledger-with-state-as-hashes'),
|
||||
|
||||
22
test/fixtures/responses/submit.json
vendored
22
test/fixtures/responses/submit.json
vendored
@@ -1,4 +1,24 @@
|
||||
{
|
||||
"resultCode": "tesSUCCESS",
|
||||
"resultMessage": "The transaction was applied. Only final in a validated ledger."
|
||||
"resultMessage": "The transaction was applied. Only final in a validated ledger.",
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
1
test/fixtures/rippled/index.js
vendored
1
test/fixtures/rippled/index.js
vendored
@@ -7,6 +7,7 @@ module.exports = {
|
||||
},
|
||||
ledger: {
|
||||
normal: require('./ledger'),
|
||||
normalByHash: require('./ledger-by-hash'),
|
||||
notFound: require('./ledger-not-found'),
|
||||
withoutCloseTime: require('./ledger-without-close-time'),
|
||||
withSettingsTx: require('./ledger-with-settings-tx'),
|
||||
|
||||
28
test/fixtures/rippled/ledger-by-hash.json
vendored
Normal file
28
test/fixtures/rippled/ledger-by-hash.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"id":1,
|
||||
"result": {
|
||||
"ledger": {
|
||||
"accepted": true,
|
||||
"account_hash": "A155BFE86054BE654796EC449E7C374CD5CAA3789BA75D302E7F0F4CE470CCB3",
|
||||
"close_flags": 0,
|
||||
"close_time": 597496230,
|
||||
"close_time_human": "2018-Dec-07 11:10:30.000000000",
|
||||
"close_time_resolution": 10,
|
||||
"closed": true,
|
||||
"hash": "15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D",
|
||||
"ledger_hash": "15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D",
|
||||
"ledger_index": "14995338",
|
||||
"parent_close_time": 597496222,
|
||||
"parent_hash": "E0BC4F5FB8D9025087BE238664833DFA5658C9E7CE413B3B6F7DF4FFF1EDBF40",
|
||||
"seqNum": "14995338",
|
||||
"totalCoins": "99997114637345372",
|
||||
"total_coins": "99997114637345372",
|
||||
"transaction_hash": "52C0B6604D2EF203710FEA24F4A3750A4F2BCD5C67D6EB5FB1B2DBAE9A14DCE8"
|
||||
},
|
||||
"ledger_hash": "15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D",
|
||||
"ledger_index": 14995338,
|
||||
"validated": true
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
19
test/fixtures/rippled/submit.json
vendored
19
test/fixtures/rippled/submit.json
vendored
@@ -7,7 +7,22 @@
|
||||
"engine_result": "tesSUCCESS",
|
||||
"engine_result_code": 0,
|
||||
"engine_result_message": "The transaction was applied. Only final in a validated ledger.",
|
||||
"tx_blob": "12000322000000002400000017201B0086955468400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402207660BDEF67105CE1EBA9AD35DC7156BAB43FF1D47633199EE257D70B6B9AAFBF02207F5517BC8AEF2ADC1325897ECDBA8C673838048BCA62F4E98B252F19BE88796D770A726970706C652E636F6D81144FBFF73DA4ECF9B701940F27341FA8020C313443",
|
||||
"tx_json": {}
|
||||
"tx_blob": "1200002280000000240000016861D4838D7EA4C6800000000000000000000000000055534400000000004B4E9C06F24296074F7BC48F92A97916C6DC5EA9684000000000002710732103AB40A0490F9B7ED8DF29D246BF2D6269820A0EE7742ACDD457BEA7C7D0931EDB7446304402200E5C2DD81FDF0BE9AB2A8D797885ED49E804DBF28E806604D878756410CA98B102203349581946B0DDA06B36B35DBC20EDA27552C1F167BCF5C6ECFF49C6A46F858081144B4E9C06F24296074F7BC48F92A97916C6DC5EA983143E9D4A2B8AA0780F682D136F7A56D6724EF53754",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,11 +238,40 @@ module.exports = function createMockRippled(port) {
|
||||
} else if (request.account === addresses.NOTFOUND) {
|
||||
conn.send(createResponse(request, fixtures.account_info.notfound));
|
||||
} else if (request.account === addresses.THIRD_ACCOUNT) {
|
||||
const response = _.assign({}, fixtures.account_info.normal);
|
||||
const response = Object.assign({}, fixtures.account_info.normal);
|
||||
response.Account = addresses.THIRD_ACCOUNT;
|
||||
conn.send(createResponse(request, response));
|
||||
} else if (request.account === undefined) {
|
||||
const response = Object.assign({}, {
|
||||
error: 'invalidParams',
|
||||
error_code: 31,
|
||||
error_message: 'Missing field \'account\'.',
|
||||
id: 2,
|
||||
request: { command: 'account_info', id: 2 },
|
||||
status: 'error',
|
||||
type: 'response'
|
||||
});
|
||||
conn.send(createResponse(request, response));
|
||||
} else {
|
||||
assert(false, 'Unrecognized account address: ' + request.account);
|
||||
const response = Object.assign({}, {
|
||||
account: request.account,
|
||||
error: 'actNotFound',
|
||||
error_code: 19,
|
||||
error_message: 'Account not found.',
|
||||
id: 2,
|
||||
ledger_current_index: 17714714,
|
||||
request:
|
||||
|
||||
// This will be inaccurate, but that's OK because this is just a mock rippled
|
||||
{ account: 'rogvkYnY8SWjxkJNgU4ZRVfLeRyt5DR9i',
|
||||
command: 'account_info',
|
||||
id: 2 },
|
||||
|
||||
status: 'error',
|
||||
type: 'response',
|
||||
validated: false
|
||||
});
|
||||
conn.send(createResponse(request, response));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -268,6 +297,8 @@ module.exports = function createMockRippled(port) {
|
||||
const response = _.assign({}, fixtures.ledger.normal,
|
||||
{ result: { ledger: fullLedger } });
|
||||
conn.send(createLedgerResponse(request, response));
|
||||
} else if (request.ledger_hash === '15F20E5FA6EA9770BBFFDBD62787400960B04BE32803B20C41F117F41C13830D') {
|
||||
conn.send(createLedgerResponse(request, fixtures.ledger.normalByHash));
|
||||
} else if (request.ledger_index === 'validated' ||
|
||||
request.ledger_index === 14661789 ||
|
||||
request.ledger_index === 14661788 /* getTransaction - order */) {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<!-- encoding must be set for mocha's special characters to render properly -->
|
||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
|
||||
<script src="/test/vendor/lodash.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="deb"></div>
|
||||
<div id="mocha"></div>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script src="/node_modules/mocha-in-sauce/client.js"></script>
|
||||
|
||||
<script src="/build/ripple-latest-min.js"></script>
|
||||
<script>
|
||||
mocha.ui('bdd')
|
||||
</script>
|
||||
|
||||
<script src="../test-compiled-for-web/api-test.js"></script>
|
||||
|
||||
<script src="../test-compiled-for-web/broadcast-api-test.js"></script>
|
||||
|
||||
<script src="../test-compiled-for-web/connection-test.js"></script>
|
||||
|
||||
<script src="/test-compiled-for-web/rangeset-test.js"></script>
|
||||
|
||||
<script>
|
||||
mochaSaucePlease({xunit: false});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user