Compare commits

...

62 Commits

Author SHA1 Message Date
Alan Cohen
eb04e878ba 0.16.9 2016-03-22 18:39:33 -07:00
Chris Clark
c71febd116 Merge pull request #709 from darkdarkdragon/reconnect_fix
Reconnection fix
2016-03-22 18:12:28 -07:00
Ivan Tivonenko
69c1ccbb6b [TASK] emit connected and disconnected events from api 2016-03-23 02:05:07 +02:00
Ivan Tivonenko
499b8c8d8b [FIX] fix multiple reconnections issue 2016-03-23 01:18:08 +02:00
Chris Clark
ea009f9a84 Merge pull request #708 from clark800/fix-maker-exchange-rate
Fix makerExchangeRate for getOrders when rippled provides quality
2016-03-22 14:14:23 -07:00
Chris Clark
dc784d4567 Fix makerExchangeRate for getOrders when rippled provides quality 2016-03-22 13:21:26 -07:00
Alan Cohen
9ffc8a2c0b Merge pull request #707 from ripple/fix/filter-source-amount
FIX: Filtering source_amount pathfind correctly
2016-03-21 14:48:37 -07:00
Alan Cohen
5b20fe573e FIX: Filtering source_amount pathfind correctly
Bug:

```js
api.connect().then(() => {
  const pathfind = {
    source: {
      address: USDCold,
      amount: {
        currency: 'USD',
        value: '1.00' // <<<< Rippled response has "1" not "1.00"
      }
    },
    destination: {
      address: EURCold,
      amount: {currency: 'EUR'}
    }
  };
  return api.getPaths(pathfind).then(paths => {
    console.log('PATHS: \n', JSON.stringify(paths, null, 2));
  });
}).catch(console.log);

```
2016-03-21 11:44:55 -07:00
Chris Clark
7e466bb80f Merge pull request #705 from darkdarkdragon/code_comment
[FIX] add small code comment
2016-03-15 12:14:05 -07:00
Ivan Tivonenko
1f8418b447 [FIX] add small code comment 2016-03-15 20:58:22 +02:00
Alan Cohen
ccfc57fc62 Merge pull request #704 from darkdarkdragon/fix_edge_test
[FIX] fix test hang in microsoft edge
2016-03-15 10:27:01 -07:00
Ivan Tivonenko
1d31fccd72 [FIX] fix test hang in microsoft edge 2016-03-15 05:04:47 +02:00
Alan Cohen
9ac1a89e48 0.16.8 2016-03-09 12:30:10 -08:00
Chris Clark
bfc0696324 Merge pull request #701 from h0vhannes/develop
Documentation fixes
2016-03-09 12:24:55 -08:00
Hovhannes Kuloghlyan
e33e782f9e [DOC] Fix transaction spelling in getTransaction description 2016-03-09 22:14:09 +04:00
Hovhannes Kuloghlyan
f2b591d1b2 [DOC] Fix transaction responses link in submit() description 2016-03-09 22:07:54 +04:00
Chris Clark
fe9af5153d Merge pull request #699 from darkdarkdragon/develop_connection_error
[FIX] fix connection error handling
2016-02-24 14:12:12 -08:00
Ivan Tivonenko
0dfdd0a601 [FIX] change eslint version to 2.1 2016-02-24 23:08:49 +02:00
Ivan Tivonenko
4acc42e1b6 [FIX] fix connection error handling
if connection to server can't be established, reject Promise of `connect()` method caller,
instead of throwing error event on base object.
2016-02-24 23:08:47 +02:00
Alan Cohen
7c9a179865 Merge pull request #698 from h0vhannes/develop
[FIX] Fix typo in PrepareTrustline description
2016-02-23 08:44:43 -07:00
Hovhannes Kuloghlyan
c6296a4918 [FIX] Modify PrepareTrustline doc source to pass tests 2016-02-23 16:45:32 +04:00
Hovhannes Kuloghlyan
cc399f1164 [FIX] Fix typo in PrepareTrustline description 2016-02-21 17:02:27 +03:00
Alan Cohen
e4ffb96646 Update version to 0.16.7 2016-02-12 11:38:51 -08:00
Chris Clark
8d34428dac Merge pull request #694 from lumberj/add-deliveredAmount
Add deliveredAmount to payment outcome
2016-02-12 11:25:01 -08:00
Alan Cohen
353637a0c0 Add TODO comment for fixing workaround for rippled bug 2016-02-12 10:56:35 -08:00
Alan Cohen
00713d8ec1 Update version to 0.16.6 2016-02-11 17:02:18 -08:00
Alan Cohen
d949881e9f Merge pull request #695 from lumberj/fix-typo
Fix typo in common/connection.js
2016-02-11 16:30:23 -08:00
Alan Cohen
5075441a69 Fix lint error 2016-02-11 13:39:35 -08:00
Alan Cohen
94a852cb8b Fix typo in common/connection.js 2016-02-11 13:36:12 -08:00
Alan Cohen
06f847c2d0 Fix lint errors 2016-02-10 19:45:51 -08:00
Alan Cohen
0c2f9d0e62 Add deliveredAmount to payment outcome
It is impossible to reliably compute the delivered amount from the metadata due
to fixed precision. If the partial payment flag is not set and the transaction
succeeded, the delivered amount should always be considered to be the amount
specified in the transaction.
2016-02-10 17:25:27 -08:00
Chris Clark
11ed6b124f Merge pull request #692 from darkdarkdragon/develop-minify
fixes for minified version
2016-02-08 17:22:14 -08:00
Ivan Tivonenko
8767fc0068 fixes for minified version test minified version in SauceLabs 2016-02-09 03:08:23 +02:00
Chris Clark
66b07623b0 Merge pull request #691 from darkdarkdragon/develop-sauce
add SauceLabs testing
2016-02-05 10:10:37 -08:00
Ivan Tivonenko
4f3635eef0 add SauceLabs testing 2016-02-05 07:59:34 +02:00
Chris Clark
f638833759 Merge pull request #686 from darkdarkdragon/develop-RLJS-565
switch from Babel 5 to Babel 6
2016-01-21 15:42:00 -08:00
Ivan Tivonenko
ab9d1936d9 fix reconnect error - in case of connection was unexpectedly closed before it was open,
resolve first promise that was returned from call to connect
2016-01-21 18:35:59 +02:00
Ivan Tivonenko
0fefb2bd2c do not pack lodash inside browser version 2016-01-16 01:12:48 +02:00
Ivan Tivonenko
6740eee495 combine with different transaction test
test for trying to sign already signed transaction
ignore http server in coverage
2016-01-16 01:12:46 +02:00
Ivan Tivonenko
aa6020e00d switch from Babel 5 to Babel 6
use of isparta for coverage reporting
2016-01-16 01:12:43 +02:00
Alan Cohen
7bfe4a6cd8 Update version to 0.16.5 2016-01-15 14:44:05 -08:00
Chris Clark
aa467681e4 Merge pull request #688 from lumberj/source-amount-paths
Filter insufficient source funds paths from pathfind results
2016-01-15 13:47:17 -08:00
Alan Cohen
6b8cd6151d Filter insufficient source funds paths from pathfind results
When pathfinding with source amount, we need to filter out paths where source
amount is not equal to the specified source amount. This is due to the behavior
of rippled when specifying a source amount during pathfinding.

Example:

 {
  "command": "ripple_path_find",
  "source_account": "rhFQQ4ATC6MDF9ghTq3qAoCsGbGtjnhcXF",
  "destination_account": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
  "destination_amount": {
    "currency": "EUR",
    "issuer": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
    "value": -1
  },
  "send_max": {
    "currency": "USD",
    "issuer": "rhFQQ4ATC6MDF9ghTq3qAoCsGbGtjnhcXF",
    "value": "1234567891"
  },
  "id": 2
}
{
  "id": 2,
  "result": {
    "alternatives": [
      {
        "destination_amount": {
          "currency": "EUR",
          "issuer": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
          "value": "3999889.62127857"
        },
        "paths_canonical": [],
        "paths_computed": [
          [
            {
              "account": "rcsxQxEqU2qquAKp3tBUJy8Z2t19ioQPJ",
              "type": 1,
              "type_hex": "0000000000000001"
            },
            {
              "currency": "EUR",
              "issuer": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
              "type": 48,
              "type_hex": "0000000000000030"
            }
          ]
        ],
        "source_amount": {
          "currency": "USD",
          "issuer": "rhFQQ4ATC6MDF9ghTq3qAoCsGbGtjnhcXF",
          "value": "4170759.906037564"
        }
      }
    ],
    "destination_account": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
    "destination_amount": {
      "currency": "EUR",
      "issuer": "rp91GUd5R3Rk3ipqW7XBdtrUJcX8epzGyb",
      "value": "-1"
    },
    "destination_currencies": [
      "EUR",
      "XRP"
    ],
    "full_reply": true,
    "id": 2,
    "source_account": "rhFQQ4ATC6MDF9ghTq3qAoCsGbGtjnhcXF",
    "status": "success"
  },
  "status": "success",
  "type": "response"
}
2016-01-15 13:38:57 -08:00
Chris Clark
0d6aaee12a Merge pull request #681 from darkdarkdragon/develop-RLJS-564
fix for browser
2016-01-13 11:31:14 -08:00
Ivan Tivonenko
dc03c6e0ac fix to work in browser
run unit tests and integration tests in PhantomJS
add JUnit reporter to unit test so CircleCI can show results
2016-01-13 07:11:39 +02:00
Alan Cohen
0f4d957d14 Update version to 0.16.4 2016-01-04 19:48:59 -08:00
Alan Cohen
71a13224a1 Merge branch 'develop' into release
* develop:
  Update ws to 1.0.1
  run integration tests using standalone server
  Add multisignature support
  Add sample code to cancel all orders for a specified account
  Remove references to browser support
  add check for windows eol on commit removes windows eols from some files
  Fix error on ledger subscription message without validated_ledgers
2016-01-04 19:47:24 -08:00
Alan Cohen
8097ed60ba Update version to 0.16.3 2016-01-04 19:45:47 -08:00
Alan Cohen
408bb74214 Merge pull request #682 from lumberj/update-ws
Update ws to 1.0.1
2016-01-04 19:12:11 -08:00
Alan Cohen
9433b43873 Update ws to 1.0.1
Also, addresses remote memory disclosure vulnerability
https://nodesecurity.io/advisories/67
2016-01-04 19:06:30 -08:00
Chris Clark
896bf48c79 Merge pull request #672 from darkdarkdragon/develop-RLJS-558
testing circleci standalone integration
2015-12-18 16:11:13 -08:00
Ivan Tivonenko
3dd21a7e11 run integration tests using standalone server 2015-12-19 02:04:05 +02:00
Chris Clark
ed79a04018 Merge pull request #679 from clark800/multisign
Add multisignature support
2015-12-17 13:27:52 -08:00
Chris Clark
ebfe20defb Add multisignature support 2015-12-17 11:45:08 -08:00
Chris Clark
28b148348d Merge pull request #678 from clark800/cancel-all
Add sample code to cancel all orders for a specified account
2015-12-16 12:05:09 -08:00
Chris Clark
fe099f2c8b Add sample code to cancel all orders for a specified account 2015-12-16 11:40:39 -08:00
Chris Clark
107c8c9f0f Merge pull request #677 from lumberj/no-browser
Remove references to browser support
2015-12-16 11:35:33 -08:00
Alan Cohen
39e818b3e5 Remove references to browser support 2015-12-16 10:34:57 -08:00
Chris Clark
588ffa3d5c Merge pull request #674 from darkdarkdragon/develop-eol
add check for windows eol
2015-12-14 12:57:57 -08:00
Ivan Tivonenko
691e4dd114 add check for windows eol on commit
removes windows eols from some files
2015-12-14 22:15:30 +02:00
Chris Clark
55bc42725f Merge pull request #675 from clark800/fix-validated-ledgers
Fix error on ledger subscription message without validated_ledgers
2015-12-14 11:30:35 -08:00
Chris Clark
cce55b9361 Fix error on ledger subscription message without validated_ledgers 2015-12-14 11:07:58 -08:00
122 changed files with 3857 additions and 1169 deletions

4
.babelrc Normal file
View File

@@ -0,0 +1,4 @@
{
"presets": ["es2015", "stage-1"],
"plugins": ["syntax-flow", "transform-flow-strip-types"]
}

View File

@@ -4,6 +4,11 @@
.*/ripple-lib/test/fixtures/.*
.*/node_modules/flow-bin/.*
.*/node_modules/webpack/.*
.*/node_modules/babel-core/.*
.*/node_modules/babel-eslint/.*
.*/node_modules/babel-preset-es2015/.*
.*/node_modules/babel-preset-stage-1/.*
.*/node_modules/babel-register/.*
[include]
./node_modules/

View File

