Compare commits

..

38 Commits

Author SHA1 Message Date
Chris Clark
3caed3c761 Bump version to 0.16.0 2015-12-09 13:17:04 -08:00
Chris Clark
ce1c55427a Merge pull request #668 from clark800/fix-ws-error
BREAKING CHANGE: Change error event format and fix crash due to error event on websocket
2015-12-09 13:06:45 -08:00
Chris Clark
9cd72595f0 BREAKING CHANGE: Change error event format and fix crash due to error event on websocket 2015-12-09 12:56:45 -08:00
Chris Clark
ad1d3e135f Merge pull request #663 from darkdarkdragon/develop-http-server
http server example
2015-12-09 11:15:52 -08:00
Ivan Tivonenko
76866ab901 http server example
allows to use both positional and named parameters
2015-12-09 21:00:47 +02:00
Chris Clark
20b647dfbf Merge pull request #667 from clark800/fix-server-info
BREAKING CHANGE: Fix types of XRP values in getServerInfo response
2015-12-07 17:03:09 -08:00
Chris Clark
99d08065e4 BREAKING CHANGE: Fix types of XRP values in getServerInfo response 2015-12-07 16:47:56 -08:00
Chris Clark
261fba3d21 Merge pull request #666 from clark800/fix-deliver-min
Fix DeliverMin value when specifying minAmount
2015-12-04 15:35:30 -08:00
Chris Clark
e1d9de7b1f Fix DeliverMin value when specifying minAmount 2015-12-04 15:18:03 -08:00
Chris Clark
391b2f3622 Merge pull request #665 from clark800/fix-quality
Fix parsing of quality for getTrustlines
2015-12-04 13:55:46 -08:00
Chris Clark
86ff315ef2 Fix parsing of quality for getTrustlines 2015-12-04 13:40:59 -08:00
Chris Clark
8d8a850864 Merge pull request #662 from lumberj/doc-fix
getFee returns a string not float
2015-12-02 13:32:11 -08:00
Alan Cohen
7bf2da6014 getFee returns a string not float
> api.connect().then(() => api.getFee().then(fee => console.log(typeof fee)));
> string
2015-12-02 13:17:35 -08:00
Chris Clark
7eae3ce709 Merge pull request #661 from clark800/client-cert
Add support for client certificates
2015-11-30 16:48:52 -08:00
Chris Clark
5f5e48e414 Add support for client certificates 2015-11-30 16:26:27 -08:00
Chris Clark
4f6a37f7b1 Merge pull request #660 from clark800/null-max
Allow setting maxLedgerVersion to null to specify no maximum
2015-11-30 16:13:41 -08:00
Chris Clark
82613e7e8b Allow setting maxLedgerVersion to null to specify no maximum 2015-11-30 15:52:36 -08:00
Chris Clark
588aa382a1 Merge pull request #659 from clark800/doc-fixes
Fix generateAddress docs and add error event listener to boilerplate
2015-11-30 15:32:02 -08:00
Chris Clark
809d981987 Fix generateAddress docs and add error event listener to boilerplate 2015-11-30 15:16:37 -08:00
Chris Clark
cfc21fde8c Bump version to 0.15.2 2015-11-25 13:13:53 -08:00
Geert Weening
5c06ef547b Merge pull request #658 from clark800/fix-proxy-auth
Fix support for proxy credentials in proxy URL and fix error when the…
2015-11-25 13:11:42 -08:00
Chris Clark
0990ad4a6f Fix support for proxy credentials in proxy URL and fix error when there are more than 10 outstanding requests 2015-11-25 12:53:50 -08:00
Chris Clark
d8f967d2b8 Bump version to 0.15.1 2015-11-25 11:46:33 -08:00
Geert Weening
fe1c3e7130 Merge pull request #657 from clark800/fix-polyfill
Fix babel-polyfill require
2015-11-25 11:38:52 -08:00
Chris Clark
062148674c Fix babel-polyfill require 2015-11-25 11:31:32 -08:00
Chris Clark
11320693fd Merge pull request #656 from clark800/fix-samples
Fix samples
2015-11-25 11:08:39 -08:00
Chris Clark
7af7eaccb4 Merge pull request #655 from darkdarkdragon/develop-RLJS-549-3
add unit tests for RippleAPIBroadcast
2015-11-25 11:04:55 -08:00
Chris Clark
5d5cf868a2 Fix samples 2015-11-25 10:26:30 -08:00
Ivan Tivonenko
ddf8fe5b1a add unit tests for RippleAPIBroadcast 2015-11-25 05:58:48 +02:00
Chris Clark
dc24f6afe0 Bump version to 0.15.0 2015-11-24 17:53:53 -08:00
Chris Clark
f7dac6ab25 Merge pull request #654 from clark800/mDuo13-docs-patch-3
Docs: SusPay warnings, offline mode, and other tweaks
2015-11-24 17:49:01 -08:00
mDuo13
4b4fc36ebd Docs: SusPay warnings, offline mode, and other tweaks 2015-11-24 17:37:52 -08:00
Chris Clark
7626ea5ed8 Merge pull request #653 from clark800/multi
BREAKING CHANGE: "servers" parameter changed to single "server" and a…
2015-11-24 17:08:58 -08:00
Chris Clark
7061e9afe4 BREAKING CHANGE: "servers" parameter changed to single "server" and added a new broadcast wrapper class for multiple servers 2015-11-24 16:47:12 -08:00
Chris Clark
a124635c2c Merge pull request #652 from darkdarkdragon/develop-RLJS-549-2
fix handling memos in prepareSettings
2015-11-24 16:33:43 -08:00
Ivan Tivonenko
c9704137b7 fix handling memos in prepareSettings
boost coverage back to 99.88%
move connection tests to separate file
group test fixtures into namespaces
2015-11-25 02:10:54 +02:00
Chris Clark
ab8d75d3cc Merge pull request #650 from clark800/cancel-doc-fix
Fix prepareOrderCancellation documentation
2015-11-24 14:32:24 -08:00
Chris Clark
5e720891f5 Fix prepareOrderCancellation documentation 2015-11-23 18:24:19 -08:00
80 changed files with 1878 additions and 812 deletions

View File