@@ -8,24 +8,49 @@ var rename = require('gulp-rename');
var webpack = require('webpack');
var bump = require('gulp-bump');
var argv = require('yargs').argv;
var assert = require('assert');
var fs = require('fs');
var pkg = require('./package.json');
var uglifyOptions = {
mangle: {
except: ['_', 'RippleError', 'RippledError', 'UnexpectedError',
'LedgerVersionError', 'ConnectionError', 'NotConnectedError',
'DisconnectedError', 'TimeoutError', 'ResponseFormatError',
'ValidationError', 'NotFoundError', 'MissingLedgerHistoryError',
'PendingLedgerVersionError'
]
}
};
function webpackConfig(extension, overrides) {
overrides = overrides || {};
var defaults = {
cache: true,
externals: [{
'lodash': '_'
}],
entry: './src/index.js',
output: {
library: 'ripple',
path: './build/',
filename: ['ripple-', extension].join(pkg.version)
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/^ws$/, './wswrapper'),
new webpack.NormalModuleReplacementPlugin(/^\.\/wallet$/, './wallet-web'),
new webpack.NormalModuleReplacementPlugin(/^.*setup-api$/,
'./setup-api-web')
],
module: {
loaders: [{
test: /jayson/,
loader: 'null'
}, {
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader?optional=runtime'
exclude: [/node_modules/],
loader: 'babel-loader'
}, {
test: /\.json/,
loader: 'json-loader'
@@ -35,15 +60,70 @@ function webpackConfig(extension, overrides) {
return _.assign({}, defaults, overrides);
}
function webpackConfigForWebTest(testFileName, path) {
var match = testFileName.match(/\/?([^\/]*)-test.js$/);
if (!match) {
assert(false, 'wrong filename:' + testFileName);
}
var configOverrides = {
externals: [{
'lodash': '_',
'ripple-api': 'ripple',
'net': 'null'
}],
entry: testFileName,
output: {
library: match[1].replace(/-/g, '_'),
path: './test-compiled-for-web/' + (path ? path : ''),
filename: match[1] + '-test.js'
}
};
return webpackConfig('.js', configOverrides);
}
gulp.task('build-tests', function(callback) {
var times = 0;
function done() {
if (++times >= 5) {
callback();
}
}
webpack(webpackConfigForWebTest('./test/rangeset-test.js'), done);
webpack(webpackConfigForWebTest('./test/connection-test.js'), done);
webpack(webpackConfigForWebTest('./test/api-test.js'), done);
webpack(webpackConfigForWebTest('./test/broadcast-api-test.js'), done);
webpack(webpackConfigForWebTest('./test/integration/integration-test.js',
'integration/'), done);
});
function createLink(from, to) {
if (fs.existsSync(to)) {
fs.unlinkSync(to);
}
fs.linkSync(from, to);
}
function createBuildLink(callback) {
return function(err, res) {
createLink('./build/ripple-' + pkg.version + '.js',
'./build/ripple-latest.js');
callback(err, res);
};
}
gulp.task('build', function(callback) {
webpack(webpackConfig('.js'), callback);
webpack(webpackConfig('.js'), createBuildLink(callback));
});
gulp.task('build-min', ['build'], function() {
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
.pipe(uglify())
.pipe(uglify(uglifyOptions))
.pipe(rename(['ripple-', '-min.js'].join(pkg.version)))
.pipe(gulp.dest('./build/'));
.pipe(gulp.dest('./build/'))
.on('end', function() {
createLink('./build/ripple-' + pkg.version + '-min.js',
'./build/ripple-latest-min.js');
});
});
gulp.task('build-debug', function(callback) {

View File

@@ -1,3 +1,11 @@
##0.16.5
**Changes**
+ [Filter insufficient source funds paths from pathfind results](https://github.com/ripple/ripple-lib/pull/688)
##0.16.4
**Changes**
+ [Update ws to 1.0.1](https://github.com/ripple/ripple-lib/pull/682)
##0.16.2
**Changes**
+ [Bump ripple-binary-codec dependency version to 0.1.1 to fix issue with computeLedgerHash for transactions with DeliverMin]

View File

@@ -1,6 +1,6 @@
#ripple-lib
A JavaScript API for interacting with Ripple in Node.js and the browser
A JavaScript API for interacting with Ripple in Node.js
[![Circle CI](https://circleci.com/gh/ripple/ripple-lib/tree/develop.svg?style=svg)](https://circleci.com/gh/ripple/ripple-lib/tree/develop) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
@@ -8,7 +8,7 @@ A JavaScript API for interacting with Ripple in Node.js and the browser
###Features
+ Connect to a rippled server in JavaScript (Node.js or browser)
+ Connect to a rippled server in Node.js
+ Issue [rippled API](https://ripple.com/build/rippled-apis/) requests
+ Listen to events on the Ripple network (transaction, ledger, etc.)
+ Sign and submit transactions to the Ripple network

View File

@@ -1,7 +1,18 @@
machine:
node:
version: 0.12.0
hosts:
testripple.circleci.com: 127.0.0.1
dependencies:
pre:
- wget https://s3-us-west-2.amazonaws.com/ripple-debs/rippled_0.30.1-b11-1.deb
- sudo dpkg -i rippled_0.30.1-b11-1.deb
test:
pre:
- rippled -a --start --conf "$HOME/$CIRCLE_PROJECT_REPONAME/test/integration/rippled.cfg":
background: true
override:
- scripts/ci.sh "$CIRCLE_NODE_INDEX" "$CIRCLE_NODE_TOTAL":
parallel: true
post:
- killall /usr/bin/rippled

View File

@@ -54,12 +54,15 @@
- [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
- [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
- [sign](#sign)
- [combine](#combine)
- [submit](#submit)
- [generateAddress](#generateaddress)
- [computeLedgerHash](#computeledgerhash)
- [API Events](#api-events)
- [ledger](#ledger)
- [error](#error)
- [connected](#connected)
- [disconnected](#disconnected)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -89,6 +92,14 @@ const api = new RippleAPI({
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
console.log('connected');
});
api.on('disconnected', (code) => {
// code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
// will be 1000 if this was normal closure
console.log('disconnected, code:', code);
});
api.connect().then(() => {
/* insert code here */
}).then(() => {
@@ -137,7 +148,7 @@ If you omit the `server` parameter, RippleAPI operates [offline](#offline-functi
1. Install [NodeJS](https://nodejs.org) and the Node Package Manager (npm). Most Linux distros have a package for NodeJS, but make sure you have version `0.12.0` or higher.
2. Use npm to install [Babel](https://babeljs.io/) globally:
`npm install -g babel`
`npm install -g babel-cli`
3. Use npm to install RippleAPI:
`npm install ripple-lib`
@@ -269,7 +280,7 @@ Executing a transaction with `RippleAPI` requires the following four steps:
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place.
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method.
3. [Submit](#submit) - Submit the transaction to the connected server.
4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted.
@@ -290,6 +301,7 @@ maxFee | [value](#value) | *Optional* The maximum fee to pay for the transaction
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.
signersCount | integer | *Optional* Number of signers that will be signing this transaction.
We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the 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.
@@ -480,6 +492,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre
regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key.
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances.
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
*signers.* weights | array | *Optional* Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight.
*signers.weights[].* address | [address](#ripple-address) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, represented as billionths of a unit. Use `null` to set no fee.
### Example
@@ -803,7 +821,7 @@ Name | Type | Description
id | [id](#transaction-id) | A hash of the transaction that can be used to identify it.
address | [address](#ripple-address) | The address of the account that initiated the transaction.
sequence | [sequence](#account-sequence-number) | The account sequence number of the transaction for the account that initiated it.
type | [transactionType](#transaction-types) | The type of the tranasction.
type | [transactionType](#transaction-types) | The type of the transaction.
specification | object | A specification that would produce the same outcome as this transaction. The structure of the specification depends on the value of the `type` field (see [Transaction Types](#transaction-types) for details). *Note:* This is **not** necessarily the same as the original specification.
outcome | object | The outcome of the transaction (what effects it had).
*outcome.* result | string | Result code returned by rippled. See [Transaction Results](https://ripple.com/build/transactions/#full-transaction-response-list) for a complete list.
@@ -820,6 +838,7 @@ outcome | object | The outcome of the transaction (what effects it had).
*outcome.orderbookChanges.\*[].* makerExchangeRate | [value](#value) | *Optional* The exchange rate between the `quantity` currency and the `totalPrice` currency from the point of view of the maker.
*outcome.* ledgerVersion | integer | The ledger version that the transaction was validated in.
*outcome.* indexInLedger | integer | The ordering index of the transaction in the ledger.
*outcome.* deliveredAmount | [amount](#amount) | *Optional* For payment transactions, it is impossible to reliably compute the actual delivered amount from the balanceChanges due to fixed precision. If the payment is not a partial payment and the transaction succeeded, the deliveredAmount should always be considered to be the amount specified in the transaction.
*outcome.* timestamp | date-time string | *Optional* The timestamp when the transaction was validated. (May be missing when requesting transactions in binary mode.)
### Example
@@ -859,6 +878,11 @@ return api.getTransaction(id).then(transaction => {
"result": "tesSUCCESS",
"timestamp": "2013-03-12T23:56:50.000Z",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -993,6 +1017,11 @@ return api.getTransactions(address).then(transaction => {
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -1085,6 +1114,11 @@ return api.getTransactions(address).then(transaction => {
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -1599,18 +1633,18 @@ paths | string | The paths of trustlines and orders to use in executing the paym
### Example
```javascript
const pathfind = {
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"amount": {
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "100"
}
}
const pathfind = {
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"amount": {
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "100"
}
}
};
return api.getPaths(pathfind)
.then(paths => {/* ... */});
@@ -2644,6 +2678,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre
regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key.
requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this accounts issuances.
requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag.
signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.
*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`.
*signers.* weights | array | *Optional* Weights of signatures for each signer.
*signers.* weights[] | object | An association of an address and a weight.
*signers.weights[].* address | [address](#ripple-address) | A Ripple account address
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, represented as billionths of a unit. Use `null` to set no fee.
### Example
@@ -2894,7 +2934,7 @@ const trustline = {
}
]
};
return api.preparePayment(address, trustline).then(prepared =>
return api.prepareTrustline(address, trustline).then(prepared =>
{/* ... */});
```
@@ -3282,7 +3322,7 @@ return api.prepareSuspendedPaymentExecution(address, suspendedPaymentExecution).
## sign
`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}`
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).
@@ -3292,6 +3332,8 @@ Name | Type | Description
---- | ---- | -----------
txJSON | string | Transaction represented as a JSON string in rippled format.
secret | secret string | The secret of the account that is initiating the transaction.
options | object | *Optional* Options that control the type of signature that will be generated.
*options.* signAs | [address](#ripple-address) | *Optional* The account that the signature should count for in multisigning.
### Return Value
@@ -3319,6 +3361,44 @@ return api.sign(txJSON, secret);
```
## combine
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit).
### Parameters
Name | Type | Description
---- | ---- | -----------
signedTransactions | array\<string\> | An array of signed transactions (from the output of [sign](#sign)) to combine.
### Return Value
This method returns an object with the following structure:
Name | Type | Description
---- | ---- | -----------
signedTransaction | string | The signed transaction represented as an uppercase hexadecimal string.
id | [id](#transaction-id) | The [Transaction ID](#transaction-id) of the signed transaction.
### Example
```javascript
const signedTransactions = [ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
"12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ];
return api.combine(signedTransactions);
```
```json
{
"signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
"id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC"
}
```
## submit
`submit(signedTransaction: string): Promise<Object>`
@@ -3337,7 +3417,7 @@ This method returns an object with the following structure:
Name | Type | Description
---- | ---- | -----------
resultCode | string | The result code returned by rippled. [List of tranasction responses](http://pages.lightthenight.org/gba/SanFran15/ripple)
resultCode | string | The result code returned by rippled. [List of transaction responses](https://ripple.com/build/transactions/#full-transaction-response-list)
resultMessage | string | Human-readable explanation of the status of the transaction.
### Example
@@ -3523,3 +3603,35 @@ api.on('error', (errorCode, errorMessage, data) => {
tooBusy: The server is too busy to help you now.
```
## connected
This event is emitted after connection successfully opened.
### Example
```javascript
api.on('connected', () => {
console.log('Connection is open now.');
});
```
## disconnected
This event is emitted when connection is closed.
### Return Value
The only parameter is a number containing the [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) send by the server.
### Example
```javascript
api.on('disconnected', (code) => {
if (code !== 1000) {
console.log('Connection is closed due to error.');
} else {
console.log('Connection is closed normally.');
}
});
```

38
docs/samples/cancelall.js Normal file
View File

@@ -0,0 +1,38 @@
'use strict';
const RippleAPI = require('../../dist/npm').RippleAPI; // require('ripple-lib')
const address = 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K';
const secret = '';
const api = new RippleAPI({server: 'wss://s1.ripple.com:443'});
const instructions = {maxLedgerVersionOffset: 5};
function fail(message) {
console.error(message);
process.exit(1);
}
function cancelOrder(orderSequence) {
console.log('Cancelling order: ' + orderSequence.toString());
return api.prepareOrderCancellation(address, {orderSequence}, instructions)
.then(prepared => {
const signing = api.sign(prepared.txJSON, secret);
return api.submit(signing.signedTransaction);
});
}
function cancelAllOrders(orderSequences) {
if (orderSequences.length === 0) {
return Promise.resolve();
}
const orderSequence = orderSequences.pop();
return cancelOrder(orderSequence).then(() => cancelAllOrders(orderSequences));
}
api.connect().then(() => {
console.log('Connected...');
return api.getOrders(address).then(orders => {
const orderSequences = orders.map(order => order.properties.sequence);
return cancelAllOrders(orderSequences);
}).then(() => process.exit(0));
}).catch(fail);

View File

@@ -11,6 +11,14 @@ const api = new RippleAPI({
api.on('error', (errorCode, errorMessage) => {
console.log(errorCode + ': ' + errorMessage);
});
api.on('connected', () => {
console.log('connected');
});
api.on('disconnected', (code) => {
// code - [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) sent by the server
// will be 1000 if this was normal closure
console.log('disconnected, code:', code);
});
api.connect().then(() => {
/* insert code here */
}).then(() => {
@@ -47,7 +55,7 @@ If you omit the `server` parameter, RippleAPI operates [offline](#offline-functi
1. Install [NodeJS](https://nodejs.org) and the Node Package Manager (npm). Most Linux distros have a package for NodeJS, but make sure you have version `0.12.0` or higher.
2. Use npm to install [Babel](https://babeljs.io/) globally:
`npm install -g babel`
`npm install -g babel-cli`
3. Use npm to install RippleAPI:
`npm install ripple-lib`

24
docs/src/combine.md.ejs Normal file
View File

@@ -0,0 +1,24 @@
## combine
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit).
### Parameters
<%- renderSchema("input/combine.json") %>
### Return Value
This method returns an object with the following structure:
<%- renderSchema("output/sign.json") %>
### Example
```javascript
const signedTransactions = <%- importFile('test/fixtures/requests/combine.json') %>;
return api.combine(signedTransactions);
```
<%- renderFixture("responses/combine.json") %>

View File

@@ -47,3 +47,35 @@ api.on('error', (errorCode, errorMessage, data) => {
```
tooBusy: The server is too busy to help you now.
```
## connected
This event is emitted after connection successfully opened.
### Example
```javascript
api.on('connected', () => {
console.log('Connection is open now.');
});
```
## disconnected
This event is emitted when connection is closed.
### Return Value
The only parameter is a number containing the [close code](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) send by the server.
### Example
```javascript
api.on('disconnected', (code) => {
if (code !== 1000) {
console.log('Connection is closed due to error.');
} else {
console.log('Connection is closed normally.');
}
});
```

View File

@@ -31,6 +31,7 @@
<% include prepareSuspendedPaymentCancellation.md.ejs %>
<% include prepareSuspendedPaymentExecution.md.ejs %>
<% include sign.md.ejs %>
<% include combine.md.ejs %>
<% include submit.md.ejs %>
<% include generateAddress.md.ejs %>
<% include computeLedgerHash.md.ejs %>

View File

@@ -23,7 +23,7 @@ All "prepare*" methods have the same return type.
```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const trustline = <%- importFile('test/fixtures/requests/prepare-trustline.json') %>;
return api.preparePayment(address, trustline).then(prepared =>
return api.prepareTrustline(address, trustline).then(prepared =>
{/* ... */});
```

View File

@@ -1,6 +1,6 @@
## sign
`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}`
`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}`
Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit).

View File

@@ -30,7 +30,7 @@ Executing a transaction with `RippleAPI` requires the following four steps:
* [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation)
* [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation)
* [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution)
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place.
2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method.
3. [Submit](#submit) - Submit the transaction to the connected server.
4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted.

153
npm-shrinkwrap.json generated
View File

@@ -1,20 +1,20 @@
{
"name": "ripple-lib",
"version": "0.16.2",
"version": "0.16.9",
"dependencies": {
"ajv": {
"version": "1.4.10",
"from": "ajv@>=1.4.8 <2.0.0",
"from": "https://registry.npmjs.org/ajv/-/ajv-1.4.10.tgz",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-1.4.10.tgz",
"dependencies": {
"json-stable-stringify": {
"version": "1.0.0",
"from": "json-stable-stringify@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz",
"dependencies": {
"jsonify": {
"version": "0.0.0",
"from": "jsonify@>=0.0.0 <0.1.0",
"from": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
}
}
@@ -23,155 +23,160 @@
},
"ajv-i18n": {
"version": "0.1.1",
"from": "ajv-i18n@>=0.1.0 <0.2.0",
"from": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-0.1.1.tgz",
"resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-0.1.1.tgz"
},
"babel-polyfill": {
"version": "6.3.14",
"from": "babel-polyfill@>=6.2.0 <7.0.0",
"from": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz",
"dependencies": {
"core-js": {
"version": "1.2.6",
"from": "core-js@>=1.0.1 <2.0.0",
"from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
},
"babel-regenerator-runtime": {
"version": "6.3.13",
"from": "babel-regenerator-runtime@>=6.3.13 <7.0.0",
"from": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz",
"resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz"
},
"babel-runtime": {
"version": "5.8.34",
"from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz"
}
}
},
"babel-runtime": {
"version": "5.8.34",
"from": "babel-runtime@>=5.5.4 <6.0.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"version": "6.3.19",
"from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.3.19.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.3.19.tgz",
"dependencies": {
"core-js": {
"version": "1.2.6",
"from": "core-js@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
}
}
},
"bignumber.js": {
"version": "2.1.2",
"from": "bignumber.js@>=2.0.3 <3.0.0",
"from": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.2.tgz",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.2.tgz"
},
"https-proxy-agent": {
"version": "1.0.0",
"from": "https-proxy-agent@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
"dependencies": {
"agent-base": {
"version": "2.0.1",
"from": "agent-base@>=2.0.0 <3.0.0",
"from": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.1.tgz",
"dependencies": {
"semver": {
"version": "5.0.3",
"from": "semver@>=5.0.1 <5.1.0",
"from": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz"
}
}
},
"debug": {
"version": "2.2.0",
"from": "debug@>=2.0.0 <3.0.0",
"from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
"dependencies": {
"ms": {
"version": "0.7.1",
"from": "ms@0.7.1",
"from": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
}
}
},
"extend": {
"version": "3.0.0",
"from": "extend@>=3.0.0 <4.0.0",
"from": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
}
}
},
"jayson": {
"version": "1.2.2",
"from": "jayson@>=1.2.2 <2.0.0",
"from": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz",
"dependencies": {
"JSONStream": {
"version": "1.0.3",
"from": "JSONStream@1.0.3",
"from": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz",
"dependencies": {
"jsonparse": {
"version": "1.0.0",
"from": "jsonparse@>=1.0.0 <1.1.0",
"from": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz"
},
"through": {
"version": "2.3.8",
"from": "through@>=2.2.7 <3.0.0",
"from": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
}
}
},
"commander": {
"version": "1.3.2",
"from": "commander@1.3.2",
"from": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz",
"resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz",
"dependencies": {
"keypress": {
"version": "0.1.0",
"from": "keypress@>=0.1.0 <0.2.0",
"from": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz",
"resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz"
}
}
},
"eyes": {
"version": "0.1.8",
"from": "eyes@0.1.8",
"from": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
},
"lodash": {
"version": "3.6.0",
"from": "lodash@3.6.0",
"from": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz"
}
}
},
"lodash": {
"version": "3.10.1",
"from": "lodash@>=3.1.0 <4.0.0",
"from": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz"
},
"ripple-address-codec": {
"version": "2.0.1",
"from": "ripple-address-codec@>=2.0.1 <3.0.0",
"from": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz",
"dependencies": {
"hash.js": {
"version": "1.0.3",
"from": "hash.js@>=1.0.3 <2.0.0",
"from": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"x-address-codec": {
"version": "0.7.2",
"from": "x-address-codec@>=0.7.0 <0.8.0",
"from": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.2.tgz",
"resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.2.tgz",
"dependencies": {
"base-x": {
"version": "1.0.1",
"from": "base-x@>=1.0.1 <2.0.0",
"from": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz"
}
}
@@ -179,77 +184,89 @@
}
},
"ripple-binary-codec": {
"version": "0.1.1",
"from": "ripple-binary-codec@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.1.1.tgz",
"version": "0.1.2",
"from": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.1.2.tgz",
"resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.1.2.tgz",
"dependencies": {
"babel-runtime": {
"version": "5.8.34",
"from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"dependencies": {
"core-js": {
"version": "1.2.6",
"from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
}
}
},
"bn.js": {
"version": "3.3.0",
"from": "bn.js@>=3.2.0 <4.0.0",
"from": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
},
"create-hash": {
"version": "1.1.2",
"from": "create-hash@>=1.1.2 <2.0.0",
"from": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"dependencies": {
"cipher-base": {
"version": "1.0.2",
"from": "cipher-base@>=1.0.1 <2.0.0",
"from": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz"
},
"ripemd160": {
"version": "1.0.1",
"from": "ripemd160@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
},
"sha.js": {
"version": "2.4.4",
"from": "sha.js@>=2.3.6 <3.0.0",
"from": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
}
}
},
"decimal.js": {
"version": "4.0.3",
"from": "decimal.js@>=4.0.2 <5.0.0",
"from": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.0 <3.0.0",
"from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"ripple-hashes": {
"version": "0.1.0",
"from": "ripple-hashes@>=0.1.0 <0.2.0",
"from": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.1.0.tgz",
"resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.1.0.tgz",
"dependencies": {
"create-hash": {
"version": "1.1.2",
"from": "create-hash@>=1.1.2 <2.0.0",
"from": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz",
"dependencies": {
"cipher-base": {
"version": "1.0.2",
"from": "cipher-base@>=1.0.1 <2.0.0",
"from": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz"
},
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
"ripemd160": {
"version": "1.0.1",
"from": "ripemd160@>=1.0.0 <2.0.0",
"from": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz"
},
"sha.js": {
"version": "2.4.4",
"from": "sha.js@>=2.3.6 <3.0.0",
"from": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz"
}
}
@@ -258,39 +275,51 @@
},
"ripple-keypairs": {
"version": "0.10.0",
"from": "ripple-keypairs@>=0.10.0 <0.11.0",
"from": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
"dependencies": {
"babel-runtime": {
"version": "5.8.34",
"from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz",
"dependencies": {
"core-js": {
"version": "1.2.6",
"from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz"
}
}
},
"bn.js": {
"version": "3.3.0",
"from": "bn.js@>=3.1.1 <4.0.0",
"from": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz"
},
"brorand": {
"version": "1.0.5",
"from": "brorand@>=1.0.5 <2.0.0",
"from": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz",
"resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz"
},
"elliptic": {
"version": "5.2.1",
"from": "elliptic@>=5.1.0 <6.0.0",
"from": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <2.1.0",
"from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
},
"hash.js": {
"version": "1.0.3",
"from": "hash.js@>=1.0.3 <2.0.0",
"from": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz",
"dependencies": {
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.1 <3.0.0",
"from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
}
}
@@ -299,22 +328,22 @@
},
"ripple-lib-transactionparser": {
"version": "0.6.0",
"from": "ripple-lib-transactionparser@>=0.6.0 <0.7.0",
"from": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.0.tgz",
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.0.tgz"
},
"ws": {
"version": "0.7.2",
"from": "ws@>=0.7.1 <0.8.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
"version": "1.0.1",
"from": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz",
"dependencies": {
"options": {
"version": "0.0.6",
"from": "options@>=0.0.5",
"from": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
},
"ultron": {
"version": "1.0.2",
"from": "ultron@>=1.0.0 <1.1.0",
"from": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.16.2",
"version": "0.16.9",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -16,40 +16,49 @@
},
"dependencies": {
"ajv": "^1.4.8",
"babel-polyfill": "^6.2.0",
"babel-runtime": "^5.5.4",
"babel-polyfill": "^6.3.14",
"babel-runtime": "^6.3.19",
"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.1",
"ripple-binary-codec": "^0.1.2",
"ripple-hashes": "^0.1.0",
"ripple-keypairs": "^0.10.0",
"ripple-lib-transactionparser": "^0.6.0",
"ws": "^0.7.1"
"ws": "^1.0.1"
},
"devDependencies": {
"assert-diff": "^1.0.1",
"babel": "^5.8.21",
"babel-core": "^5.8.22",
"babel-eslint": "^4.1.3",
"babel-loader": "^5.3.2",
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.8",
"babel-loader": "^6.2.1",
"babel-plugin-syntax-flow": "^6.3.13",
"babel-plugin-transform-flow-strip-types": "^6.4.0",
"babel-preset-es2015": "^6.3.13",
"babel-preset-stage-1": "^6.3.13",
"babel-register": "^6.3.13",
"coveralls": "^2.10.0",
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eslint": "^1.3.0",
"eslint-plugin-flowtype": "^1.0.0",
"eslint": "^2.1.0",
"eventemitter2": "^0.4.14",
"flow-bin": "^0.14",
"gulp": "^3.8.10",
"gulp-bump": "^0.1.13",
"gulp-rename": "^1.2.0",
"gulp-uglify": "^1.1.0",
"istanbul": "^0.3.5",
"http-server": "^0.8.5",
"isparta": "^4.0.0",
"json-loader": "^0.5.2",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "^2.1.0",
"mocha-junit-reporter": "^1.9.1",
"mocha-phantomjs": "^4.0.1",
"mocha-in-sauce": "^0.0.1",
"null-loader": "^0.1.1",
"webpack": "^1.5.3",
"yargs": "^1.3.1"
},
@@ -63,11 +72,12 @@
"watch": "babel -w -D --optional runtime -d dist/npm/ src/",
"compile-with-source-maps": "babel -D --optional runtime -s -t -d dist/npm/ src/",
"prepublish": "npm run clean && npm run compile",
"test": "istanbul test _mocha",
"test": "babel-node node_modules/isparta/lib/cli cover ./node_modules/mocha/bin/_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",
"start": "babel-node scripts/http.js"
"start": "babel-node scripts/http.js",
"sauce": "babel-node scripts/sauce-runner.js"
},
"repository": {
"type": "git",

18
scripts/checkeol.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
function checkEOL {
local changedFiles=$(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD)
local result=0
for name in $changedFiles; do
grep -c -U -q $'\r' $name
if [ $? -eq 0 ]; then
echo "windows eol found in $name" >&2
result=1
fi
done
if [ $result -eq 1 ]; then
false
fi
}
checkEOL

View File

@@ -3,6 +3,10 @@
NODE_INDEX="$1"
TOTAL_NODES="$2"
function checkEOL {
./scripts/checkeol.sh
}
typecheck() {
npm install -g flow-bin
flow --version
@@ -11,7 +15,7 @@ typecheck() {
lint() {
echo "eslint $(node_modules/.bin/eslint --version)"
npm list babel-eslint | grep babel-eslint
npm list babel-eslint
REPO_URL="https://raw.githubusercontent.com/ripple/javascript-style-guide"
curl "$REPO_URL/es6/eslintrc" > ./eslintrc
echo "parser: babel-eslint" >> ./eslintrc
@@ -20,6 +24,7 @@ lint() {
unittest() {
# test "src"
mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
npm test --coverage
npm run coveralls
@@ -29,12 +34,33 @@ unittest() {
mkdir -p test-compiled/node_modules
ln -nfs ../../dist/npm test-compiled/node_modules/ripple-api
mocha --opts test-compiled/mocha.opts test-compiled
# compile tests for browser testing
gulp build-min build-tests
node --harmony test-compiled/mocked-server.js > /dev/null &
echo "Running tests in PhantomJS"
mocha-phantomjs test/localrunner.html
echo "Running tests using minified version in PhantomJS"
mocha-phantomjs test/localrunnermin.html
echo "Running tests in SauceLabs"
http-server &
npm run sauce
pkill -f mocked-server.js
pkill -f http-server
rm -rf test-compiled
}
integrationtest() {
mocha test/integration/integration-test.js
mocha test/integration/http-integration-test.js
# run integration tests in PhantomJS
gulp build-tests build-min
echo "Running integragtion tests in PhantomJS"
mocha-phantomjs test/localintegrationrunner.html
}
doctest() {
@@ -47,6 +73,7 @@ doctest() {
}
oneNode() {
checkEOL
doctest
lint
typecheck
@@ -57,7 +84,7 @@ oneNode() {
twoNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) typecheck; unittest;;
1) checkEOL; typecheck; unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
@@ -65,7 +92,7 @@ twoNodes() {
threeNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) typecheck;;
1) checkEOL; typecheck;;
2) unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac

View File

@@ -16,28 +16,3 @@ echo ""
echo "publish to npm"
npm publish
exit_on_error
rm -rf dist/bower
echo ""
echo "publish to bower"
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
exit_on_error
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
exit_on_error
git commit -m "[TASK] add v$version"
exit_on_error
git tag "v$version"
exit_on_error
git push origin master
git push --tags origin master
cd ../..

View File

@@ -16,28 +16,3 @@ echo ""
echo "publish rc to npm"
npm publish --tag beta
exit_on_error
rm -rf dist/bower
echo ""
echo "publish to bower"
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
exit_on_error
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
exit_on_error
git commit -m "[TASK] add v$version"
exit_on_error
git tag "v$version"
exit_on_error
git push origin master
git push --tags origin master
cd ../..

View File

@@ -1,12 +0,0 @@
rm -rf dist/bower
git clone git@github.com:ripple/bower-ripple.git dist/bower
gulp bower
cd dist/bower
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
echo "version: $version"
git add ripple.js ripple-debug.js ripple-min.js bower.json
git commit -m "[TASK] add v$version"
git tag "v$version"
git push origin master
git push --tags origin master
cd ..

96
scripts/sauce-runner.js Normal file
View File

@@ -0,0 +1,96 @@
const _ = require('lodash');
const MochaSauce = require('mocha-in-sauce');
const testUrl = 'http://testripple.circleci.com:8080/test/saucerunner.html';
function main() {
// uncomment for more debug info
// process.env.DEBUG = '*';
// configure
const config = {
name: 'RippleAPI',
host: 'localhost',
port: 4445,
maxDuration: 180000,
// the current build name (optional)
build: Date.now(),
url: testUrl,
runSauceConnect: true
};
if (process.env.CIRCLE_BUILD_NUM) {
config.build = process.env.CIRCLE_BUILD_NUM;
config.tags = [process.env.CIRCLE_BRANCH, process.env.CIRCLE_SHA1];
config.tunnelIdentifier = process.env.CIRCLE_BUILD_NUM;
}
const sauce = new MochaSauce(config);
sauce.concurrency(5);
// setup what browsers to test with
sauce.browser({browserName: 'firefox', platform: 'Linux',
version: '43'});
sauce.browser({browserName: 'firefox', platform: 'Windows 8.1',
version: '43'});
sauce.browser({browserName: 'firefox', platform: 'OS X 10.11',
version: '43'});
sauce.browser({browserName: 'safari', platform: 'OS X 10.11',
version: '9'});
sauce.browser({browserName: 'chrome', platform: 'OS X 10.11',
version: '47'});
sauce.browser({browserName: 'chrome', platform: 'Linux',
version: '47'});
sauce.browser({browserName: 'chrome', platform: 'Windows 8.1',
version: '47'});
sauce.browser({browserName: 'internet explorer', platform: 'Windows 10',
version: '11'});
sauce.browser({browserName: 'MicrosoftEdge', platform: 'Windows 10',
version: '20'});
sauce.on('init', function(browser) {
console.log(' init : %s %s', browser.browserName, browser.platform);
});
sauce.on('start', function(browser) {
console.log(' start : %s %s', browser.browserName, browser.platform);
});
sauce.on('end', function(browser, res) {
console.log(' end : %s %s : %d failures', browser.browserName,
browser.platform, res && res.failures);
});
sauce.on('connected', sauceConnectProcess => {
sauceConnectProcess.on('exit', function(code, /* signal */) {
if (code > 0) {
console.log('something wrong - exiting');
process.exit();
} else {
console.log('normal tunnel exit');
}
});
});
sauce.start(function(err, res) {
let failure = false;
if (err) {
console.log('Error starting Sauce');
console.error(err);
process.exitCode = 2;
} else {
console.log('-------------- done --------------');
failure = _.some(res, 'failures');
console.log('Tests are failed:', failure);
if (failure) {
process.exitCode = 1;
}
}
});
}
main();

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line
/* eslint-disable max-len */
// Enable core-js polyfills. This allows use of ES6/7 extensions listed here:
@@ -44,6 +44,7 @@ const prepareSuspendedPaymentCancellation =
require('./transaction/suspended-payment-cancellation');
const prepareSettings = require('./transaction/settings');
const sign = require('./transaction/sign');
const combine = require('./transaction/combine');
const submit = require('./transaction/submit');
const errors = require('./common').errors;
const generateAddress =
@@ -88,6 +89,12 @@ class RippleAPI extends EventEmitter {
this.connection.on('error', (errorCode, errorMessage, data) => {
this.emit('error', errorCode, errorMessage, data);
});
this.connection.on('connected', () => {
this.emit('connected');
});
this.connection.on('disconnected', onError => {
this.emit('disconnected', onError);
});
} else {
// use null object pattern to provide better error message if user
// tries to call a method that requires a connection
@@ -125,6 +132,7 @@ _.assign(RippleAPI.prototype, {
prepareSuspendedPaymentCancellation,
prepareSettings,
sign,
combine,
submit,
generateAddress,

View File

@@ -11,6 +11,9 @@ class RippleAPIBroadcast extends RippleAPI {
_.assign({}, options, {server})
));
// exposed for testing
this._apis = apis;
this.getMethodNames().forEach(name => {
this[name] = function() { // eslint-disable-line no-loop-func
return Promise.race(apis.map(api => api[name].apply(api, arguments)));

View File

@@ -0,0 +1,21 @@
'use strict';
function setPrototypeOf(object, prototype) {
// Object.setPrototypeOf not supported on Internet Explorer 9
/* eslint-disable */
Object.setPrototypeOf ? Object.setPrototypeOf(object, prototype) :
object.__proto__ = prototype;
/* eslint-enable */
}
function getConstructorName(object) {
// hack for internet explorer
return process.browser ?
object.constructor.toString().match(/^function\s+([^(]*)/)[1] :
object.constructor.name;
}
module.exports = {
getConstructorName,
setPrototypeOf
};

View File

@@ -1,4 +1,5 @@
'use strict';
'use strict'; // eslint-disable-line
const _ = require('lodash');
const {EventEmitter} = require('events');
const WebSocket = require('ws');
@@ -36,6 +37,19 @@ class Connection extends EventEmitter {
this._ledgerVersion = null;
this._availableLedgerVersions = new RangeSet();
this._nextRequestID = 1;
this._retry = 0;
this._retryTimer = null;
}
_updateLedgerVersions(data) {
this._ledgerVersion = Number(data.ledger_index);
if (data.validated_ledgers) {
this._availableLedgerVersions.reset();
this._availableLedgerVersions.parseAndAddRanges(
data.validated_ledgers);
} else {
this._availableLedgerVersions.addValue(this._ledgerVersion);
}
}
// return value is array of arguments to Connection.emit
@@ -48,10 +62,7 @@ class Connection extends EventEmitter {
return [data.id.toString(), data];
} else if (isStreamMessageType(data.type)) {
if (data.type === 'ledgerClosed') {
this._ledgerVersion = Number(data.ledger_index);
this._availableLedgerVersions.reset();
this._availableLedgerVersions.parseAndAddRanges(
data.validated_ledgers);
this._updateLedgerVersions(data);
}
return [data.type, data];
} else if (data.type === undefined && data.error) {
@@ -73,7 +84,7 @@ class Connection extends EventEmitter {
}
// we don't want this inside the try/catch or exceptions in listener
// will be caught
this.emit.apply(this, parameters);
this.emit(...parameters);
}
get _state() {
@@ -88,26 +99,80 @@ class Connection extends EventEmitter {
return this._state === WebSocket.OPEN && this._isReady;
}
_onUnexpectedClose() {
_onUnexpectedClose(beforeOpen, resolve, reject, code) {
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
}
this._ws = null;
this._isReady = false;
this.connect().then();
if (beforeOpen) {
// connection was closed before it was properly opened, so we must return
// error to connect's caller
this.connect().then(resolve, reject);
} else {
// if first parameter ws lib sends close code,
// but sometimes it forgots about it, so default to 1006 - CLOSE_ABNORMAL
this.emit('disconnected', code || 1006);
this._retryConnect();
}
}
_calculateTimeout(retriesCount) {
return (retriesCount < 40)
// First, for 2 seconds: 20 times per second
? (1000 / 20)
: (retriesCount < 40 + 60)
// Then, for 1 minute: once per second
? (1000)
: (retriesCount < 40 + 60 + 60)
// Then, for 10 minutes: once every 10 seconds
? (10 * 1000)
// Then: once every 30 seconds
: (30 * 1000);
}
_retryConnect() {
this._retry += 1;
const retryTimeout = this._calculateTimeout(this._retry);
this._retryTimer = setTimeout(() => {
this.connect().catch(this._retryConnect.bind(this));
}, retryTimeout);
}
_clearReconnectTimer() {
clearTimeout(this._retryTimer);
this._retryTimer = null;
}
_onOpen() {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._onUnexpectedCloseBound =
this._onUnexpectedClose.bind(this, false, null, null);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
this._retry = 0;
this._ws.on('error', error =>
this.emit('error', 'websocket', error.message, error));
const request = {
command: 'subscribe',
streams: ['ledger']
};
return this.request(request).then(response => {
this._ledgerVersion = Number(response.ledger_index);
this._availableLedgerVersions.parseAndAddRanges(
response.validated_ledgers);
return this.request(request).then(data => {
this._updateLedgerVersions(data);
this._isReady = true;
this.emit('connected');
});
}
_onOpenError(reject, error) {
this._onOpenErrorBound = null;
reject(new NotConnectedError(error && error.message));
}
_createWebSocket() {
const options = {};
if (this._proxyURL !== undefined) {
@@ -142,14 +207,17 @@ class Connection extends EventEmitter {
cert: this._certificate
}, _.isUndefined);
const websocketOptions = _.assign({}, options, optionsOverrides);
const websocket = new WebSocket(this._url, websocketOptions);
const websocket = new WebSocket(this._url, null, websocketOptions);
// we will have a listener for each outstanding request,
// so we have to raise the limit (the default is 10)
websocket.setMaxListeners(Infinity);
if (typeof websocket.setMaxListeners === 'function') {
websocket.setMaxListeners(Infinity);
}
return websocket;
}
connect() {
this._clearReconnectTimer();
return new Promise((resolve, reject) => {
if (!this._url) {
reject(new ConnectionError(
@@ -165,10 +233,18 @@ class Connection extends EventEmitter {
// 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));
// In case if there is connection error (say, server is not responding)
// we must return this error to connection's caller. After successful
// opening, we will forward all errors to main api object.
this._onOpenErrorBound = this._onOpenError.bind(this, reject);
this._ws.once('error', this._onOpenErrorBound);
this._ws.on('message', this._onMessage.bind(this));
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this);
// in browser close event can came before open event, so we must
// resolve connect's promise after reconnect in that case.
// after open event we will rebound _onUnexpectedCloseBound
// without resolve and reject functions
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
resolve, reject);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.once('open', () => this._onOpen().then(resolve, reject));
}
@@ -176,6 +252,8 @@ class Connection extends EventEmitter {
}
disconnect() {
this._clearReconnectTimer();
this._retry = 0;
return new Promise(resolve => {
if (this._state === WebSocket.CLOSED) {
resolve();
@@ -183,9 +261,10 @@ class Connection extends EventEmitter {
this._ws.once('close', resolve);
} else {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._ws.once('close', () => {
this._ws.once('close', code => {
this._ws = null;
this._isReady = false;
this.emit('disconnected', code || 1000); // 1000 - CLOSE_NORMAL
resolve();
});
this._ws.close();

View File

@@ -1,13 +1,27 @@
'use strict';
const util = require('util');
const browserHacks = require('./browser-hacks');
class RippleError extends Error {
// this is needed because extending builtins doesn't work in babel 6.x
function extendableBuiltin(cls) {
function ExtendableBuiltin() {
cls.apply(this, arguments);
}
ExtendableBuiltin.prototype = Object.create(cls.prototype);
browserHacks.setPrototypeOf(ExtendableBuiltin, cls);
return ExtendableBuiltin;
}
class RippleError extends extendableBuiltin(Error) {
constructor(message, data) {
super(message);
this.name = this.constructor.name;
this.name = browserHacks.getConstructorName(this);
this.message = message;
this.data = data;
Error.captureStackTrace(this, this.constructor.name);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor.name);
}
}
toString() {

View File

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

View File

@@ -0,0 +1,19 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "combineParameters",
"type": "object",
"properties": {
"signedTransactions": {
"type": "array",
"description": "An array of signed transactions (from the output of [sign](#sign)) to combine.",
"items": {
"type": "string",
"pattern": "^[A-F0-9]+$",
"description": "A single-signed transaction represented as an uppercase hexadecimal string (from the output of [sign](#sign))"
},
"minLength": 1
}
},
"additionalProperties": false,
"required": ["signedTransactions"]
}

View File

@@ -11,6 +11,17 @@
"type": "string",
"format": "secret",
"description": "The secret of the account that is initiating the transaction."
},
"options": {
"type": "object",
"description": "Options that control the type of signature that will be generated.",
"properties": {
"signAs": {
"$ref": "address",
"description": "The account that the signature should count for in multisigning."
}
},
"additionalProperties": false
}
},
"additionalProperties": false,

View File

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

View File

@@ -28,6 +28,11 @@
"description": "Offset from current validated legder version to highest ledger version that the transaction can be included in.",
"type": "integer",
"minimum": 0
},
"signersCount": {
"description": "Number of signers that will be signing this transaction.",
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false,

View File

@@ -68,6 +68,35 @@
],
"description": "The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key."
},
"signers": {
"type": "object",
"description": "Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.",
"properties": {
"threshold": {
"$ref": "uint32",
"description": "A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`."
},
"weights": {
"type": "array",
"description": "Weights of signatures for each signer.",
"items": {
"type": "object",
"description": "An association of an address and a weight.",
"properties": {
"address": {"$ref": "address"},
"weight": {
"$ref": "uint32",
"description": "The weight that the signature of this account counts as towards the threshold."
}
},
"required": ["address", "weight"],
"additionalProperties": false
},
"minItems": 1,
"maxItems": 8
}
}
},
"memos": {"$ref": "memos"}
},
"additionalProperties": false

View File

@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "transactionType",
"link": "transaction-types",
"description": "The type of the tranasction.",
"description": "The type of the transaction.",
"type": "string",
"enum": ["payment", "order", "orderCancellation", "trustline", "settings",
"suspendedPaymentCreation", "suspendedPaymentCancellation",

View File

@@ -17,6 +17,10 @@
"$ref": "value",
"description": "The XRP fee that was charged for the transaction."
},
"deliveredAmount": {
"$ref": "amount",
"description": "For payment transactions, it is impossible to reliably compute the actual delivered amount from the balanceChanges due to fixed precision. If the payment is not a partial payment and the transaction succeeded, the deliveredAmount should always be considered to be the amount specified in the transaction."
},
"balanceChanges": {
"type": "object",
"additionalProperties": {

View File

@@ -5,7 +5,7 @@
"properties": {
"resultCode": {
"type": "string",
"description": "The result code returned by rippled. [List of tranasction responses](http://pages.lightthenight.org/gba/SanFran15/ripple)"
"description": "The result code returned by rippled. [List of transaction responses](https://ripple.com/build/transactions/#full-transaction-response-list)"
},
"resultMessage": {
"type": "string",

View File

@@ -1,55 +1,55 @@
/* @flow */
'use strict';
export type RippledAmountIOU = {
currency: string,
value: string,
issuer?: string
}
export type RippledAmount = string | RippledAmountIOU
export type Amount = {
value: string,
currency: string,
counterparty?: string
}
// Amount where counterparty and value are optional
export type LaxLaxAmount = {
currency: string,
value?: string,
counterparty?: string
}
// A currency-counterparty pair, or just currency if it's XRP
export type Issue = {
currency: string,
counterparty?: string
}
export type Adjustment = {
address: string,
amount: Amount,
tag?: number
}
export type MaxAdjustment = {
address: string,
maxAmount: Amount,
tag?: number
}
export type MinAdjustment = {
address: string,
minAmount: Amount,
tag?: number
}
export type Memo = {
type?: string,
format?: string,
data?: string
}
/* @flow */
'use strict';
export type RippledAmountIOU = {
currency: string,
value: string,
issuer?: string
}
export type RippledAmount = string | RippledAmountIOU
export type Amount = {
value: string,
currency: string,
counterparty?: string
}
// Amount where counterparty and value are optional
export type LaxLaxAmount = {
currency: string,
value?: string,
counterparty?: string
}
// A currency-counterparty pair, or just currency if it's XRP
export type Issue = {
currency: string,
counterparty?: string
}
export type Adjustment = {
address: string,
amount: Amount,
tag?: number
}
export type MaxAdjustment = {
address: string,
maxAmount: Amount,
tag?: number
}
export type MinAdjustment = {
address: string,
minAmount: Amount,
tag?: number
}
export type Memo = {
type?: string,
format?: string,
data?: string
}

View File

@@ -35,12 +35,13 @@ function toRippledAmount(amount: Amount): RippledAmount {
};
}
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g;
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
let newKey;
return _.reduce(obj, (result, value, key) => {
newKey = key;
// taking this out of function leads to error in PhantomJS
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g;
if (FINDSNAKE.test(key)) {
newKey = key.replace(FINDSNAKE, r => r[0] + r[2].toUpperCase());
}

View File

@@ -47,6 +47,7 @@ module.exports = {
prepareSuspendedPaymentExecution: _.partial(schemaValidate,
'prepareSuspendedPaymentExecutionParameters'),
sign: _.partial(schemaValidate, 'signParameters'),
combine: _.partial(schemaValidate, 'combineParameters'),
submit: _.partial(schemaValidate, 'submitParameters'),
computeLedgerHash: _.partial(schemaValidate, 'computeLedgerHashParameters'),
generateAddress: _.partial(schemaValidate, 'generateAddressParameters'),

59
src/common/wswrapper.js Normal file
View File

@@ -0,0 +1,59 @@
'use strict';
const {EventEmitter} = require('events');
function unsused() {}
/**
* Provides `EventEmitter` interface for native browser `WebSocket`,
* same, as `ws` package provides.
*/
class WSWrapper extends EventEmitter {
constructor(url, protocols = null, websocketOptions = {}) {
super();
unsused(protocols);
unsused(websocketOptions);
this.setMaxListeners(Infinity);
this._ws = new WebSocket(url);
this._ws.onclose = () => {
this.emit('close');
};
this._ws.onopen = () => {
this.emit('open');
};
this._ws.onerror = error => {
this.emit('error', error);
};
this._ws.onmessage = message => {
this.emit('message', message.data);
};
}
close() {
if (this.readyState === 1) {
this._ws.close();
}
}
send(message) {
this._ws.send(message);
}
get readyState() {
return this._ws.readyState;
}
}
WSWrapper.CONNECTING = 0;
WSWrapper.OPEN = 1;
WSWrapper.CLOSING = 2;
WSWrapper.CLOSED = 3;
module.exports = WSWrapper;

View File

@@ -8,6 +8,7 @@ const jayson = require('jayson');
const RippleAPI = require('./api').RippleAPI;
/* istanbul ignore next */
function createHTTPServer(options, httpPort) {
const rippleAPI = new RippleAPI(options);

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict'; // eslint-disable-line strict
const utils = require('./utils');
const flags = require('./flags').orderFlags;
const parseAmount = require('./amount');
@@ -31,11 +31,14 @@ function parseAccountOrder(address: string, order: Object): Object {
expirationTime: utils.parseTimestamp(order.expiration)
});
const makerExchangeRate = order.quality ?
utils.adjustQualityForXRP(order.quality.toString(),
takerGetsAmount.currency, takerPaysAmount.currency) :
computeQuality(takerGetsAmount, takerPaysAmount);
const properties = {
maker: address,
sequence: order.seq,
makerExchangeRate: order.quality ? order.quality.toString()
: computeQuality(takerGetsAmount, takerPaysAmount)
makerExchangeRate: makerExchangeRate
};
return {specification, properties};

View File

@@ -1,6 +1,6 @@
/* @flow */
'use strict';
const utils = require('./utils');
const utils = require('../utils');
import type {Amount, RippledAmount} from '../../common/types.js';
@@ -8,7 +8,7 @@ function parseAmount(amount: RippledAmount): Amount {
if (typeof amount === 'string') {
return {
currency: 'XRP',
value: utils.dropsToXrp(amount)
value: utils.common.dropsToXrp(amount)
};
}
return {

View File

@@ -1,5 +1,6 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const AccountFields = require('./utils').constants.AccountFields;
@@ -22,6 +23,26 @@ function parseFields(data: Object): Object {
settings[info.name] = parseField(info, fieldValue);
}
}
if (data.RegularKey) {
settings.regularKey = data.RegularKey;
}
// TODO: this isn't implemented in rippled yet, may have to change this later
if (data.SignerQuorum || data.SignerEntries) {
settings.signers = {};
if (data.SignerQuorum) {
settings.signers.threshold = data.SignerQuorum;
}
if (data.SignerEntries) {
settings.signers.weights = _.map(data.SignerEntries, entry => {
return {
address: entry.SignerEntry.Account,
weight: entry.SignerEntry.SignerWeight
};
});
}
}
return settings;
}

View File

@@ -6,10 +6,6 @@ const utils = require('./utils');
const parseAmount = require('./amount');
const txFlags = utils.txFlags;
function isPartialPayment(tx) {
return (tx.Flags & txFlags.Payment.PartialPayment) !== 0;
}
function isNoDirectRipple(tx) {
return (tx.Flags & txFlags.Payment.NoRippleDirect) !== 0;
}
@@ -45,7 +41,7 @@ function parsePayment(tx: Object): Object {
memos: utils.parseMemos(tx),
invoiceID: tx.InvoiceID,
paths: tx.Paths ? JSON.stringify(tx.Paths) : undefined,
allowPartialPayment: isPartialPayment(tx) || undefined,
allowPartialPayment: utils.isPartialPayment(tx) || undefined,
noDirectRipple: isNoDirectRipple(tx) || undefined,
limitQuality: isQualityLimited(tx) || undefined
});

View File

@@ -52,10 +52,10 @@ function parseFlags(tx: Object) {
function parseSettings(tx: Object) {
const txType = tx.TransactionType;
assert(txType === 'AccountSet' || txType === 'SetRegularKey');
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
txType === 'SignerListSet');
const regularKey = tx.RegularKey ? {regularKey: tx.RegularKey} : {};
return _.assign(regularKey, parseFlags(tx), parseFields(tx));
return _.assign({}, parseFlags(tx), parseFields(tx));
}
module.exports = parseSettings;

View File

@@ -22,7 +22,8 @@ function parseTransactionType(type) {
SetRegularKey: 'settings',
SuspendedPaymentCreate: 'suspendedPaymentCreation',
SuspendedPaymentFinish: 'suspendedPaymentExecution',
SuspendedPaymentCancel: 'suspendedPaymentCancellation'
SuspendedPaymentCancel: 'suspendedPaymentCancellation',
SignerListSet: 'settings'
};
return mapping[type] || null;
}

View File

@@ -4,6 +4,9 @@ const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const utils = require('../utils');
const BigNumber = require('bignumber.js');
const parseAmount = require('./amount');
import type {Amount} from '../common/types.js';
function adjustQualityForXRP(
quality: string, takerGetsCurrency: string, takerPaysCurrency: string
@@ -35,19 +38,39 @@ function removeEmptyCounterparty(amount) {
}
function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
_.forEach(balanceChanges, (changes) => {
_.forEach(balanceChanges, changes => {
_.forEach(changes, removeEmptyCounterparty);
});
}
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
_.forEach(orderbookChanges, (changes) => {
_.forEach(changes, (change) => {
_.forEach(orderbookChanges, changes => {
_.forEach(changes, change => {
_.forEach(change, removeEmptyCounterparty);
});
});
}
function isPartialPayment(tx) {
return (tx.Flags & utils.common.txFlags.Payment.PartialPayment) !== 0;
}
function parseDeliveredAmount(tx: Object): Amount | void {
let deliveredAmount;
// TODO: Workaround for existing rippled bug where delivered_amount may not be
// provided for account_tx
if (tx.TransactionType === 'Payment') {
if (tx.meta.delivered_amount) {
deliveredAmount = parseAmount(tx.meta.delivered_amount);
} else if (tx.Amount && !isPartialPayment(tx)) {
deliveredAmount = parseAmount(tx.Amount);
}
}
return deliveredAmount;
}
function parseOutcome(tx: Object): ?Object {
const metadata = tx.meta || tx.metaData;
if (!metadata) {
@@ -58,15 +81,16 @@ function parseOutcome(tx: Object): ?Object {
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);
return {
return utils.common.removeUndefined({
result: tx.meta.TransactionResult,
timestamp: parseTimestamp(tx.date),
fee: utils.common.dropsToXrp(tx.Fee),
balanceChanges: balanceChanges,
orderbookChanges: orderbookChanges,
ledgerVersion: tx.ledger_index,
indexInLedger: tx.meta.TransactionIndex
};
indexInLedger: tx.meta.TransactionIndex,
deliveredAmount: parseDeliveredAmount(tx)
});
}
function hexToString(hex: string): ?string {
@@ -77,7 +101,7 @@ function parseMemos(tx: Object): ?Array<Object> {
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
return undefined;
}
return tx.Memos.map((m) => {
return tx.Memos.map(m => {
return utils.common.removeUndefined({
type: m.Memo.parsed_memo_type || hexToString(m.Memo.MemoType),
format: m.Memo.parsed_memo_format || hexToString(m.Memo.MemoFormat),
@@ -93,6 +117,7 @@ module.exports = {
hexToString,
parseTimestamp,
adjustQualityForXRP,
isPartialPayment,
dropsToXrp: utils.common.dropsToXrp,
constants: utils.common.constants,
txFlags: utils.common.txFlags,

View File

@@ -85,6 +85,20 @@ function conditionallyAddDirectXRPPath(connection: Connection, address: string,
xrpBalance => addDirectXrpPath(paths, xrpBalance));
}
function filterSourceFundsLowPaths(pathfind: PathFind,
paths: RippledPathsResponse
): RippledPathsResponse {
if (pathfind.source.amount &&
pathfind.destination.amount.value === undefined && paths.alternatives) {
paths.alternatives = _.filter(paths.alternatives, alt => {
return alt.source_amount &&
pathfind.source.amount &&
new BigNumber(alt.source_amount.value).eq(pathfind.source.amount.value);
});
}
return paths;
}
function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
if (paths.alternatives && paths.alternatives.length > 0) {
return parsePathfind(paths);
@@ -116,7 +130,9 @@ function getPaths(pathfind: PathFind): Promise<GetPaths> {
const address = pathfind.source.address;
return requestPathFind(this.connection, pathfind).then(paths =>
conditionallyAddDirectXRPPath(this.connection, address, paths)
).then(paths => formatResponse(pathfind, paths));
)
.then(paths => filterSourceFundsLowPaths(pathfind, paths))
.then(paths => formatResponse(pathfind, paths));
}
module.exports = getPaths;

View File

@@ -1,135 +1,135 @@
/* @flow */
'use strict';
import type {Amount, Memo} from '../common/types.js';
type Outcome = {
result: string,
ledgerVersion: number,
indexInLedger: number,
fee: string,
balanceChanges: {
[key: string]: [{
currency: string,
counterparty?: string,
value: string
}]
},
orderbookChanges: Object,
timestamp?: string
}
type Adjustment = {
address: string,
amount: {
currency: string,
counterparty?: string,
value: string
},
tag?: number
}
type Trustline = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
type Settings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string,
messageKey?: string,
domain?: string,
transferRate?: number,
regularKey?: string
}
type OrderCancellation = {
orderSequence: number
}
type Payment = {
source: Adjustment,
destination: Adjustment,
paths?: string,
memos?: Array<Memo>,
invoiceID?: string,
allowPartialPayment?: boolean,
noDirectRipple?: boolean,
limitQuality?: boolean
}
type PaymentTransaction = {
type: string,
specification: Payment,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type Order = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
passive?: boolean
}
type OrderTransaction = {
type: string,
specification: Order,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type OrderCancellationTransaction = {
type: string,
specification: OrderCancellation,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type TrustlineTransaction = {
type: string,
specification: Trustline,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type SettingsTransaction = {
type: string,
specification: Settings,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
}
export type TransactionType = PaymentTransaction | OrderTransaction |
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction
/* @flow */
'use strict';
import type {Amount, Memo} from '../common/types.js';
type Outcome = {
result: string,
ledgerVersion: number,
indexInLedger: number,
fee: string,
balanceChanges: {
[key: string]: [{
currency: string,
counterparty?: string,
value: string
}]
},
orderbookChanges: Object,
timestamp?: string
}
type Adjustment = {
address: string,
amount: {
currency: string,
counterparty?: string,
value: string
},
tag?: number
}
type Trustline = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
type Settings = {
passwordSpent?: boolean,
requireDestinationTag?: boolean,
requireAuthorization?: boolean,
disallowIncomingXRP?: boolean,
disableMasterKey?: boolean,
enableTransactionIDTracking?: boolean,
noFreeze?: boolean,
globalFreeze?: boolean,
defaultRipple?: boolean,
emailHash?: string,
messageKey?: string,
domain?: string,
transferRate?: number,
regularKey?: string
}
type OrderCancellation = {
orderSequence: number
}
type Payment = {
source: Adjustment,
destination: Adjustment,
paths?: string,
memos?: Array<Memo>,
invoiceID?: string,
allowPartialPayment?: boolean,
noDirectRipple?: boolean,
limitQuality?: boolean
}
type PaymentTransaction = {
type: string,
specification: Payment,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type Order = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
passive?: boolean
}
type OrderTransaction = {
type: string,
specification: Order,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type OrderCancellationTransaction = {
type: string,
specification: OrderCancellation,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type TrustlineTransaction = {
type: string,
specification: Trustline,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
type SettingsTransaction = {
type: string,
specification: Settings,
outcome: Outcome,
id: string,
address: string,
sequence: number
}
export type TransactionOptions = {
minLedgerVersion?: number,
maxLedgerVersion?: number
}
export type TransactionType = PaymentTransaction | OrderTransaction |
OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction

View File

@@ -1,33 +1,33 @@
/* @flow */
'use strict';
export type TrustLineSpecification = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
export type Trustline = {
specification: TrustLineSpecification,
counterparty: {
limit: string,
ripplingDisabled?: boolean,
frozen?: boolean,
authorized?: boolean
},
state: {
balance: string
}
}
export type TrustlinesOptions = {
counterparty?: string,
currency?: string,
limit?: number,
ledgerVersion?: number
}
/* @flow */
'use strict';
export type TrustLineSpecification = {
currency: string,
counterparty: string,
limit: string,
qualityIn?: number,
qualityOut?: number,
ripplingDisabled?: boolean,
authorized?: boolean,
frozen?: boolean
}
export type Trustline = {
specification: TrustLineSpecification,
counterparty: {
limit: string,
ripplingDisabled?: boolean,
frozen?: boolean,
authorized?: boolean
},
state: {
balance: string
}
}
export type TrustlinesOptions = {
counterparty?: string,
currency?: string,
limit?: number,
ledgerVersion?: number
}

View File

@@ -1,50 +1,50 @@
/* @flow */
'use strict';
import type {Amount} from '../common/types.js';
export type OrdersOptions = {
limit?: number,
ledgerVersion?: number
}
export type OrderSpecification = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
}
export type Order = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
}
}
export type GetLedger = {
accepted: boolean,
closed: boolean,
stateHash: string,
closeTime: number,
closeTimeResolution: number,
closeFlags: number,
ledgerHash: string,
ledgerVersion: number,
parentLedgerHash: string,
parentCloseTime: number,
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}
/* @flow */
'use strict';
import type {Amount} from '../common/types.js';
export type OrdersOptions = {
limit?: number,
ledgerVersion?: number
}
export type OrderSpecification = {
direction: string,
quantity: Amount,
totalPrice: Amount,
immediateOrCancel?: boolean,
fillOrKill?: boolean,
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
}
export type Order = {
specification: OrderSpecification,
properties: {
maker: string,
sequence: number,
makerExchangeRate: string
}
}
export type GetLedger = {
accepted: boolean,
closed: boolean,
stateHash: string,
closeTime: number,
closeTimeResolution: number,
closeFlags: number,
ledgerHash: string,
ledgerVersion: number,
parentLedgerHash: string,
parentCloseTime: number,
totalDrops: string,
transactionHash: string,
transactions?: Array<Object>,
rawTransactions?: string,
transactionHashes?: Array<string>,
rawState?: string,
stateHashes?: Array<string>
}

View File

@@ -0,0 +1,39 @@
/* @flow */
'use strict';
const _ = require('lodash');
const binary = require('ripple-binary-codec');
const utils = require('./utils');
const BigNumber = require('bignumber.js');
const {decodeAddress} = require('ripple-address-codec');
const {validate} = utils.common;
const {computeBinaryTransactionHash} = require('ripple-hashes');
function addressToBigNumber(address) {
const hex = (new Buffer(decodeAddress(address))).toString('hex');
return new BigNumber(hex, 16);
}
function compareSigners(a, b) {
return addressToBigNumber(a.Signer.Account)
.comparedTo(addressToBigNumber(b.Signer.Account));
}
function combine(signedTransactions: Array<string>): Object {
validate.combine({signedTransactions});
const txs = _.map(signedTransactions, binary.decode);
const tx = _.omit(txs[0], 'Signers');
if (!_.every(txs, _tx => _.isEqual(tx, _.omit(_tx, 'Signers')))) {
throw new utils.common.errors.ValidationError(
'txJSON is not the same for all signedTransactions');
}
const unsortedSigners = _.reduce(txs, (accumulator, _tx) =>
accumulator.concat(_tx.Signers || []), []);
const signers = unsortedSigners.sort(compareSigners);
const signedTx = _.assign({}, tx, {Signers: signers});
const signedTransaction = binary.encode(signedTx);
const id = computeBinaryTransactionHash(signedTransaction);
return {signedTransaction, id};
}
module.exports = combine;

View File

@@ -1,53 +1,53 @@
/* @flow */
'use strict';
type SettingPasswordSpent = {
passwordSpent?: boolean,
}
type SettingRequireDestinationTag = {
requireDestinationTag?: boolean,
}
type SettingRequireAuthorization = {
requireAuthorization?: boolean,
}
type SettingDisallowIncomingXRP = {
disallowIncomingXRP?: boolean,
}
type SettingDisableMasterKey = {
disableMasterKey?: boolean,
}
type SettingEnableTransactionIDTracking = {
enableTransactionIDTracking?: boolean,
}
type SettingNoFreeze = {
noFreeze?: boolean,
}
type SettingGlobalFreeze = {
globalFreeze?: boolean,
}
type SettingDefaultRipple = {
defaultRipple?: boolean,
}
type SettingEmailHash = {
emailHash?: ?string,
}
type SettingMessageKey = {
messageKey?: string,
}
type SettingDomain = {
domain?: string,
}
type SettingTransferRate = {
transferRate?: ?number,
}
type SettingRegularKey = {
regularKey?: string
}
export type Settings = SettingRegularKey |
SettingTransferRate | SettingDomain | SettingMessageKey |
SettingEmailHash | SettingDefaultRipple |
SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking |
SettingDisableMasterKey | SettingDisallowIncomingXRP |
SettingRequireAuthorization | SettingRequireDestinationTag |
SettingPasswordSpent
/* @flow */
'use strict';
type SettingPasswordSpent = {
passwordSpent?: boolean,
}
type SettingRequireDestinationTag = {
requireDestinationTag?: boolean,
}
type SettingRequireAuthorization = {
requireAuthorization?: boolean,
}
type SettingDisallowIncomingXRP = {
disallowIncomingXRP?: boolean,
}
type SettingDisableMasterKey = {
disableMasterKey?: boolean,
}
type SettingEnableTransactionIDTracking = {
enableTransactionIDTracking?: boolean,
}
type SettingNoFreeze = {
noFreeze?: boolean,
}
type SettingGlobalFreeze = {
globalFreeze?: boolean,
}
type SettingDefaultRipple = {
defaultRipple?: boolean,
}
type SettingEmailHash = {
emailHash?: ?string,
}
type SettingMessageKey = {
messageKey?: string,
}
type SettingDomain = {
domain?: string,
}
type SettingTransferRate = {
transferRate?: ?number,
}
type SettingRegularKey = {
regularKey?: string
}
export type Settings = SettingRegularKey |
SettingTransferRate | SettingDomain | SettingMessageKey |
SettingEmailHash | SettingDefaultRipple |
SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking |
SettingDisableMasterKey | SettingDisallowIncomingXRP |
SettingRequireAuthorization | SettingRequireDestinationTag |
SettingPasswordSpent

View File

@@ -70,7 +70,17 @@ function convertTransferRate(transferRate: number | string): number | string {
return (new BigNumber(transferRate)).shift(9).toNumber();
}
function createSettingsTransaction(account: string, settings: Settings
function formatSignerEntry(signer: Object): Object {
return {
SignerEntry: {
Account: signer.address,
SignerWeight: signer.weight
}
};
}
function createSettingsTransactionWithoutMemos(
account: string, settings: Settings
): Object {
if (settings.regularKey !== undefined) {
const removeRegularKey = {
@@ -83,15 +93,20 @@ function createSettingsTransaction(account: string, settings: Settings
return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey});
}
if (settings.signers !== undefined) {
return {
TransactionType: 'SignerListSet',
Account: account,
SignerQuorum: settings.signers.threshold,
SignerEntries: _.map(settings.signers.weights, formatSignerEntry)
};
}
const txJSON: Object = {
TransactionType: 'AccountSet',
Account: account
};
if (settings.memos !== undefined) {
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
}
setTransactionFlags(txJSON, _.omit(settings, 'memos'));
setTransactionFields(txJSON, settings);
@@ -101,6 +116,15 @@ function createSettingsTransaction(account: string, settings: Settings
return txJSON;
}
function createSettingsTransaction(account: string, settings: Settings
): Object {
const txJSON = createSettingsTransactionWithoutMemos(account, settings);
if (settings.memos !== undefined) {
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
}
return txJSON;
}
function prepareSettings(address: string, settings: Settings,
instructions: Instructions = {}
): Promise<Prepare> {

View File

@@ -6,23 +6,38 @@ const binary = require('ripple-binary-codec');
const {computeBinaryTransactionHash} = require('ripple-hashes');
const validate = utils.common.validate;
function computeSignature(txJSON, privateKey) {
const signingData = binary.encodeForSigning(txJSON);
function computeSignature(tx: Object, privateKey: string, signAs: ?string) {
const signingData = signAs ?
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx);
return keypairs.sign(signingData, privateKey);
}
function sign(txJSON: string, secret: string
function sign(txJSON: string, secret: string, options: Object = {}
): {signedTransaction: string; id: string} {
validate.sign({txJSON, secret});
// we can't validate that the secret matches the account because
// the secret could correspond to the regular key
const tx = JSON.parse(txJSON);
const keypair = keypairs.deriveKeypair(secret);
if (tx.SigningPubKey === undefined) {
tx.SigningPubKey = keypair.publicKey;
if (tx.TxnSignature || tx.Signers) {
throw new utils.common.errors.ValidationError(
'txJSON must not contain "TxnSignature" or "Signers" properties');
}
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
const keypair = keypairs.deriveKeypair(secret);
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey;
if (options.signAs) {
const signer = {
Account: options.signAs,
SigningPubKey: keypair.publicKey,
TxnSignature: computeSignature(tx, keypair.privateKey, options.signAs)
};
tx.Signers = [{Signer: signer}];
} else {
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
}
const serialized = binary.encode(tx);
return {
signedTransaction: serialized,

View File

@@ -3,15 +3,7 @@
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
type Submit = {
success: boolean,
engineResult: string,
engineResultCode: number,
engineResultMessage?: string,
txBlob?: string,
txJson?: Object
}
import type {Submit} from './types.js';
function isImmediateRejection(engineResult: string): boolean {
// note: "tel" errors mean the local server refused to process the
@@ -23,7 +15,7 @@ function isImmediateRejection(engineResult: string): boolean {
return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej');
}
function formatResponse(response) {
function formatSubmitResponse(response) {
const data = {
resultCode: response.engine_result,
resultMessage: response.engine_result_message
@@ -36,11 +28,12 @@ function formatResponse(response) {
function submit(signedTransaction: string): Promise<Submit> {
validate.submit({signedTransaction});
const request = {
command: 'submit',
tx_blob: signedTransaction
};
return this.connection.request(request).then(formatResponse);
return this.connection.request(request).then(formatSubmitResponse);
}
module.exports = submit;

View File

@@ -1,19 +1,29 @@
/* @flow */
'use strict';
export type Instructions = {
sequence?: number,
fee?: string,
maxFee?: string,
maxLedgerVersion?: number,
maxLedgerVersionOffset?: number
}
export type Prepare = {
txJSON: string,
instructions: {
fee: string,
sequence: number,
maxLedgerVersion?: number
}
}
/* @flow */
'use strict';
export type Instructions = {
sequence?: number,
fee?: string,
maxFee?: string,
maxLedgerVersion?: number,
maxLedgerVersionOffset?: number,
signersCount?: number
}
export type Prepare = {
txJSON: string,
instructions: {
fee: string,
sequence: number,
maxLedgerVersion?: number
}
}
export type Submit = {
success: boolean,
engineResult: string,
engineResultCode: number,
engineResultMessage?: string,
txBlob?: string,
txJson?: Object
}

View File

@@ -27,6 +27,10 @@ function setCanonicalFlag(txJSON) {
txJSON.Flags = txJSON.Flags >>> 0;
}
function scaleValue(value, multiplier) {
return (new BigNumber(value)).times(multiplier).toString();
}
function prepareTransaction(txJSON: Object, api: Object,
instructions: Instructions
): Promise<Prepare> {
@@ -51,8 +55,10 @@ function prepareTransaction(txJSON: Object, api: Object,
}
function prepareFee(): Promise<Object> {
const multiplier = instructions.signersCount === undefined ? 1 :
instructions.signersCount + 1;
if (instructions.fee !== undefined) {
txJSON.Fee = common.xrpToDrops(instructions.fee);
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier);
return Promise.resolve(txJSON);
}
const cushion = api._feeCushion;
@@ -60,9 +66,10 @@ function prepareTransaction(txJSON: Object, api: Object,
const feeDrops = common.xrpToDrops(fee);
if (instructions.maxFee !== undefined) {
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString();
const normalFee = BigNumber.min(feeDrops, maxFeeDrops).toString();
txJSON.Fee = scaleValue(normalFee, multiplier);
} else {
txJSON.Fee = feeDrops;
txJSON.Fee = scaleValue(feeDrops, multiplier);
}
return txJSON;
});

View File

@@ -14,8 +14,12 @@ const address = addresses.ACCOUNT;
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const binary = require('ripple-binary-codec');
assert.options.strict = true;
// how long before each test case times out
const TIMEOUT = process.browser ? 25000 : 10000;
function unused() {
}
@@ -37,6 +41,7 @@ function checkResult(expected, schemaName, response) {
describe('RippleAPI', function() {
this.timeout(TIMEOUT);
const instructions = {maxLedgerVersionOffset: 100};
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
@@ -94,7 +99,7 @@ describe('RippleAPI', function() {
});
it('preparePayment with all options specified', function() {
return this.api.getLedgerVersion().then((ver) => {
return this.api.getLedgerVersion().then(ver => {
const localInstructions = {
maxLedgerVersion: ver + 100,
fee: '0.000012'
@@ -183,20 +188,20 @@ describe('RippleAPI', function() {
it('prepareSettings', function() {
return this.api.prepareSettings(
address, requests.prepareSettings, instructions).then(
address, requests.prepareSettings.domain, instructions).then(
_.partial(checkResult, responses.prepareSettings.flags, 'prepare'));
});
it('prepareSettings - no maxLedgerVersion', function() {
return this.api.prepareSettings(
address, requests.prepareSettings, {maxLedgerVersion: null}).then(
address, requests.prepareSettings.domain, {maxLedgerVersion: null}).then(
_.partial(checkResult, responses.prepareSettings.noMaxLedgerVersion,
'prepare'));
});
it('prepareSettings - no instructions', function() {
return this.api.prepareSettings(
address, requests.prepareSettings).then(
address, requests.prepareSettings.domain).then(
_.partial(
checkResult,
responses.prepareSettings.noInstructions,
@@ -244,6 +249,23 @@ describe('RippleAPI', function() {
'prepare'));
});
it('prepareSettings - set signers', function() {
const settings = requests.prepareSettings.signers;
return this.api.prepareSettings(address, settings, instructions).then(
_.partial(checkResult, responses.prepareSettings.signers,
'prepare'));
});
it('prepareSettings - fee for multisign', function() {
const localInstructions = _.defaults({
signersCount: 4
}, instructions);
return this.api.prepareSettings(
address, requests.prepareSettings.domain, localInstructions).then(
_.partial(checkResult, responses.prepareSettings.flagsMultisign,
'prepare'));
});
it('prepareSuspendedPaymentCreation', function() {
const localInstructions = _.defaults({
maxFee: '0.000012'
@@ -305,6 +327,15 @@ describe('RippleAPI', function() {
schemaValidator.schemaValidate('sign', result);
});
it('sign - already signed', function() {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const result = this.api.sign(requests.sign.normal.txJSON, secret);
assert.throws(() => {
const tx = JSON.stringify(binary.decode(result.signedTransaction));
this.api.sign(tx, secret);
}, /txJSON must not contain "TxnSignature" or "Signers" properties/);
});
it('sign - SuspendedPaymentExecution', function() {
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
const result = this.api.sign(requests.sign.suspended.txJSON, secret);
@@ -312,6 +343,14 @@ describe('RippleAPI', function() {
schemaValidator.schemaValidate('sign', result);
});
it('sign - signAs', function() {
const txJSON = requests.sign.signAs;
const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb';
const signature = this.api.sign(JSON.stringify(txJSON), secret,
{signAs: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'});
assert.deepEqual(signature, responses.sign.signAs);
});
it('submit', function() {
return this.api.submit(responses.sign.normal.signedTransaction).then(
_.partial(checkResult, responses.submit, 'submit'));
@@ -326,6 +365,21 @@ describe('RippleAPI', function() {
});
});
it('combine', function() {
const combined = this.api.combine(requests.combine.setDomain);
checkResult(responses.combine.single, 'sign', combined);
});
it('combine - different transactions', function() {
const request = [requests.combine.setDomain[0]];
const tx = binary.decode(requests.combine.setDomain[0]);
tx.Flags = 0;
request.push(binary.encode(tx));
assert.throws(() => {
this.api.combine(request);
}, /txJSON is not the same for all signedTransactions/);
});
describe('RippleAPI', function() {
it('getBalances', function() {
@@ -914,7 +968,11 @@ describe('RippleAPI', function() {
});
it('getServerInfo - error', function() {
this.mockRippled.returnErrorOnServerInfo = true;
this.api.connection._send(JSON.stringify({
command: 'config',
data: {returnErrorOnServerInfo: true}
}));
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw NetworkError');
}).catch(error => {
@@ -1012,6 +1070,15 @@ describe('RippleAPI', function() {
});
});
it('getPaths - no paths source amount', function() {
return this.api.getPaths(requests.getPaths.NoPathsSource).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
});
});
it('getPaths - no paths with source currencies', function() {
const pathfind = requests.getPaths.NoPathsWithCurrencies;
return this.api.getPaths(pathfind).then(() => {
@@ -1035,7 +1102,7 @@ describe('RippleAPI', function() {
});
it('getLedgerVersion', function(done) {
this.api.getLedgerVersion().then((ver) => {
this.api.getLedgerVersion().then(ver => {
assert.strictEqual(ver, 8819951);
done();
}, done);
@@ -1255,7 +1322,7 @@ describe('RippleAPI - offline', function() {
it('prepareSettings and sign', function() {
const api = new RippleAPI();
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const settings = requests.prepareSettings;
const settings = requests.prepareSettings.domain;
const instructions = {
sequence: 23,
maxLedgerVersion: 8820051,

View File

@@ -8,6 +8,8 @@ const ledgerClosed = require('./fixtures/rippled/ledger-close');
const RippleAPI = require('ripple-api').RippleAPI;
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const TIMEOUT = process.browser ? 25000 : 10000;
function checkResult(expected, schemaName, response) {
if (expected.txJSON) {
assert(response.txJSON);
@@ -21,12 +23,15 @@ function checkResult(expected, schemaName, response) {
}
describe('RippleAPIBroadcast', function() {
this.timeout(TIMEOUT);
beforeEach(setupAPI.setupBroadcast);
afterEach(setupAPI.teardown);
it('base', function() {
const expected = {request_server_info: 1};
this.mocks.forEach(mock => mock.expect(_.assign({}, expected)));
if (!process.browser) {
this.mocks.forEach(mock => mock.expect(_.assign({}, expected)));
}
assert(this.api.isConnected());
return this.api.getServerInfo().then(
_.partial(checkResult, responses.getServerInfo, 'getServerInfo'));
@@ -39,14 +44,16 @@ describe('RippleAPIBroadcast', function() {
});
const ledgerNext = _.assign({}, ledgerClosed);
ledgerNext.ledger_index++;
this.mocks.forEach(mock => mock.socket.send(JSON.stringify(ledgerNext)));
this.api._apis.forEach(api => api.connection._send(JSON.stringify({
command: 'echo',
data: ledgerNext
})));
setTimeout(() => {
console.log('-- ledgerVersion', this.api.ledgerVersion);
assert.strictEqual(gotLedger, 1);
done();
}, 50);
}, 1250);
});
it('error propagation', function(done) {
@@ -55,8 +62,10 @@ describe('RippleAPIBroadcast', function() {
assert.strictEqual(info, 'info');
done();
});
this.mocks[1].socket.send(
JSON.stringify({error: 'type', error_message: 'info'}));
this.api._apis[1].connection._send(JSON.stringify({
command: 'echo',
data: {error: 'type', error_message: 'info'}
}));
});
});

View File

@@ -1,5 +1,5 @@
'use strict'; // eslint-disable-line
/* eslint-disable max-nested-callbacks */
'use strict';
const _ = require('lodash');
const net = require('net');
@@ -7,8 +7,11 @@ const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClose = require('./fixtures/rippled/ledger-close.json');
const TIMEOUT = 200000; // how long before each test case times out
function unused() {
}
@@ -26,6 +29,7 @@ function createServer() {
}
describe('Connection', function() {
this.timeout(TIMEOUT);
beforeEach(setupAPI.setup);
afterEach(setupAPI.teardown);
@@ -46,6 +50,9 @@ describe('Connection', function() {
messages.push(message);
}
};
connection._ws = {
send: function() {}
};
connection._onMessage(message1);
connection._send(message2);
@@ -53,11 +60,15 @@ describe('Connection', function() {
});
it('with proxy', function(done) {
createServer().then((server) => {
if (process.browser) {
done();
return;
}
createServer().then(server => {
const port = server.address().port;
const expect = 'CONNECT localhost';
server.on('connection', (socket) => {
socket.on('data', (data) => {
server.on('connection', socket => {
socket.on('data', data => {
const got = data.toString('ascii', 0, expect.length);
assert.strictEqual(got, expect);
server.close();
@@ -95,10 +106,33 @@ describe('Connection', function() {
});
});
it('should throw NotConnectedError if server not responding ', function(
done
) {
if (process.browser) {
const phantomTest = /PhantomJS/;
if (phantomTest.test(navigator.userAgent)) {
// inside PhantomJS this one just hangs, so skip as not very relevant
done();
return;
}
}
// Address where no one listens
const connection =
new utils.common.Connection('ws://testripple.circleci.com:129');
connection.on('error', done);
connection.connect().catch(error => {
assert(error instanceof this.api.errors.NotConnectedError);
done();
});
});
it('DisconnectedError', function() {
this.api.connection._send = function() {
this._ws.close();
};
this.api.connection._send(JSON.stringify({
command: 'config',
data: {disconnectOnServerInfo: true}
}));
return this.api.getServerInfo().then(() => {
assert(false, 'Should throw DisconnectedError');
}).catch(error => {
@@ -160,6 +194,81 @@ describe('Connection', function() {
}, 1);
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.timeout(7000);
const self = this;
function breakConnection() {
setTimeout(() => {
self.mockRippled.close();
setTimeout(() => {
self.mockRippled = setupAPI.createMockRippled(self._mockedServerPort);
}, 1500);
}, 21);
}
let connectsCount = 0;
let disconnectsCount = 0;
let code = 0;
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1;
});
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < 3) {
breakConnection();
}
if (connectsCount === 3) {
if (disconnectsCount !== 3) {
done(new Error('disconnectsCount must be equal to 3 (got ' +
disconnectsCount + ' instead)'));
} else if (code !== 1006) {
done(new Error('disconnect must send code 1006 (got ' + code +
' instead)'));
} else {
done();
}
}
});
breakConnection();
});
it('should emit disconnected event with code 1000 (CLOSE_NORMAL)',
function(done
) {
this.api.once('disconnected', code => {
assert.strictEqual(code, 1000);
done();
});
this.api.disconnect();
});
it('should emit disconnected event with code 1006 (CLOSE_ABNORMAL)',
function(done
) {
if (process.browser) {
// can't be tested in browser this way, so skipping
done();
return;
}
this.api.once('disconnected', code => {
assert.strictEqual(code, 1006);
done();
});
this.mockRippled.close();
});
it('should emit connected event on after reconnect', function(done) {
this.api.once('connected', done);
this.api.connection._ws.close();
});
it('Multiply connect calls', function() {
return this.api.connect().then(() => {
return this.api.connect();
@@ -167,7 +276,7 @@ describe('Connection', function() {
});
it('hasLedgerVersion', function() {
return this.api.connection.hasLedgerVersion(8819951).then((result) => {
return this.api.connection.hasLedgerVersion(8819951).then(result => {
assert(result);
});
});
@@ -261,4 +370,13 @@ describe('Connection', function() {
this.api.connection._onMessage(JSON.stringify({type: 'unknown'}));
});
it('ledger close without validated_ledgers', function(done) {
const message = _.omit(ledgerClose, 'validated_ledgers');
this.api.on('ledger', function(ledger) {
assert.strictEqual(ledger.ledgerVersion, 8819951);
done();
});
this.api.connection._ws.emit('message', JSON.stringify(message));
});
});

View File

@@ -6,5 +6,6 @@ module.exports = {
FOURTH_ACCOUNT: 'rJnZ4YHCUsHvQu7R6mZohevKJDHFzVD6Zr',
ISSUER: 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM',
NOTFOUND: 'rajTAg3hon5Lcu1RxQQPxTgHvqfhc1EaUS',
SECRET: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
SECRET: 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV',
SOURCE_LOW_FUNDS: 'rhVgDEfS1r1fLyRUZCpab4TdowZcAJwHy2'
};

2
test/fixtures/requests/combine.json vendored Normal file
View File

@@ -0,0 +1,2 @@
[ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
"12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ]

View File

@@ -1,9 +1,9 @@
{
"base": {
"currency": "USD",
"counterparty": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw"
},
"counter": {
"currency": "XRP"
}
{
"base": {
"currency": "USD",
"counterparty": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw"
},
"counter": {
"currency": "XRP"
}
}

View File

@@ -0,0 +1,13 @@
{
"source": {
"address": "rhVgDEfS1r1fLyRUZCpab4TdowZcAJwHy2",
"amount": {
"value": "1000002",
"currency": "USD"
}
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {"currency": "USD"}
}
}

View File

@@ -1,13 +1,13 @@
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"amount": {
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "100"
}
}
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"amount": {
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"value": "100"
}
}
}

View File

@@ -1,15 +1,15 @@
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "5"
}
},
"destination": {
"address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"amount": {
"currency": "USD"
}
}
}
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "5.00"
}
},
"destination": {
"address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
"amount": {
"currency": "USD"
}
}
}

View File

@@ -1,20 +1,20 @@
{
"source": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"currencies": [
{
"currency": "LTC"
},
{
"currency": "USD"
}
]
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "0.000001"
}
}
{
"source": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"currencies": [
{
"currency": "LTC"
},
{
"currency": "USD"
}
]
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "0.000001"
}
}
}

View File

@@ -1,12 +1,12 @@
{
"source": {
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J"
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "0.000002",
"currency": "XRP"
}
}
}
{
"source": {
"address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J"
},
"destination": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "0.000002",
"currency": "XRP"
}
}
}

View File

@@ -20,7 +20,10 @@ module.exports = {
allOptions: require('./prepare-payment-all-options'),
noCounterparty: require('./prepare-payment-no-counterparty')
},
prepareSettings: require('./prepare-settings'),
prepareSettings: {
domain: require('./prepare-settings'),
signers: require('./prepare-settings-signers')
},
prepareSuspendedPaymentCreation: {
normal: require('./prepare-suspended-payment-creation'),
full: require('./prepare-suspended-payment-creation-full')
@@ -40,7 +43,8 @@ module.exports = {
},
sign: {
normal: require('./sign'),
suspended: require('./sign-suspended.json')
suspended: require('./sign-suspended.json'),
signAs: require('./sign-as')
},
getPaths: {
normal: require('./getpaths/normal'),
@@ -49,6 +53,7 @@ module.exports = {
XrpToXrpNotEnough: require('./getpaths/xrp2xrp-not-enough'),
NotAcceptCurrency: require('./getpaths/not-accept-currency'),
NoPaths: require('./getpaths/no-paths'),
NoPathsSource: require('./getpaths/no-paths-source-amount'),
NoPathsWithCurrencies: require('./getpaths/no-paths-with-currencies'),
sendAll: require('./getpaths/send-all'),
invalid: require('./getpaths/invalid'),
@@ -61,5 +66,8 @@ module.exports = {
computeLedgerHash: {
header: require('./compute-ledger-hash'),
transactions: require('./compute-ledger-hash-transactions')
},
combine: {
setDomain: require('./combine.json')
}
};

View File

@@ -1,17 +1,17 @@
{
"source": {
"address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"amount": {
"value": "0.01",
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
}
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"minAmount": {
"value": "0.01",
"currency": "XRP"
}
}
}
{
"source": {
"address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM",
"amount": {
"value": "0.01",
"currency": "USD",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
}
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"minAmount": {
"value": "0.01",
"currency": "XRP"
}
}
}

View File

@@ -1,17 +1,17 @@
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "0.01",
"currency": "XRP"
}
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"minAmount": {
"value": "0.01",
"currency": "XRP"
}
},
"allowPartialPayment": true
}
{
"source": {
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"value": "0.01",
"currency": "XRP"
}
},
"destination": {
"address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"minAmount": {
"value": "0.01",
"currency": "XRP"
}
},
"allowPartialPayment": true
}

View File

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

8
test/fixtures/requests/sign-as.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Amount": "1000000000",
"Destination": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Fee": "50",
"Sequence": 2,
"TransactionType": "Payment"
}

4
test/fixtures/responses/combine.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1",
"id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC"
}

View File

@@ -34,6 +34,10 @@
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "XRP",
"value": "10000"
},
"balanceChanges": {
"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj": [
{

View File

@@ -24,6 +24,11 @@
"result": "tesSUCCESS",
"timestamp": "2013-03-12T23:56:50.000Z",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{

View File

@@ -31,4 +31,4 @@
"ledgerVersion": 14,
"indexInLedger": 0
}
}
}

View File

@@ -32,4 +32,4 @@
"ledgerVersion": 14,
"indexInLedger": 0
}
}
}

View File

@@ -30,6 +30,11 @@
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -122,6 +127,11 @@
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{

View File

@@ -331,4 +331,4 @@
"balance": "0"
}
}
]
]

View File

@@ -81,13 +81,15 @@ module.exports = {
regularKey: require('./prepare-settings-regular-key.json'),
removeRegularKey: require('./prepare-settings-remove-regular-key.json'),
flags: require('./prepare-settings.json'),
flagsMultisign: require('./prepare-settings-multisign.json'),
flagSet: require('./prepare-settings-flag-set.json'),
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'),
signed: require('./prepare-settings-signed.json'),
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json')
noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json'),
signers: require('./prepare-settings-signers.json')
},
prepareSuspendedPaymentCreation: {
normal: require('./prepare-suspended-payment-creation'),
@@ -108,7 +110,11 @@ module.exports = {
},
sign: {
normal: require('./sign.json'),
suspended: require('./sign-suspended.json')
suspended: require('./sign-suspended.json'),
signAs: require('./sign-as')
},
combine: {
single: require('./combine.json')
},
submit: require('./submit.json'),
ledgerEvent: require('./ledger-event.json')

View File

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

View File

@@ -1,8 +1,8 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"WalletLocator\":\"0\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"WalletLocator\":\"0\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}

View File

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

View File

@@ -1,8 +1,8 @@
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TransferRate\":1000000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}
{
"txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TransferRate\":1000000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}

View File

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

View File

@@ -0,0 +1,8 @@
{
"txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":2,\"SignerEntries\":[{\"SignerEntry\":{\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rwBYyfufTzk77zUSKEu4MvixfarC35av1J\",\"SignerWeight\":1}}],\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}",
"instructions": {
"fee": "0.000012",
"sequence": 23,
"maxLedgerVersion": 8820051
}
}

4
test/fixtures/responses/sign-as.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"signedTransaction": "120000240000000261400000003B9ACA00684000000000000032730081142E244E6F20104E57C0C60BD823CB312BF10928C78314B5F762798A53D543A014CAF8B297CFF8F2F937E8F3E01073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100BB6FC77F26BC88587204CAA79B2230C420D7EC937B8AC3A0CF9B0BE988BAB0D002203BF86893BA3B764375FFFAD9D54A4AAEDABD07C4D72ADB9C1B20C10B4DD712898114B5F762798A53D543A014CAF8B297CFF8F2F937E8E1F1",
"id": "AB7632D7C07E591658635CED6A5DDE832B22CA066907CB131DEFAAA925B98185"
}

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line strict
const _ = require('lodash');
const addresses = require('../addresses');
@@ -160,6 +160,7 @@ module.exports = function(request, options = {}) {
{
'flags': 0,
'seq': 814018,
'quality': '16922629533.64839',
'taker_gets': {
'currency': 'NZD',
'issuer': 'rsP3mgGb2tcYUrxiLFiHJiQXhsziegtwBc',

View File

@@ -29,4 +29,4 @@
},
"status": "success",
"type": "response"
}
}

View File

@@ -38,7 +38,8 @@ module.exports = {
sendUSD: require('./path-find-send-usd'),
sendAll: require('./path-find-send-all'),
XrpToXrp: require('./path-find-xrp-to-xrp'),
srcActNotFound: require('./path-find-srcActNotFound')
srcActNotFound: require('./path-find-srcActNotFound'),
sourceAmountLow: require('./path-find-srcAmtLow')
},
tx: {
Payment: require('./tx/payment.json'),

View File

@@ -1,12 +1,12 @@
{
"fee_base": 10,
"fee_ref": 10,
"ledger_hash": "9141FA171F2C0CE63E609466AF728FF66C12F7ACD4B4B50B0947A7F3409D593A",
"ledger_index": 14804627,
"ledger_time": 490945840,
"reserve_base": 20000000,
"reserve_inc": 5000000,
"txn_count": 19,
"type": "ledgerClosed",
"validated_ledgers": "13983423-14804627"
{
"fee_base": 10,
"fee_ref": 10,
"ledger_hash": "9141FA171F2C0CE63E609466AF728FF66C12F7ACD4B4B50B0947A7F3409D593A",
"ledger_index": 14804627,
"ledger_time": 490945840,
"reserve_base": 20000000,
"reserve_inc": 5000000,
"txn_count": 19,
"type": "ledgerClosed",
"validated_ledgers": "13983423-14804627"
}

View File

@@ -1,13 +1,13 @@
{
"id": 0,
"status": "error",
"type": "response",
"error": "lgrNotFound",
"error_code": 20,
"error_message": "ledgerNotFound",
"request": {
"command": "ledger",
"id": 3,
"ledger_index": 34
}
{
"id": 0,
"status": "error",
"type": "response",
"error": "lgrNotFound",
"error_code": 20,
"error_message": "ledgerNotFound",
"request": {
"command": "ledger",
"id": 3,
"ledger_index": 34
}
}

View File

@@ -0,0 +1,97 @@
{
"id": 0,
"result": {
"full_reply": true,
"alternatives": [
{
"paths_canonical": [],
"paths_computed": [
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
}
],
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
},
{
"account": "rLMJ4db4uwHcd6NHg6jvTaYb8sH5Gy4tg5",
"type": 1,
"type_hex": "0000000000000001"
},
{
"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"type": 1,
"type_hex": "0000000000000001"
}
],
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
},
{
"currency": "USD",
"issuer": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"type": 48,
"type_hex": "0000000000000030"
},
{
"account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"type": 1,
"type_hex": "0000000000000001"
}
],
[
{
"account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"type": 1,
"type_hex": "0000000000000001"
},
{
"account": "rLMJ4db4uwHcd6NHg6jvTaYb8sH5Gy4tg5",
"type": 1,
"type_hex": "0000000000000001"
},
{
"account": "r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X",
"type": 1,
"type_hex": "0000000000000001"
}
]
],
"source_amount": {
"currency": "USD",
"issuer": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo",
"value": "0.000001002"
}
}
],
"source_account": "rhVgDEfS1r1fLyRUZCpab4TdowZcAJwHy2",
"destination_amount": {
"currency": "USD",
"value": "-1",
"issuer": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
},
"destination_account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"destination_currencies": [
"JOE",
"BTC",
"DYM",
"CNY",
"EUR",
"015841551A748AD2C1F76FF6ECB0CCCD00000000",
"MXN",
"USD",
"XRP"
]
},
"status": "success",
"type": "response"
}

View File

@@ -1,95 +1,95 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"TransactionType": "OfferCancel",
"Flags": 0,
"Sequence": 466,
"OfferSequence": 465,
"LastLedgerSequence": 14661888,
"Fee": "12000",
"SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025",
"TxnSignature": "3045022100E4148E9809C5CE13BC5583E8CA665614D9FF02D6589D13BA7FBB67CF45EAC0BF02201B84DC18A921260BCEE685908260888BC20D4375DB4A8702F25B346CAD7F3387",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b",
"hash": "809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E",
"ledger_index": 14661789,
"inLedger": 14661789,
"meta": {
"TransactionIndex": 4,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 14661788,
"PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9",
"PreviousFields": {
"Sequence": 466,
"OwnerCount": 4,
"Balance": "71827095"
},
"FinalFields": {
"Flags": 0,
"Sequence": 467,
"OwnerCount": 3,
"Balance": "71815095",
"Domain": "726970706C652E636F6D",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"FinalFields": {
"Flags": 0,
"RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"DeletedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"FinalFields": {
"Flags": 0,
"ExchangeRate": "550435C0500F1000",
"RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
"TakerGetsIssuer": "0000000000000000000000000000000000000000"
}
}
},
{
"DeletedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C",
"FinalFields": {
"Flags": 0,
"Sequence": 465,
"PreviousTxnLgrSeq": 14661788,
"BookNode": "0000000000000000",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"TransactionType": "OfferCancel",
"Flags": 0,
"Sequence": 466,
"OfferSequence": 465,
"LastLedgerSequence": 14661888,
"Fee": "12000",
"SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025",
"TxnSignature": "3045022100E4148E9809C5CE13BC5583E8CA665614D9FF02D6589D13BA7FBB67CF45EAC0BF02201B84DC18A921260BCEE685908260888BC20D4375DB4A8702F25B346CAD7F3387",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b",
"hash": "809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E",
"ledger_index": 14661789,
"inLedger": 14661789,
"meta": {
"TransactionIndex": 4,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 14661788,
"PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9",
"PreviousFields": {
"Sequence": 466,
"OwnerCount": 4,
"Balance": "71827095"
},
"FinalFields": {
"Flags": 0,
"Sequence": 467,
"OwnerCount": 3,
"Balance": "71815095",
"Domain": "726970706C652E636F6D",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"FinalFields": {
"Flags": 0,
"RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"DeletedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"FinalFields": {
"Flags": 0,
"ExchangeRate": "550435C0500F1000",
"RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3",
"TakerGetsCurrency": "0000000000000000000000000000000000000000",
"TakerGetsIssuer": "0000000000000000000000000000000000000000"
}
}
},
{
"DeletedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C",
"FinalFields": {
"Flags": 0,
"Sequence": 465,
"PreviousTxnLgrSeq": 14661788,
"BookNode": "0000000000000000",
"OwnerNode": "0000000000000000",
"PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}

View File

@@ -48,4 +48,4 @@
},
"status": "success",
"type": "response"
}
}

View File

@@ -1,92 +1,92 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"TransactionType": "OfferCreate",
"Flags": 0,
"Sequence": 465,
"LastLedgerSequence": 14661886,
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Fee": "12000",
"SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025",
"TxnSignature": "3045022100FA4CBD0A54A38906F8D4C18FBA4DBCE45B98F9C5A33BC9102CB5911E9E20E88F022032C47AC74E60042FF1517C866680A41B396D61146FBA9E60B4CF74E373CA7AD2",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b",
"hash": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"ledger_index": 14661788,
"inLedger": 14661788,
"meta": {
"TransactionIndex": 2,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 14660978,
"PreviousTxnID": "566D4DE22972C5BAD2506CFFA928B21D2BD33FA52FE16712D17D727681FAA4B1",
"LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9",
"PreviousFields": {
"Sequence": 465,
"OwnerCount": 3,
"Balance": "71839095"
},
"FinalFields": {
"Flags": 0,
"Sequence": 466,
"OwnerCount": 4,
"Balance": "71827095",
"Domain": "726970706C652E636F6D",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"FinalFields": {
"Flags": 0,
"RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"NewFields": {
"ExchangeRate": "550435C0500F1000",
"RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C",
"NewFields": {
"Sequence": 465,
"BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"TransactionType": "OfferCreate",
"Flags": 0,
"Sequence": 465,
"LastLedgerSequence": 14661886,
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Fee": "12000",
"SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025",
"TxnSignature": "3045022100FA4CBD0A54A38906F8D4C18FBA4DBCE45B98F9C5A33BC9102CB5911E9E20E88F022032C47AC74E60042FF1517C866680A41B396D61146FBA9E60B4CF74E373CA7AD2",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b",
"hash": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2",
"ledger_index": 14661788,
"inLedger": 14661788,
"meta": {
"TransactionIndex": 2,
"AffectedNodes": [
{
"ModifiedNode": {
"LedgerEntryType": "AccountRoot",
"PreviousTxnLgrSeq": 14660978,
"PreviousTxnID": "566D4DE22972C5BAD2506CFFA928B21D2BD33FA52FE16712D17D727681FAA4B1",
"LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9",
"PreviousFields": {
"Sequence": 465,
"OwnerCount": 3,
"Balance": "71839095"
},
"FinalFields": {
"Flags": 0,
"Sequence": 466,
"OwnerCount": 4,
"Balance": "71827095",
"Domain": "726970706C652E636F6D",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"ModifiedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"FinalFields": {
"Flags": 0,
"RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250",
"Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"NewFields": {
"ExchangeRate": "550435C0500F1000",
"RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPaysCurrency": "0000000000000000000000005553440000000000",
"TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "Offer",
"LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C",
"NewFields": {
"Sequence": 465,
"BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000",
"TakerPays": {
"value": "237",
"currency": "USD",
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q"
},
"TakerGets": "200",
"Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b"
}
}
}
],
"TransactionResult": "tesSUCCESS"
},
"validated": true
}
}

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