@@ -1,3 +1,37 @@
##0.16.0
**Breaking Changes**
+ [Fix types of XRP values in getServerInfo response](https://github.com/ripple/ripple-lib/commit/99d08065e4bda3dda6ae1f183adbd11abc70a9b7)
+ [Change error event format and fix crash due to error event on webscocket](https://github.com/ripple/ripple-lib/commit/9cd72595f0efc062d77b9d625695d6030c524cc6)
**Changes**
+ [Fix generateAddress docs and add error event listener to boilerplate](https://github.com/ripple/ripple-lib/commit/809d981987a2890fac3a73a40a05c598b9040334)
+ [Allow setting maxLedgerVersion to null to specify no maximum](https://github.com/ripple/ripple-lib/commit/82613e7e8b360d1ae1552eab4559ab4763c06d7e)
+ [Add support for client certificates](https://github.com/ripple/ripple-lib/commit/5f5e48e4140345d166b8c1a3ee0847b0d9e2d893)
+ [getFee returns a string not float](https://github.com/ripple/ripple-lib/commit/7bf2da6014c87e164542e69356efeaabb575a157)
+ [Fix parsing of quality for getTrustlines](https://github.com/ripple/ripple-lib/commit/86ff315ef2a39dfdc2ce97e0e1c4aa73f04e363b)
+ [Fix DeliverMin value when specifying minAmount](Fix DeliverMin value when specifying minAmount)
+ [http server example](https://github.com/ripple/ripple-lib/commit/76866ab901ea46a2dd73181605e0f7f4220043d4)
##0.15.2
**Changes**
+ [Fix support for proxy credentials in proxy URL and fix error when there are more than 10 outstanding requests](https://github.com/ripple/ripple-lib/commit/0990ad4a6f1d59ca9d2cb859b4e2d71693f3fc4b)
##0.15.1
**Changes**
+ [Fix babel-polyfill require](https://github.com/ripple/ripple-lib/commit/062148674c3b1293ab82c28e25615ddd530339fa)
+ [Fix samples](https://github.com/ripple/ripple-lib/commit/5d5cf868a2ddb1b1cd40e4a4f0a782d0066c2055)
+ [add unit tests for RippleAPIBroadcast](https://github.com/ripple/ripple-lib/commit/ddf8fe5b1a9c750490dca98fb9ffaaf8017f87e0)
##0.15.0
**Breaking Changes**
+ ["servers" parameter changed to single "server"](https://github.com/ripple/ripple-lib/commit/7061e9afe46f0682254d098adeff3dd7157521a1)
**Changes**
+ [fix handling memos in prepareSettings](https://github.com/ripple/ripple-lib/commit/c9704137b7b538e8dbf31c483bcdcf2dcfd7cd75)
+ [Docs: SusPay warnings, offline mode, and other tweaks](https://github.com/ripple/ripple-lib/commit/4b4fc36ebd93f1360781a65f2869bd2c4f0a5093)
+ [Fix prepareOrderCancellation documentation](https://github.com/ripple/ripple-lib/commit/5e720891f579fd73d43c64e5ec519d9121023c10)
##0.14.0
**Breaking Changes**
+ [prepareOrderCancellation now takes orderCancellation specification](https://github.com/ripple/ripple-lib/commit/7f33d8a71e56289e5a5e0ead1c74f75ebcde72bc)

View File

@@ -4,6 +4,7 @@
- [Introduction](#introduction)
- [Boilerplate](#boilerplate)
- [Offline functionality](#offline-functionality)
- [Basic Types](#basic-types)
- [Ripple Address](#ripple-address)
- [Account Sequence Number](#account-sequence-number)
@@ -83,7 +84,10 @@ Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_c
const {RippleAPI} = require('ripple-lib');
const api = new RippleAPI({
servers: ['wss://s1.ripple.com'] //Public rippled server hosted by Ripple, Inc.
server: 'wss://s1.ripple.com' // Public rippled server hosted by Ripple, Inc.
});
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.connect().then(() => {
/* insert code here */
@@ -94,7 +98,7 @@ api.connect().then(() => {
RippleAPI is designed to work in [NodeJS](https://nodejs.org) (version `0.12.0` or greater) using [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
The code samples in this documentation are written in ES6, but `RippleAPI` will work with ES5 also. Regardless of whether you use ES5 or ES6, the methods that return promises will return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise_).
The code samples in this documentation are written in ES6, but `RippleAPI` will work with ES5 also. Regardless of whether you use ES5 or ES6, the methods that return promises will return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
<aside class="notice">
All the code snippets in this documentation assume that you have surrounded them with this boilerplate.
@@ -104,19 +108,30 @@ All the code snippets in this documentation assume that you have surrounded them
If you omit the "catch" section, errors may not be visible.
</aside>
<aside class="notice">
The "error" event is emitted whenever an error occurs that cannot be associated with a specific request. If the listener is not registered, an exception will be thrown whenever the event is emitted.
</aside>
### Parameters
The RippleAPI constructor optionally takes one argument, an object with the following options:
Name | Type | Description
---- | ---- | -----------
authorization | string | *Optional* Username and password for HTTP basic authentication to the rippled server in the format **username:password**.
certificate | string | *Optional* A string containing the certificate key of the client in PEM format. (Can be an array of certificates).
feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`.
key | string | *Optional* A string containing the private key of the client in PEM format. (Can be an array of keys).
passphrase | string | *Optional* The passphrase for the private key of the client.
proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server.
proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**.
servers | array\<uri string\> | *Optional* Array of rippled servers to connect to. Currently only one server is supported.
server | uri string | *Optional* URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.
timeout | integer | *Optional* Timeout in milliseconds before considering a request to have failed.
trace | boolean | *Optional* If true, log rippled requests and responses to stdout.
trustedCertificates | array\<string\> | *Optional* Array of PEM-formatted SSL certificates to trust when connecting to a proxy. This is useful if you want to use a self-signed certificate on the proxy server. Note: Each element must contain a single certificate; concatenated certificates are not valid.
If you omit the `server` parameter, RippleAPI operates [offline](#offline-functionality).
### Installation ###
@@ -134,6 +149,34 @@ Instead of using babel-node in production, we recommend using Babel to transpile
</aside>
## Offline functionality
RippleAPI can also function without internet connectivity. This can be useful in order to generate secrets and sign transactions from a secure, isolated machine.
To instantiate RippleAPI in offline mode, use the following boilerplate code:
```javascript
const {RippleAPI} = require('ripple-lib');
const api = new RippleAPI();
/* insert code here */
```
Methods that depend on the state of the Ripple Consensus Ledger are unavailable in offline mode. To prepare transactions offline, you **must** specify the `fee`, `sequence`, and `maxLedgerVersion` parameters in the [transaction instructions](#transaction-instructions). The following methods should work offline:
* [preparePayment](#preparepayment)
* [prepareTrustline](#preparetrustline)
* [prepareOrder](#prepareorder)
* [prepareOrderCancellation](#prepareordercancellation)
* [prepareSettings](#preparesettings)
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
* [sign](#sign)
* [generateAddress](#generateaddress)
* [computeLedgerHash](#computeledgerhash)
# Basic Types
## Ripple Address
@@ -211,6 +254,8 @@ Type | Description
[suspendedPaymentCancellation](#suspended-payment-cancellation) | A `suspendedPaymentCancellation` transaction unlocks the funds in a suspended payment and sends them back to the creator of the suspended payment, but it will only work after the suspended payment expires.
[suspendedPaymentExecution](#suspended-payment-execution) | A `suspendedPaymentExecution` transaction unlocks the funds in a suspended payment and sends them to the destination of the suspended payment, but it will only work if the cryptographic condition is provided.
The three "suspended payment" transaction types are not supported by the production Ripple peer-to-peer network at this time. They are available for testing purposes if you [configure RippleAPI](#boilerplate) to connect to the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) instead.
## Transaction Flow
Executing a transaction with `RippleAPI` requires the following four steps:
@@ -242,11 +287,11 @@ Name | Type | Description
---- | ---- | -----------
fee | [value](#value) | *Optional* An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
maxFee | [value](#value) | *Optional* The maximum fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
maxLedgerVersionOffset | integer | *Optional* Offset from current legder version to highest ledger version that the transaction can be included in.
maxLedgerVersion | integer,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version.
maxLedgerVersionOffset | integer | *Optional* Offset from current validated legder version to highest ledger version that the transaction can be included in.
sequence | [sequence](#account-sequence-number) | *Optional* The initiating account's sequence number for this transaction.
We recommended that you specify a `maxLedgerVersion` because without it there is no way to know that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the network ledger version exceeds the transaction's `maxLedgerVersion`.
We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the network 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
@@ -281,12 +326,12 @@ Name | Type | Description
source | object | The source of the funds to be sent.
*source.* address | [address](#ripple-address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
destination | object | The destination of the funds to be sent.
*destination.* address | [address](#ripple-address) | The address to receive at.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with destination.minAmount).
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* address | [address](#ripple-address) | The address to send to.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
allowPartialPayment | boolean | *Optional* A boolean that, if set to true, indicates that this payment should go through even if the whole amount cannot be delivered because of a lack of liquidity or funds in the source account account
@@ -348,7 +393,14 @@ ripplingDisabled | boolean | *Optional* If true, payments cannot ripple through
"qualityIn": 0.91,
"qualityOut": 0.87,
"ripplingDisabled": true,
"frozen": false
"frozen": false,
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}
```
@@ -435,7 +487,14 @@ transferRate | number,null | *Optional* The fee to charge when users transfer t
```json
{
"domain": "ripple.com"
"domain": "ripple.com",
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}
```
@@ -449,11 +508,11 @@ Name | Type | Description
source | object | Fields pertaining to the source of the payment.
*source.* address | [address](#ripple-address) | The address to send from.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
destination | object | Fields pertaining to the destination of the payment.
*destination.* address | [address](#ripple-address) | The address to receive at.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with destination.minAmount).
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
allowCancelAfter | date-time string | *Optional* If present, the suspended payment may be cancelled after this time.
allowExecuteAfter | date-time string | *Optional* If present, the suspended payment can not be executed before this time.
digest | string | *Optional* If present, proof is required upon execution.
@@ -539,7 +598,7 @@ proof | string | *Optional* A value that produces the digest when hashed. It mus
`connect(): Promise<void>`
Tells the RippleAPI instance to connect to its server(s).
Tells the RippleAPI instance to connect to its rippled server.
### Parameters
@@ -557,7 +616,7 @@ See [Boilerplate](#boilerplate) for code sample.
`disconnect(): Promise<void>`
Tells the RippleAPI instance to disconnect from its server(s).
Tells the RippleAPI instance to disconnect from its rippled server.
### Parameters
@@ -575,7 +634,7 @@ See [Boilerplate](#boilerplate) for code sample
`isConnected(): boolean`
Checks if the RippleAPI instance is connected to its server(s).
Checks if the RippleAPI instance is connected to its rippled server.
### Parameters
@@ -624,10 +683,10 @@ pubkeyNode | string | Public key used to verify this node for internal communica
serverState | string | A string indicating to what extent the server is participating in the network. See [Possible Server States](https://ripple.com/build/rippled-apis/#possible-server-states) for more details.
validatedLedger | object | Information about the fully-validated ledger with the highest sequence number (the most recent).
*validatedLedger.* age | integer | The time since the ledger was closed, in seconds.
*validatedLedger.* baseFeeXRP | number | Base fee, in XRP. This may be represented in scientific notation such as 1e-05 for 0.00005.
*validatedLedger.* baseFeeXRP | [value](#value) | Base fee, in XRP. This may be represented in scientific notation such as 1e-05 for 0.00005.
*validatedLedger.* hash | string | Unique hash for the ledger, as an uppercase hexadecimal string.
*validatedLedger.* reserveBaseXRP | integer | Minimum amount of XRP necessary for every account to keep in reserve.
*validatedLedger.* reserveIncrementXRP | integer | Amount of XRP added to the account reserve for each object an account is responsible for in the ledger.
*validatedLedger.* reserveBaseXRP | [value](#value) | Minimum amount of XRP necessary for every account to keep in reserve.
*validatedLedger.* reserveIncrementXRP | [value](#value) | Amount of XRP added to the account reserve for each object an account is responsible for in the ledger.
*validatedLedger.* ledgerVersion | integer | Identifying sequence number of this ledger version.
validationQuorum | number | Minimum number of trusted validations required in order to validate a ledger version. Some circumstances may cause the server to require more validations.
load | object | *Optional* *(Admin only)* Detailed information about the current load state of the server.
@@ -658,10 +717,10 @@ return api.getServerInfo().then(info => {/* ... */});
"serverState": "full",
"validatedLedger": {
"age": 5,
"baseFeeXRP": 0.00001,
"baseFeeXRP": "0.00001",
"hash": "4482DEE5362332F54A4036ED57EE1767C9F33CF7CE5A6670355C16CECE381D46",
"reserveBaseXRP": 20,
"reserveIncrementXRP": 5,
"reserveBaseXRP": "20",
"reserveIncrementXRP": "5",
"ledgerVersion": 6595042
},
"validationQuorum": 3
@@ -673,7 +732,7 @@ return api.getServerInfo().then(info => {/* ... */});
`getFee(): Promise<number>`
Returns the estimated transaction fee for the server(s) the RippleAPI instance is connected to.
Returns the estimated transaction fee for the rippled server the RippleAPI instance is connected to.
### Parameters
@@ -681,7 +740,7 @@ This method has no parameters.
### Return Value
This method returns a promise that resolves with a floating point value representing the estimated fee to submit a transaction, expressed in XRP.
This method returns a promise that resolves with a string encoded floating point value representing the estimated fee to submit a transaction, expressed in XRP.
### Example
@@ -690,7 +749,7 @@ return api.getFee().then(fee => {/* ... */});
```
```json
0.012
"0.012"
```
## getLedgerVersion
@@ -1527,12 +1586,12 @@ Name | Type | Description
source | object | Properties of the source of the payment.
*source.* address | [address](#ripple-address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field is exclusive with source.amount)
destination | object | Properties of the destination of the payment.
*destination.* address | [address](#ripple-address) | The address to receive at.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field is exclusive with destination.minAmount).
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* address | [address](#ripple-address) | The address to send to.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field is exclusive with destination.amount)
paths | string | The paths of trustlines and orders to use in executing the payment.
@@ -2744,7 +2803,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -2813,7 +2872,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -2826,7 +2885,14 @@ const trustline = {
"qualityIn": 0.91,
"qualityOut": 0.87,
"ripplingDisabled": true,
"frozen": false
"frozen": false,
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
};
return api.preparePayment(address, trustline).then(prepared =>
{/* ... */});
@@ -2835,7 +2901,7 @@ return api.preparePayment(address, trustline).then(prepared =>
```json
{
"txJSON": "{\"Flags\":2149711872,\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"value\":\"10000\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"QualityIn\":910000000,\"QualityOut\":870000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"txJSON": "{\"TransactionType\":\"TrustSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"LimitAmount\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"10000\"},\"Flags\":2149711872,\"QualityIn\":910000000,\"QualityOut\":870000000,\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
@@ -2864,7 +2930,7 @@ instructions | [instructions](#transaction-instructions) | *Optional* Instructio
This method returns a promise that resolves with an object with the following structure:
<aside class="notice">
All "prepare" methods have the same return type.
All "prepare*" methods have the same return type.
</aside>
Name | Type | Description
@@ -2873,7 +2939,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -2912,7 +2978,7 @@ return api.prepareOrder(address, order)
## prepareOrderCancellation
`prepareOrderCancellation(address: string, sequence: number, instructions: Object): Promise<Object>`
`prepareOrderCancellation(address: string, orderCancellation: Object, instructions: Object): Promise<Object>`
Prepare an order cancellation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
@@ -2929,7 +2995,7 @@ instructions | [instructions](#transaction-instructions) | *Optional* Instructio
This method returns a promise that resolves with an object with the following structure:
<aside class="notice">
All "prepare" methods have the same return type.
All "prepare*" methods have the same return type.
</aside>
Name | Type | Description
@@ -2938,7 +3004,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -2990,14 +3056,21 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const settings = {
"domain": "ripple.com"
"domain": "ripple.com",
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
};
return api.prepareSettings(address, settings)
.then(prepared => {/* ... */});
@@ -3006,7 +3079,14 @@ return api.prepareSettings(address, settings)
```json
{
"domain": "ripple.com"
"domain": "ripple.com",
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}
```
@@ -3017,6 +3097,8 @@ return api.prepareSettings(address, settings)
Prepare a suspended payment creation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
Name | Type | Description
@@ -3039,7 +3121,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -3087,6 +3169,8 @@ return api.prepareSuspendedPaymentCreation(address, suspendedPaymentCreation).th
Prepare a suspended payment cancellation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
Name | Type | Description
@@ -3109,7 +3193,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -3142,6 +3226,8 @@ return api.prepareSuspendedPaymentCancellation(address, suspendedPaymentCancella
Prepare a suspended payment execution transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
Name | Type | Description
@@ -3164,7 +3250,7 @@ txJSON | string | The prepared transaction in rippled JSON format.
instructions | object | The instructions for how to execute the transaction after adding automatic defaults.
*instructions.* fee | [value](#value) | An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.
*instructions.* sequence | [sequence](#account-sequence-number) | The initiating account's sequence number for this transaction.
*instructions.* maxLedgerVersion | integer | *Optional* The highest ledger version that the transaction can be included in.
*instructions.* maxLedgerVersion | integer,null | The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum.
### Example
@@ -3279,7 +3365,11 @@ Generate a new Ripple address and corresponding secret.
### Parameters
This method has no parameters.
Name | Type | Description
---- | ---- | -----------
options | object | *Optional* Options to control how the address and secret are generated.
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
### Return Value
@@ -3293,8 +3383,7 @@ secret | secret string | The secret corresponding to the `address`.
### Example
```javascript
return api.generateAddress()
.then(result => {/* ... */});
return api.generateAddress();
```
@@ -3375,7 +3464,7 @@ Name | Type | Description
baseFeeXRP | [value](#value) | Base fee, in XRP.
ledgerHash | string | Unique hash of the ledger that was closed, as hex.
ledgerTimestamp | date-time string | The time at which this ledger closed.
reserveBaseXRP | [value](#value) | The minimum reserve, in drops of XRP, that is required for an account.
reserveBaseXRP | [value](#value) | The minimum reserve, in XRP, that is required for an account.
reserveIncrementXRP | [value](#value) | The increase in account reserve that is added for each item the account owns, such as offers or trust lines.
transactionCount | integer | Number of new transactions included in this ledger.
ledgerVersion | integer | Ledger version of the ledger that closed.
@@ -3406,16 +3495,26 @@ api.on('ledger', ledger => {
## error
This event is emitted when there is an error on the connection to the server.
This event is emitted when there is an error on the connection to the server that cannot be associated to a specific request.
### Return Value
The first parameter is a string indicating the error type, which may be `badMessage` (meaning that rippled returned a malformed message), or one of the [rippled Universal Errors](https://ripple.com/build/rippled-apis/#universal-errors). The second parameter is a message explaining the error, or the message that caused the error in the case of `badMessage`.
The first parameter is a string indicating the error type:
* `badMessage` - rippled returned a malformed message
* `websocket` - the websocket library emitted an error
* one of the error codes found in the [rippled Universal Errors](https://ripple.com/build/rippled-apis/#universal-errors).
The second parameter is a message explaining the error.
The third parameter is:
* the message that caused the error for `badMessage`
* the error object emitted for `websocket`
* the parsed response for rippled errors
### Example
```javascript
api.on('error', (errorCode, errorMessage) => {
api.on('error', (errorCode, errorMessage, data) => {
console.log(errorCode + ': ' + errorMessage);
});
```

View File

@@ -1,7 +1,7 @@
'use strict';
const RippleAPI = require('../../src').RippleAPI; // require('ripple-lib')
const api = new RippleAPI({servers: ['wss://s1.ripple.com:443']});
const api = new RippleAPI({server: 'wss://s1.ripple.com:443'});
const address = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
api.connect().then(() => {

View File

@@ -4,13 +4,13 @@ const RippleAPI = require('../../src').RippleAPI; // require('ripple-lib')
const address = 'INSERT ADDRESS HERE';
const secret = 'INSERT SECRET HERE';
const api = new RippleAPI({servers: ['wss://s1.ripple.com:443']});
const api = new RippleAPI({server: 'wss://s1.ripple.com:443'});
const instructions = {maxLedgerVersionOffset: 5};
const payment = {
source: {
address: address,
amount: {
maxAmount: {
value: '0.01',
currency: 'XRP'
}

View File

@@ -6,7 +6,10 @@ Use the following [boilerplate code](https://en.wikipedia.org/wiki/Boilerplate_c
const {RippleAPI} = require('ripple-lib');
const api = new RippleAPI({
servers: ['wss://s1.ripple.com'] //Public rippled server hosted by Ripple, Inc.
server: 'wss://s1.ripple.com' // Public rippled server hosted by Ripple, Inc.
});
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.connect().then(() => {
/* insert code here */
@@ -17,7 +20,7 @@ api.connect().then(() => {
RippleAPI is designed to work in [NodeJS](https://nodejs.org) (version `0.12.0` or greater) using [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
The code samples in this documentation are written in ES6, but `RippleAPI` will work with ES5 also. Regardless of whether you use ES5 or ES6, the methods that return promises will return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise_).
The code samples in this documentation are written in ES6, but `RippleAPI` will work with ES5 also. Regardless of whether you use ES5 or ES6, the methods that return promises will return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
<aside class="notice">
All the code snippets in this documentation assume that you have surrounded them with this boilerplate.
@@ -27,10 +30,18 @@ All the code snippets in this documentation assume that you have surrounded them
If you omit the "catch" section, errors may not be visible.
</aside>
<aside class="notice">
The "error" event is emitted whenever an error occurs that cannot be associated with a specific request. If the listener is not registered, an exception will be thrown whenever the event is emitted.
</aside>
### Parameters
The RippleAPI constructor optionally takes one argument, an object with the following options:
<%- renderSchema('input/api-options.json') %>
If you omit the `server` parameter, RippleAPI operates [offline](#offline-functionality).
### Installation ###

View File

@@ -2,7 +2,7 @@
`connect(): Promise<void>`
Tells the RippleAPI instance to connect to its server(s).
Tells the RippleAPI instance to connect to its rippled server.
### Parameters

View File

@@ -2,7 +2,7 @@
`disconnect(): Promise<void>`
Tells the RippleAPI instance to disconnect from its server(s).
Tells the RippleAPI instance to disconnect from its rippled server.
### Parameters

View File

@@ -20,16 +20,26 @@ api.on('ledger', ledger => {
## error
This event is emitted when there is an error on the connection to the server.
This event is emitted when there is an error on the connection to the server that cannot be associated to a specific request.
### Return Value
The first parameter is a string indicating the error type, which may be `badMessage` (meaning that rippled returned a malformed message), or one of the [rippled Universal Errors](https://ripple.com/build/rippled-apis/#universal-errors). The second parameter is a message explaining the error, or the message that caused the error in the case of `badMessage`.
The first parameter is a string indicating the error type:
* `badMessage` - rippled returned a malformed message
* `websocket` - the websocket library emitted an error
* one of the error codes found in the [rippled Universal Errors](https://ripple.com/build/rippled-apis/#universal-errors).
The second parameter is a message explaining the error.
The third parameter is:
* the message that caused the error for `badMessage`
* the error object emitted for `websocket`
* the parsed response for rippled errors
### Example
```javascript
api.on('error', (errorCode, errorMessage) => {
api.on('error', (errorCode, errorMessage, data) => {
console.log(errorCode + ': ' + errorMessage);
});
```

View File

@@ -6,7 +6,7 @@ Generate a new Ripple address and corresponding secret.
### Parameters
This method has no parameters.
<%- renderSchema('input/generate-address.json') %>
### Return Value
@@ -17,8 +17,7 @@ This method returns an object with the following structure:
### Example
```javascript
return api.generateAddress()
.then(result => {/* ... */});
return api.generateAddress();
```
<%- renderFixture('responses/generate-address.json') %>

View File

@@ -2,7 +2,7 @@
`getFee(): Promise<number>`
Returns the estimated transaction fee for the server(s) the RippleAPI instance is connected to.
Returns the estimated transaction fee for the rippled server the RippleAPI instance is connected to.
### Parameters
@@ -10,7 +10,7 @@ This method has no parameters.
### Return Value
This method returns a promise that resolves with a floating point value representing the estimated fee to submit a transaction, expressed in XRP.
This method returns a promise that resolves with a string encoded floating point value representing the estimated fee to submit a transaction, expressed in XRP.
### Example
@@ -19,5 +19,5 @@ return api.getFee().then(fee => {/* ... */});
```
```json
0.012
"0.012"
```

View File

@@ -1,5 +1,6 @@
<% include introduction.md.ejs %>
<% include boilerplate.md.ejs %>
<% include offline.md.ejs %>
<% include basictypes.md.ejs %>
<% include transactions.md.ejs %>
<% include specifications.md.ejs %>

View File

@@ -2,7 +2,7 @@
`isConnected(): boolean`
Checks if the RippleAPI instance is connected to its server(s).
Checks if the RippleAPI instance is connected to its rippled server.
### Parameters

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

@@ -0,0 +1,27 @@
## Offline functionality
RippleAPI can also function without internet connectivity. This can be useful in order to generate secrets and sign transactions from a secure, isolated machine.
To instantiate RippleAPI in offline mode, use the following boilerplate code:
```javascript
const {RippleAPI} = require('ripple-lib');
const api = new RippleAPI();
/* insert code here */
```
Methods that depend on the state of the Ripple Consensus Ledger are unavailable in offline mode. To prepare transactions offline, you **must** specify the `fee`, `sequence`, and `maxLedgerVersion` parameters in the [transaction instructions](#transaction-instructions). The following methods should work offline:
* [preparePayment](#preparepayment)
* [prepareTrustline](#preparetrustline)
* [prepareOrder](#prepareorder)
* [prepareOrderCancellation](#prepareordercancellation)
* [prepareSettings](#preparesettings)
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
* [sign](#sign)
* [generateAddress](#generateaddress)
* [computeLedgerHash](#computeledgerhash)

View File

@@ -13,7 +13,7 @@ Prepare an order transaction. The prepared transaction must subsequently be [sig
This method returns a promise that resolves with an object with the following structure:
<aside class="notice">
All "prepare" methods have the same return type.
All "prepare*" methods have the same return type.
</aside>
<%- renderSchema('output/prepare.json') %>

View File

@@ -1,6 +1,6 @@
## prepareOrderCancellation
`prepareOrderCancellation(address: string, sequence: number, instructions: Object): Promise<Object>`
`prepareOrderCancellation(address: string, orderCancellation: Object, instructions: Object): Promise<Object>`
Prepare an order cancellation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
@@ -13,7 +13,7 @@ Prepare an order cancellation transaction. The prepared transaction must subsequ
This method returns a promise that resolves with an object with the following structure:
<aside class="notice">
All "prepare" methods have the same return type.
All "prepare*" methods have the same return type.
</aside>
<%- renderSchema("output/prepare.json") %>

View File

@@ -4,6 +4,8 @@
Prepare a suspended payment cancellation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
<%- renderSchema('input/prepare-suspended-payment-cancellation.json') %>

View File

@@ -4,6 +4,8 @@
Prepare a suspended payment creation transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
<%- renderSchema('input/prepare-suspended-payment-creation.json') %>

View File

@@ -4,6 +4,8 @@
Prepare a suspended payment execution transaction. The prepared transaction must subsequently be [signed](#sign) and [submitted](#submit).
**Caution:** Suspended Payments are currently available on the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) only.
### Parameters
<%- renderSchema('input/prepare-suspended-payment-execution.json') %>

View File

@@ -15,6 +15,8 @@ Type | Description
[suspendedPaymentCancellation](#suspended-payment-cancellation) | A `suspendedPaymentCancellation` transaction unlocks the funds in a suspended payment and sends them back to the creator of the suspended payment, but it will only work after the suspended payment expires.
[suspendedPaymentExecution](#suspended-payment-execution) | A `suspendedPaymentExecution` transaction unlocks the funds in a suspended payment and sends them to the destination of the suspended payment, but it will only work if the cryptographic condition is provided.
The three "suspended payment" transaction types are not supported by the production Ripple peer-to-peer network at this time. They are available for testing purposes if you [configure RippleAPI](#boilerplate) to connect to the [Ripple Test Net](https://ripple.com/build/ripple-test-net/) instead.
## Transaction Flow
Executing a transaction with `RippleAPI` requires the following four steps:
@@ -44,7 +46,7 @@ Transaction instructions indicate how to execute a transaction, complementary wi
<%- renderSchema("objects/instructions.json") %>
We recommended that you specify a `maxLedgerVersion` because without it there is no way to know that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the network ledger version exceeds the transaction's `maxLedgerVersion`.
We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the network 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

40
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.14.0",
"version": "0.16.0",
"dependencies": {
"ajv": {
"version": "1.4.8",
@@ -91,6 +91,44 @@
}
}
},
"jayson": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz",
"dependencies": {
"JSONStream": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz",
"dependencies": {
"jsonparse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz"
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
}
}
},
"commander": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz",
"dependencies": {
"keypress": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz"
}
}
},
"eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
},
"lodash": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz"
}
}
},
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.1.0 <4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.14.0",
"version": "0.16.0",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -20,6 +20,7 @@
"babel-runtime": "^5.5.4",
"bignumber.js": "^2.0.3",
"https-proxy-agent": "^1.0.0",
"jayson": "^1.2.2",
"lodash": "^3.1.0",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.1.0",
@@ -65,7 +66,8 @@
"test": "istanbul test _mocha",
"coveralls": "cat ./coverage/lcov.info | coveralls",
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'parser: babel-eslint' >> eslintrc; fi; eslint -c eslintrc src/",
"perf": "./scripts/perf_test.sh"
"perf": "./scripts/perf_test.sh",
"start": "babel-node scripts/http.js"
},
"repository": {
"type": "git",

View File

@@ -34,6 +34,7 @@ unittest() {
integrationtest() {
mocha test/integration/integration-test.js
mocha test/integration/http-integration-test.js
}
doctest() {

16
scripts/http.js Normal file
View File

@@ -0,0 +1,16 @@
'use strict';
const createHTTPServer = require('../src/index').createHTTPServer;
const port = 5990;
const serverUrl = 'wss://s1.ripple.com';
function main() {
const server = createHTTPServer({server: serverUrl}, port);
server.start().then(() => {
console.log('Server started on port ' + String(port));
});
}
main();

145
src/api.js Normal file
View File

@@ -0,0 +1,145 @@
/* @flow */
'use strict';
/* eslint-disable max-len */
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
// https://github.com/zloirock/core-js/blob/fb0890f32dabe8d4d88a4350d1b268446127132e/shim.js#L1-L103
/* eslint-enable max-len */
// In node.js env, polyfill might be already loaded (from any npm package),
// that's why we do this check.
if (!global._babelPolyfill) {
require('babel-polyfill');
}
const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const common = require('./common');
const server = require('./server/server');
const connect = server.connect;
const disconnect = server.disconnect;
const getServerInfo = server.getServerInfo;
const getFee = server.getFee;
const isConnected = server.isConnected;
const getLedgerVersion = server.getLedgerVersion;
const getTransaction = require('./ledger/transaction');
const getTransactions = require('./ledger/transactions');
const getTrustlines = require('./ledger/trustlines');
const getBalances = require('./ledger/balances');
const getBalanceSheet = require('./ledger/balance-sheet');
const getPaths = require('./ledger/pathfind');
const getOrders = require('./ledger/orders');
const getOrderbook = require('./ledger/orderbook');
const getSettings = require('./ledger/settings');
const getAccountInfo = require('./ledger/accountinfo');
const preparePayment = require('./transaction/payment');
const prepareTrustline = require('./transaction/trustline');
const prepareOrder = require('./transaction/order');
const prepareOrderCancellation = require('./transaction/ordercancellation');
const prepareSuspendedPaymentCreation =
require('./transaction/suspended-payment-creation');
const prepareSuspendedPaymentExecution =
require('./transaction/suspended-payment-execution');
const prepareSuspendedPaymentCancellation =
require('./transaction/suspended-payment-cancellation');
const prepareSettings = require('./transaction/settings');
const sign = require('./transaction/sign');
const submit = require('./transaction/submit');
const errors = require('./common').errors;
const generateAddress =
require('./offline/generate-address').generateAddressAPI;
const computeLedgerHash = require('./offline/ledgerhash');
const getLedger = require('./ledger/ledger');
type APIOptions = {
server?: string,
feeCushion?: number,
trace?: boolean,
proxy?: string,
timeout?: number
}
// prevent access to non-validated ledger versions
class RestrictedConnection extends common.Connection {
request(request, timeout) {
const ledger_index = request.ledger_index;
if (ledger_index !== undefined && ledger_index !== 'validated') {
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
return Promise.reject(new errors.LedgerVersionError(
`ledgerVersion ${ledger_index} is greater than server\'s ` +
`most recent validated ledger: ${this._ledgerVersion}`));
}
}
return super.request(request, timeout);
}
}
class RippleAPI extends EventEmitter {
constructor(options: APIOptions = {}) {
common.validate.apiOptions(options);
super();
this._feeCushion = options.feeCushion || 1.2;
const serverURL = options.server;
if (serverURL !== undefined) {
this.connection = new RestrictedConnection(serverURL, options);
this.connection.on('ledgerClosed', message => {
this.emit('ledger', server.formatLedgerClose(message));
});
this.connection.on('error', (errorCode, errorMessage, data) => {
this.emit('error', errorCode, errorMessage, data);
});
} else {
// use null object pattern to provide better error message if user
// tries to call a method that requires a connection
this.connection = new RestrictedConnection(null, options);
}
}
}
_.assign(RippleAPI.prototype, {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion,
getTransaction,
getTransactions,
getTrustlines,
getBalances,
getBalanceSheet,
getPaths,
getOrders,
getOrderbook,
getSettings,
getAccountInfo,
getLedger,
preparePayment,
prepareTrustline,
prepareOrder,
prepareOrderCancellation,
prepareSuspendedPaymentCreation,
prepareSuspendedPaymentExecution,
prepareSuspendedPaymentCancellation,
prepareSettings,
sign,
submit,
generateAddress,
computeLedgerHash,
errors
});
// these are exposed only for use by unit tests; they are not part of the API
RippleAPI._PRIVATE = {
validate: common.validate,
RangeSet: require('./common/rangeset').RangeSet,
ledgerUtils: require('./ledger/utils'),
schemaValidator: require('./common/schema-validator')
};
module.exports = {
RippleAPI
};

67
src/broadcast.js Normal file
View File

@@ -0,0 +1,67 @@
'use strict';
const _ = require('lodash');
const RippleAPI = require('./api').RippleAPI;
class RippleAPIBroadcast extends RippleAPI {
constructor(servers, options) {
super(options);
this.ledgerVersion = 0;
const apis = servers.map(server => new RippleAPI(
_.assign({}, options, {server})
));
this.getMethodNames().forEach(name => {
this[name] = function() { // eslint-disable-line no-loop-func
return Promise.race(apis.map(api => api[name].apply(api, arguments)));
};
});
// connection methods must be overridden to apply to all api instances
this.connect = function() {
return Promise.all(apis.map(api => api.connect()));
};
this.disconnect = function() {
return Promise.all(apis.map(api => api.disconnect()));
};
this.isConnected = function() {
return _.every(apis.map(api => api.isConnected()));
};
// synchronous methods are all passed directly to the first api instance
const defaultAPI = apis[0];
const syncMethods = ['sign', 'generateAddress', 'computeLedgerHash'];
syncMethods.forEach(name => {
this[name] = defaultAPI[name].bind(defaultAPI);
});
apis.forEach(api => {
api.on('ledger', this.onLedgerEvent.bind(this));
api.on('error', (errorCode, errorMessage, data) =>
this.emit('error', errorCode, errorMessage, data));
});
}
onLedgerEvent(ledger) {
if (ledger.ledgerVersion > this.ledgerVersion) {
this.ledgerVersion = ledger.ledgerVersion;
this.emit('ledger', ledger);
}
}
getMethodNames() {
const methodNames = [];
for (const name in RippleAPI.prototype) {
if (RippleAPI.prototype.hasOwnProperty(name)) {
if (typeof RippleAPI.prototype[name] === 'function') {
methodNames.push(name);
}
}
}
return methodNames;
}
}
module.exports = {
RippleAPIBroadcast
};

View File

@@ -1,4 +1,5 @@
'use strict';
const _ = require('lodash');
const {EventEmitter} = require('events');
const WebSocket = require('ws');
const parseURL = require('url').parse;
@@ -15,12 +16,20 @@ function isStreamMessageType(type) {
class Connection extends EventEmitter {
constructor(url, options = {}) {
super();
this.setMaxListeners(Infinity);
this._url = url;
this._trace = options.trace;
if (this._trace) {
// for easier unit testing
this._console = console;
}
this._proxyURL = options.proxy;
this._proxyAuthorization = options.proxyAuthorization;
this._authorization = options.authorization;
this._trustedCertificates = options.trustedCertificates;
this._key = options.key;
this._passphrase = options.passphrase;
this._certificate = options.certificate;
this._timeout = options.timeout || (20 * 1000);
this._isReady = false;
this._ws = null;
@@ -46,7 +55,7 @@ class Connection extends EventEmitter {
}
return [data.type, data];
} else if (data.type === undefined && data.error) {
return ['error', data.error, data.error_message]; // e.g. slowDown
return ['error', data.error, data.error_message, data]; // e.g. slowDown
}
throw new ResponseFormatError('unrecognized message type: ' + data.type);
}
@@ -54,12 +63,12 @@ class Connection extends EventEmitter {
_onMessage(message) {
let parameters;
if (this._trace) {
console.log(message);
this._console.log(message);
}
try {
parameters = this._parseMessage(message);
} catch (error) {
this.emit('error', 'badMessage', message);
this.emit('error', 'badMessage', error.message, message);
return;
}
// we don't want this inside the try/catch or exceptions in listener
@@ -99,18 +108,21 @@ class Connection extends EventEmitter {
});
}
_createWebSocket(url, proxyURL, proxyAuthorization, authorization,
trustedCertificates) {
_createWebSocket() {
const options = {};
if (proxyURL !== undefined) {
const parsedURL = parseURL(url);
const proxyOptions = parseURL(proxyURL);
proxyOptions.secureEndpoint = (parsedURL.protocol === 'wss:');
proxyOptions.secureProxy = (proxyOptions.protocol === 'https:');
proxyOptions.auth = proxyAuthorization;
if (trustedCertificates) {
proxyOptions.ca = trustedCertificates;
}
if (this._proxyURL !== undefined) {
const parsedURL = parseURL(this._url);
const parsedProxyURL = parseURL(this._proxyURL);
const proxyOverrides = _.omit({
secureEndpoint: (parsedURL.protocol === 'wss:'),
secureProxy: (parsedProxyURL.protocol === 'https:'),
auth: this._proxyAuthorization,
ca: this._trustedCertificates,
key: this._key,
passphrase: this._passphrase,
cert: this._certificate
}, _.isUndefined);
const proxyOptions = _.assign({}, parsedProxyURL, proxyOverrides);
let HttpsProxyAgent;
try {
HttpsProxyAgent = require('https-proxy-agent');
@@ -119,11 +131,22 @@ class Connection extends EventEmitter {
}
options.agent = new HttpsProxyAgent(proxyOptions);
}
if (authorization !== undefined) {
const base64 = new Buffer(authorization).toString('base64');
if (this._authorization !== undefined) {
const base64 = new Buffer(this._authorization).toString('base64');
options.headers = {Authorization: `Basic ${base64}`};
}
return new WebSocket(url, options);
const optionsOverrides = _.omit({
ca: this._trustedCertificates,
key: this._key,
passphrase: this._passphrase,
cert: this._certificate
}, _.isUndefined);
const websocketOptions = _.assign({}, options, optionsOverrides);
const websocket = new WebSocket(this._url, websocketOptions);
// we will have a listener for each outstanding request,
// so we have to raise the limit (the default is 10)
websocket.setMaxListeners(Infinity);
return websocket;
}
connect() {
@@ -137,9 +160,13 @@ class Connection extends EventEmitter {
} else if (this._state === WebSocket.CONNECTING) {
this._ws.once('open', resolve);
} else {
this._ws = this._createWebSocket(this._url, this._proxyURL,
this._proxyAuthorization, this._authorization,
this._trustedCertificates);
this._ws = this._createWebSocket();
// when an error causes the connection to close, the close event
// should still be emitted; the "ws" documentation says: "The close
// event is also emitted when then underlying net.Socket closes the
// connection (end or close)."
this._ws.on('error', error =>
this.emit('error', 'websocket', error.messsage, error));
this._ws.on('message', this._onMessage.bind(this));
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this);
this._ws.once('close', this._onUnexpectedCloseBound);
@@ -198,7 +225,7 @@ class Connection extends EventEmitter {
_send(message) {
if (this._trace) {
console.log(message);
this._console.log(message);
}
return new Promise((resolve, reject) => {
this._ws.send(message, undefined, (error, result) => {

View File

@@ -92,8 +92,9 @@ function loadSchemas() {
require('./schemas/input/prepare-suspended-payment-cancellation.json'),
require('./schemas/input/prepare-suspended-payment-execution.json'),
require('./schemas/input/compute-ledger-hash'),
require('./schemas/input/sign'),
require('./schemas/input/submit')
require('./schemas/input/sign.json'),
require('./schemas/input/submit.json'),
require('./schemas/input/generate-address.json')
];
const titles = _.map(schemas, schema => schema.title);
const duplicates = _.keys(_.pick(_.countBy(titles), count => count > 1));

View File

@@ -12,15 +12,11 @@
"minimum": 1,
"description": "Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`."
},
"servers": {
"type": "array",
"description": "Array of rippled servers to connect to. Currently only one server is supported.",
"items": {
"type": "string",
"description": "URI for rippled websocket port. Must start with `wss://` or `ws://`.",
"format": "uri",
"pattern": "^wss?://"
}
"server": {
"type": "string",
"description": "URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.",
"format": "uri",
"pattern": "^wss?://"
},
"proxy": {
"format": "uri",
@@ -46,6 +42,18 @@
"type": "string",
"description": "A PEM-formatted SSL certificate to trust when connecting to a proxy."
}
},
"key": {
"type": "string",
"description": "A string containing the private key of the client in PEM format. (Can be an array of keys)."
},
"passphrase": {
"type": "string",
"description": "The passphrase for the private key of the client."
},
"certificate": {
"type": "string",
"description": "A string containing the certificate key of the client in PEM format. (Can be an array of certificates)."
}
},
"additionalProperties": false

View File

@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "generateAddressParameters",
"type": "object",
"properties": {
"options": {
"type": "object",
"description": "Options to control how the address and secret are generated.",
"properties": {
"entropy": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 255
},
"description": "The entropy to use to generate the seed."
},
"algorithm": {
"type": "string",
"enum": ["ecdsa-secp256k1", "ed25519"],
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}

View File

@@ -18,11 +18,14 @@
"$ref": "value"
},
"maxLedgerVersion": {
"description": "The highest ledger version that the transaction can be included in.",
"$ref": "ledgerVersion"
"description": "The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version.",
"oneOf": [
{"$ref": "ledgerVersion"},
{"type": "null"}
]
},
"maxLedgerVersionOffset": {
"description": "Offset from current legder version to highest ledger version that the transaction can be included in.",
"description": "Offset from current validated legder version to highest ledger version that the transaction can be included in.",
"type": "integer",
"minimum": 0
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "tag",
"description": "An arbitrary unsigned 32-bit integer most commonly used to identify a non-Ripple account.",
"description": "An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.",
"type": "integer",
"$ref": "uint32"
}

View File

@@ -83,7 +83,7 @@
"description": "The time since the ledger was closed, in seconds."
},
"baseFeeXRP": {
"type": "number",
"$ref": "value",
"description": "Base fee, in XRP. This may be represented in scientific notation such as 1e-05 for 0.00005."
},
"hash": {
@@ -91,13 +91,11 @@
"description": "Unique hash for the ledger, as an uppercase hexadecimal string."
},
"reserveBaseXRP": {
"type": "integer",
"minimum": 0,
"$ref": "value",
"description": "Minimum amount of XRP necessary for every account to keep in reserve."
},
"reserveIncrementXRP": {
"type": "integer",
"minimum": 0,
"$ref": "value",
"description": "Amount of XRP added to the account reserve for each object an account is responsible for in the ledger."
},
"ledgerVersion": {

View File

@@ -23,7 +23,7 @@
},
"reserveBaseXRP": {
"$ref": "value",
"description": "The minimum reserve, in drops of XRP, that is required for an account."
"description": "The minimum reserve, in XRP, that is required for an account."
},
"reserveIncrementXRP": {
"$ref": "value",

View File

@@ -21,12 +21,15 @@
"description": "The initiating account's sequence number for this transaction."
},
"maxLedgerVersion": {
"$ref": "ledgerVersion",
"description": "The highest ledger version that the transaction can be included in."
"oneOf": [
{"$ref": "ledgerVersion"},
{"type": "null"}
],
"description": "The highest ledger version that the transaction can be included in. Set to `null` if there is no maximum."
}
},
"additionalProperties": false,
"required": ["fee", "sequence"]
"required": ["fee", "sequence", "maxLedgerVersion"]
}
},
"additionalProperties": false,

View File

@@ -6,7 +6,7 @@ import type {Connection} from './connection';
export type GetServerInfoResponse = {
buildVersion: string,
completeLedgers: string,
hostid: string,
hostID: string,
ioLatencyMs: number,
load?: {
jobTypes: Array<Object>,
@@ -23,11 +23,11 @@ export type GetServerInfoResponse = {
serverState: string,
validatedLedger: {
age: number,
baseFeeXrp: number,
baseFeeXRP: string,
hash: string,
reserveBaseXrp: number,
reserveIncXrp: number,
seq: number
reserveBaseXRP: string,
reserveIncrementXRP: string,
ledgerVersion: number
},
validationQuorum: number
}
@@ -49,6 +49,12 @@ function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
reserveIncXrp: 'reserveIncrementXRP',
seq: 'ledgerVersion'
});
info.validatedLedger.baseFeeXRP =
info.validatedLedger.baseFeeXRP.toString();
info.validatedLedger.reserveBaseXRP =
info.validatedLedger.reserveBaseXRP.toString();
info.validatedLedger.reserveIncrementXRP =
info.validatedLedger.reserveIncrementXRP.toString();
return info;
});
}

View File

@@ -2,8 +2,6 @@
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const errors = require('./errors');
const keypairs = require('ripple-keypairs');
const {deriveKeypair} = require('ripple-keypairs');
import type {Amount, RippledAmount} from './types.js';
@@ -37,21 +35,6 @@ function toRippledAmount(amount: Amount): RippledAmount {
};
}
function generateAddress(options?: Object): Object {
const secret = keypairs.generateSeed(options);
const keypair = keypairs.deriveKeypair(secret);
const address = keypairs.deriveAddress(keypair.publicKey);
return {secret, address};
}
function generateAddressAPI(options?: Object): Object {
try {
return generateAddress(options);
} catch (error) {
throw new errors.UnexpectedError(error.message);
}
}
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g;
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
@@ -101,8 +84,6 @@ module.exports = {
dropsToXrp,
xrpToDrops,
toRippledAmount,
generateAddress,
generateAddressAPI,
convertKeysFromSnakeCaseToCamelCase,
removeUndefined,
rippleTimeToISO8601,

View File

@@ -49,6 +49,7 @@ module.exports = {
sign: _.partial(schemaValidate, 'signParameters'),
submit: _.partial(schemaValidate, 'submitParameters'),
computeLedgerHash: _.partial(schemaValidate, 'computeLedgerHashParameters'),
generateAddress: _.partial(schemaValidate, 'generateAddressParameters'),
apiOptions: _.partial(schemaValidate, 'api-options'),
instructions: _.partial(schemaValidate, 'instructions')
};

85
src/http.js Normal file
View File

@@ -0,0 +1,85 @@
/* eslint-disable new-cap */
'use strict';
const assert = require('assert-diff');
const _ = require('lodash');
const jayson = require('jayson');
const RippleAPI = require('./api').RippleAPI;
function createHTTPServer(options, httpPort) {
const rippleAPI = new RippleAPI(options);
const methodNames = _.filter(_.keys(RippleAPI.prototype), k => {
return typeof RippleAPI.prototype[k] === 'function'
&& k !== 'connect'
&& k !== 'disconnect'
&& k !== 'constructor'
&& k !== 'RippleAPI';
});
function applyPromiseWithCallback(fnName, callback, args_) {
try {
let args = args_;
if (!_.isArray(args_)) {
const fnParameters = jayson.Utils.getParameterNames(rippleAPI[fnName]);
args = fnParameters.map(name => args_[name]);
const defaultArgs = _.omit(args_, fnParameters);
assert(_.size(defaultArgs) <= 1,
'Function must have no more than one default argument');
if (_.size(defaultArgs) > 0) {
args.push(defaultArgs[_.keys(defaultArgs)[0]]);
}
}
Promise.resolve(rippleAPI[fnName].apply(rippleAPI, args))
.then(res => callback(null, res))
.catch(err => {
callback({code: 99, message: err.message, data: {name: err.name}});
});
} catch (err) {
callback({code: 99, message: err.message, data: {name: err.name}});
}
}
const methods = {};
_.forEach(methodNames, fn => {
methods[fn] = jayson.Method((args, cb) => {
applyPromiseWithCallback(fn, cb, args);
}, {collect: true});
});
const server = jayson.server(methods);
let httpServer = null;
return {
server: server,
start: function() {
if (httpServer !== null) {
return Promise.reject('Already started');
}
return new Promise((resolve) => {
rippleAPI.connect().then(() => {
httpServer = server.http();
httpServer.listen(httpPort, resolve);
});
});
},
stop: function() {
if (httpServer === null) {
return Promise.reject('Not started');
}
return new Promise((resolve) => {
rippleAPI.disconnect();
httpServer.close(() => {
httpServer = null;
resolve();
});
});
}
};
}
module.exports = {
createHTTPServer
};

View File

@@ -1,146 +1,9 @@
/* @flow */
'use strict';
/* eslint-disable max-len */
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
// https://github.com/zloirock/core-js/blob/fb0890f32dabe8d4d88a4350d1b268446127132e/shim.js#L1-L103
/* eslint-enable max-len */
// In node.js env, polyfill might be already loaded (from any npm package),
// that's why we do this check.
if (!global._babelPolyfill) {
require('babel-core/polyfill');
}
const _ = require('lodash');
const EventEmitter = require('events').EventEmitter;
const common = require('./common');
const server = require('./server/server');
const connect = server.connect;
const disconnect = server.disconnect;
const getServerInfo = server.getServerInfo;
const getFee = server.getFee;
const isConnected = server.isConnected;
const getLedgerVersion = server.getLedgerVersion;
const getTransaction = require('./ledger/transaction');
const getTransactions = require('./ledger/transactions');
const getTrustlines = require('./ledger/trustlines');
const getBalances = require('./ledger/balances');
const getBalanceSheet = require('./ledger/balance-sheet');
const getPaths = require('./ledger/pathfind');
const getOrders = require('./ledger/orders');
const getOrderbook = require('./ledger/orderbook');
const getSettings = require('./ledger/settings');
const getAccountInfo = require('./ledger/accountinfo');
const preparePayment = require('./transaction/payment');
const prepareTrustline = require('./transaction/trustline');
const prepareOrder = require('./transaction/order');
const prepareOrderCancellation = require('./transaction/ordercancellation');
const prepareSuspendedPaymentCreation =
require('./transaction/suspended-payment-creation');
const prepareSuspendedPaymentExecution =
require('./transaction/suspended-payment-execution');
const prepareSuspendedPaymentCancellation =
require('./transaction/suspended-payment-cancellation');
const prepareSettings = require('./transaction/settings');
const sign = require('./transaction/sign');
const submit = require('./transaction/submit');
const errors = require('./common').errors;
const generateAddress = common.generateAddressAPI;
const computeLedgerHash = require('./offline/ledgerhash');
const getLedger = require('./ledger/ledger');
type APIOptions = {
servers?: Array<string>,
feeCushion?: number,
trace?: boolean,
proxy?: string,
timeout?: number
}
// prevent access to non-validated ledger versions
class RestrictedConnection extends common.Connection {
request(request, timeout) {
const ledger_index = request.ledger_index;
if (ledger_index !== undefined && ledger_index !== 'validated') {
if (!_.isNumber(ledger_index) || ledger_index > this._ledgerVersion) {
return Promise.reject(new errors.LedgerVersionError(
`ledgerVersion ${ledger_index} is greater than server\'s ` +
`most recent validated ledger: ${this._ledgerVersion}`));
}
}
return super.request(request, timeout);
}
}
class RippleAPI extends EventEmitter {
constructor(options: APIOptions = {}) {
common.validate.apiOptions(options);
super();
this._feeCushion = options.feeCushion || 1.2;
if (options.servers !== undefined) {
const servers: Array<string> = options.servers;
if (servers.length === 1) {
this.connection = new RestrictedConnection(servers[0], options);
this.connection.on('ledgerClosed', message => {
this.emit('ledger', server.formatLedgerClose(message));
});
this.connection.on('error', (type, info) => {
this.emit('error', type, info);
});
} else {
throw new errors.RippleError('Multi-server not implemented');
}
} else {
// use null object pattern to provide better error message if user
// tries to call a method that requires a connection
this.connection = new RestrictedConnection(null, options);
}
}
}
_.assign(RippleAPI.prototype, {
connect,
disconnect,
isConnected,
getServerInfo,
getFee,
getLedgerVersion,
getTransaction,
getTransactions,
getTrustlines,
getBalances,
getBalanceSheet,
getPaths,
getOrders,
getOrderbook,
getSettings,
getAccountInfo,
getLedger,
preparePayment,
prepareTrustline,
prepareOrder,
prepareOrderCancellation,
prepareSuspendedPaymentCreation,
prepareSuspendedPaymentExecution,
prepareSuspendedPaymentCancellation,
prepareSettings,
sign,
submit,
generateAddress,
computeLedgerHash,
errors
});
// these are exposed only for use by unit tests; they are not part of the API
RippleAPI._PRIVATE = {
validate: common.validate,
RangeSet: require('./common/rangeset').RangeSet,
ledgerUtils: require('./ledger/utils'),
schemaValidator: require('./common/schema-validator')
module.exports = {
RippleAPI: require('./api').RippleAPI,
// Broadcast api is experimental
RippleAPIBroadcast: require('./broadcast').RippleAPIBroadcast,
// HTTP server is experimental
createHTTPServer: require('./http').createHTTPServer
};
module.exports.RippleAPI = RippleAPI;

View File

@@ -24,8 +24,8 @@ function parseAccountTrustline(trustline: Trustline): AccountTrustline {
limit: trustline.limit,
currency: trustline.currency,
counterparty: trustline.account,
qualityIn: trustline.quality_in || undefined,
qualityOut: trustline.quality_out || undefined,
qualityIn: utils.parseQuality(trustline.quality_in) || undefined,
qualityOut: utils.parseQuality(trustline.quality_out) || undefined,
ripplingDisabled: trustline.no_ripple || undefined,
frozen: trustline.freeze || undefined,
authorized: trustline.authorized || undefined

View File

@@ -3,7 +3,6 @@
const assert = require('assert');
const utils = require('./utils');
const flags = utils.txFlags.TrustSet;
const BigNumber = require('bignumber.js');
function parseFlag(flagsValue, trueValue, falseValue) {
if (flagsValue & trueValue) {
@@ -15,13 +14,6 @@ function parseFlag(flagsValue, trueValue, falseValue) {
return undefined;
}
function parseQuality(quality?: number) {
if (typeof quality === 'number') {
return (new BigNumber(quality)).shift(-9).toNumber();
}
return undefined;
}
function parseTrustline(tx: Object): Object {
assert(tx.TransactionType === 'TrustSet');
@@ -29,8 +21,8 @@ function parseTrustline(tx: Object): Object {
limit: tx.LimitAmount.value,
currency: tx.LimitAmount.currency,
counterparty: tx.LimitAmount.issuer,
qualityIn: parseQuality(tx.QualityIn),
qualityOut: parseQuality(tx.QualityOut),
qualityIn: utils.parseQuality(tx.QualityIn),
qualityOut: utils.parseQuality(tx.QualityOut),
ripplingDisabled: parseFlag(
tx.Flags, flags.SetNoRipple, flags.ClearNoRipple),
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),

View File

@@ -17,6 +17,13 @@ function adjustQualityForXRP(
(new BigNumber(quality)).shift(shift).toString();
}
function parseQuality(quality: ?number) {
if (typeof quality === 'number') {
return (new BigNumber(quality)).shift(-9).toNumber();
}
return undefined;
}
function parseTimestamp(rippleTime: number): string | void {
return rippleTime ? utils.common.rippleTimeToISO8601(rippleTime) : undefined;
}
@@ -80,6 +87,7 @@ function parseMemos(tx: Object): ?Array<Object> {
}
module.exports = {
parseQuality,
parseOutcome,
parseMemos,
hexToString,

View File

@@ -170,11 +170,11 @@ function getTransactions(address: string, options: TransactionsOptions = {}
const ledgerVersion = tx.outcome.ledgerVersion;
const bound = options.earliestFirst ?
{minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion};
const newOptions = _.assign(defaults, options, {startTx: tx}, bound);
const newOptions = _.assign({}, defaults, options, {startTx: tx}, bound);
return getTransactionsInternal(this.connection, address, newOptions);
});
}
const newOptions = _.assign(defaults, options);
const newOptions = _.assign({}, defaults, options);
return getTransactionsInternal(this.connection, address, newOptions);
}

View File

@@ -0,0 +1,24 @@
'use strict';
const keypairs = require('ripple-keypairs');
const common = require('../common');
const {errors, validate} = common;
function generateAddress(options?: Object): Object {
const secret = keypairs.generateSeed(options);
const keypair = keypairs.deriveKeypair(secret);
const address = keypairs.deriveAddress(keypair.publicKey);
return {secret, address};
}
function generateAddressAPI(options?: Object): Object {
validate.generateAddress({options});
try {
return generateAddress(options);
} catch (error) {
throw new errors.UnexpectedError(error.message);
}
}
module.exports = {
generateAddressAPI
};

View File

@@ -64,7 +64,7 @@ function createMaximalAmount(amount: Amount): Amount {
const maxXRPValue = '100000000000';
const maxIOUValue = '9999999999999999e80';
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue;
return _.assign(amount, {value: maxValue});
return _.assign({}, amount, {value: maxValue});
}
function createPaymentTransaction(address: string, paymentArgument: Payment

View File

@@ -87,15 +87,17 @@ function createSettingsTransaction(account: string, settings: Settings
TransactionType: 'AccountSet',
Account: account
};
setTransactionFlags(txJSON, settings);
if (settings.memos !== undefined) {
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
}
setTransactionFlags(txJSON, _.omit(settings, 'memos'));
setTransactionFields(txJSON, settings);
if (txJSON.TransferRate !== undefined) {
txJSON.TransferRate = convertTransferRate(txJSON.TransferRate);
}
if (settings.memos !== undefined) {
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
}
return txJSON;
}

View File

@@ -10,7 +10,8 @@ function formatPrepareResponse(txJSON: Object): Object {
const instructions = {
fee: common.dropsToXrp(txJSON.Fee),
sequence: txJSON.Sequence,
maxLedgerVersion: txJSON.LastLedgerSequence
maxLedgerVersion: txJSON.LastLedgerSequence === undefined ?
null : txJSON.LastLedgerSequence
};
return {
txJSON: JSON.stringify(txJSON),
@@ -36,7 +37,9 @@ function prepareTransaction(txJSON: Object, api: Object,
function prepareMaxLedgerVersion(): Promise<Object> {
if (instructions.maxLedgerVersion !== undefined) {
txJSON.LastLedgerSequence = instructions.maxLedgerVersion;
if (instructions.maxLedgerVersion !== null) {
txJSON.LastLedgerSequence = instructions.maxLedgerVersion;
}
return Promise.resolve(txJSON);
}
const offset = instructions.maxLedgerVersionOffset !== undefined ?

View File

@@ -1,7 +1,6 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const _ = require('lodash');
const net = require('net');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
@@ -15,6 +14,7 @@ const address = addresses.ACCOUNT;
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
assert.options.strict = true;
function unused() {
}
@@ -35,314 +35,90 @@ function checkResult(expected, schemaName, response) {
return response;
}
function createServer() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.on('listening', function() {
resolve(server);
});
server.on('error', function(error) {
reject(error);
});
server.listen(0, '0.0.0.0');
});
}
describe('RippleAPI', function() {
const instructions = {maxLedgerVersionOffset: 100};
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
describe('Connection', function() {
it('connection default options', function() {
const connection = new utils.common.Connection('url');
assert.strictEqual(connection._url, 'url');
assert(_.isUndefined(connection._proxyURL));
assert(_.isUndefined(connection._authorization));
});
it('with proxy', function(done) {
createServer().then((server) => {
const port = server.address().port;
const expect = 'CONNECT localhost';
server.on('connection', (socket) => {
socket.on('data', (data) => {
const got = data.toString('ascii', 0, expect.length);
assert.strictEqual(got, expect);
server.close();
done();
});
});
const options = {
proxy: 'ws://localhost:' + port,
authorization: 'authorization',
trustedCertificates: 'something'
};
const connection =
new utils.common.Connection(this.api.connection._url, options);
connection.connect().catch(done);
connection.connect().catch(done);
}, done);
});
it('Multiply disconnect calls', function() {
this.api.disconnect();
return this.api.disconnect();
});
it('reconnect', function() {
return this.api.connection.reconnect();
});
it('NotConnectedError', function() {
const connection = new utils.common.Connection('url');
return connection.getLedgerVersion().then(() => {
assert(false, 'Should throw NotConnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.NotConnectedError);
});
});
it('DisconnectedError', function() {
this.api.connection._send = function() {
this._ws.close();
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw DisconnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.DisconnectedError);
});
});
it('TimeoutError', function() {
this.api.connection._send = function() {
return Promise.resolve({});
};
const request = {command: 'server_info'};
return this.api.connection.request(request, 1).then(() => {
assert(false, 'Should throw TimeoutError');
}).catch(error => {
assert(error instanceof this.api.errors.TimeoutError);
});
});
it('DisconnectedError on send', function() {
this.api.connection._ws.send = function(message, options, callback) {
unused(message, options);
callback({message: 'not connected'});
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw DisconnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.DisconnectedError);
assert.strictEqual(error.message, 'not connected');
});
});
it('ResponseFormatError', function() {
this.api.connection._send = function(message) {
const parsed = JSON.parse(message);
setTimeout(() => {
this._ws.emit('message', JSON.stringify({
id: parsed.id,
type: 'response',
status: 'unrecognized'
}));
}, 2);
return new Promise(() => {});
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw ResponseFormatError');
}).catch(error => {
assert(error instanceof this.api.errors.ResponseFormatError);
});
});
it('reconnect on unexpected close ', function(done) {
this.api.connection.on('connected', () => {
done();
});
setTimeout(() => {
this.api.connection._ws.close();
}, 1);
});
it('Multiply connect calls', function() {
return this.api.connect().then(() => {
return this.api.connect();
});
});
it('hasLedgerVersion', function() {
return this.api.connection.hasLedgerVersion(8819951).then((result) => {
assert(result);
});
});
it('Cannot connect because no server', function() {
const connection = new utils.common.Connection();
return connection.connect().then(() => {
assert(false, 'Should throw ConnectionError');
}).catch(error => {
assert(error instanceof this.api.errors.ConnectionError);
});
});
it('connect multiserver error', function() {
const options = {
servers: ['wss://server1.com', 'wss://server2.com']
};
assert.throws(function() {
const api = new RippleAPI(options);
unused(api);
}, this.api.errors.RippleError);
});
it('connect throws error', function(done) {
this.api.once('error', (type, info) => {
assert.strictEqual(type, 'type');
assert.strictEqual(info, 'info');
done();
});
this.api.connection.emit('error', 'type', 'info');
});
it('connection emit stream messages', function(done) {
let transactionCount = 0;
let pathFindCount = 0;
this.api.connection.on('transaction', () => {
transactionCount++;
});
this.api.connection.on('path_find', () => {
pathFindCount++;
});
this.api.connection.on('1', () => {
assert.strictEqual(transactionCount, 1);
assert.strictEqual(pathFindCount, 1);
done();
});
this.api.connection._onMessage(JSON.stringify({
type: 'transaction'
}));
this.api.connection._onMessage(JSON.stringify({
type: 'path_find'
}));
this.api.connection._onMessage(JSON.stringify({
type: 'response', id: 1
}));
});
it('connection - invalid message id', function(done) {
this.api.on('error', (type, message) => {
assert.strictEqual(type, 'badMessage');
assert.strictEqual(message,
'{"type":"response","id":"must be integer"}');
done();
});
this.api.connection._onMessage(JSON.stringify({
type: 'response', id: 'must be integer'
}));
});
it('connection - error message', function(done) {
this.api.on('error', (type, message) => {
assert.strictEqual(type, 'slowDown');
assert.strictEqual(message, 'slow down');
done();
});
this.api.connection._onMessage(JSON.stringify({
error: 'slowDown', error_message: 'slow down'
}));
});
it('connection - unrecognized message type', function(done) {
this.api.on('error', (type, message) => {
assert.strictEqual(type, 'badMessage');
assert.strictEqual(message, '{"type":"unknown"}');
done();
});
this.api.connection._onMessage(JSON.stringify({type: 'unknown'}));
});
});
it('error inspect', function() {
const error = new this.api.errors.RippleError('mess', {data: 1});
assert.strictEqual(error.inspect(), '[RippleError(mess, { data: 1 })]');
});
it('preparePayment', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
}, instructions);
return this.api.preparePayment(
address, requests.preparePayment, localInstructions).then(
_.partial(checkResult, responses.preparePayment.normal, 'prepare'));
});
describe('preparePayment', function() {
it('preparePayment - min amount xrp', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
}, instructions);
return this.api.preparePayment(
address, requests.preparePaymentMinAmountXRP, localInstructions).then(
_.partial(checkResult, responses.preparePayment.minAmountXRP, 'prepare'));
});
it('preparePayment - min amount xrp2xrp', function() {
return this.api.preparePayment(
address, requests.preparePaymentMinAmount, instructions).then(
_.partial(checkResult,
responses.preparePayment.minAmountXRPXRP, 'prepare'));
});
it('preparePayment - XRP to XRP no partial', function() {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePaymentWrongPartial);
}, /XRP to XRP payments cannot be partial payments/);
});
it('preparePayment - address must match payment.source.address', function() {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePaymentWrongAddress);
}, /address must match payment.source.address/);
});
it('preparePayment - wrong amount', function() {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePaymentWrongAmount);
}, this.api.errors.ValidationError);
});
it('preparePayment with all options specified', function() {
return this.api.getLedgerVersion().then((ver) => {
const localInstructions = {
maxLedgerVersion: ver + 100,
fee: '0.000012'
};
it('normal', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
}, instructions);
return this.api.preparePayment(
address, requests.preparePaymentAllOptions, localInstructions).then(
_.partial(checkResult, responses.preparePayment.allOptions, 'prepare'));
address, requests.preparePayment.normal, localInstructions).then(
_.partial(checkResult, responses.preparePayment.normal, 'prepare'));
});
});
it('preparePayment without counterparty set', function() {
const localInstructions = _.defaults({sequence: 23}, instructions);
return this.api.preparePayment(
address, requests.preparePaymentNoCounterparty, localInstructions).then(
_.partial(checkResult, responses.preparePayment.noCounterparty,
'prepare'));
});
it('preparePayment - min amount xrp', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
}, instructions);
return this.api.preparePayment(
address, requests.preparePayment.minAmountXRP, localInstructions).then(
_.partial(checkResult,
responses.preparePayment.minAmountXRP, 'prepare'));
});
it('preparePayment - destination.minAmount', function() {
return this.api.preparePayment(address, responses.getPaths.sendAll[0],
instructions).then(_.partial(checkResult,
responses.preparePayment.minAmount, 'prepare'));
it('preparePayment - min amount xrp2xrp', function() {
return this.api.preparePayment(
address, requests.preparePayment.minAmount, instructions).then(
_.partial(checkResult,
responses.preparePayment.minAmountXRPXRP, 'prepare'));
});
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/);
});
it('preparePayment - address must match payment.source.address', function(
) {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePayment.wrongAddress);
}, /address must match payment.source.address/);
});
it('preparePayment - wrong amount', function() {
assert.throws(() => {
this.api.preparePayment(address, requests.preparePayment.wrongAmount);
}, this.api.errors.ValidationError);
});
it('preparePayment with all options specified', function() {
return this.api.getLedgerVersion().then((ver) => {
const localInstructions = {
maxLedgerVersion: ver + 100,
fee: '0.000012'
};
return this.api.preparePayment(
address, requests.preparePayment.allOptions, localInstructions).then(
_.partial(checkResult,
responses.preparePayment.allOptions, 'prepare'));
});
});
it('preparePayment without counterparty set', function() {
const localInstructions = _.defaults({sequence: 23}, instructions);
return this.api.preparePayment(
address, requests.preparePayment.noCounterparty, localInstructions)
.then(_.partial(checkResult, responses.preparePayment.noCounterparty,
'prepare'));
});
it('preparePayment - destination.minAmount', function() {
return this.api.preparePayment(address, responses.getPaths.sendAll[0],
instructions).then(_.partial(checkResult,
responses.preparePayment.minAmount, 'prepare'));
});
});
it('prepareOrder - buy order', function() {
@@ -365,17 +141,25 @@ describe('RippleAPI', function() {
});
it('prepareOrderCancellation', function() {
const request = requests.prepareOrderCancellation;
const request = requests.prepareOrderCancellation.simple;
return this.api.prepareOrderCancellation(address, request, instructions)
.then(_.partial(checkResult, responses.prepareOrder.cancellation,
.then(_.partial(checkResult, responses.prepareOrderCancellation.normal,
'prepare'));
});
it('prepareOrderCancellation - no instructions', function() {
const request = requests.prepareOrderCancellation;
const request = requests.prepareOrderCancellation.simple;
return this.api.prepareOrderCancellation(address, request)
.then(_.partial(checkResult,
responses.prepareOrder.cancellationNoInstructions,
responses.prepareOrderCancellation.noInstructions,
'prepare'));
});
it('prepareOrderCancellation - with memos', function() {
const request = requests.prepareOrderCancellation.withMemos;
return this.api.prepareOrderCancellation(address, request)
.then(_.partial(checkResult,
responses.prepareOrderCancellation.withMemos,
'prepare'));
});
@@ -403,6 +187,13 @@ describe('RippleAPI', function() {
_.partial(checkResult, responses.prepareSettings.flags, 'prepare'));
});
it('prepareSettings - no maxLedgerVersion', function() {
return this.api.prepareSettings(
address, requests.prepareSettings, {maxLedgerVersion: null}).then(
_.partial(checkResult, responses.prepareSettings.noMaxLedgerVersion,
'prepare'));
});
it('prepareSettings - no instructions', function() {
return this.api.prepareSettings(
address, requests.prepareSettings).then(
@@ -458,63 +249,71 @@ describe('RippleAPI', function() {
maxFee: '0.000012'
}, instructions);
return this.api.prepareSuspendedPaymentCreation(
address, requests.prepareSuspendedPaymentCreation,
address, requests.prepareSuspendedPaymentCreation.normal,
localInstructions).then(
_.partial(checkResult, responses.prepareSuspendedPaymentCreation,
_.partial(checkResult, responses.prepareSuspendedPaymentCreation.normal,
'prepare'));
});
it('prepareSuspendedPaymentCreation full', function() {
return this.api.prepareSuspendedPaymentCreation(
address, requests.prepareSuspendedPaymentCreationFull).then(
_.partial(checkResult, responses.prepareSuspendedPaymentCreationFull,
address, requests.prepareSuspendedPaymentCreation.full).then(
_.partial(checkResult, responses.prepareSuspendedPaymentCreation.full,
'prepare'));
});
it('prepareSuspendedPaymentExecution', function() {
return this.api.prepareSuspendedPaymentExecution(
address, requests.prepareSuspendedPaymentExecution, instructions).then(
_.partial(checkResult, responses.prepareSuspendedPaymentExecution,
'prepare'));
address,
requests.prepareSuspendedPaymentExecution.normal, instructions).then(
_.partial(checkResult,
responses.prepareSuspendedPaymentExecution.normal,
'prepare'));
});
it('prepareSuspendedPaymentExecution - simple', function() {
return this.api.prepareSuspendedPaymentExecution(
address, requests.prepareSuspendedPaymentExecutionSimple).then(
_.partial(checkResult, responses.prepareSuspendedPaymentExecutionSimple,
'prepare'));
address,
requests.prepareSuspendedPaymentExecution.simple).then(
_.partial(checkResult,
responses.prepareSuspendedPaymentExecution.simple,
'prepare'));
});
it('prepareSuspendedPaymentCancellation', function() {
return this.api.prepareSuspendedPaymentCancellation(
address, requests.prepareSuspendedPaymentCancellation, instructions).then(
_.partial(checkResult, responses.prepareSuspendedPaymentCancellation,
'prepare'));
address,
requests.prepareSuspendedPaymentCancellation.normal, instructions).then(
_.partial(checkResult,
responses.prepareSuspendedPaymentCancellation.normal,
'prepare'));
});
it('prepareSuspendedPaymentCancellation with memos', function() {
return this.api.prepareSuspendedPaymentCancellation(
address, requests.prepareSuspendedPaymentCancellationMemos).then(
_.partial(checkResult, responses.prepareSuspendedPaymentCancellationMemos,
'prepare'));
address,
requests.prepareSuspendedPaymentCancellation.memos).then(
_.partial(checkResult,
responses.prepareSuspendedPaymentCancellation.memos,
'prepare'));
});
it('sign', function() {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const result = this.api.sign(requests.sign.txJSON, secret);
assert.deepEqual(result, responses.sign);
const result = this.api.sign(requests.sign.normal.txJSON, secret);
assert.deepEqual(result, responses.sign.normal);
schemaValidator.schemaValidate('sign', result);
});
it('sign - SuspendedPaymentExecution', function() {
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
const result = this.api.sign(requests.signSuspended.txJSON, secret);
assert.deepEqual(result, responses.signSuspended);
const result = this.api.sign(requests.sign.suspended.txJSON, secret);
assert.deepEqual(result, responses.sign.suspended);
schemaValidator.schemaValidate('sign', result);
});
it('submit', function() {
return this.api.submit(responses.sign.signedTransaction).then(
return this.api.submit(responses.sign.normal.signedTransaction).then(
_.partial(checkResult, responses.submit, 'submit'));
});
@@ -860,7 +659,7 @@ describe('RippleAPI', function() {
it('getTransactions', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2};
return this.api.getTransactions(address, options).then(
_.partial(checkResult, responses.getTransactions,
_.partial(checkResult, responses.getTransactions.normal,
'getTransactions'));
});
@@ -868,7 +667,7 @@ describe('RippleAPI', function() {
const options = {types: ['payment', 'order'], initiated: true, limit: 2,
earliestFirst: true
};
const expected = _.cloneDeep(responses.getTransactions)
const expected = _.cloneDeep(responses.getTransactions.normal)
.sort(utils.compareTransactions);
return this.api.getTransactions(address, options).then(
_.partial(checkResult, expected, 'getTransactions'));
@@ -951,7 +750,8 @@ describe('RippleAPI', function() {
limit: 2
};
return this.api.getTransactions(address, options).then(
_.partial(checkResult, responses.getTransactions, 'getTransactions'));
_.partial(checkResult, responses.getTransactions.normal,
'getTransactions'));
});
it('getTransactions - start transaction with zero ledger version', function(
@@ -966,18 +766,19 @@ describe('RippleAPI', function() {
it('getTransactions - no options', function() {
return this.api.getTransactions(addresses.OTHER_ACCOUNT).then(
_.partial(checkResult, responses.getTransactionsOne, 'getTransactions'));
_.partial(checkResult, responses.getTransactions.one, 'getTransactions'));
});
it('getTrustlines', function() {
it('getTrustlines - filtered', function() {
const options = {currency: 'USD'};
return this.api.getTrustlines(address, options).then(
_.partial(checkResult, responses.getTrustlines, 'getTrustlines'));
_.partial(checkResult,
responses.getTrustlines.filtered, 'getTrustlines'));
});
it('getTrustlines - ono options', function() {
it('getTrustlines - no options', function() {
return this.api.getTrustlines(address).then(
_.partial(checkResult, responses.getTrustlinesAll, 'getTrustlines'));
_.partial(checkResult, responses.getTrustlines.all, 'getTrustlines'));
});
it('generateAddress', function() {
@@ -1045,59 +846,66 @@ describe('RippleAPI', function() {
}, this.api.errors.ValidationError);
});
it('getOrderbook', function() {
return this.api.getOrderbook(address, requests.getOrderbook, undefined)
.then(
_.partial(checkResult, responses.getOrderbook, 'getOrderbook'));
});
describe('getOrderbook', function() {
it('getOrderbook - invalid options', function() {
assert.throws(() => {
this.api.getOrderbook(address, requests.getOrderbook,
{invalid: 'options'});
}, this.api.errors.ValidationError);
});
it('getOrderbook with XRP', function() {
return this.api.getOrderbook(address, requests.getOrderbookWithXRP).then(
_.partial(checkResult, responses.getOrderbookWithXRP, 'getOrderbook'));
});
it('getOrderbook - sorted so that best deals come first', function() {
return this.api.getOrderbook(address, requests.getOrderbook)
.then(data => {
const bidRates = data.bids.map(bid => bid.properties.makerExchangeRate);
const askRates = data.asks.map(ask => ask.properties.makerExchangeRate);
// makerExchangeRate = quality = takerPays.value/takerGets.value
// so the best deal for the taker is the lowest makerExchangeRate
// bids and asks should be sorted so that the best deals come first
assert.deepEqual(_.sortBy(bidRates, x => Number(x)), bidRates);
assert.deepEqual(_.sortBy(askRates, x => Number(x)), askRates);
it('normal', function() {
return this.api.getOrderbook(address,
requests.getOrderbook.normal, undefined).then(
_.partial(checkResult,
responses.getOrderbook.normal, 'getOrderbook'));
});
});
it('getOrderbook - currency & counterparty are correct', function() {
return this.api.getOrderbook(address, requests.getOrderbook)
.then(data => {
const orders = _.flatten([data.bids, data.asks]);
_.forEach(orders, order => {
const quantity = order.specification.quantity;
const totalPrice = order.specification.totalPrice;
const {base, counter} = requests.getOrderbook;
assert.strictEqual(quantity.currency, base.currency);
assert.strictEqual(quantity.counterparty, base.counterparty);
assert.strictEqual(totalPrice.currency, counter.currency);
assert.strictEqual(totalPrice.counterparty, counter.counterparty);
it('invalid options', function() {
assert.throws(() => {
this.api.getOrderbook(address, requests.getOrderbook.normal,
{invalid: 'options'});
}, this.api.errors.ValidationError);
});
it('with XRP', function() {
return this.api.getOrderbook(address, requests.getOrderbook.withXRP).then(
_.partial(checkResult, responses.getOrderbook.withXRP, 'getOrderbook'));
});
it('sorted so that best deals come first', function() {
return this.api.getOrderbook(address, requests.getOrderbook.normal)
.then(data => {
const bidRates = data.bids.map(bid => bid.properties.makerExchangeRate);
const askRates = data.asks.map(ask => ask.properties.makerExchangeRate);
// makerExchangeRate = quality = takerPays.value/takerGets.value
// so the best deal for the taker is the lowest makerExchangeRate
// bids and asks should be sorted so that the best deals come first
assert.deepEqual(_.sortBy(bidRates, x => Number(x)), bidRates);
assert.deepEqual(_.sortBy(askRates, x => Number(x)), askRates);
});
});
});
it('getOrderbook - direction is correct for bids and asks', function() {
return this.api.getOrderbook(address, requests.getOrderbook)
.then(data => {
assert(_.every(data.bids, bid => bid.specification.direction === 'buy'));
assert(_.every(data.asks, ask => ask.specification.direction === 'sell'));
it('currency & counterparty are correct', function() {
return this.api.getOrderbook(address, requests.getOrderbook.normal)
.then(data => {
const orders = _.flatten([data.bids, data.asks]);
_.forEach(orders, order => {
const quantity = order.specification.quantity;
const totalPrice = order.specification.totalPrice;
const {base, counter} = requests.getOrderbook.normal;
assert.strictEqual(quantity.currency, base.currency);
assert.strictEqual(quantity.counterparty, base.counterparty);
assert.strictEqual(totalPrice.currency, counter.currency);
assert.strictEqual(totalPrice.counterparty, counter.counterparty);
});
});
});
it('direction is correct for bids and asks', function() {
return this.api.getOrderbook(address, requests.getOrderbook.normal)
.then(data => {
assert(
_.every(data.bids, bid => bid.specification.direction === 'buy'));
assert(
_.every(data.asks, ask => ask.specification.direction === 'sell'));
});
});
});
it('getServerInfo', function() {
@@ -1455,7 +1263,8 @@ describe('RippleAPI - offline', function() {
};
return api.prepareSettings(address, settings, instructions).then(data => {
checkResult(responses.prepareSettings.flags, 'prepare', data);
assert.deepEqual(api.sign(data.txJSON, secret), responses.sign);
assert.deepEqual(api.sign(data.txJSON, secret),
responses.prepareSettings.signed);
});
});
@@ -1499,7 +1308,7 @@ describe('RippleAPI - offline', function() {
/* eslint-disable no-unused-vars */
it('RippleAPI - implicit server port', function() {
const api = new RippleAPI({servers: ['wss://s1.ripple.com']});
const api = new RippleAPI({server: 'wss://s1.ripple.com'});
});
/* eslint-enable no-unused-vars */
it('RippleAPI invalid options', function() {
@@ -1507,12 +1316,12 @@ describe('RippleAPI - offline', function() {
});
it('RippleAPI valid options', function() {
const api = new RippleAPI({servers: ['wss://s:1']});
const api = new RippleAPI({server: 'wss://s:1'});
assert.deepEqual(api.connection._url, 'wss://s:1');
});
it('RippleAPI invalid server uri', function() {
assert.throws(() => new RippleAPI({servers: ['wss//s:1']}));
assert.throws(() => new RippleAPI({server: 'wss//s:1'}));
});
});

View File

@@ -0,0 +1,62 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const responses = require('./fixtures').responses;
const ledgerClosed = require('./fixtures/rippled/ledger-close');
const RippleAPI = require('ripple-api').RippleAPI;
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
function checkResult(expected, schemaName, response) {
if (expected.txJSON) {
assert(response.txJSON);
assert.deepEqual(JSON.parse(response.txJSON), JSON.parse(expected.txJSON));
}
assert.deepEqual(_.omit(response, 'txJSON'), _.omit(expected, 'txJSON'));
if (schemaName) {
schemaValidator.schemaValidate(schemaName, response);
}
return response;
}
describe('RippleAPIBroadcast', function() {
beforeEach(setupAPI.setupBroadcast);
afterEach(setupAPI.teardown);
it('base', function() {
const expected = {request_server_info: 1};
this.mocks.forEach(mock => mock.expect(_.assign({}, expected)));
assert(this.api.isConnected());
return this.api.getServerInfo().then(
_.partial(checkResult, responses.getServerInfo, 'getServerInfo'));
});
it('ledger', function(done) {
let gotLedger = 0;
this.api.on('ledger', () => {
gotLedger++;
});
const ledgerNext = _.assign({}, ledgerClosed);
ledgerNext.ledger_index++;
this.mocks.forEach(mock => mock.socket.send(JSON.stringify(ledgerNext)));
setTimeout(() => {
console.log('-- ledgerVersion', this.api.ledgerVersion);
assert.strictEqual(gotLedger, 1);
done();
}, 50);
});
it('error propagation', function(done) {
this.api.once('error', (type, info) => {
assert.strictEqual(type, 'type');
assert.strictEqual(info, 'info');
done();
});
this.mocks[1].socket.send(
JSON.stringify({error: 'type', error_message: 'info'}));
});
});

264
test/connection-test.js Normal file
View File

@@ -0,0 +1,264 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const _ = require('lodash');
const net = require('net');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
const utils = RippleAPI._PRIVATE.ledgerUtils;
function unused() {
}
function createServer() {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.on('listening', function() {
resolve(server);
});
server.on('error', function(error) {
reject(error);
});
server.listen(0, '0.0.0.0');
});
}
describe('Connection', function() {
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
it('default options', function() {
const connection = new utils.common.Connection('url');
assert.strictEqual(connection._url, 'url');
assert(_.isUndefined(connection._proxyURL));
assert(_.isUndefined(connection._authorization));
});
it('trace', function() {
const connection = new utils.common.Connection('url', {trace: true});
const message1 = '{"type": "transaction"}';
const message2 = '{"type": "path_find"}';
const messages = [];
connection._console = {
log: function(message) {
messages.push(message);
}
};
connection._onMessage(message1);
connection._send(message2);
assert.deepEqual(messages, [message1, message2]);
});
it('with proxy', function(done) {
createServer().then((server) => {
const port = server.address().port;
const expect = 'CONNECT localhost';
server.on('connection', (socket) => {
socket.on('data', (data) => {
const got = data.toString('ascii', 0, expect.length);
assert.strictEqual(got, expect);
server.close();
done();
});
});
const options = {
proxy: 'ws://localhost:' + port,
authorization: 'authorization',
trustedCertificates: ['path/to/pem']
};
const connection =
new utils.common.Connection(this.api.connection._url, options);
connection.connect().catch(done);
connection.connect().catch(done);
}, done);
});
it('Multiply disconnect calls', function() {
this.api.disconnect();
return this.api.disconnect();
});
it('reconnect', function() {
return this.api.connection.reconnect();
});
it('NotConnectedError', function() {
const connection = new utils.common.Connection('url');
return connection.getLedgerVersion().then(() => {
assert(false, 'Should throw NotConnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.NotConnectedError);
});
});
it('DisconnectedError', function() {
this.api.connection._send = function() {
this._ws.close();
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw DisconnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.DisconnectedError);
});
});
it('TimeoutError', function() {
this.api.connection._send = function() {
return Promise.resolve({});
};
const request = {command: 'server_info'};
return this.api.connection.request(request, 1).then(() => {
assert(false, 'Should throw TimeoutError');
}).catch(error => {
assert(error instanceof this.api.errors.TimeoutError);
});
});
it('DisconnectedError on send', function() {
this.api.connection._ws.send = function(message, options, callback) {
unused(message, options);
callback({message: 'not connected'});
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw DisconnectedError');
}).catch(error => {
assert(error instanceof this.api.errors.DisconnectedError);
assert.strictEqual(error.message, 'not connected');
});
});
it('ResponseFormatError', function() {
this.api.connection._send = function(message) {
const parsed = JSON.parse(message);
setTimeout(() => {
this._ws.emit('message', JSON.stringify({
id: parsed.id,
type: 'response',
status: 'unrecognized'
}));
}, 2);
return new Promise(() => {});
};
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw ResponseFormatError');
}).catch(error => {
assert(error instanceof this.api.errors.ResponseFormatError);
});
});
it('reconnect on unexpected close ', function(done) {
this.api.connection.on('connected', () => {
done();
});
setTimeout(() => {
this.api.connection._ws.close();
}, 1);
});
it('Multiply connect calls', function() {
return this.api.connect().then(() => {
return this.api.connect();
});
});
it('hasLedgerVersion', function() {
return this.api.connection.hasLedgerVersion(8819951).then((result) => {
assert(result);
});
});
it('Cannot connect because no server', function() {
const connection = new utils.common.Connection();
return connection.connect().then(() => {
assert(false, 'Should throw ConnectionError');
}).catch(error => {
assert(error instanceof this.api.errors.ConnectionError);
});
});
it('connect multiserver error', function() {
const options = {
servers: ['wss://server1.com', 'wss://server2.com']
};
assert.throws(function() {
const api = new RippleAPI(options);
unused(api);
}, this.api.errors.RippleError);
});
it('connect throws error', function(done) {
this.api.once('error', (type, info) => {
assert.strictEqual(type, 'type');
assert.strictEqual(info, 'info');
done();
});
this.api.connection.emit('error', 'type', 'info');
});
it('emit stream messages', function(done) {
let transactionCount = 0;
let pathFindCount = 0;
this.api.connection.on('transaction', () => {
transactionCount++;
});
this.api.connection.on('path_find', () => {
pathFindCount++;
});
this.api.connection.on('1', () => {
assert.strictEqual(transactionCount, 1);
assert.strictEqual(pathFindCount, 1);
done();
});
this.api.connection._onMessage(JSON.stringify({
type: 'transaction'
}));
this.api.connection._onMessage(JSON.stringify({
type: 'path_find'
}));
this.api.connection._onMessage(JSON.stringify({
type: 'response', id: 1
}));
});
it('invalid message id', function(done) {
this.api.on('error', (errorCode, errorMessage, message) => {
assert.strictEqual(errorCode, 'badMessage');
assert.strictEqual(errorMessage, 'valid id not found in response');
assert.strictEqual(message,
'{"type":"response","id":"must be integer"}');
done();
});
this.api.connection._onMessage(JSON.stringify({
type: 'response', id: 'must be integer'
}));
});
it('propagate error message', function(done) {
this.api.on('error', (errorCode, errorMessage, data) => {
assert.strictEqual(errorCode, 'slowDown');
assert.strictEqual(errorMessage, 'slow down');
assert.deepEqual(data, {error: 'slowDown', error_message: 'slow down'});
done();
});
this.api.connection._onMessage(JSON.stringify({
error: 'slowDown', error_message: 'slow down'
}));
});
it('unrecognized message type', function(done) {
this.api.on('error', (errorCode, errorMessage, message) => {
assert.strictEqual(errorCode, 'badMessage');
assert.strictEqual(errorMessage, 'unrecognized message type: unknown');
assert.strictEqual(message, '{"type":"unknown"}');
done();
});
this.api.connection._onMessage(JSON.stringify({type: 'unknown'}));
});
});

View File

@@ -6,35 +6,42 @@ module.exports = {
sell: require('./prepare-order-sell'),
expiration: require('./prepare-order-expiration')
},
prepareOrderCancellation: require('./prepare-order-cancellation'),
preparePayment: require('./prepare-payment'),
preparePaymentMinAmountXRP: require('./prepare-payment-min-xrp'),
preparePaymentMinAmount: require('./prepare-payment-min'),
preparePaymentWrongAddress: require('./prepare-payment-wrong-address'),
preparePaymentWrongAmount: require('./prepare-payment-wrong-amount'),
preparePaymentWrongPartial: require('./prepare-payment-wrong-partial'),
preparePaymentAllOptions: require('./prepare-payment-all-options'),
preparePaymentNoCounterparty: require('./prepare-payment-no-counterparty'),
prepareOrderCancellation: {
simple: require('./prepare-order-cancellation'),
withMemos: require('./prepare-order-cancellation-memos')
},
preparePayment: {
normal: require('./prepare-payment'),
minAmountXRP: require('./prepare-payment-min-xrp'),
minAmount: require('./prepare-payment-min'),
wrongAddress: require('./prepare-payment-wrong-address'),
wrongAmount: require('./prepare-payment-wrong-amount'),
wrongPartial: require('./prepare-payment-wrong-partial'),
allOptions: require('./prepare-payment-all-options'),
noCounterparty: require('./prepare-payment-no-counterparty')
},
prepareSettings: require('./prepare-settings'),
prepareSuspendedPaymentCreation:
require('./prepare-suspended-payment-creation'),
prepareSuspendedPaymentCreationFull:
require('./prepare-suspended-payment-creation-full'),
prepareSuspendedPaymentExecution:
require('./prepare-suspended-payment-execution'),
prepareSuspendedPaymentExecutionSimple:
require('./prepare-suspended-payment-execution-simple'),
prepareSuspendedPaymentCancellation:
require('./prepare-suspended-payment-cancellation'),
prepareSuspendedPaymentCancellationMemos:
require('./prepare-suspended-payment-cancellation-memos'),
prepareSuspendedPaymentCreation: {
normal: require('./prepare-suspended-payment-creation'),
full: require('./prepare-suspended-payment-creation-full')
},
prepareSuspendedPaymentExecution: {
normal: require('./prepare-suspended-payment-execution'),
simple: require('./prepare-suspended-payment-execution-simple')
},
prepareSuspendedPaymentCancellation: {
normal: require('./prepare-suspended-payment-cancellation'),
memos: require('./prepare-suspended-payment-cancellation-memos')
},
prepareTrustline: {
simple: require('./prepare-trustline-simple'),
complex: require('./prepare-trustline'),
frozen: require('./prepare-trustline-frozen.json')
},
sign: require('./sign'),
signSuspended: require('./sign-suspended.json'),
sign: {
normal: require('./sign'),
suspended: require('./sign-suspended.json')
},
getPaths: {
normal: require('./getpaths/normal'),
UsdToUsd: require('./getpaths/usd2usd'),
@@ -47,8 +54,10 @@ module.exports = {
invalid: require('./getpaths/invalid'),
issuer: require('./getpaths/issuer')
},
getOrderbook: require('./get-orderbook'),
getOrderbookWithXRP: require('./get-orderbook-with-xrp'),
getOrderbook: {
normal: require('./get-orderbook'),
withXRP: require('./get-orderbook-with-xrp')
},
computeLedgerHash: {
header: require('./compute-ledger-hash'),
transactions: require('./compute-ledger-hash-transactions')

View File

@@ -0,0 +1,10 @@
{
"orderSequence": 23,
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}

View File

@@ -9,5 +9,12 @@
"currency": "XRP",
"value": "2"
},
"immediateOrCancel": true
"immediateOrCancel": true,
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}

View File

@@ -1,3 +1,10 @@
{
"domain": "ripple.com"
"domain": "ripple.com",
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}

View File

@@ -5,5 +5,12 @@
"qualityIn": 0.91,
"qualityOut": 0.87,
"ripplingDisabled": true,
"frozen": false
"frozen": false,
"memos": [
{
"type": "test",
"format": "plain/text",
"data": "texted data"
}
]
}

View File

@@ -13,10 +13,10 @@
"serverState": "full",
"validatedLedger": {
"age": 5,
"baseFeeXRP": 0.00001,
"baseFeeXRP": "0.00001",
"hash": "4482DEE5362332F54A4036ED57EE1767C9F33CF7CE5A6670355C16CECE381D46",
"reserveBaseXRP": 20,
"reserveIncrementXRP": 5,
"reserveBaseXRP": "20",
"reserveIncrementXRP": "5",
"ledgerVersion": 6595042
},
"validationQuorum": 3

View File

@@ -3,7 +3,8 @@
"specification": {
"limit": "0",
"currency": "ASP",
"counterparty": "r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z"
"counterparty": "r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z",
"qualityIn": 1
},
"counterparty": {
"limit": "10"

View File

@@ -5,8 +5,10 @@ module.exports = {
getAccountInfo: require('./get-account-info.json'),
getBalances: require('./get-balances.json'),
getBalanceSheet: require('./get-balance-sheet.json'),
getOrderbook: require('./get-orderbook.json'),
getOrderbookWithXRP: require('./get-orderbook-with-xrp.json'),
getOrderbook: {
normal: require('./get-orderbook.json'),
withXRP: require('./get-orderbook-with-xrp.json')
},
getOrders: require('./get-orders.json'),
getPaths: {
XrpToUsd: require('./get-paths.json'),
@@ -43,10 +45,14 @@ module.exports = {
suspendedPaymentExecutionSimple:
require('./get-transaction-suspended-payment-execution-simple.json')
},
getTransactions: require('./get-transactions.json'),
getTransactionsOne: require('./get-transactions-one.json'),
getTrustlines: require('./get-trustlines.json'),
getTrustlinesAll: require('./get-trustlines-all.json'),
getTransactions: {
normal: require('./get-transactions.json'),
one: require('./get-transactions-one.json')
},
getTrustlines: {
filtered: require('./get-trustlines.json'),
all: require('./get-trustlines-all.json')
},
getLedger: {
header: require('./get-ledger'),
full: require('./get-ledger-full'),
@@ -56,15 +62,17 @@ module.exports = {
prepareOrder: {
buy: require('./prepare-order.json'),
sell: require('./prepare-order-sell.json'),
expiration: require('./prepare-order-expiration'),
cancellation: require('./prepare-order-cancellation.json'),
cancellationNoInstructions:
require('./prepare-order-cancellation-no-instructions.json')
expiration: require('./prepare-order-expiration')
},
prepareOrderCancellation: {
normal: require('./prepare-order-cancellation.json'),
withMemos: require('./prepare-order-cancellation-memos.json'),
noInstructions: require('./prepare-order-cancellation-no-instructions.json')
},
preparePayment: {
normal: require('./prepare-payment.json'),
minAmountXRP: require('./prepare-payment-min-amont-xrp.json'),
minAmountXRPXRP: require('./prepare-payment-min-amont-xrp-xrp.json'),
minAmountXRP: require('./prepare-payment-min-amount-xrp.json'),
minAmountXRPXRP: require('./prepare-payment-min-amount-xrp-xrp.json'),
allOptions: require('./prepare-payment-all-options.json'),
noCounterparty: require('./prepare-payment-no-counterparty.json'),
minAmount: require('./prepare-payment-min-amount.json')
@@ -77,27 +85,31 @@ module.exports = {
flagClear: require('./prepare-settings-flag-clear.json'),
setTransferRate: require('./prepare-settings-set-transfer-rate.json'),
fieldClear: require('./prepare-settings-field-clear.json'),
noInstructions: require('./prepare-settings-no-instructions.json')
noInstructions: require('./prepare-settings-no-instructions.json'),
signed: require('./prepare-settings-signed.json'),
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json')
},
prepareSuspendedPaymentCreation: {
normal: require('./prepare-suspended-payment-creation'),
full: require('./prepare-suspended-payment-creation-full')
},
prepareSuspendedPaymentExecution: {
normal: require('./prepare-suspended-payment-execution'),
simple: require('./prepare-suspended-payment-execution-simple')
},
prepareSuspendedPaymentCancellation: {
normal: require('./prepare-suspended-payment-cancellation'),
memos: require('./prepare-suspended-payment-cancellation-memos')
},
prepareSuspendedPaymentCreation:
require('./prepare-suspended-payment-creation'),
prepareSuspendedPaymentCreationFull:
require('./prepare-suspended-payment-creation-full'),
prepareSuspendedPaymentExecution:
require('./prepare-suspended-payment-execution'),
prepareSuspendedPaymentExecutionSimple:
require('./prepare-suspended-payment-execution-simple'),
prepareSuspendedPaymentCancellation:
require('./prepare-suspended-payment-cancellation'),
prepareSuspendedPaymentCancellationMemos:
require('./prepare-suspended-payment-cancellation-memos'),
prepareTrustline: {
simple: require('./prepare-trustline-simple.json'),
frozen: require('./prepare-trustline-frozen.json'),
complex: require('./prepare-trustline.json')
},
sign: require('./sign.json'),
signSuspended: require('./sign-suspended.json'),
sign: {
normal: require('./sign.json'),
suspended: require('./sign-suspended.json')
},
submit: require('./submit.json'),
ledgerEvent: require('./ledger-event.json')
};

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
{
"txJSON": "{\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"100000000000000000\",\"Flags\":2147614720,\"SendMax\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"0.01\"},\"DeliverMin\":\"100000000000000000\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"txJSON": "{\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":\"100000000000000000\",\"Flags\":2147614720,\"SendMax\":{\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\",\"value\":\"0.01\"},\"DeliverMin\":\"10000\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,

View File

@@ -1,5 +1,5 @@
{
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"txJSON": "{\"Flags\":2147614720,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\",\"Amount\":{\"value\":\"9999999999999999e80\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"SendMax\":{\"value\":\"5\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"DeliverMin\":{\"value\":\"4.93463759481038\",\"currency\":\"USD\",\"issuer\":\"ra5nK24KXen9AHvsdFTKHSANinZseWnPcX\"},\"Paths\":[[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"},{\"account\":\"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn\"}],[{\"account\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\"},{\"currency\":\"XRP\"},{\"issuer\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\",\"currency\":\"USD\"},{\"account\":\"rfsEoNBUBbvkf4jPcFe2u9CyaQagLVHGfP\"},{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
{
"signedTransaction": "12000322000000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402207660BDEF67105CE1EBA9AD35DC7156BAB43FF1D47633199EE257D70B6B9AAFBF022045A812486A675750B5A3F37131E9F92299728D37FF6BB7195CA5EE881268CB4C770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304",
"id": "29D23159EBA79170DCA5EF467CBC15114DBD35B7A8C3DBF76809BA354D00D250"
}
{
"signedTransaction": "12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402202FBF6A6F74DFDA17C7341D532B66141206BC71A147C08DBDA6A950AA9A1741DC022055859A39F2486A46487F8DA261E3D80B4FDD26178A716A929F26377D1BEC7E43770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304F9EA7C04746573747D0B74657874656420646174617E0A706C61696E2F74657874E1F1",
"id": "4755D26FAC39E3E477870D4E03CC6783DDDF967FFBE240606755D3D03702FC16"
}

View File

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

View File

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

View File

@@ -22,7 +22,7 @@ module.exports.normal = function(request, options = {}) {
currency: 'ASP',
limit: '0',
limit_peer: '10',
quality_in: 0,
quality_in: 1000000000,
quality_out: 0
},
{

View File

@@ -5,11 +5,13 @@ module.exports = {
success: require('./submit'),
failure: require('./submit-failed')
},
ledger: require('./ledger'),
ledgerNotFound: require('./ledger-not-found'),
ledgerWithoutCloseTime: require('./ledger-without-close-time'),
ledgerWithSettingsTx: require('./ledger-with-settings-tx'),
ledgerWithStateAsHashes: require('./ledger-with-state-as-hashes'),
ledger: {
normal: require('./ledger'),
notFound: require('./ledger-not-found'),
withoutCloseTime: require('./ledger-without-close-time'),
withSettingsTx: require('./ledger-with-settings-tx'),
withStateAsHashes: require('./ledger-with-state-as-hashes')
},
subscribe: require('./subscribe'),
unsubscribe: require('./unsubscribe'),
account_info: {
@@ -17,14 +19,20 @@ module.exports = {
notfound: require('./account-info-not-found')
},
account_offers: require('./account-offers'),
account_tx: require('./account-tx'),
account_tx_one: require('./get-transactions-one'),
account_tx: {
normal: require('./account-tx'),
one: require('./get-transactions-one')
},
gateway_balances: require('./gateway-balances'),
book_offers: require('./book-offers'),
book_offers_1: require('./book-offers-1'),
book_offers_2: require('./book-offers-2'),
server_info: require('./server-info'),
server_info_error: require('./server-info-error'),
book_offers: {
fabric: require('./book-offers'),
usd_xrp: require('./book-offers-usd-xrp'),
xrp_usd: require('./book-offers-xrp-usd')
},
server_info: {
normal: require('./server-info'),
error: require('./server-info-error')
},
path_find: {
generate: require('./path-find'),
sendUSD: require('./path-find-send-usd'),

View File

@@ -0,0 +1,17 @@
'use strict';
const {RippleAPIBroadcast} = require('../../src');
function main() {
const servers = ['wss://s1.ripple.com', 'wss://s2.ripple.com'];
const api = new RippleAPIBroadcast(servers);
api.connect().then(() => {
api.getServerInfo().then(info => {
console.log(JSON.stringify(info, null, 2));
});
api.on('ledger', ledger => {
console.log(JSON.stringify(ledger, null, 2));
});
});
}
main();

View File

@@ -0,0 +1,74 @@
{
"jsonrpc": "2.0",
"id": "2",
"result": {
"type": "order",
"address": "rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV",
"sequence": 7973823,
"id": "4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466",
"specification": {
"direction": "buy",
"quantity": {
"currency": "USD",
"value": "0.000709756467",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
},
"totalPrice": {
"currency": "JPY",
"value": "0.086630181788",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN"
}
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2015-12-03T00:53:21.000Z",
"fee": "0.010001",
"balanceChanges": {
"rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [
{
"currency": "XRP",
"value": "-0.010001"
}
]
},
"orderbookChanges": {
"rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [
{
"direction": "buy",
"quantity": {
"currency": "USD",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0.000709260645"
},
"totalPrice": {
"currency": "JPY",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN",
"value": "0.086665436143"
},
"sequence": 7973725,
"status": "cancelled",
"makerExchangeRate": "0.008183892870852266"
},
{
"direction": "buy",
"quantity": {
"currency": "USD",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"value": "0.000709756467"
},
"totalPrice": {
"currency": "JPY",
"counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN",
"value": "0.086630181788"
},
"sequence": 7973823,
"status": "created",
"makerExchangeRate": "0.008192946757712049"
}
]
},
"ledgerVersion": 17445469,
"indexInLedger": 2
}
}
}

View File

@@ -0,0 +1,72 @@
{
"jsonrpc": "2.0",
"id": "3",
"result": [
{
"type": "order",
"address": "rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM",
"sequence": 3372206,
"id": "848397FA686BD4A59F91EC1F4DE717360470EE8BD67CAA01D5FD333EDA8D97B3",
"specification": {
"direction": "buy",
"quantity": {
"currency": "JPY",
"value": "27865.90216965619",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
}
},
"outcome": {
"result": "tesSUCCESS",
"fee": "0.011",
"balanceChanges": {
"rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [
{
"currency": "XRP",
"value": "-0.011"
}
]
},
"orderbookChanges": {
"rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [
{
"direction": "buy",
"quantity": {
"currency": "JPY",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg",
"value": "27880.6855384734"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
},
"sequence": 3372204,
"status": "cancelled",
"makerExchangeRate": "0.697017138461835"
},
{
"direction": "buy",
"quantity": {
"currency": "JPY",
"counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg",
"value": "27865.90216965619"
},
"totalPrice": {
"currency": "XRP",
"value": "40000"
},
"sequence": 3372206,
"status": "created",
"makerExchangeRate": "0.6966475542414048"
}
]
},
"ledgerVersion": 17533547,
"indexInLedger": 12
}
}
]
}

View File

@@ -0,0 +1,6 @@
'use strict';
module.exports = {
getTransaction: require('./get-transaction'),
getTransactions: require('./get-transactions')
};

View File

@@ -0,0 +1,157 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const assert = require('assert-diff');
const _ = require('lodash');
const jayson = require('jayson');
const createHTTPServer = require('../../src/index').createHTTPServer;
const apiFixtures = require('../fixtures');
const apiRequests = apiFixtures.requests;
const apiResponses = apiFixtures.responses;
const fixtures = require('./fixtures');
const TIMEOUT = 20000; // how long before each test case times out
const apiOptions = {
server: 'wss://s1.ripple.com'
};
const httpPort = 3000;
function createClient() {
return jayson.client.http({port: httpPort, hostname: 'localhost'});
}
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
function makePositionalParams(params) {
return params.map(value => value[_.keys(value)[0]]);
}
function makeNamedParams(params) {
return _.reduce(params, _.assign, {});
}
function random() {
return _.fill(Array(16), 0);
}
describe('http server integration tests', function() {
this.timeout(TIMEOUT);
let server = null;
let client = null;
function createTestInternal(testName, methodName, params, testFunc, id) {
it(testName, function() {
return new Promise((resolve, reject) => {
client.request(methodName, params, id,
(err, result) => err ? reject(err) : resolve(testFunc(result)));
});
});
}
function createTest(name, params, testFunc, id) {
createTestInternal(name + ' - positional params', name,
makePositionalParams(params), testFunc, id);
createTestInternal(name + ' - named params', name,
makeNamedParams(params), testFunc, id);
}
beforeEach(function() {
server = createHTTPServer(apiOptions, httpPort);
return server.start().then(() => {
this.client = createClient();
client = this.client;
});
});
afterEach(function() {
return server.stop();
});
createTest(
'getLedgerVersion',
[],
result => assert(_.isNumber(result.result))
);
createTest(
'getServerInfo',
[],
result => assert(_.isNumber(result.result.validatedLedger.ledgerVersion))
);
createTest(
'getTransaction',
[{id: '4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466'}],
result => assert.deepEqual(result, fixtures.getTransaction),
'2'
);
createTest(
'getTransactions',
[{address: 'rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM'}, {
options: {
binary: true,
limit: 1,
start:
'FBAAC31D6BAEEFA9E501266FD62DA7A7982662BC19BC42F49BB41405C2F820DB'
}
}],
result => assert.deepEqual(result, fixtures.getTransactions),
'3'
);
createTest(
'prepareSettings',
[
{address},
{settings: apiRequests.prepareSettings},
{instructions: {
maxFee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051
}}
],
result => {
const got = JSON.parse(result.result.txJSON);
const expected = JSON.parse(apiResponses.prepareSettings.flags.txJSON);
assert.deepEqual(got, expected);
}
);
createTest(
'sign',
[{txJSON: apiRequests.sign.normal.txJSON},
{secret: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'}],
result => assert.deepEqual(result.result, apiResponses.sign.normal)
);
createTest(
'generateAddress',
[{options: {entropy: random()}}],
result => assert.deepEqual(result.result, apiResponses.generateAddress)
);
createTest(
'computeLedgerHash',
[{ledger: _.assign({}, apiResponses.getLedger.full,
{parentCloseTime: apiResponses.getLedger.full.closeTime})
}],
result => {
assert.strictEqual(result.result,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E');
}
);
createTest(
'isConnected',
[],
result => assert(result.result)
);
});

View File

@@ -58,7 +58,7 @@ function testTransaction(testcase, type, lastClosedLedgerVersion, prepared) {
}
function setup() {
this.api = new RippleAPI({servers: ['wss://s1.ripple.com']});
this.api = new RippleAPI({server: 'wss://s1.ripple.com'});
console.log('CONNECTING...');
return this.api.connect().then(() => {
console.log('CONNECTED...');

View File

@@ -71,6 +71,7 @@ module.exports = function(port) {
};
mock.on('connection', function(conn) {
this.socket = conn;
conn.on('message', function(requestJSON) {
const request = JSON.parse(requestJSON);
mock.emit('request_' + request.command, request, conn);
@@ -97,9 +98,9 @@ module.exports = function(port) {
mock.on('request_server_info', function(request, conn) {
assert.strictEqual(request.command, 'server_info');
if (mock.returnErrorOnServerInfo) {
conn.send(createResponse(request, fixtures.server_info_error));
conn.send(createResponse(request, fixtures.server_info.error));
} else {
conn.send(createResponse(request, fixtures.server_info));
conn.send(createResponse(request, fixtures.server_info.normal));
}
});
@@ -139,19 +140,20 @@ module.exports = function(port) {
mock.on('request_ledger', function(request, conn) {
assert.strictEqual(request.command, 'ledger');
if (request.ledger_index === 34) {
conn.send(createLedgerResponse(request, fixtures.ledgerNotFound));
conn.send(createLedgerResponse(request, fixtures.ledger.notFound));
} else if (request.ledger_index === 6) {
conn.send(createResponse(request, fixtures.ledgerWithStateAsHashes));
conn.send(createResponse(request, fixtures.ledger.withStateAsHashes));
} else if (request.ledger_index === 9038215) {
conn.send(createLedgerResponse(request, fixtures.ledgerWithoutCloseTime));
conn.send(
createLedgerResponse(request, fixtures.ledger.withoutCloseTime));
} else if (request.ledger_index === 4181996) {
conn.send(createLedgerResponse(request, fixtures.ledgerWithSettingsTx));
conn.send(createLedgerResponse(request, fixtures.ledger.withSettingsTx));
} else if (request.ledger_index === 38129) {
const response = _.assign({}, fixtures.ledger,
const response = _.assign({}, fixtures.ledger.normal,
{result: {ledger: fullLedger}});
conn.send(createLedgerResponse(request, response));
} else {
conn.send(createLedgerResponse(request, fixtures.ledger));
conn.send(createLedgerResponse(request, fixtures.ledger.normal));
}
});
@@ -263,7 +265,7 @@ module.exports = function(port) {
if (request.account === addresses.ACCOUNT) {
conn.send(transactionsResponse(request));
} else if (request.account === addresses.OTHER_ACCOUNT) {
conn.send(createResponse(request, fixtures.account_tx_one));
conn.send(createResponse(request, fixtures.account_tx.one));
} else {
assert(false, 'Unrecognized account address: ' + request.account);
}
@@ -279,16 +281,18 @@ module.exports = function(port) {
mock.on('request_book_offers', function(request, conn) {
if (request.taker_pays.issuer === 'rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw') {
conn.send(createResponse(request, fixtures.book_offers_2));
conn.send(createResponse(request, fixtures.book_offers.xrp_usd));
} else if (request.taker_gets.issuer
=== 'rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw') {
conn.send(createResponse(request, fixtures.book_offers_1));
conn.send(createResponse(request, fixtures.book_offers.usd_xrp));
} else if (isBTC(request.taker_gets.currency)
&& isUSD(request.taker_pays.currency)) {
conn.send(fixtures.book_offers.requestBookOffersBidsResponse(request));
conn.send(
fixtures.book_offers.fabric.requestBookOffersBidsResponse(request));
} else if (isUSD(request.taker_gets.currency)
&& isBTC(request.taker_pays.currency)) {
conn.send(fixtures.book_offers.requestBookOffersAsksResponse(request));
conn.send(
fixtures.book_offers.fabric.requestBookOffersAsksResponse(request));
} else {
assert(false, 'Unrecognized order book: ' + JSON.stringify(request));
}

View File

@@ -1,52 +1,77 @@
'use strict';
const net = require('net');
const RippleAPI = require('ripple-api').RippleAPI;
const RippleAPIBroadcast = require('ripple-api').RippleAPIBroadcast;
const ledgerClosed = require('./fixtures/rippled/ledger-close');
const createMockRippled = require('./mock-rippled');
// using a free port instead of a constant port enables parallelization
function getFreePort(callback) {
const server = net.createServer();
let port;
server.on('listening', function() {
port = server.address().port;
server.close();
function getFreePort() {
return new Promise((resolve, reject) => {
const server = net.createServer();
let port;
server.on('listening', function() {
port = server.address().port;
server.close();
});
server.on('close', function() {
resolve(port);
});
server.on('error', function(error) {
reject(error);
});
server.listen(0);
});
server.on('close', function() {
callback(null, port);
});
server.on('error', function(error) {
callback(error);
});
server.listen(0);
}
function setupMockRippledConnection(testcase, port, done) {
testcase.mockRippled = createMockRippled(port);
testcase.api = new RippleAPI({servers: ['ws://localhost:' + port]});
testcase.api.connect().then(() => {
testcase.api.once('ledger', () => done());
testcase.api.connection._ws.emit('message', JSON.stringify(ledgerClosed));
}).catch(done);
function setupMockRippledConnection(testcase, port) {
return new Promise((resolve, reject) => {
testcase.mockRippled = createMockRippled(port);
testcase.api = new RippleAPI({server: 'ws://localhost:' + port});
testcase.api.connect().then(() => {
testcase.api.once('ledger', () => resolve());
testcase.api.connection._ws.emit('message', JSON.stringify(ledgerClosed));
}).catch(reject);
});
}
function setup(done) {
getFreePort((error, port) => {
if (error) {
throw new Error('Unable to obtain a free port: ' + error);
}
setupMockRippledConnection(this, port, done);
function setupMockRippledConnectionForBroadcast(testcase, ports) {
return new Promise((resolve, reject) => {
const servers = ports.map(port => 'ws://localhost:' + port);
testcase.mocks = ports.map(port => createMockRippled(port));
testcase.api = new RippleAPIBroadcast(servers);
testcase.api.connect().then(() => {
testcase.api.once('ledger', () => resolve());
testcase.mocks[0].socket.send(JSON.stringify(ledgerClosed));
}).catch(reject);
});
}
function setup() {
return getFreePort().then(port => {
return setupMockRippledConnection(this, port);
});
}
function setupBroadcast() {
return Promise.all([getFreePort(), getFreePort()]).then(ports => {
return setupMockRippledConnectionForBroadcast(this, ports);
});
}
function teardown(done) {
this.api.disconnect().then(() => {
this.mockRippled.close();
if (this.mockRippled !== undefined) {
this.mockRippled.close();
} else {
this.mocks.forEach(mock => mock.close());
}
setImmediate(done);
}).catch(done);
}
module.exports = {
setup: setup,
teardown: teardown
teardown: teardown,
setupBroadcast: setupBroadcast
};