Compare commits

...

82 Commits

Author SHA1 Message Date
Matthew Fettig
1eddbf995f 0.17.5 2017-03-24 12:31:10 -07:00
Matthew Fettig
592385ac73 Merge pull request #750 from ripple/feature/dependancy-update
update ripple-binary-codec to version 0.1.8
2017-03-24 12:26:35 -07:00
Matthew Fettig
56d626f5b1 Merge pull request #744 from choiip/develop
Added .json in missing require()
2017-03-24 10:53:42 -07:00
Matthew Fettig
7a14300409 update ripple-binary-codec to version 0.1.8 2017-03-24 10:40:19 -07:00
alex.choi
30fa8d658e Added .json in missing require() 2017-02-15 18:51:49 +08:00
Chris Clark
c0101cb5e7 Merge pull request #740 from wilsonianb/shamapv2
Check SHAMapV2 ledger close flag
2016-12-02 12:31:46 -08:00
wilsonianb
fd640cd65e Check SHAMapV2 ledger close flag 2016-12-02 12:25:32 -08:00
Chris Clark
11528eff92 Merge pull request #741 from wilsonianb/fix-lint-errors
Fix eslint errors
2016-12-02 11:55:21 -08:00
wilsonianb
3c9175459d Fix eslint errors 2016-12-02 08:16:16 -08:00
Chris Clark
bd4e0e01e2 Merge pull request #739 from clark800/develop
Update ripple-binary-codec and ripple-hashes dependencies
2016-11-30 14:59:52 -08:00
Chris Clark
cfcf6e473c Update ripple-binary-codec and ripple-hashes dependencies 2016-11-30 14:49:40 -08:00
Chris Clark
50c6af3158 Merge pull request #734 from clark800/bump-codec
Bump version to 0.17.3 and bump ripple-binary-codec to 0.1.4
2016-09-29 11:24:04 -07:00
Chris Clark
1d4310cd3a Disable flow type checking in CircleCI 2016-09-29 11:19:46 -07:00
Chris Clark
2191596e68 Bump version to 0.17.3 and bump ripple-binary-codec to 0.1.4 2016-09-29 11:19:32 -07:00
Chris Clark
acf8f87a88 Merge pull request #727 from clark800/order-seq
Add orderToReplace option
2016-08-01 14:34:27 -07:00
Chris Clark
cfac146620 Add orderToReplace option 2016-08-01 14:27:08 -07:00
Chris Clark
f3234ad853 Merge pull request #726 from clark800/doc-typos
Fix typos in docs
2016-08-01 14:24:22 -07:00
Chris Clark
b65fee3d85 Merge pull request #725 from shekenahglory/develop
update parseDeliveredAmount for cases where delivered_amount is not p…
2016-08-01 14:21:43 -07:00
Chris Clark
82b294bc7d Update flow 2016-08-01 14:13:00 -07:00
Chris Clark
2a58573823 Fix typos in docs 2016-08-01 11:53:46 -07:00
Matthew Fettig
a96f71b7fd fix missing deliveredAmount data from getLedger requests 2016-07-19 21:29:50 -07:00
Chris Clark
077f4a4c79 Merge pull request #723 from clark800/fix-coverage
Fix code coverage tool
2016-07-08 15:04:38 -07:00
Chris Clark
9a495467fb Disable sauce tests because they usually timeout 2016-07-08 11:45:27 -07:00
Chris Clark
db2e62b219 Fix code coverage tool 2016-07-08 11:28:10 -07:00
Alan Cohen
2fafa493a2 Version 0.17.2 2016-06-24 10:59:04 -07:00
Alan Cohen
7617c3005c Merge pull request #721 from mDuo13/fix_transferrate_docs
[DOC] fix transferRate description
2016-06-22 11:13:14 -07:00
mDuo13
5cdbb71277 [DOC] fix transferRate description 2016-06-16 15:11:08 -07:00
Chris Clark
f9339c36bf Merge pull request #720 from clark800/update-binary-codec
0.17.1
2016-05-11 18:20:29 -07:00
Chris Clark
67dc57e9d0 Disable PhantomJS tests due to issue downloading from bitbucket 2016-05-11 17:49:57 -07:00
Chris Clark
757f3190d1 0.17.1 2016-05-11 17:49:54 -07:00
Chris Clark
0d94a15ee7 0.17.0 2016-05-05 18:35:13 -07:00
Chris Clark
7f1c80da1b Merge pull request #718 from clark800/pseudo
Add support for parsing SetFee and EnableAmendment pseudo-transactions
2016-05-05 16:15:32 -07:00
Chris Clark
f74e11bce0 Add support for parsing SetFee and EnableAmendment pseudo-transactions 2016-05-05 15:37:23 -07:00
Chris Clark
d4c843e8e3 Merge pull request #714 from darkdarkdragon/remove_tej
[FIX] remove check for `tej` class of errors.
2016-04-05 15:15:42 -07:00
Ivan Tivonenko
bae190b282 [FIX] remove check for tej class of errors. 2016-04-05 23:50:41 +03:00
Chris Clark
d2cbd70da8 Merge pull request #712 from darkdarkdragon/reconnect_fix_browser
[FIX] on reconnect wait for server to be synced
2016-03-30 15:50:58 -07:00
Ivan Tivonenko
5da78ce583 [FIX] handle websocket errors in browsers
emit not RippledNotInitializedError if server doesn't have any
completed ledgers on connect
2016-03-31 00:46:00 +03:00
Chris Clark
14bbe3e30b Merge pull request #680 from ripple/sublimator-patch-1
Fix typo in docs
2016-03-29 10:27:34 -07:00
Nicholas Dudfield
e52e2bbc68 Fix IOU Amount precision related typo in docs 2016-03-29 13:51:30 +07:00
Alan Cohen
b56752e45b 0.16.10 2016-03-24 14:52:36 -07:00
Chris Clark
4632f511ab Merge pull request #711 from darkdarkdragon/reconnect_fix2
[FIX] on connect return error if server doesn't have validated ledgers
2016-03-24 14:27:50 -07:00
Ivan Tivonenko
e17b6f172d [FIX] on connect return error if server doesn't have validated ledgers 2016-03-24 22:26:33 +02:00
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
130 changed files with 7966 additions and 1765 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

@@ -9,13 +9,28 @@ 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',
@@ -35,7 +50,7 @@ function webpackConfig(extension, overrides) {
}, {
test: /\.js$/,
exclude: [/node_modules/],
loader: 'babel-loader?optional=runtime'
loader: 'babel-loader'
}, {
test: /\.json/,
loader: 'json-loader'
@@ -52,6 +67,7 @@ function webpackConfigForWebTest(testFileName, path) {
}
var configOverrides = {
externals: [{
'lodash': '_',
'ripple-api': 'ripple',
'net': 'null'
}],
@@ -65,18 +81,6 @@ function webpackConfigForWebTest(testFileName, path) {
return webpackConfig('.js', configOverrides);
}
gulp.task('build-for-web-tests', function(callback) {
var configOverrides = {
output: {
library: 'ripple',
path: './test-compiled-for-web/',
filename: 'ripple-for-web-tests.js'
}
};
var config = webpackConfig('-debug.js', configOverrides);
webpack(config, callback);
});
gulp.task('build-tests', function(callback) {
var times = 0;
function done() {
@@ -92,15 +96,34 @@ gulp.task('build-tests', function(callback) {
'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

@@ -30,7 +30,7 @@ Then see the [documentation](https://github.com/ripple/ripple-lib/blob/develop/d
##Generating Documentation
The continuous integration tests require that the documentation stays up-to-date. If you make changes the the JSON schemas, fixtures, or documentation sources, you must update the documentation by running `npm run docgen`.
The continuous integration tests require that the documentation stays up-to-date. If you make changes to the JSON schemas, fixtures, or documentation sources, you must update the documentation by running `npm run docgen`.
##More Information

View File

@@ -1,8 +1,11 @@
machine:
node:
version: 0.12.0
hosts:
testripple.circleci.com: 127.0.0.1
dependencies:
pre:
- npm -g install npm@latest-2
- 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:

View File

@@ -61,6 +61,8 @@
- [API Events](#api-events)
- [ledger](#ledger)
- [error](#error)
- [connected](#connected)
- [disconnected](#disconnected)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -90,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(() => {
@@ -138,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`
@@ -201,7 +211,7 @@ A *value* is a quantity of a currency represented as a decimal string. Be carefu
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11).
**Non-XRP values** have 15 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount
@@ -420,6 +430,7 @@ expirationTime | date-time string | *Optional* Time after which the offer is no
fillOrKill | boolean | *Optional* Treat the offer as a [Fill or Kill order](http://en.wikipedia.org/wiki/Fill_or_kill). Only attempt to match existing offers in the ledger, and only do so if the entire quantity can be exchanged.
immediateOrCancel | boolean | *Optional* Treat the offer as an [Immediate or Cancel order](http://en.wikipedia.org/wiki/Immediate_or_cancel). If enabled, the offer will never become a ledger node: it only attempts to match existing offers in the ledger.
memos | [memos](#transaction-memos) | *Optional* Array of memos to attach to the transaction.
orderToReplace | [sequence](#account-sequence-number) | *Optional* The [account sequence number](#account-sequence-number) of an order to cancel before the new order is created, effectively replacing the old order.
passive | boolean | *Optional* 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.
### Example
@@ -488,7 +499,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
*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.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
### Example
@@ -811,7 +822,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.
@@ -828,6 +839,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
@@ -867,6 +879,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": [
{
@@ -1001,6 +1018,11 @@ return api.getTransactions(address).then(transaction => {
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -1093,6 +1115,11 @@ return api.getTransactions(address).then(transaction => {
"outcome": {
"result": "tesSUCCESS",
"fee": "0.00001",
"deliveredAmount": {
"currency": "USD",
"value": "0.001",
"counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM"
},
"balanceChanges": {
"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo": [
{
@@ -1485,7 +1512,7 @@ options | object | *Optional* Options to determine how the balances will be calc
### Return Value
This method returns a promise that resolves with an array of objects with the following structure:
This method returns a promise that resolves with an object with the following structure:
Name | Type | Description
---- | ---- | -----------
@@ -2658,7 +2685,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
*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.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
### Example
@@ -2908,7 +2935,7 @@ const trustline = {
}
]
};
return api.preparePayment(address, trustline).then(prepared =>
return api.prepareTrustline(address, trustline).then(prepared =>
{/* ... */});
```
@@ -3025,7 +3052,7 @@ instructions | object | The instructions for how to execute the transaction afte
```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const orderCancellation = {orderSequence: 123};
return api.prepareOrderCancellation(address, sequence)
return api.prepareOrderCancellation(address, orderCancellation)
.then(prepared => {/* ... */});
```
@@ -3391,7 +3418,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
@@ -3577,3 +3604,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

@@ -21,7 +21,7 @@ A *value* is a quantity of a currency represented as a decimal string. Be carefu
**XRP** has 6 significant digits past the decimal point. In other words, XRP cannot be divided into positive values smaller than `0.000001` (1e-6). XRP has a maximum value of `100000000000` (1e11).
**Non-XRP values** have 15 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
**Non-XRP values** have 16 decimal digits of precision, with a maximum value of `9999999999999999e80`. The smallest positive non-XRP value is `1e-81`.
## Amount

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`

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

@@ -10,7 +10,7 @@ Returns aggregate balances by currency plus a breakdown of assets and obligation
### Return Value
This method returns a promise that resolves with an array of objects with the following structure:
This method returns a promise that resolves with an object with the following structure:
<%- renderSchema('output/get-balance-sheet.json') %>

View File

@@ -23,7 +23,7 @@ All "prepare*" methods have the same return type.
```javascript
const address = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59';
const orderCancellation = {orderSequence: 123};
return api.prepareOrderCancellation(address, sequence)
return api.prepareOrderCancellation(address, orderCancellation)
.then(prepared => {/* ... */});
```

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 =>
{/* ... */});
```

4309
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ripple-lib",
"version": "0.16.5",
"version": "0.17.5",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -15,43 +15,48 @@
"test": "test"
},
"dependencies": {
"ajv": "^1.4.8",
"babel-polyfill": "^6.2.0",
"babel-runtime": "^5.5.4",
"ajv": "^4.0.5",
"ajv-i18n": "^1.2.0",
"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-hashes": "^0.1.0",
"ripple-binary-codec": "^0.1.8",
"ripple-hashes": "^0.2.0",
"ripple-keypairs": "^0.10.0",
"ripple-lib-transactionparser": "^0.6.0",
"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",
"coveralls": "^2.10.0",
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^6.0.4",
"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.11.9",
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eslint": "^1.3.0",
"eslint-plugin-flowtype": "^1.0.0",
"eslint": "^2.9.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",
"istanbul": "^1.1.0-alpha.1",
"json-loader": "^0.5.2",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "^2.1.0",
"mocha-in-sauce": "^0.0.1",
"mocha-junit-reporter": "^1.9.1",
"mocha-phantomjs": "^4.0.1",
"null-loader": "^0.1.1",
"webpack": "^1.5.3",
"yargs": "^1.3.1"
@@ -66,11 +71,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/.bin/istanbul 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",

View File

@@ -15,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
@@ -35,14 +35,21 @@ unittest() {
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-tests build-for-web-tests
node --harmony test-compiled/mocked-server.js > /dev/null &
#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 in PhantomJS"
#mocha-phantomjs test/localrunner.html
#echo "Running tests using minified version in PhantomJS"
#mocha-phantomjs test/localrunnermin.html
pkill -f mocked-server.js
#echo "Running tests in SauceLabs"
#http-server &
#npm run sauce
#pkill -f mocked-server.js
#pkill -f http-server
rm -rf test-compiled
}
@@ -51,9 +58,9 @@ integrationtest() {
mocha test/integration/http-integration-test.js
# run integration tests in PhantomJS
gulp build-tests build-for-web-tests
echo "Running integragtion tests in PhantomJS"
mocha-phantomjs test/localintegrationrunner.html
#gulp build-tests build-min
#echo "Running integragtion tests in PhantomJS"
#mocha-phantomjs test/localintegrationrunner.html
}
doctest() {
@@ -69,7 +76,6 @@ oneNode() {
checkEOL
doctest
lint
typecheck
unittest
integrationtest
}
@@ -77,7 +83,7 @@ oneNode() {
twoNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) checkEOL; typecheck; unittest;;
1) checkEOL; unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac
}
@@ -85,7 +91,7 @@ twoNodes() {
threeNodes() {
case "$NODE_INDEX" in
0) doctest; lint; integrationtest;;
1) checkEOL; typecheck;;
1) checkEOL;;
2) unittest;;
*) echo "ERROR: invalid usage"; exit 2;;
esac

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

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
'use strict' // eslint-disable-line 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,124 +1,224 @@
'use strict';
const _ = require('lodash');
const {EventEmitter} = require('events');
const WebSocket = require('ws');
const parseURL = require('url').parse;
const RangeSet = require('./rangeset').RangeSet;
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const {EventEmitter} = require('events')
const WebSocket = require('ws')
const parseURL = require('url').parse
const RangeSet = require('./rangeset').RangeSet
const {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError} = require('./errors');
TimeoutError, ResponseFormatError, ConnectionError,
RippledNotInitializedError} = require('./errors')
function isStreamMessageType(type) {
return type === 'ledgerClosed' ||
type === 'transaction' ||
type === 'path_find';
type === 'path_find'
}
class Connection extends EventEmitter {
constructor(url, options = {}) {
super();
this.setMaxListeners(Infinity);
this._url = url;
this._trace = options.trace;
super()
this.setMaxListeners(Infinity)
this._url = url
this._trace = options.trace
if (this._trace) {
// for easier unit testing
this._console = console;
this._console = console
}
this._proxyURL = options.proxy;
this._proxyAuthorization = options.proxyAuthorization;
this._authorization = options.authorization;
this._trustedCertificates = options.trustedCertificates;
this._key = options.key;
this._passphrase = options.passphrase;
this._certificate = options.certificate;
this._timeout = options.timeout || (20 * 1000);
this._isReady = false;
this._ws = null;
this._ledgerVersion = null;
this._availableLedgerVersions = new RangeSet();
this._nextRequestID = 1;
this._proxyURL = options.proxy
this._proxyAuthorization = options.proxyAuthorization
this._authorization = options.authorization
this._trustedCertificates = options.trustedCertificates
this._key = options.key
this._passphrase = options.passphrase
this._certificate = options.certificate
this._timeout = options.timeout || (20 * 1000)
this._isReady = false
this._ws = null
this._ledgerVersion = null
this._availableLedgerVersions = new RangeSet()
this._nextRequestID = 1
this._retry = 0
this._retryTimer = null
this._onOpenErrorBound = null
this._onUnexpectedCloseBound = null
}
_updateLedgerVersions(data) {
this._ledgerVersion = Number(data.ledger_index);
this._ledgerVersion = Number(data.ledger_index)
if (data.validated_ledgers) {
this._availableLedgerVersions.reset();
this._availableLedgerVersions.reset()
this._availableLedgerVersions.parseAndAddRanges(
data.validated_ledgers);
data.validated_ledgers)
} else {
this._availableLedgerVersions.addValue(this._ledgerVersion);
this._availableLedgerVersions.addValue(this._ledgerVersion)
}
}
// return value is array of arguments to Connection.emit
_parseMessage(message) {
const data = JSON.parse(message);
const data = JSON.parse(message)
if (data.type === 'response') {
if (!(Number.isInteger(data.id) && data.id >= 0)) {
throw new ResponseFormatError('valid id not found in response');
throw new ResponseFormatError('valid id not found in response')
}
return [data.id.toString(), data];
return [data.id.toString(), data]
} else if (isStreamMessageType(data.type)) {
if (data.type === 'ledgerClosed') {
this._updateLedgerVersions(data);
this._updateLedgerVersions(data)
}
return [data.type, data];
return [data.type, data]
} else if (data.type === undefined && data.error) {
return ['error', data.error, data.error_message, data]; // e.g. slowDown
return ['error', data.error, data.error_message, data] // e.g. slowDown
}
throw new ResponseFormatError('unrecognized message type: ' + data.type);
throw new ResponseFormatError('unrecognized message type: ' + data.type)
}
_onMessage(message) {
let parameters;
let parameters
if (this._trace) {
this._console.log(message);
this._console.log(message)
}
try {
parameters = this._parseMessage(message);
parameters = this._parseMessage(message)
} catch (error) {
this.emit('error', 'badMessage', error.message, message);
return;
this.emit('error', 'badMessage', error.message, message)
return
}
// 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() {
return this._ws ? this._ws.readyState : WebSocket.CLOSED;
return this._ws ? this._ws.readyState : WebSocket.CLOSED
}
get _shouldBeConnected() {
return this._ws !== null;
return this._ws !== null
}
isConnected() {
return this._state === WebSocket.OPEN && this._isReady;
return this._state === WebSocket.OPEN && this._isReady
}
_onUnexpectedClose() {
this._ws = null;
this._isReady = false;
this.connect().then();
_onUnexpectedClose(beforeOpen, resolve, reject, code) {
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound)
this._onOpenErrorBound = null
}
// just in case
this._ws.removeAllListeners('open')
this._ws = null
this._isReady = false
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.emit('reconnecting', this._retry)
this.connect().catch(this._retryConnect.bind(this))
}, retryTimeout)
}
_clearReconnectTimer() {
clearTimeout(this._retryTimer)
this._retryTimer = null
}
_onOpen() {
if (!this._ws) {
return Promise.reject(new DisconnectedError())
}
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound)
this._onOpenErrorBound = null
}
const request = {
command: 'subscribe',
streams: ['ledger']
};
}
return this.request(request).then(data => {
this._updateLedgerVersions(data);
this._isReady = true;
this.emit('connected');
});
if (_.isEmpty(data) || !data.ledger_index) {
// rippled instance doesn't have validated ledgers
return this._disconnect(false).then(() => {
throw new RippledNotInitializedError('Rippled not initialized')
})
}
this._updateLedgerVersions(data)
this._rebindOnUnxpectedClose()
this._retry = 0
this._ws.on('error', error => {
if (process.browser && error && error.type === 'error') {
// we are in browser, ignore error - `close` event will be fired
// after error
return
}
this.emit('error', 'websocket', error.message, error)
})
this._isReady = true
this.emit('connected')
return undefined
})
}
_rebindOnUnxpectedClose() {
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound)
}
this._onUnexpectedCloseBound =
this._onUnexpectedClose.bind(this, false, null, null)
this._ws.once('close', this._onUnexpectedCloseBound)
}
_unbindOnUnxpectedClose() {
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound)
}
this._onUnexpectedCloseBound = null
}
_onOpenError(reject, error) {
this._onOpenErrorBound = null
this._unbindOnUnxpectedClose()
reject(new NotConnectedError(error && error.message))
}
_createWebSocket() {
const options = {};
const options = {}
if (this._proxyURL !== undefined) {
const parsedURL = parseURL(this._url);
const parsedProxyURL = parseURL(this._proxyURL);
const parsedURL = parseURL(this._url)
const parsedProxyURL = parseURL(this._proxyURL)
const proxyOverrides = _.omit({
secureEndpoint: (parsedURL.protocol === 'wss:'),
secureProxy: (parsedProxyURL.protocol === 'https:'),
@@ -127,183 +227,206 @@ class Connection extends EventEmitter {
key: this._key,
passphrase: this._passphrase,
cert: this._certificate
}, _.isUndefined);
const proxyOptions = _.assign({}, parsedProxyURL, proxyOverrides);
let HttpsProxyAgent;
}, _.isUndefined)
const proxyOptions = _.assign({}, parsedProxyURL, proxyOverrides)
let HttpsProxyAgent
try {
HttpsProxyAgent = require('https-proxy-agent');
HttpsProxyAgent = require('https-proxy-agent')
} catch (error) {
throw new Error('"proxy" option is not supported in the browser');
throw new Error('"proxy" option is not supported in the browser')
}
options.agent = new HttpsProxyAgent(proxyOptions);
options.agent = new HttpsProxyAgent(proxyOptions)
}
if (this._authorization !== undefined) {
const base64 = new Buffer(this._authorization).toString('base64');
options.headers = {Authorization: `Basic ${base64}`};
const base64 = new Buffer(this._authorization).toString('base64')
options.headers = {Authorization: `Basic ${base64}`}
}
const optionsOverrides = _.omit({
ca: this._trustedCertificates,
key: this._key,
passphrase: this._passphrase,
cert: this._certificate
}, _.isUndefined);
const websocketOptions = _.assign({}, options, optionsOverrides);
const websocket = new WebSocket(this._url, null, websocketOptions);
}, _.isUndefined)
const websocketOptions = _.assign({}, options, optionsOverrides)
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)
if (typeof websocket.setMaxListeners === 'function') {
websocket.setMaxListeners(Infinity);
websocket.setMaxListeners(Infinity)
}
return websocket;
return websocket
}
connect() {
this._clearReconnectTimer()
return new Promise((resolve, reject) => {
if (!this._url) {
reject(new ConnectionError(
'Cannot connect because no server was specified'));
'Cannot connect because no server was specified'))
}
if (this._state === WebSocket.OPEN) {
resolve();
resolve()
} else if (this._state === WebSocket.CONNECTING) {
this._ws.once('open', resolve);
this._ws.once('open', resolve)
} else {
this._ws = this._createWebSocket();
this._ws = this._createWebSocket()
// when an error causes the connection to close, the close event
// should still be emitted; the "ws" documentation says: "The close
// event is also emitted when then underlying net.Socket closes the
// connection (end or close)."
this._ws.on('error', error =>
this.emit('error', 'websocket', error.messsage, error));
this._ws.on('message', this._onMessage.bind(this));
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.once('open', () => this._onOpen().then(resolve, reject));
// 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))
// 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))
}
});
})
}
disconnect() {
return this._disconnect(true)
}
_disconnect(calledByUser) {
if (calledByUser) {
this._clearReconnectTimer()
this._retry = 0
}
return new Promise(resolve => {
if (this._state === WebSocket.CLOSED) {
resolve();
resolve()
} else if (this._state === WebSocket.CLOSING) {
this._ws.once('close', resolve);
this._ws.once('close', resolve)
} else {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._ws.once('close', () => {
this._ws = null;
this._isReady = false;
resolve();
});
this._ws.close();
if (this._onUnexpectedCloseBound) {
this._ws.removeListener('close', this._onUnexpectedCloseBound)
this._onUnexpectedCloseBound = null
}
this._ws.once('close', code => {
this._ws = null
this._isReady = false
if (calledByUser) {
this.emit('disconnected', code || 1000) // 1000 - CLOSE_NORMAL
}
resolve()
})
this._ws.close()
}
});
})
}
reconnect() {
return this.disconnect().then(() => this.connect());
return this.disconnect().then(() => this.connect())
}
_whenReady(promise) {
return new Promise((resolve, reject) => {
if (!this._shouldBeConnected) {
reject(new NotConnectedError());
reject(new NotConnectedError())
} else if (this._state === WebSocket.OPEN && this._isReady) {
promise.then(resolve, reject);
promise.then(resolve, reject)
} else {
this.once('connected', () => promise.then(resolve, reject));
this.once('connected', () => promise.then(resolve, reject))
}
});
})
}
getLedgerVersion() {
return this._whenReady(Promise.resolve(this._ledgerVersion));
return this._whenReady(Promise.resolve(this._ledgerVersion))
}
hasLedgerVersions(lowLedgerVersion, highLedgerVersion) {
return this._whenReady(Promise.resolve(
this._availableLedgerVersions.containsRange(
lowLedgerVersion, highLedgerVersion || this._ledgerVersion)));
lowLedgerVersion, highLedgerVersion || this._ledgerVersion)))
}
hasLedgerVersion(ledgerVersion) {
return this.hasLedgerVersions(ledgerVersion, ledgerVersion);
return this.hasLedgerVersions(ledgerVersion, ledgerVersion)
}
_send(message) {
if (this._trace) {
this._console.log(message);
this._console.log(message)
}
return new Promise((resolve, reject) => {
this._ws.send(message, undefined, (error, result) => {
if (error) {
reject(new DisconnectedError(error.message));
reject(new DisconnectedError(error.message))
} else {
resolve(result);
resolve(result)
}
});
});
})
})
}
request(request, timeout) {
return new Promise((resolve, reject) => {
if (!this._shouldBeConnected) {
reject(new NotConnectedError());
reject(new NotConnectedError())
}
let timer = null;
const self = this;
const id = this._nextRequestID;
this._nextRequestID += 1;
const eventName = id.toString();
let timer = null
const self = this
const id = this._nextRequestID
this._nextRequestID += 1
const eventName = id.toString()
function onDisconnect() {
clearTimeout(timer);
self.removeAllListeners(eventName);
reject(new DisconnectedError());
clearTimeout(timer)
self.removeAllListeners(eventName)
reject(new DisconnectedError())
}
function cleanup() {
clearTimeout(timer);
self.removeAllListeners(eventName);
clearTimeout(timer)
self.removeAllListeners(eventName)
if (self._ws !== null) {
self._ws.removeListener('close', onDisconnect);
self._ws.removeListener('close', onDisconnect)
}
}
function _resolve(response) {
cleanup();
resolve(response);
cleanup()
resolve(response)
}
function _reject(error) {
cleanup();
reject(error);
cleanup()
reject(error)
}
this.once(eventName, response => {
if (response.status === 'error') {
_reject(new RippledError(response.error));
_reject(new RippledError(response.error))
} else if (response.status === 'success') {
_resolve(response.result);
_resolve(response.result)
} else {
_reject(new ResponseFormatError(
'unrecognized status: ' + response.status));
'unrecognized status: ' + response.status))
}
});
})
this._ws.once('close', onDisconnect);
this._ws.once('close', onDisconnect)
// JSON.stringify automatically removes keys with value of 'undefined'
const message = JSON.stringify(Object.assign({}, request, {id}));
const message = JSON.stringify(Object.assign({}, request, {id}))
this._whenReady(this._send(message)).then(() => {
const delay = timeout || this._timeout;
timer = setTimeout(() => _reject(new TimeoutError()), delay);
}).catch(_reject);
});
const delay = timeout || this._timeout
timer = setTimeout(() => _reject(new TimeoutError()), delay)
}).catch(_reject)
})
}
}
module.exports = Connection;
module.exports = Connection

View File

@@ -1,5 +1,5 @@
'use strict';
const flagIndices = require('./txflags').txFlagIndices.AccountSet;
'use strict' // eslint-disable-line strict
const flagIndices = require('./txflags').txFlagIndices.AccountSet
const accountRootFlags = {
PasswordSpent: 0x00010000, // password set fee is spent
@@ -10,7 +10,7 @@ const accountRootFlags = {
NoFreeze: 0x00200000, // permanently disallowed freezing trustlines
GlobalFreeze: 0x00400000, // trustlines globally frozen
DefaultRipple: 0x00800000
};
}
const AccountFlags = {
passwordSpent: accountRootFlags.PasswordSpent,
@@ -21,7 +21,7 @@ const AccountFlags = {
noFreeze: accountRootFlags.NoFreeze,
globalFreeze: accountRootFlags.GlobalFreeze,
defaultRipple: accountRootFlags.DefaultRipple
};
}
const AccountFlagIndices = {
requireDestinationTag: flagIndices.asfRequireDest,
@@ -32,7 +32,7 @@ const AccountFlagIndices = {
noFreeze: flagIndices.asfNoFreeze,
globalFreeze: flagIndices.asfGlobalFreeze,
defaultRipple: flagIndices.asfDefaultRipple
};
}
const AccountFields = {
EmailHash: {name: 'emailHash', encoding: 'hex',
@@ -40,10 +40,10 @@ const AccountFields = {
MessageKey: {name: 'messageKey'},
Domain: {name: 'domain', encoding: 'hex'},
TransferRate: {name: 'transferRate', defaults: 0, shift: 9}
};
}
module.exports = {
AccountFields,
AccountFlagIndices,
AccountFlags
};
}

View File

@@ -1,31 +1,43 @@
'use strict';
const util = require('util');
'use strict' // eslint-disable-line 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.message = message;
this.data = data;
super(message)
this.name = browserHacks.getConstructorName(this)
this.message = message
this.data = data
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor.name);
Error.captureStackTrace(this, this.constructor.name)
}
}
toString() {
let result = '[' + this.name + '(' + this.message;
let result = '[' + this.name + '(' + this.message
if (this.data) {
result += ', ' + util.inspect(this.data);
result += ', ' + util.inspect(this.data)
}
result += ')]';
return result;
result += ')]'
return result
}
/* console.log in node uses util.inspect on object, and util.inspect allows
us to cutomize its output:
https://nodejs.org/api/util.html#util_custom_inspect_function_on_objects */
inspect() {
return this.toString();
return this.toString()
}
}
@@ -41,6 +53,8 @@ class NotConnectedError extends ConnectionError {}
class DisconnectedError extends ConnectionError {}
class RippledNotInitializedError extends ConnectionError {}
class TimeoutError extends ConnectionError {}
class ResponseFormatError extends ConnectionError {}
@@ -49,20 +63,20 @@ class ValidationError extends RippleError {}
class NotFoundError extends RippleError {
constructor(message) {
super(message || 'Not found');
super(message || 'Not found')
}
}
class MissingLedgerHistoryError extends RippleError {
constructor(message) {
super(message || 'Server is missing ledger history in the specified range');
super(message || 'Server is missing ledger history in the specified range')
}
}
class PendingLedgerVersionError extends RippleError {
constructor(message) {
super(message || 'maxLedgerVersion is greater than server\'s'
+ ' most recent validated ledger');
+ ' most recent validated ledger')
}
}
@@ -73,6 +87,7 @@ module.exports = {
RippledError,
NotConnectedError,
DisconnectedError,
RippledNotInitializedError,
TimeoutError,
ResponseFormatError,
ValidationError,
@@ -80,4 +95,4 @@ module.exports = {
PendingLedgerVersionError,
MissingLedgerHistoryError,
LedgerVersionError
};
}

View File

@@ -1,5 +1,5 @@
'use strict';
const utils = require('./utils');
'use strict' // eslint-disable-line strict
const utils = require('./utils')
module.exports = {
Connection: require('./connection'),
@@ -19,4 +19,4 @@ module.exports = {
iso8601ToRippleTime: utils.iso8601ToRippleTime,
rippleTimeToISO8601: utils.rippleTimeToISO8601,
isValidSecret: utils.isValidSecret
};
}

View File

@@ -1,61 +1,61 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const ranges = Symbol();
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const ranges = Symbol()
function mergeIntervals(intervals: Array<[number, number]>) {
const stack = [[-Infinity, -Infinity]];
const stack = [[-Infinity, -Infinity]]
_.forEach(_.sortBy(intervals, x => x[0]), interval => {
const lastInterval = stack.pop();
const lastInterval = stack.pop()
if (interval[0] <= lastInterval[1] + 1) {
stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])]);
stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])])
} else {
stack.push(lastInterval);
stack.push(interval);
stack.push(lastInterval)
stack.push(interval)
}
});
return stack.slice(1);
})
return stack.slice(1)
}
class RangeSet {
constructor() {
this.reset();
this.reset()
}
reset() {
this[ranges] = [];
this[ranges] = []
}
serialize() {
return this[ranges].map(range =>
range[0].toString() + '-' + range[1].toString()).join(',');
range[0].toString() + '-' + range[1].toString()).join(',')
}
addRange(start: number, end: number) {
assert(start <= end, 'invalid range');
this[ranges] = mergeIntervals(this[ranges].concat([[start, end]]));
assert(start <= end, 'invalid range')
this[ranges] = mergeIntervals(this[ranges].concat([[start, end]]))
}
addValue(value: number) {
this.addRange(value, value);
this.addRange(value, value)
}
parseAndAddRanges(rangesString: string) {
const rangeStrings = rangesString.split(',');
const rangeStrings = rangesString.split(',')
_.forEach(rangeStrings, rangeString => {
const range = rangeString.split('-').map(Number);
this.addRange(range[0], range.length === 1 ? range[0] : range[1]);
});
const range = rangeString.split('-').map(Number)
this.addRange(range[0], range.length === 1 ? range[0] : range[1])
})
}
containsRange(start: number, end: number) {
return _.some(this[ranges], range => range[0] <= start && range[1] >= end);
return _.some(this[ranges], range => range[0] <= start && range[1] >= end)
}
containsValue(value: number) {
return this.containsRange(value, value);
return this.containsRange(value, value)
}
}
module.exports.RangeSet = RangeSet;
module.exports.RangeSet = RangeSet

View File

@@ -1,12 +1,12 @@
// flow is disabled for this file until support for requiring json is added:
// https://github.com/facebook/flow/issues/167
'use strict';
const _ = require('lodash');
const assert = require('assert');
const Ajv = require('ajv');
const ValidationError = require('./errors').ValidationError;
const {isValidAddress} = require('ripple-address-codec');
const {isValidSecret} = require('./utils');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const Ajv = require('ajv')
const ValidationError = require('./errors').ValidationError
const {isValidAddress} = require('ripple-address-codec')
const {isValidSecret} = require('./utils')
function loadSchemas() {
// listed explicitly for webpack (instead of scanning schemas directory)
@@ -56,7 +56,7 @@ function loadSchemas() {
require('./schemas/output/submit.json'),
require('./schemas/output/get-account-info.json'),
require('./schemas/output/get-balances.json'),
require('./schemas/output/get-balance-sheet'),
require('./schemas/output/get-balance-sheet.json'),
require('./schemas/output/get-ledger.json'),
require('./schemas/output/get-orderbook.json'),
require('./schemas/output/get-orders.json'),
@@ -91,32 +91,32 @@ function loadSchemas() {
require('./schemas/input/prepare-suspended-payment-creation.json'),
require('./schemas/input/prepare-suspended-payment-cancellation.json'),
require('./schemas/input/prepare-suspended-payment-execution.json'),
require('./schemas/input/compute-ledger-hash'),
require('./schemas/input/compute-ledger-hash.json'),
require('./schemas/input/sign.json'),
require('./schemas/input/submit.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));
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates);
const ajv = new Ajv();
_.forEach(schemas, schema => ajv.addSchema(schema, schema.title));
ajv.addFormat('address', isValidAddress);
ajv.addFormat('secret', isValidSecret);
return ajv;
]
const titles = _.map(schemas, schema => schema.title)
const duplicates = _.keys(_.pick(_.countBy(titles), count => count > 1))
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
const ajv = new Ajv()
_.forEach(schemas, schema => ajv.addSchema(schema, schema.title))
ajv.addFormat('address', isValidAddress)
ajv.addFormat('secret', isValidSecret)
return ajv
}
const ajv = loadSchemas();
const ajv = loadSchemas()
function schemaValidate(schemaName: string, object: any): void {
const isValid = ajv.validate(schemaName, object);
const isValid = ajv.validate(schemaName, object)
if (!isValid) {
throw new ValidationError(ajv.errorsText());
throw new ValidationError(ajv.errorsText())
}
}
module.exports = {
schemaValidate,
isValidSecret
};
}

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

@@ -55,7 +55,7 @@
"description": " The domain that owns this account, as a hexadecimal string representing the ASCII for the domain in lowercase."
},
"transferRate": {
"description": " The fee to charge when users transfer this accounts issuances, represented as billionths of a unit. Use `null` to set no fee.",
"description": " The fee to charge when users transfer this accounts issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.",
"oneOf": [
{"type": "null"},
{"type": "number", "minimum": 1, "maximum": 4.294967295}

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

@@ -34,6 +34,10 @@
"format": "date-time",
"description": "Time after which the offer is no longer active, as an [ISO 8601 date-time](https://en.wikipedia.org/wiki/ISO_8601)."
},
"orderToReplace": {
"$ref": "sequence",
"description": "The [account sequence number](#account-sequence-number) of an order to cancel before the new order is created, effectively replacing the old order."
},
"memos": {"$ref": "memos"}
},
"required": ["direction", "quantity", "totalPrice"],

View File

@@ -1,7 +1,7 @@
'use strict';
const _ = require('lodash');
const {convertKeysFromSnakeCaseToCamelCase} = require('./utils');
import type {Connection} from './connection';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const {convertKeysFromSnakeCaseToCamelCase} = require('./utils')
import type {Connection} from './connection'
export type GetServerInfoResponse = {
buildVersion: string,
@@ -34,44 +34,46 @@ export type GetServerInfoResponse = {
function renameKeys(object, mapping) {
_.forEach(mapping, (to, from) => {
object[to] = object[from];
delete object[from];
});
object[to] = object[from]
delete object[from]
})
}
function getServerInfo(connection: Connection): Promise<GetServerInfoResponse> {
return connection.request({command: 'server_info'}).then(response => {
const info = convertKeysFromSnakeCaseToCamelCase(response.info);
renameKeys(info, {hostid: 'hostID'});
renameKeys(info.validatedLedger, {
baseFeeXrp: 'baseFeeXRP',
reserveBaseXrp: 'reserveBaseXRP',
reserveIncXrp: 'reserveIncrementXRP',
seq: 'ledgerVersion'
});
info.validatedLedger.baseFeeXRP =
info.validatedLedger.baseFeeXRP.toString();
info.validatedLedger.reserveBaseXRP =
info.validatedLedger.reserveBaseXRP.toString();
info.validatedLedger.reserveIncrementXRP =
info.validatedLedger.reserveIncrementXRP.toString();
return info;
});
const info = convertKeysFromSnakeCaseToCamelCase(response.info)
renameKeys(info, {hostid: 'hostID'})
if (info.validatedLedger) {
renameKeys(info.validatedLedger, {
baseFeeXrp: 'baseFeeXRP',
reserveBaseXrp: 'reserveBaseXRP',
reserveIncXrp: 'reserveIncrementXRP',
seq: 'ledgerVersion'
})
info.validatedLedger.baseFeeXRP =
info.validatedLedger.baseFeeXRP.toString()
info.validatedLedger.reserveBaseXRP =
info.validatedLedger.reserveBaseXRP.toString()
info.validatedLedger.reserveIncrementXRP =
info.validatedLedger.reserveIncrementXRP.toString()
}
return info
})
}
function computeFeeFromServerInfo(cushion: number,
serverInfo: GetServerInfoResponse
): number {
return (Number(serverInfo.validatedLedger.baseFeeXRP)
* Number(serverInfo.loadFactor) * cushion).toString();
* Number(serverInfo.loadFactor) * cushion).toString()
}
function getFee(connection: Connection, cushion: number) {
return getServerInfo(connection).then(
_.partial(computeFeeFromServerInfo, cushion));
_.partial(computeFeeFromServerInfo, cushion))
}
module.exports = {
getServerInfo,
getFee
};
}

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict' // eslint-disable-line strict
const txFlags = {
// Universal flags can apply to any transaction type
@@ -36,7 +36,7 @@ const txFlags = {
PartialPayment: 0x00020000,
LimitQuality: 0x00040000
}
};
}
// The following are integer (as opposed to bit) flags
// that can be set for particular transactions in the
@@ -52,9 +52,9 @@ const txFlagIndices = {
asfGlobalFreeze: 7,
asfDefaultRipple: 8
}
};
}
module.exports = {
txFlags,
txFlagIndices
};
}

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
export type RippledAmountIOU = {
currency: string,

View File

@@ -1,59 +1,59 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const {deriveKeypair} = require('ripple-keypairs');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const BigNumber = require('bignumber.js')
const {deriveKeypair} = require('ripple-keypairs')
import type {Amount, RippledAmount} from './types.js';
import type {Amount, RippledAmount} from './types.js'
function isValidSecret(secret: string): boolean {
try {
deriveKeypair(secret);
return true;
deriveKeypair(secret)
return true
} catch (err) {
return false;
return false
}
}
function dropsToXrp(drops: string): string {
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
return (new BigNumber(drops)).dividedBy(1000000.0).toString()
}
function xrpToDrops(xrp: string): string {
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
return (new BigNumber(xrp)).times(1000000.0).floor().toString()
}
function toRippledAmount(amount: Amount): RippledAmount {
if (amount.currency === 'XRP') {
return xrpToDrops(amount.value);
return xrpToDrops(amount.value)
}
return {
currency: amount.currency,
issuer: amount.counterparty ? amount.counterparty :
(amount.issuer ? amount.issuer : undefined),
value: amount.value
};
}
}
function convertKeysFromSnakeCaseToCamelCase(obj: any): any {
if (typeof obj === 'object') {
let newKey;
let newKey
return _.reduce(obj, (result, value, key) => {
newKey = key;
newKey = key
// taking this out of function leads to error in PhantomJS
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g;
const FINDSNAKE = /([a-zA-Z]_[a-zA-Z])/g
if (FINDSNAKE.test(key)) {
newKey = key.replace(FINDSNAKE, r => r[0] + r[2].toUpperCase());
newKey = key.replace(FINDSNAKE, r => r[0] + r[2].toUpperCase())
}
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value);
return result;
}, {});
result[newKey] = convertKeysFromSnakeCaseToCamelCase(value)
return result
}, {})
}
return obj;
return obj
}
function removeUndefined(obj: Object): Object {
return _.omit(obj, _.isUndefined);
return _.omit(obj, _.isUndefined)
}
/**
@@ -62,7 +62,7 @@ function removeUndefined(obj: Object): Object {
*
*/
function rippleToUnixTimestamp(rpepoch: number): number {
return (rpepoch + 0x386D4380) * 1000;
return (rpepoch + 0x386D4380) * 1000
}
/**
@@ -70,15 +70,15 @@ function rippleToUnixTimestamp(rpepoch: number): number {
* @return {Number} seconds since ripple epoch ( 1/1/2000 GMT)
*/
function unixToRippleTimestamp(timestamp: number): number {
return Math.round(timestamp / 1000) - 0x386D4380;
return Math.round(timestamp / 1000) - 0x386D4380
}
function rippleTimeToISO8601(rippleTime: number): string {
return new Date(rippleToUnixTimestamp(rippleTime)).toISOString();
return new Date(rippleToUnixTimestamp(rippleTime)).toISOString()
}
function iso8601ToRippleTime(iso8601: string): number {
return unixToRippleTimestamp(Date.parse(iso8601));
return unixToRippleTimestamp(Date.parse(iso8601))
}
module.exports = {
@@ -90,4 +90,4 @@ module.exports = {
rippleTimeToISO8601,
iso8601ToRippleTime,
isValidSecret
};
}

View File

@@ -1,25 +1,25 @@
/* @flow */
'use strict';
const _ = require('lodash');
const ValidationError = require('./errors').ValidationError;
const schemaValidate = require('./schema-validator').schemaValidate;
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const ValidationError = require('./errors').ValidationError
const schemaValidate = require('./schema-validator').schemaValidate
function error(text) {
return new ValidationError(text);
return new ValidationError(text)
}
function validateLedgerRange(options) {
if (!_.isUndefined(options) && !_.isUndefined(options.minLedgerVersion)
&& !_.isUndefined(options.maxLedgerVersion)) {
if (Number(options.minLedgerVersion) > Number(options.maxLedgerVersion)) {
throw error('minLedgerVersion must not be greater than maxLedgerVersion');
throw error('minLedgerVersion must not be greater than maxLedgerVersion')
}
}
}
function validateOptions(schema, instance) {
schemaValidate(schema, instance);
validateLedgerRange(instance.options);
schemaValidate(schema, instance)
validateLedgerRange(instance.options)
}
module.exports = {
@@ -53,4 +53,4 @@ module.exports = {
generateAddress: _.partial(schemaValidate, 'generateAddressParameters'),
apiOptions: _.partial(schemaValidate, 'api-options'),
instructions: _.partial(schemaValidate, 'instructions')
};
}

View File

@@ -1,6 +1,6 @@
'use strict';
'use strict' // eslint-disable-line strict
const {EventEmitter} = require('events');
const {EventEmitter} = require('events')
function unsused() {}
@@ -10,53 +10,50 @@ function unsused() {}
*/
class WSWrapper extends EventEmitter {
constructor(url, protocols = null, websocketOptions = {}) {
super();
unsused(protocols);
unsused(websocketOptions);
this.setMaxListeners(Infinity);
super()
unsused(protocols)
unsused(websocketOptions)
this.setMaxListeners(Infinity)
this._ws = new WebSocket(url);
this._ws = new WebSocket(url)
this._ws.onclose = () => {
this.emit('close');
};
this.emit('close')
}
this._ws.onopen = () => {
this.emit('open');
};
this.emit('open')
}
this._ws.onerror = (error) => {
if (this.listenerCount('error') > 0) {
this.emit('error', error);
}
};
this._ws.onmessage = (message) => {
this.emit('message', message.data);
};
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();
this._ws.close()
}
}
send(message) {
this._ws.send(message);
this._ws.send(message)
}
get readyState() {
return this._ws.readyState;
return this._ws.readyState
}
}
WSWrapper.CONNECTING = 0;
WSWrapper.OPEN = 1;
WSWrapper.CLOSING = 2;
WSWrapper.CLOSED = 3;
WSWrapper.CONNECTING = 0
WSWrapper.OPEN = 1
WSWrapper.CLOSING = 2
WSWrapper.CLOSED = 3
module.exports = WSWrapper;
module.exports = WSWrapper

View File

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

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict' // eslint-disable-line strict
module.exports = {
RippleAPI: require('./api').RippleAPI,
@@ -6,4 +6,4 @@ module.exports = {
RippleAPIBroadcast: require('./broadcast').RippleAPIBroadcast,
// HTTP server is experimental
createHTTPServer: require('./http').createHTTPServer
};
}

View File

@@ -1,8 +1,8 @@
/* @flow */
'use strict';
const utils = require('./utils');
const {validate, removeUndefined} = utils.common;
'use strict' // eslint-disable-line strict
const utils = require('./utils')
const {validate, removeUndefined} = utils.common
type AccountData = {
Sequence: number,
@@ -39,7 +39,7 @@ type AccountInfoResponse = {
}
function formatAccountInfo(response: AccountDataResponse) {
const data = response.account_data;
const data = response.account_data
return removeUndefined({
sequence: data.Sequence,
xrpBalance: utils.common.dropsToXrp(data.Balance),
@@ -47,20 +47,20 @@ function formatAccountInfo(response: AccountDataResponse) {
previousInitiatedTransactionID: data.AccountTxnID,
previousAffectingTransactionID: data.PreviousTxnID,
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
});
})
}
function getAccountInfo(address: string, options: AccountInfoOptions = {}
): Promise<AccountInfoResponse> {
validate.getAccountInfo({address, options});
validate.getAccountInfo({address, options})
const request = {
command: 'account_info',
account: address,
ledger_index: options.ledgerVersion || 'validated'
};
}
return this.connection.request(request).then(formatAccountInfo);
return this.connection.request(request).then(formatAccountInfo)
}
module.exports = getAccountInfo;
module.exports = getAccountInfo

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
import type {Amount} from '../common/types.js';
const _ = require('lodash')
const utils = require('./utils')
const {validate} = utils.common
import type {Amount} from '../common/types.js'
type BalanceSheetOptions = {
excludeAddresses?: Array<string>,
@@ -21,35 +21,35 @@ type GetBalanceSheet = {
}
function formatBalanceSheet(balanceSheet): GetBalanceSheet {
const result = {};
const result = {}
if (!_.isUndefined(balanceSheet.balances)) {
result.balances = [];
result.balances = []
_.forEach(balanceSheet.balances, (balances, counterparty) => {
_.forEach(balances, (balance) => {
result.balances.push(Object.assign({counterparty}, balance));
});
});
_.forEach(balances, balance => {
result.balances.push(Object.assign({counterparty}, balance))
})
})
}
if (!_.isUndefined(balanceSheet.assets)) {
result.assets = [];
result.assets = []
_.forEach(balanceSheet.assets, (assets, counterparty) => {
_.forEach(assets, (balance) => {
result.assets.push(Object.assign({counterparty}, balance));
});
});
_.forEach(assets, balance => {
result.assets.push(Object.assign({counterparty}, balance))
})
})
}
if (!_.isUndefined(balanceSheet.obligations)) {
result.obligations = _.map(balanceSheet.obligations, (value, currency) =>
({currency, value}));
({currency, value}))
}
return result;
return result
}
function getBalanceSheet(address: string, options: BalanceSheetOptions = {}
): Promise<GetBalanceSheet> {
validate.getBalanceSheet({address, options});
validate.getBalanceSheet({address, options})
return utils.ensureLedgerVersion.call(this, options).then(_options => {
const request = {
@@ -58,10 +58,10 @@ function getBalanceSheet(address: string, options: BalanceSheetOptions = {}
strict: true,
hotwallet: _options.excludeAddresses,
ledger_index: _options.ledgerVersion
};
}
return this.connection.request(request).then(formatBalanceSheet);
});
return this.connection.request(request).then(formatBalanceSheet)
})
}
module.exports = getBalanceSheet;
module.exports = getBalanceSheet

View File

@@ -1,9 +1,9 @@
/* @flow */
'use strict';
const utils = require('./utils');
const {validate} = utils.common;
import type {Connection} from '../common/connection.js';
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
'use strict' // eslint-disable-line strict
const utils = require('./utils')
const {validate} = utils.common
import type {Connection} from '../common/connection.js'
import type {TrustlinesOptions, Trustline} from './trustlines-types.js'
type Balance = {
@@ -19,38 +19,38 @@ function getTrustlineBalanceAmount(trustline: Trustline) {
currency: trustline.specification.currency,
counterparty: trustline.specification.counterparty,
value: trustline.state.balance
};
}
}
function formatBalances(options, balances) {
const result = balances.trustlines.map(getTrustlineBalanceAmount);
const result = balances.trustlines.map(getTrustlineBalanceAmount)
if (!(options.counterparty ||
(options.currency && options.currency !== 'XRP')
)) {
const xrpBalance = {
currency: 'XRP',
value: balances.xrp
};
result.unshift(xrpBalance);
}
result.unshift(xrpBalance)
}
if (options.limit && result.length > options.limit) {
const toRemove = result.length - options.limit;
result.splice(-toRemove, toRemove);
const toRemove = result.length - options.limit
result.splice(-toRemove, toRemove)
}
return result;
return result
}
function getLedgerVersionHelper(connection: Connection, optionValue?: number
): Promise<number> {
if (optionValue !== undefined && optionValue !== null) {
return Promise.resolve(optionValue);
return Promise.resolve(optionValue)
}
return connection.getLedgerVersion();
return connection.getLedgerVersion()
}
function getBalances(address: string, options: TrustlinesOptions = {}
): Promise<GetBalances> {
validate.getTrustlines({address, options});
validate.getTrustlines({address, options})
return Promise.all([
getLedgerVersionHelper(this.connection, options.ledgerVersion).then(
@@ -58,7 +58,7 @@ function getBalances(address: string, options: TrustlinesOptions = {}
utils.getXRPBalance(this.connection, address, ledgerVersion)),
this.getTrustlines(address, options)
]).then(results =>
formatBalances(options, {xrp: results[0], trustlines: results[1]}));
formatBalances(options, {xrp: results[0], trustlines: results[1]}))
}
module.exports = getBalances;
module.exports = getBalances

View File

@@ -1,9 +1,9 @@
/* @flow */
'use strict';
const utils = require('./utils');
const {validate} = utils.common;
const parseLedger = require('./parse/ledger');
import type {GetLedger} from './types.js';
'use strict' // eslint-disable-line strict
const utils = require('./utils')
const {validate} = utils.common
const parseLedger = require('./parse/ledger')
import type {GetLedger} from './types.js'
type LedgerOptions = {
ledgerVersion?: number,
@@ -14,7 +14,7 @@ type LedgerOptions = {
function getLedger(options: LedgerOptions = {}): Promise<GetLedger> {
validate.getLedger({options});
validate.getLedger({options})
const request = {
command: 'ledger',
@@ -22,10 +22,10 @@ function getLedger(options: LedgerOptions = {}): Promise<GetLedger> {
expand: options.includeAllData,
transactions: options.includeTransactions,
accounts: options.includeState
};
}
return this.connection.request(request).then(response =>
parseLedger(response.ledger));
parseLedger(response.ledger))
}
module.exports = getLedger;
module.exports = getLedger

View File

@@ -1,12 +1,12 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
const parseOrderbookOrder = require('./parse/orderbook-order');
import type {Connection} from '../common/connection.js';
import type {OrdersOptions, OrderSpecification} from './types.js';
import type {Amount, Issue} from '../common/types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const {validate} = utils.common
const parseOrderbookOrder = require('./parse/orderbook-order')
import type {Connection} from '../common/connection.js'
import type {OrdersOptions, OrderSpecification} from './types.js'
import type {Amount, Issue} from '../common/types.js'
type Orderbook = {
base: Issue,
@@ -46,31 +46,31 @@ function getBookOffers(connection: Connection, account: string,
ledger_index: ledgerVersion || 'validated',
limit: limit,
taker: account
})).then(data => data.offers);
})).then(data => data.offers)
}
function isSameIssue(a: Amount, b: Amount) {
return a.currency === b.currency && a.counterparty === b.counterparty;
return a.currency === b.currency && a.counterparty === b.counterparty
}
function directionFilter(direction: string, order: OrderbookItem) {
return order.specification.direction === direction;
return order.specification.direction === direction
}
function flipOrder(order: OrderbookItem) {
const specification = order.specification;
const specification = order.specification
const flippedSpecification = {
quantity: specification.totalPrice,
totalPrice: specification.quantity,
direction: specification.direction === 'buy' ? 'sell' : 'buy'
};
const newSpecification = _.merge({}, specification, flippedSpecification);
return _.merge({}, order, {specification: newSpecification});
}
const newSpecification = _.merge({}, specification, flippedSpecification)
return _.merge({}, order, {specification: newSpecification})
}
function alignOrder(base: Amount, order: OrderbookItem) {
const quantity = order.specification.quantity;
return isSameIssue(quantity, base) ? order : flipOrder(order);
const quantity = order.specification.quantity
return isSameIssue(quantity, base) ? order : flipOrder(order)
}
function formatBidsAndAsks(orderbook: Orderbook, offers) {
@@ -84,24 +84,24 @@ function formatBidsAndAsks(orderbook: Orderbook, offers) {
// for asks: lowest quality => lowest totalPrice/quantity => lowest price
// for both bids and asks, lowest quality is closest to mid-market
// we sort the orders so that earlier orders are closer to mid-market
const orders = _.sortBy(offers, 'quality').map(parseOrderbookOrder);
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base));
const bids = alignedOrders.filter(_.partial(directionFilter, 'buy'));
const asks = alignedOrders.filter(_.partial(directionFilter, 'sell'));
return {bids, asks};
const orders = _.sortBy(offers, 'quality').map(parseOrderbookOrder)
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base))
const bids = alignedOrders.filter(_.partial(directionFilter, 'buy'))
const asks = alignedOrders.filter(_.partial(directionFilter, 'sell'))
return {bids, asks}
}
function getOrderbook(address: string, orderbook: Orderbook,
options: OrdersOptions = {}
): Promise<GetOrderbook> {
validate.getOrderbook({address, orderbook, options});
validate.getOrderbook({address, orderbook, options})
const getter = _.partial(getBookOffers, this.connection, address,
options.ledgerVersion, options.limit);
const getOffers = _.partial(getter, orderbook.base, orderbook.counter);
const getReverseOffers = _.partial(getter, orderbook.counter, orderbook.base);
options.ledgerVersion, options.limit)
const getOffers = _.partial(getter, orderbook.base, orderbook.counter)
const getReverseOffers = _.partial(getter, orderbook.counter, orderbook.base)
return Promise.all([getOffers(), getReverseOffers()]).then(data =>
formatBidsAndAsks(orderbook, _.flatten(data)));
formatBidsAndAsks(orderbook, _.flatten(data)))
}
module.exports = getOrderbook;
module.exports = getOrderbook

View File

@@ -1,11 +1,11 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
const parseAccountOrder = require('./parse/account-order');
import type {Connection} from '../common/connection.js';
import type {OrdersOptions, Order} from './types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const {validate} = utils.common
const parseAccountOrder = require('./parse/account-order')
import type {Connection} from '../common/connection.js'
import type {OrdersOptions, Order} from './types.js'
type GetOrders = Array<Order>
@@ -22,20 +22,20 @@ function requestAccountOffers(connection: Connection, address: string,
return {
marker: data.marker,
results: data.offers.map(_.partial(parseAccountOrder, address))
};
});
}
})
}
function getOrders(address: string, options: OrdersOptions = {}
): Promise<GetOrders> {
validate.getOrders({address, options});
validate.getOrders({address, options})
return utils.ensureLedgerVersion.call(this, options).then(_options => {
const getter = _.partial(requestAccountOffers, this.connection, address,
_options.ledgerVersion);
_options.ledgerVersion)
return utils.getRecursive(getter, _options.limit).then(orders =>
_.sortBy(orders, (order) => order.properties.sequence));
});
_.sortBy(orders, order => order.properties.sequence))
})
}
module.exports = getOrders;
module.exports = getOrders

View File

@@ -1,24 +1,24 @@
/* @flow */
'use strict';
const utils = require('./utils');
const flags = require('./flags').orderFlags;
const parseAmount = require('./amount');
const BigNumber = require('bignumber.js');
'use strict' // eslint-disable-line strict
const utils = require('./utils')
const flags = require('./flags').orderFlags
const parseAmount = require('./amount')
const BigNumber = require('bignumber.js')
// TODO: remove this function once rippled provides quality directly
function computeQuality(takerGets, takerPays) {
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value);
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString();
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value)
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString()
}
// rippled 'account_offers' returns a different format for orders than 'tx'
// the flags are also different
function parseAccountOrder(address: string, order: Object): Object {
const direction = (order.flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(order.taker_gets);
const takerPaysAmount = parseAmount(order.taker_pays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
const direction = (order.flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.taker_gets)
const takerPaysAmount = parseAmount(order.taker_pays)
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
@@ -29,16 +29,19 @@ function parseAccountOrder(address: string, order: Object): Object {
passive: ((order.flags & flags.Passive) !== 0) || undefined,
// rippled currently does not provide "expiration" in account_offers
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};
return {specification, properties}
}
module.exports = parseAccountOrder;
module.exports = parseAccountOrder

View File

@@ -1,6 +1,6 @@
/* @flow */
'use strict';
const utils = require('./utils');
'use strict' // eslint-disable-line strict
const utils = require('./utils')
type Trustline = {
account: string, limit: number, currency: string, quality_in: ?number,
@@ -29,18 +29,18 @@ function parseAccountTrustline(trustline: Trustline): AccountTrustline {
ripplingDisabled: trustline.no_ripple || undefined,
frozen: trustline.freeze || undefined,
authorized: trustline.authorized || undefined
});
})
// rippled doesn't provide the counterparty's qualities
const counterparty = utils.removeUndefined({
limit: trustline.limit_peer,
ripplingDisabled: trustline.no_ripple_peer || undefined,
frozen: trustline.freeze_peer || undefined,
authorized: trustline.peer_authorized || undefined
});
})
const state = {
balance: trustline.balance
};
return {specification, counterparty, state};
}
return {specification, counterparty, state}
}
module.exports = parseAccountTrustline;
module.exports = parseAccountTrustline

View File

@@ -0,0 +1,9 @@
'use strict' // eslint-disable-line strict
function parseAmendment(tx: Object) {
return {
amendment: tx.Amendment
}
}
module.exports = parseAmendment

View File

@@ -1,21 +1,21 @@
/* @flow */
'use strict';
const utils = require('./utils');
import type {Amount, RippledAmount} from '../../common/types.js';
'use strict' // eslint-disable-line strict
const utils = require('../utils')
import type {Amount, RippledAmount} from '../../common/types.js'
function parseAmount(amount: RippledAmount): Amount {
if (typeof amount === 'string') {
return {
currency: 'XRP',
value: utils.dropsToXrp(amount)
};
value: utils.common.dropsToXrp(amount)
}
}
return {
currency: amount.currency,
value: amount.value,
counterparty: amount.issuer
};
}
}
module.exports = parseAmount;
module.exports = parseAmount

View File

@@ -1,12 +1,12 @@
/* @flow */
'use strict';
const assert = require('assert');
'use strict' // eslint-disable-line strict
const assert = require('assert')
function parseOrderCancellation(tx: Object): Object {
assert(tx.TransactionType === 'OfferCancel');
assert(tx.TransactionType === 'OfferCancel')
return {
orderSequence: tx.OfferSequence
};
}
}
module.exports = parseOrderCancellation;
module.exports = parseOrderCancellation

View File

@@ -0,0 +1,15 @@
'use strict' // eslint-disable-line strict
const BigNumber = require('bignumber.js')
const {dropsToXrp} = require('./utils')
function parseFeeUpdate(tx: Object) {
const baseFeeDrops = (new BigNumber(tx.BaseFee, 16)).toString()
return {
baseFeeXRP: dropsToXrp(baseFeeDrops),
referenceFeeUnits: tx.ReferenceFeeUnits,
reserveBaseXRP: dropsToXrp(tx.ReserveBase),
reserveIncrementXRP: dropsToXrp(tx.ReserveIncrement)
}
}
module.exports = parseFeeUpdate

View File

@@ -1,49 +1,49 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const AccountFields = require('./utils').constants.AccountFields;
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const BigNumber = require('bignumber.js')
const AccountFields = require('./utils').constants.AccountFields
function parseField(info, value) {
if (info.encoding === 'hex' && !info.length) { // e.g. "domain"
return new Buffer(value, 'hex').toString('ascii');
return new Buffer(value, 'hex').toString('ascii')
}
if (info.shift) {
return (new BigNumber(value)).shift(-info.shift).toNumber();
return (new BigNumber(value)).shift(-info.shift).toNumber()
}
return value;
return value
}
function parseFields(data: Object): Object {
const settings = {};
const settings = {}
for (const fieldName in AccountFields) {
const fieldValue = data[fieldName];
const fieldValue = data[fieldName]
if (fieldValue !== undefined) {
const info = AccountFields[fieldName];
settings[info.name] = parseField(info, fieldValue);
const info = AccountFields[fieldName]
settings[info.name] = parseField(info, fieldValue)
}
}
if (data.RegularKey) {
settings.regularKey = 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 = {};
settings.signers = {}
if (data.SignerQuorum) {
settings.signers.threshold = 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;
return settings
}
module.exports = parseFields;
module.exports = parseFields

View File

@@ -1,9 +1,9 @@
'use strict';
'use strict' // eslint-disable-line strict
const orderFlags = {
Passive: 0x00010000,
Sell: 0x00020000 // offer was placed as a sell
};
}
const trustlineFlags = {
LowReserve: 0x00010000, // entry counts toward reserve
@@ -14,9 +14,9 @@ const trustlineFlags = {
HighNoRipple: 0x00200000,
LowFreeze: 0x00400000,
HighFreeze: 0x00800000
};
}
module.exports = {
orderFlags,
trustlineFlags
};
}

View File

@@ -1,46 +1,48 @@
/* @flow */
'use strict';
const _ = require('lodash');
const {removeUndefined, rippleTimeToISO8601} = require('./utils');
const parseTransaction = require('./transaction');
import type {GetLedger} from '../types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const {removeUndefined, rippleTimeToISO8601} = require('./utils')
const parseTransaction = require('./transaction')
import type {GetLedger} from '../types.js'
function parseTransactionWrapper(ledgerVersion, tx) {
const transaction = _.assign({}, _.omit(tx, 'metaData'),
{meta: tx.metaData});
const result = parseTransaction(transaction);
const transaction = _.assign({}, _.omit(tx, 'metaData'), {
meta: tx.metaData,
ledger_index: ledgerVersion
})
const result = parseTransaction(transaction)
if (!result.outcome.ledgerVersion) {
result.outcome.ledgerVersion = ledgerVersion;
result.outcome.ledgerVersion = ledgerVersion
}
return result;
return result
}
function parseTransactions(transactions, ledgerVersion) {
if (_.isEmpty(transactions)) {
return {};
return {}
}
if (_.isString(transactions[0])) {
return {transactionHashes: transactions};
return {transactionHashes: transactions}
}
return {
transactions: _.map(transactions,
_.partial(parseTransactionWrapper, ledgerVersion)),
rawTransactions: JSON.stringify(transactions)
};
}
}
function parseState(state) {
if (_.isEmpty(state)) {
return {};
return {}
}
if (_.isString(state[0])) {
return {stateHashes: state};
return {stateHashes: state}
}
return {rawState: JSON.stringify(state)};
return {rawState: JSON.stringify(state)}
}
function parseLedger(ledger: Object): GetLedger {
const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10);
const ledgerVersion = parseInt(ledger.ledger_index || ledger.seqNum, 10)
return removeUndefined(_.assign({
stateHash: ledger.account_hash,
closeTime: rippleTimeToISO8601(ledger.close_time),
@@ -55,7 +57,7 @@ function parseLedger(ledger: Object): GetLedger {
},
parseTransactions(ledger.transactions, ledgerVersion),
parseState(ledger.accountState)
));
))
}
module.exports = parseLedger;
module.exports = parseLedger

View File

@@ -1,18 +1,18 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const flags = utils.txFlags.OfferCreate;
'use strict' // eslint-disable-line strict
const assert = require('assert')
const utils = require('./utils')
const parseAmount = require('./amount')
const flags = utils.txFlags.OfferCreate
function parseOrder(tx: Object): Object {
assert(tx.TransactionType === 'OfferCreate');
assert(tx.TransactionType === 'OfferCreate')
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(tx.TakerGets);
const takerPaysAmount = parseAmount(tx.TakerPays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(tx.TakerGets)
const takerPaysAmount = parseAmount(tx.TakerPays)
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount
return utils.removeUndefined({
direction: direction,
@@ -23,7 +23,7 @@ function parseOrder(tx: Object): Object {
|| undefined,
fillOrKill: ((tx.Flags & flags.FillOrKill) !== 0) || undefined,
expirationTime: utils.parseTimestamp(tx.Expiration)
});
})
}
module.exports = parseOrder;
module.exports = parseOrder

View File

@@ -1,16 +1,16 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const flags = require('./flags').orderFlags;
const parseAmount = require('./amount');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const flags = require('./flags').orderFlags
const parseAmount = require('./amount')
function parseOrderbookOrder(order: Object): Object {
const direction = (order.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
const takerGetsAmount = parseAmount(order.TakerGets);
const takerPaysAmount = parseAmount(order.TakerPays);
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
const direction = (order.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.TakerGets)
const takerPaysAmount = parseAmount(order.TakerPays)
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
@@ -20,25 +20,25 @@ function parseOrderbookOrder(order: Object): Object {
totalPrice: totalPrice,
passive: ((order.Flags & flags.Passive) !== 0) || undefined,
expirationTime: utils.parseTimestamp(order.Expiration)
});
})
const properties = {
maker: order.Account,
sequence: order.Sequence,
makerExchangeRate: utils.adjustQualityForXRP(order.quality,
takerGetsAmount.currency, takerPaysAmount.currency)
};
}
const takerGetsFunded = order.taker_gets_funded ?
parseAmount(order.taker_gets_funded) : undefined;
parseAmount(order.taker_gets_funded) : undefined
const takerPaysFunded = order.taker_pays_funded ?
parseAmount(order.taker_pays_funded) : undefined;
parseAmount(order.taker_pays_funded) : undefined
const available = utils.removeUndefined({
fundedAmount: takerGetsFunded,
priceOfFundedAmount: takerPaysFunded
});
const state = _.isEmpty(available) ? undefined : available;
return utils.removeUndefined({specification, properties, state});
})
const state = _.isEmpty(available) ? undefined : available
return utils.removeUndefined({specification, properties, state})
}
module.exports = parseOrderbookOrder;
module.exports = parseOrderbookOrder

View File

@@ -1,25 +1,25 @@
/* @flow */
'use strict';
const _ = require('lodash');
const parseAmount = require('./amount');
import type {Amount, RippledAmount} from '../../common/types.js';
import type {GetPaths, RippledPathsResponse} from '../pathfind-types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const parseAmount = require('./amount')
import type {Amount, RippledAmount} from '../../common/types.js'
import type {GetPaths, RippledPathsResponse} from '../pathfind-types.js'
function parsePaths(paths) {
return paths.map(steps => steps.map(step =>
_.omit(step, ['type', 'type_hex'])));
_.omit(step, ['type', 'type_hex'])))
}
function removeAnyCounterpartyEncoding(address: string, amount: Amount) {
return amount.counterparty === address ?
_.omit(amount, 'counterparty') : amount;
_.omit(amount, 'counterparty') : amount
}
function createAdjustment(address: string, adjustmentWithoutAddress: Object) {
const amountKey = _.keys(adjustmentWithoutAddress)[0];
const amount = adjustmentWithoutAddress[amountKey];
const amountKey = _.keys(adjustmentWithoutAddress)[0]
const amount = adjustmentWithoutAddress[amountKey]
return _.set({address: address}, amountKey,
removeAnyCounterpartyEncoding(address, amount));
removeAnyCounterpartyEncoding(address, amount))
}
function parseAlternative(sourceAddress: string, destinationAddress: string,
@@ -31,21 +31,21 @@ function parseAlternative(sourceAddress: string, destinationAddress: string,
{source: {amount: parseAmount(alternative.source_amount)},
destination: {minAmount: parseAmount(alternative.destination_amount)}} :
{source: {maxAmount: parseAmount(alternative.source_amount)},
destination: {amount: parseAmount(destinationAmount)}};
destination: {amount: parseAmount(destinationAmount)}}
return {
source: createAdjustment(sourceAddress, amounts.source),
destination: createAdjustment(destinationAddress, amounts.destination),
paths: JSON.stringify(parsePaths(alternative.paths_computed))
};
}
}
function parsePathfind(pathfindResult: RippledPathsResponse): GetPaths {
const sourceAddress = pathfindResult.source_account;
const destinationAddress = pathfindResult.destination_account;
const destinationAmount = pathfindResult.destination_amount;
const sourceAddress = pathfindResult.source_account
const destinationAddress = pathfindResult.destination_account
const destinationAmount = pathfindResult.destination_amount
return pathfindResult.alternatives.map(_.partial(parseAlternative,
sourceAddress, destinationAddress, destinationAmount));
sourceAddress, destinationAddress, destinationAmount))
}
module.exports = parsePathfind;
module.exports = parsePathfind

View File

@@ -1,43 +1,39 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const txFlags = utils.txFlags;
function isPartialPayment(tx) {
return (tx.Flags & txFlags.Payment.PartialPayment) !== 0;
}
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const utils = require('./utils')
const parseAmount = require('./amount')
const txFlags = utils.txFlags
function isNoDirectRipple(tx) {
return (tx.Flags & txFlags.Payment.NoRippleDirect) !== 0;
return (tx.Flags & txFlags.Payment.NoRippleDirect) !== 0
}
function isQualityLimited(tx) {
return (tx.Flags & txFlags.Payment.LimitQuality) !== 0;
return (tx.Flags & txFlags.Payment.LimitQuality) !== 0
}
function removeGenericCounterparty(amount, address) {
return amount.counterparty === address ?
_.omit(amount, 'counterparty') : amount;
_.omit(amount, 'counterparty') : amount
}
function parsePayment(tx: Object): Object {
assert(tx.TransactionType === 'Payment');
assert(tx.TransactionType === 'Payment')
const source = {
address: tx.Account,
maxAmount: removeGenericCounterparty(
parseAmount(tx.SendMax || tx.Amount), tx.Account),
tag: tx.SourceTag
};
}
const destination = {
address: tx.Destination,
amount: removeGenericCounterparty(parseAmount(tx.Amount), tx.Destination),
tag: tx.DestinationTag
};
}
return utils.removeUndefined({
source: utils.removeUndefined(source),
@@ -45,10 +41,10 @@ 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
});
})
}
module.exports = parsePayment;
module.exports = parsePayment

View File

@@ -1,61 +1,61 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const AccountFlags = require('./utils').constants.AccountFlags;
const parseFields = require('./fields');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const AccountFlags = require('./utils').constants.AccountFlags
const parseFields = require('./fields')
function getAccountRootModifiedNode(tx: Object) {
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
node.ModifiedNode.LedgerEntryType === 'AccountRoot');
assert(modifiedNodes.length === 1);
return modifiedNodes[0].ModifiedNode;
node.ModifiedNode.LedgerEntryType === 'AccountRoot')
assert(modifiedNodes.length === 1)
return modifiedNodes[0].ModifiedNode
}
function parseFlags(tx: Object) {
const settings = {};
const settings = {}
if (tx.TransactionType !== 'AccountSet') {
return settings;
return settings
}
const node = getAccountRootModifiedNode(tx);
const oldFlags = _.get(node.PreviousFields, 'Flags');
const newFlags = _.get(node.FinalFields, 'Flags');
const node = getAccountRootModifiedNode(tx)
const oldFlags = _.get(node.PreviousFields, 'Flags')
const newFlags = _.get(node.FinalFields, 'Flags')
if (oldFlags !== undefined && newFlags !== undefined) {
const changedFlags = oldFlags ^ newFlags;
const setFlags = newFlags & changedFlags;
const clearedFlags = oldFlags & changedFlags;
const changedFlags = oldFlags ^ newFlags
const setFlags = newFlags & changedFlags
const clearedFlags = oldFlags & changedFlags
_.forEach(AccountFlags, (flagValue, flagName) => {
if (setFlags & flagValue) {
settings[flagName] = true;
settings[flagName] = true
} else if (clearedFlags & flagValue) {
settings[flagName] = false;
settings[flagName] = false
}
});
})
}
// enableTransactionIDTracking requires a special case because it
// does not affect the Flags field; instead it adds/removes a field called
// "AccountTxnID" to/from the account root.
const oldField = _.get(node.PreviousFields, 'AccountTxnID');
const newField = _.get(node.FinalFields, 'AccountTxnID');
const oldField = _.get(node.PreviousFields, 'AccountTxnID')
const newField = _.get(node.FinalFields, 'AccountTxnID')
if (newField && !oldField) {
settings.enableTransactionIDTracking = true;
settings.enableTransactionIDTracking = true
} else if (oldField && !newField) {
settings.enableTransactionIDTracking = false;
settings.enableTransactionIDTracking = false
}
return settings;
return settings
}
function parseSettings(tx: Object) {
const txType = tx.TransactionType;
const txType = tx.TransactionType
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
txType === 'SignerListSet');
txType === 'SignerListSet')
return _.assign({}, parseFlags(tx), parseFields(tx));
return _.assign({}, parseFlags(tx), parseFields(tx))
}
module.exports = parseSettings;
module.exports = parseSettings

View File

@@ -1,16 +1,16 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
'use strict' // eslint-disable-line strict
const assert = require('assert')
const utils = require('./utils')
function parseSuspendedPaymentCancellation(tx: Object): Object {
assert(tx.TransactionType === 'SuspendedPaymentCancel');
assert(tx.TransactionType === 'SuspendedPaymentCancel')
return utils.removeUndefined({
memos: utils.parseMemos(tx),
owner: tx.Owner,
suspensionSequence: tx.OfferSequence
});
})
}
module.exports = parseSuspendedPaymentCancellation;
module.exports = parseSuspendedPaymentCancellation

View File

@@ -1,30 +1,30 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const utils = require('./utils')
const parseAmount = require('./amount')
function removeGenericCounterparty(amount, address) {
return amount.counterparty === address ?
_.omit(amount, 'counterparty') : amount;
_.omit(amount, 'counterparty') : amount
}
function parseSuspendedPaymentCreation(tx: Object): Object {
assert(tx.TransactionType === 'SuspendedPaymentCreate');
assert(tx.TransactionType === 'SuspendedPaymentCreate')
const source = {
address: tx.Account,
maxAmount: removeGenericCounterparty(
parseAmount(tx.SendMax || tx.Amount), tx.Account),
tag: tx.SourceTag
};
}
const destination = {
address: tx.Destination,
amount: removeGenericCounterparty(parseAmount(tx.Amount), tx.Destination),
tag: tx.DestinationTag
};
}
return utils.removeUndefined({
source: utils.removeUndefined(source),
@@ -33,7 +33,7 @@ function parseSuspendedPaymentCreation(tx: Object): Object {
digest: tx.Digest,
allowCancelAfter: utils.parseTimestamp(tx.CancelAfter),
allowExecuteAfter: utils.parseTimestamp(tx.FinishAfter)
});
})
}
module.exports = parseSuspendedPaymentCreation;
module.exports = parseSuspendedPaymentCreation

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
'use strict' // eslint-disable-line strict
const assert = require('assert')
const utils = require('./utils')
function parseSuspendedPaymentExecution(tx: Object): Object {
assert(tx.TransactionType === 'SuspendedPaymentFinish');
assert(tx.TransactionType === 'SuspendedPaymentFinish')
return utils.removeUndefined({
memos: utils.parseMemos(tx),
@@ -13,7 +13,7 @@ function parseSuspendedPaymentExecution(tx: Object): Object {
method: tx.Method,
digest: tx.Digest,
proof: tx.Proof ? utils.hexToString(tx.Proof) : undefined
});
})
}
module.exports = parseSuspendedPaymentExecution;
module.exports = parseSuspendedPaymentExecution

View File

@@ -1,16 +1,18 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const parsePayment = require('./payment');
const parseTrustline = require('./trustline');
const parseOrder = require('./order');
const parseOrderCancellation = require('./cancellation');
const parseSettings = require('./settings');
const parseSuspendedPaymentCreation = require('./suspended-payment-creation');
const parseSuspendedPaymentExecution = require('./suspended-payment-execution');
'use strict' // eslint-disable-line strict
const assert = require('assert')
const utils = require('./utils')
const parsePayment = require('./payment')
const parseTrustline = require('./trustline')
const parseOrder = require('./order')
const parseOrderCancellation = require('./cancellation')
const parseSettings = require('./settings')
const parseSuspendedPaymentCreation = require('./suspended-payment-creation')
const parseSuspendedPaymentExecution = require('./suspended-payment-execution')
const parseSuspendedPaymentCancellation =
require('./suspended-payment-cancellation');
require('./suspended-payment-cancellation')
const parseFeeUpdate = require('./fee-update')
const parseAmendment = require('./amendment')
function parseTransactionType(type) {
const mapping = {
@@ -23,13 +25,15 @@ function parseTransactionType(type) {
SuspendedPaymentCreate: 'suspendedPaymentCreation',
SuspendedPaymentFinish: 'suspendedPaymentExecution',
SuspendedPaymentCancel: 'suspendedPaymentCancellation',
SignerListSet: 'settings'
};
return mapping[type] || null;
SignerListSet: 'settings',
SetFee: 'feeUpdate', // pseudo-transaction
EnableAmendment: 'amendment' // pseudo-transaction
}
return mapping[type] || null
}
function parseTransaction(tx: Object): Object {
const type = parseTransactionType(tx.TransactionType);
const type = parseTransactionType(tx.TransactionType)
const mapping = {
'payment': parsePayment,
'trustline': parseTrustline,
@@ -38,12 +42,14 @@ function parseTransaction(tx: Object): Object {
'settings': parseSettings,
'suspendedPaymentCreation': parseSuspendedPaymentCreation,
'suspendedPaymentExecution': parseSuspendedPaymentExecution,
'suspendedPaymentCancellation': parseSuspendedPaymentCancellation
};
const parser = mapping[type];
assert(parser !== undefined, 'Unrecognized transaction type');
const specification = parser(tx);
const outcome = utils.parseOutcome(tx);
'suspendedPaymentCancellation': parseSuspendedPaymentCancellation,
'feeUpdate': parseFeeUpdate,
'amendment': parseAmendment
}
const parser = mapping[type]
assert(parser !== undefined, 'Unrecognized transaction type')
const specification = parser(tx)
const outcome = utils.parseOutcome(tx)
return utils.removeUndefined({
type: type,
address: tx.Account,
@@ -51,7 +57,7 @@ function parseTransaction(tx: Object): Object {
id: tx.hash,
specification: utils.removeUndefined(specification),
outcome: outcome ? utils.removeUndefined(outcome) : undefined
});
})
}
module.exports = parseTransaction;
module.exports = parseTransaction

View File

@@ -1,21 +1,21 @@
/* @flow */
'use strict';
const assert = require('assert');
const utils = require('./utils');
const flags = utils.txFlags.TrustSet;
'use strict' // eslint-disable-line strict
const assert = require('assert')
const utils = require('./utils')
const flags = utils.txFlags.TrustSet
function parseFlag(flagsValue, trueValue, falseValue) {
if (flagsValue & trueValue) {
return true;
return true
}
if (flagsValue & falseValue) {
return false;
return false
}
return undefined;
return undefined
}
function parseTrustline(tx: Object): Object {
assert(tx.TransactionType === 'TrustSet');
assert(tx.TransactionType === 'TrustSet')
return utils.removeUndefined({
limit: tx.LimitAmount.value,
@@ -27,7 +27,7 @@ function parseTrustline(tx: Object): Object {
tx.Flags, flags.SetNoRipple, flags.ClearNoRipple),
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),
authorized: parseFlag(tx.Flags, flags.SetAuth, 0)
});
})
}
module.exports = parseTrustline;
module.exports = parseTrustline

View File

@@ -1,89 +1,137 @@
/* @flow */
'use strict';
const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const utils = require('../utils');
const BigNumber = require('bignumber.js');
'use strict' // eslint-disable-line strict
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
) {
// quality = takerPays.value/takerGets.value
// using drops (1e-6 XRP) for XRP values
const numeratorShift = (takerPaysCurrency === 'XRP' ? -6 : 0);
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0);
const shift = numeratorShift - denominatorShift;
const numeratorShift = (takerPaysCurrency === 'XRP' ? -6 : 0)
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0)
const shift = numeratorShift - denominatorShift
return shift === 0 ? quality :
(new BigNumber(quality)).shift(shift).toString();
(new BigNumber(quality)).shift(shift).toString()
}
function parseQuality(quality: ?number) {
if (typeof quality === 'number') {
return (new BigNumber(quality)).shift(-9).toNumber();
return (new BigNumber(quality)).shift(-9).toNumber()
}
return undefined;
return undefined
}
function parseTimestamp(rippleTime: number): string | void {
return rippleTime ? utils.common.rippleTimeToISO8601(rippleTime) : undefined;
return rippleTime ? utils.common.rippleTimeToISO8601(rippleTime) : undefined
}
function removeEmptyCounterparty(amount) {
if (amount.counterparty === '') {
delete amount.counterparty;
delete amount.counterparty
}
}
function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
_.forEach(balanceChanges, (changes) => {
_.forEach(changes, removeEmptyCounterparty);
});
_.forEach(balanceChanges, changes => {
_.forEach(changes, removeEmptyCounterparty)
})
}
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
_.forEach(orderbookChanges, (changes) => {
_.forEach(changes, (change) => {
_.forEach(change, removeEmptyCounterparty);
});
});
_.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 {
if (tx.TransactionType !== 'Payment' ||
tx.meta.TransactionResult !== 'tesSUCCESS') {
return undefined
}
if (tx.meta.delivered_amount &&
tx.meta.delivered_amount === 'unavailable') {
return undefined
}
// parsable delivered_amount
if (tx.meta.delivered_amount) {
return parseAmount(tx.meta.delivered_amount)
}
// DeliveredAmount only present on partial payments
if (tx.meta.DeliveredAmount) {
return parseAmount(tx.meta.DeliveredAmount)
}
// no partial payment flag, use tx.Amount
if (tx.Amount && !isPartialPayment(tx)) {
return parseAmount(tx.Amount)
}
// DeliveredAmount field was introduced at
// ledger 4594095 - after that point its absence
// on a tx flagged as partial payment indicates
// the full amount was transferred. The amount
// transferred with a partial payment before
// that date must be derived from metadata.
if (tx.Amount && tx.ledger_index > 4594094) {
return parseAmount(tx.Amount)
}
return undefined
}
function parseOutcome(tx: Object): ?Object {
const metadata = tx.meta || tx.metaData;
const metadata = tx.meta || tx.metaData
if (!metadata) {
return undefined;
return undefined
}
const balanceChanges = transactionParser.parseBalanceChanges(metadata);
const orderbookChanges = transactionParser.parseOrderbookChanges(metadata);
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);
const balanceChanges = transactionParser.parseBalanceChanges(metadata)
const orderbookChanges = transactionParser.parseOrderbookChanges(metadata)
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 {
return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined;
return hex ? new Buffer(hex, 'hex').toString('utf-8') : undefined
}
function parseMemos(tx: Object): ?Array<Object> {
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
return undefined;
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),
data: m.Memo.parsed_memo_data || hexToString(m.Memo.MemoData)
});
});
})
})
}
module.exports = {
@@ -93,9 +141,10 @@ module.exports = {
hexToString,
parseTimestamp,
adjustQualityForXRP,
isPartialPayment,
dropsToXrp: utils.common.dropsToXrp,
constants: utils.common.constants,
txFlags: utils.common.txFlags,
removeUndefined: utils.common.removeUndefined,
rippleTimeToISO8601: utils.common.rippleTimeToISO8601
};
}

View File

@@ -1,8 +1,8 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
import type {Amount, LaxLaxAmount, RippledAmount, Adjustment, MaxAdjustment,
MinAdjustment} from '../common/types.js';
MinAdjustment} from '../common/types.js'
type Path = {

View File

@@ -1,33 +1,33 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const parsePathfind = require('./parse/pathfind');
const {validate, toRippledAmount} = utils.common;
const NotFoundError = utils.common.errors.NotFoundError;
const ValidationError = utils.common.errors.ValidationError;
import type {Connection} from '../common/connection';
import type {RippledAmount} from '../common/types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const BigNumber = require('bignumber.js')
const utils = require('./utils')
const parsePathfind = require('./parse/pathfind')
const {validate, toRippledAmount} = utils.common
const NotFoundError = utils.common.errors.NotFoundError
const ValidationError = utils.common.errors.ValidationError
import type {Connection} from '../common/connection'
import type {RippledAmount} from '../common/types.js'
import type {GetPaths, PathFind, RippledPathsResponse, PathFindRequest}
from './pathfind-types.js';
from './pathfind-types.js'
function addParams(request: PathFindRequest, result: RippledPathsResponse) {
return _.defaults(_.assign({}, result, {
source_account: request.source_account,
source_currencies: request.source_currencies
}), {destination_amount: request.destination_amount});
}), {destination_amount: request.destination_amount})
}
function requestPathFind(connection: Connection, pathfind: PathFind): Promise {
const destinationAmount = _.assign({value: -1}, pathfind.destination.amount);
const destinationAmount = _.assign({value: -1}, pathfind.destination.amount)
const request: PathFindRequest = {
command: 'ripple_path_find',
source_account: pathfind.source.address,
destination_account: pathfind.destination.address,
destination_amount: toRippledAmount(destinationAmount)
};
}
if (typeof request.destination_amount === 'object'
&& !request.destination_amount.issuer) {
// Convert blank issuer to sender's address
@@ -35,43 +35,43 @@ function requestPathFind(connection: Connection, pathfind: PathFind): Promise {
// https://ripple.com/build/transactions/
// #special-issuer-values-for-sendmax-and-amount
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
request.destination_amount.issuer = request.destination_account;
request.destination_amount.issuer = request.destination_account
}
if (pathfind.source.currencies && pathfind.source.currencies.length > 0) {
request.source_currencies = pathfind.source.currencies.map(amount =>
_.omit(toRippledAmount(amount), 'value'));
_.omit(toRippledAmount(amount), 'value'))
}
if (pathfind.source.amount) {
if (pathfind.destination.amount.value !== undefined) {
throw new ValidationError('Cannot specify both source.amount'
+ ' and destination.amount.value in getPaths');
+ ' and destination.amount.value in getPaths')
}
request.send_max = toRippledAmount(pathfind.source.amount);
request.send_max = toRippledAmount(pathfind.source.amount)
if (request.send_max.currency && !request.send_max.issuer) {
request.send_max.issuer = pathfind.source.address;
request.send_max.issuer = pathfind.source.address
}
}
return connection.request(request).then(paths => addParams(request, paths));
return connection.request(request).then(paths => addParams(request, paths))
}
function addDirectXrpPath(paths: RippledPathsResponse, xrpBalance: string
): RippledPathsResponse {
// Add XRP "path" only if the source acct has enough XRP to make the payment
const destinationAmount = paths.destination_amount;
const destinationAmount = paths.destination_amount
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
paths.alternatives.unshift({
paths_computed: [],
source_amount: paths.destination_amount
});
})
}
return paths;
return paths
}
function isRippledIOUAmount(amount: RippledAmount) {
// rippled XRP amounts are specified as decimal strings
return (typeof amount === 'object') &&
amount.currency && (amount.currency !== 'XRP');
amount.currency && (amount.currency !== 'XRP')
}
function conditionallyAddDirectXRPPath(connection: Connection, address: string,
@@ -79,10 +79,10 @@ function conditionallyAddDirectXRPPath(connection: Connection, address: string,
): Promise {
if (isRippledIOUAmount(paths.destination_amount)
|| !_.includes(paths.destination_currencies, 'XRP')) {
return Promise.resolve(paths);
return Promise.resolve(paths)
}
return utils.getXRPBalance(connection, address, undefined).then(
xrpBalance => addDirectXrpPath(paths, xrpBalance));
xrpBalance => addDirectXrpPath(paths, xrpBalance))
}
function filterSourceFundsLowPaths(pathfind: PathFind,
@@ -93,15 +93,15 @@ function filterSourceFundsLowPaths(pathfind: PathFind,
paths.alternatives = _.filter(paths.alternatives, alt => {
return alt.source_amount &&
pathfind.source.amount &&
alt.source_amount.value === pathfind.source.amount.value;
});
new BigNumber(alt.source_amount.value).eq(pathfind.source.amount.value)
})
}
return paths;
return paths
}
function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
if (paths.alternatives && paths.alternatives.length > 0) {
return parsePathfind(paths);
return parsePathfind(paths)
}
if (paths.destination_currencies !== undefined &&
!_.includes(paths.destination_currencies,
@@ -109,30 +109,30 @@ function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
throw new NotFoundError('No paths found. ' +
'The destination_account does not accept ' +
pathfind.destination.amount.currency + ', they only accept: ' +
paths.destination_currencies.join(', '));
paths.destination_currencies.join(', '))
} else if (paths.source_currencies && paths.source_currencies.length > 0) {
throw new NotFoundError('No paths found. Please ensure' +
' that the source_account has sufficient funds to execute' +
' the payment in one of the specified source_currencies. If it does' +
' there may be insufficient liquidity in the network to execute' +
' this payment right now');
' this payment right now')
} else {
throw new NotFoundError('No paths found.' +
' Please ensure that the source_account has sufficient funds to' +
' execute the payment. If it does there may be insufficient liquidity' +
' in the network to execute this payment right now');
' in the network to execute this payment right now')
}
}
function getPaths(pathfind: PathFind): Promise<GetPaths> {
validate.getPaths({pathfind});
validate.getPaths({pathfind})
const address = pathfind.source.address;
const address = pathfind.source.address
return requestPathFind(this.connection, pathfind).then(paths =>
conditionallyAddDirectXRPPath(this.connection, address, paths)
)
.then(paths => filterSourceFundsLowPaths(pathfind, paths))
.then(paths => formatResponse(pathfind, paths));
.then(paths => formatResponse(pathfind, paths))
}
module.exports = getPaths;
module.exports = getPaths

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const parseFields = require('./parse/fields');
const {validate} = utils.common;
const AccountFlags = utils.common.constants.AccountFlags;
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const parseFields = require('./parse/fields')
const {validate} = utils.common
const AccountFlags = utils.common.constants.AccountFlags
type SettingsOptions = {
ledgerVersion?: number
@@ -29,33 +29,33 @@ type GetSettings = {
function parseFlags(value) {
const settings = {};
const settings = {}
for (const flagName in AccountFlags) {
if (value & AccountFlags[flagName]) {
settings[flagName] = true;
settings[flagName] = true
}
}
return settings;
return settings
}
function formatSettings(response) {
const data = response.account_data;
const parsedFlags = parseFlags(data.Flags);
const parsedFields = parseFields(data);
return _.assign({}, parsedFlags, parsedFields);
const data = response.account_data
const parsedFlags = parseFlags(data.Flags)
const parsedFields = parseFields(data)
return _.assign({}, parsedFlags, parsedFields)
}
function getSettings(address: string, options: SettingsOptions = {}
): Promise<GetSettings> {
validate.getSettings({address, options});
validate.getSettings({address, options})
const request = {
command: 'account_info',
account: address,
ledger_index: options.ledgerVersion || 'validated'
};
}
return this.connection.request(request).then(formatSettings);
return this.connection.request(request).then(formatSettings)
}
module.exports = getSettings;
module.exports = getSettings

View File

@@ -1,7 +1,7 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
import type {Amount, Memo} from '../common/types.js';
import type {Amount, Memo} from '../common/types.js'
type Outcome = {
result: string,

View File

@@ -1,54 +1,57 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const {validate, errors} = utils.common;
import type {Connection} from '../common/connection.js';
import type {TransactionType, TransactionOptions} from './transaction-types';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const parseTransaction = require('./parse/transaction')
const {validate, errors} = utils.common
import type {Connection} from '../common/connection.js'
import type {TransactionType, TransactionOptions} from './transaction-types'
function attachTransactionDate(connection: Connection, tx: Object
): Promise<TransactionType> {
if (tx.date) {
return Promise.resolve(tx);
return Promise.resolve(tx)
}
if (!tx.ledger_index) {
const ledgerVersion = tx.ledger_index || tx.LedgerSequence
if (!ledgerVersion) {
return new Promise(() => {
throw new errors.NotFoundError('ledger_index not found in tx');
});
throw new errors.NotFoundError(
'ledger_index and LedgerSequence not found in tx')
})
}
const request = {
command: 'ledger',
ledger_index: tx.ledger_index
};
ledger_index: ledgerVersion
}
return connection.request(request).then(data => {
if (typeof data.ledger.close_time === 'number') {
return _.assign({date: data.ledger.close_time}, tx);
return _.assign({date: data.ledger.close_time}, tx)
}
throw new errors.UnexpectedError('Ledger missing close_time');
throw new errors.UnexpectedError('Ledger missing close_time')
}).catch(error => {
if (error instanceof errors.UnexpectedError) {
throw error;
throw error
}
throw new errors.NotFoundError('Transaction ledger not found');
});
throw new errors.NotFoundError('Transaction ledger not found')
})
}
function isTransactionInRange(tx: Object, options: TransactionOptions) {
return (!options.minLedgerVersion
|| tx.ledger_index >= options.minLedgerVersion)
&& (!options.maxLedgerVersion
|| tx.ledger_index <= options.maxLedgerVersion);
|| tx.ledger_index <= options.maxLedgerVersion)
}
function convertError(connection: Connection, options: TransactionOptions,
error: Error
): Promise<Error> {
const _error = (error.message === 'txnNotFound') ?
new errors.NotFoundError('Transaction not found') : error;
new errors.NotFoundError('Transaction not found') : error
if (_error instanceof errors.NotFoundError) {
return utils.hasCompleteLedgerRange(connection, options.minLedgerVersion,
options.maxLedgerVersion).then(hasCompleteLedgerRange => {
@@ -58,32 +61,32 @@ function convertError(connection: Connection, options: TransactionOptions,
.then(isPendingLedgerVersion => {
return isPendingLedgerVersion ?
new errors.PendingLedgerVersionError() :
new errors.MissingLedgerHistoryError();
});
new errors.MissingLedgerHistoryError()
})
}
return _error;
});
return _error
})
}
return Promise.resolve(_error);
return Promise.resolve(_error)
}
function formatResponse(options: TransactionOptions, tx: TransactionType
): TransactionType {
if (tx.validated !== true || !isTransactionInRange(tx, options)) {
throw new errors.NotFoundError('Transaction not found');
throw new errors.NotFoundError('Transaction not found')
}
return parseTransaction(tx);
return parseTransaction(tx)
}
function getTransaction(id: string, options: TransactionOptions = {}
): Promise<TransactionType> {
validate.getTransaction({id, options});
validate.getTransaction({id, options})
const request = {
command: 'tx',
transaction: id,
binary: false
};
}
return utils.ensureLedgerVersion.call(this, options).then(_options => {
return this.connection.request(request).then(tx =>
@@ -91,10 +94,10 @@ function getTransaction(id: string, options: TransactionOptions = {}
).then(_.partial(formatResponse, _options))
.catch(error => {
return convertError(this.connection, _options, error).then(_error => {
throw _error;
});
});
});
throw _error
})
})
})
}
module.exports = getTransaction;
module.exports = getTransaction

View File

@@ -1,15 +1,15 @@
/* @flow */
/* eslint-disable max-params */
'use strict';
const _ = require('lodash');
const binary = require('ripple-binary-codec');
const {computeTransactionHash} = require('ripple-hashes');
const utils = require('./utils');
const parseTransaction = require('./parse/transaction');
const getTransaction = require('./transaction');
const {validate} = utils.common;
import type {Connection} from '../common/connection.js';
import type {TransactionType} from './transaction-types';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const binary = require('ripple-binary-codec')
const {computeTransactionHash} = require('ripple-hashes')
const utils = require('./utils')
const parseTransaction = require('./parse/transaction')
const getTransaction = require('./transaction')
const {validate} = utils.common
import type {Connection} from '../common/connection.js'
import type {TransactionType} from './transaction-types'
type TransactionsOptions = {
@@ -29,21 +29,21 @@ type TransactionsOptions = {
type GetTransactionsResponse = Array<TransactionType>
function parseBinaryTransaction(transaction) {
const tx = binary.decode(transaction.tx_blob);
tx.hash = computeTransactionHash(tx);
tx.ledger_index = transaction.ledger_index;
const tx = binary.decode(transaction.tx_blob)
tx.hash = computeTransactionHash(tx)
tx.ledger_index = transaction.ledger_index
return {
tx: tx,
meta: binary.decode(transaction.meta),
validated: transaction.validated
};
}
}
function parseAccountTxTransaction(tx) {
const _tx = tx.tx_blob ? parseBinaryTransaction(tx) : tx;
const _tx = tx.tx_blob ? parseBinaryTransaction(tx) : tx
// rippled uses a different response format for 'account_tx' than 'tx'
return parseTransaction(_.assign({}, _tx.tx,
{meta: _tx.meta, validated: _tx.validated}));
{meta: _tx.meta, validated: _tx.validated}))
}
function counterpartyFilter(filters, tx: TransactionType) {
@@ -53,36 +53,36 @@ function counterpartyFilter(filters, tx: TransactionType) {
tx.specification.destination.address === filters.counterparty) ||
(tx.specification.counterparty === filters.counterparty)
))) {
return true;
return true
}
return false;
return false
}
function transactionFilter(address: string, filters: TransactionsOptions,
tx: TransactionType
) {
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
return false;
return false
}
if (filters.types && !_.includes(filters.types, tx.type)) {
return false;
return false
}
if (filters.initiated === true && tx.address !== address) {
return false;
return false
}
if (filters.initiated === false && tx.address === address) {
return false;
return false
}
if (filters.counterparty && !counterpartyFilter(filters, tx)) {
return false;
return false
}
return true;
return true
}
function orderFilter(options: TransactionsOptions, tx: TransactionType) {
return !options.startTx || (options.earliestFirst ?
utils.compareTransactions(tx, options.startTx) > 0 :
utils.compareTransactions(tx, options.startTx) < 0);
utils.compareTransactions(tx, options.startTx) < 0)
}
function formatPartialResponse(address: string,
@@ -91,11 +91,11 @@ function formatPartialResponse(address: string,
return {
marker: data.marker,
results: data.transactions
.filter((tx) => tx.validated)
.filter(tx => tx.validated)
.map(parseAccountTxTransaction)
.filter(_.partial(transactionFilter, address, options))
.filter(_.partial(orderFilter, options))
};
}
}
function getAccountTx(connection: Connection, address: string,
@@ -112,70 +112,70 @@ function getAccountTx(connection: Connection, address: string,
binary: options.binary,
limit: utils.clamp(limit, 10, 400),
marker: marker
};
}
return connection.request(request).then(response =>
formatPartialResponse(address, options, response));
formatPartialResponse(address, options, response))
}
function checkForLedgerGaps(connection: Connection,
options: TransactionsOptions, transactions: GetTransactionsResponse
) {
let {minLedgerVersion, maxLedgerVersion} = options;
let {minLedgerVersion, maxLedgerVersion} = options
// if we reached the limit on number of transactions, then we can shrink
// the required ledger range to only guarantee that there are no gaps in
// the range of ledgers spanned by those transactions
if (options.limit && transactions.length === options.limit) {
if (options.earliestFirst) {
maxLedgerVersion = _.last(transactions).outcome.ledgerVersion;
maxLedgerVersion = _.last(transactions).outcome.ledgerVersion
} else {
minLedgerVersion = _.last(transactions).outcome.ledgerVersion;
minLedgerVersion = _.last(transactions).outcome.ledgerVersion
}
}
return utils.hasCompleteLedgerRange(connection, minLedgerVersion,
maxLedgerVersion).then(hasCompleteLedgerRange => {
if (!hasCompleteLedgerRange) {
throw new utils.common.errors.MissingLedgerHistoryError();
throw new utils.common.errors.MissingLedgerHistoryError()
}
});
})
}
function formatResponse(connection: Connection, options: TransactionsOptions,
transactions: GetTransactionsResponse
) {
const compare = options.earliestFirst ? utils.compareTransactions :
_.rearg(utils.compareTransactions, 1, 0);
const sortedTransactions = transactions.sort(compare);
_.rearg(utils.compareTransactions, 1, 0)
const sortedTransactions = transactions.sort(compare)
return checkForLedgerGaps(connection, options, sortedTransactions).then(
() => sortedTransactions);
() => sortedTransactions)
}
function getTransactionsInternal(connection: Connection, address: string,
options: TransactionsOptions
): Promise<GetTransactionsResponse> {
const getter = _.partial(getAccountTx, connection, address, options);
const format = _.partial(formatResponse, connection, options);
return utils.getRecursive(getter, options.limit).then(format);
const getter = _.partial(getAccountTx, connection, address, options)
const format = _.partial(formatResponse, connection, options)
return utils.getRecursive(getter, options.limit).then(format)
}
function getTransactions(address: string, options: TransactionsOptions = {}
): Promise<GetTransactionsResponse> {
validate.getTransactions({address, options});
validate.getTransactions({address, options})
const defaults = {maxLedgerVersion: -1};
const defaults = {maxLedgerVersion: -1}
if (options.start) {
return getTransaction.call(this, options.start).then(tx => {
const ledgerVersion = tx.outcome.ledgerVersion;
const ledgerVersion = tx.outcome.ledgerVersion
const bound = options.earliestFirst ?
{minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion};
const newOptions = _.assign({}, defaults, options, {startTx: tx}, bound);
return getTransactionsInternal(this.connection, address, newOptions);
});
{minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion}
const newOptions = _.assign({}, defaults, options, {startTx: tx}, bound)
return getTransactionsInternal(this.connection, address, newOptions)
})
}
const newOptions = _.assign({}, defaults, options);
return getTransactionsInternal(this.connection, address, newOptions);
const newOptions = _.assign({}, defaults, options)
return getTransactionsInternal(this.connection, address, newOptions)
}
module.exports = getTransactions;
module.exports = getTransactions

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
export type TrustLineSpecification = {
currency: string,

View File

@@ -1,17 +1,17 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
const parseAccountTrustline = require('./parse/account-trustline');
import type {Connection} from '../common/connection.js';
import type {TrustlinesOptions, Trustline} from './trustlines-types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const {validate} = utils.common
const parseAccountTrustline = require('./parse/account-trustline')
import type {Connection} from '../common/connection.js'
import type {TrustlinesOptions, Trustline} from './trustlines-types.js'
type GetTrustlinesResponse = Array<Trustline>
function currencyFilter(currency: string, trustline: Trustline) {
return currency === null || trustline.specification.currency === currency;
return currency === null || trustline.specification.currency === currency
}
function formatResponse(options: TrustlinesOptions, data) {
@@ -19,7 +19,7 @@ function formatResponse(options: TrustlinesOptions, data) {
marker: data.marker,
results: data.lines.map(parseAccountTrustline)
.filter(_.partial(currencyFilter, options.currency || null))
};
}
}
function getAccountLines(connection: Connection, address: string,
@@ -33,20 +33,20 @@ function getAccountLines(connection: Connection, address: string,
marker: marker,
limit: utils.clamp(limit, 10, 400),
peer: options.counterparty
};
}
return connection.request(request).then(_.partial(formatResponse, options));
return connection.request(request).then(_.partial(formatResponse, options))
}
function getTrustlines(address: string, options: TrustlinesOptions = {}
): Promise<GetTrustlinesResponse> {
validate.getTrustlines({address, options});
validate.getTrustlines({address, options})
return this.getLedgerVersion().then(ledgerVersion => {
const getter = _.partial(getAccountLines, this.connection, address,
options.ledgerVersion || ledgerVersion, options);
return utils.getRecursive(getter, options.limit);
});
options.ledgerVersion || ledgerVersion, options)
return utils.getRecursive(getter, options.limit)
})
}
module.exports = getTrustlines;
module.exports = getTrustlines

View File

@@ -1,7 +1,7 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
import type {Amount} from '../common/types.js';
import type {Amount} from '../common/types.js'
export type OrdersOptions = {
limit?: number,

View File

@@ -1,12 +1,12 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const common = require('../common');
const dropsToXrp = common.dropsToXrp;
import type {TransactionType} from './transaction-types';
import type {Issue} from '../common/types.js';
import type {Connection} from '../common/connection';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const common = require('../common')
const dropsToXrp = common.dropsToXrp
import type {TransactionType} from './transaction-types'
import type {Issue} from '../common/types.js'
import type {Connection} from '../common/connection'
type RecursiveData = {
marker: string,
@@ -16,8 +16,8 @@ type RecursiveData = {
type Getter = (marker: ?string, limit: number) => Promise<RecursiveData>
function clamp(value: number, min: number, max: number): number {
assert(min <= max, 'Illegal clamp bounds');
return Math.min(Math.max(value, min), max);
assert(min <= max, 'Illegal clamp bounds')
return Math.min(Math.max(value, min), max)
}
function getXRPBalance(connection: Connection, address: string,
@@ -27,51 +27,51 @@ function getXRPBalance(connection: Connection, address: string,
command: 'account_info',
account: address,
ledger_index: ledgerVersion
};
}
return connection.request(request).then(data =>
dropsToXrp(data.account_data.Balance));
dropsToXrp(data.account_data.Balance))
}
// If the marker is omitted from a response, you have reached the end
function getRecursiveRecur(getter: Getter, marker?: string, limit: number
): Promise {
return getter(marker, limit).then(data => {
const remaining = limit - data.results.length;
const remaining = limit - data.results.length
if (remaining > 0 && data.marker !== undefined) {
return getRecursiveRecur(getter, data.marker, remaining).then(results =>
data.results.concat(results)
);
)
}
return data.results.slice(0, limit);
});
return data.results.slice(0, limit)
})
}
function getRecursive(getter: Getter, limit?: number): Promise {
return getRecursiveRecur(getter, undefined, limit || Infinity);
return getRecursiveRecur(getter, undefined, limit || Infinity)
}
function renameCounterpartyToIssuer(amount?: Issue): ?{issuer?: string} {
if (amount === undefined) {
return undefined;
return undefined
}
const issuer = amount.counterparty === undefined ?
(amount.issuer !== undefined ? amount.issuer : undefined) :
amount.counterparty;
const withIssuer = _.assign({}, amount, {issuer: issuer});
return _.omit(withIssuer, 'counterparty');
amount.counterparty
const withIssuer = _.assign({}, amount, {issuer: issuer})
return _.omit(withIssuer, 'counterparty')
}
type RequestBookOffersArgs = {taker_gets: Issue, taker_pays: Issue}
function renameCounterpartyToIssuerInOrder(order: RequestBookOffersArgs) {
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
const changes = {taker_gets: taker_gets, taker_pays: taker_pays};
return _.assign({}, order, _.omit(changes, _.isUndefined));
const taker_gets = renameCounterpartyToIssuer(order.taker_gets)
const taker_pays = renameCounterpartyToIssuer(order.taker_pays)
const changes = {taker_gets: taker_gets, taker_pays: taker_pays}
return _.assign({}, order, _.omit(changes, _.isUndefined))
}
function signum(num) {
return (num === 0) ? 0 : (num > 0 ? 1 : -1);
return (num === 0) ? 0 : (num > 0 ? 1 : -1)
}
/**
@@ -88,27 +88,27 @@ function signum(num) {
function compareTransactions(first: TransactionType, second: TransactionType
): number {
if (!first.outcome || !second.outcome) {
return 0;
return 0
}
if (first.outcome.ledgerVersion === second.outcome.ledgerVersion) {
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger);
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger)
}
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1;
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1
}
function hasCompleteLedgerRange(connection: Connection,
minLedgerVersion?: number, maxLedgerVersion?: number
): Promise<boolean> {
const firstLedgerVersion = 32570; // earlier versions have been lost
const firstLedgerVersion = 32570 // earlier versions have been lost
return connection.hasLedgerVersions(
minLedgerVersion || firstLedgerVersion, maxLedgerVersion);
minLedgerVersion || firstLedgerVersion, maxLedgerVersion)
}
function isPendingLedgerVersion(connection: Connection,
maxLedgerVersion: ?number
): Promise<boolean> {
return connection.getLedgerVersion().then(ledgerVersion =>
ledgerVersion < (maxLedgerVersion || 0));
ledgerVersion < (maxLedgerVersion || 0))
}
function ensureLedgerVersion(options: Object
@@ -116,10 +116,10 @@ function ensureLedgerVersion(options: Object
if (Boolean(options) && options.ledgerVersion !== undefined &&
options.ledgerVersion !== null
) {
return Promise.resolve(options);
return Promise.resolve(options)
}
return this.getLedgerVersion().then(ledgerVersion =>
_.assign({}, options, {ledgerVersion}));
_.assign({}, options, {ledgerVersion}))
}
module.exports = {
@@ -132,4 +132,4 @@ module.exports = {
isPendingLedgerVersion,
clamp: clamp,
common: common
};
}

View File

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

View File

@@ -1,8 +1,8 @@
/* @flow */
'use strict';
const _ = require('lodash');
const common = require('../common');
const hashes = require('ripple-hashes');
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const common = require('../common')
const hashes = require('ripple-hashes')
function convertLedgerHeader(header) {
return {
@@ -19,53 +19,56 @@ function convertLedgerHeader(header) {
total_coins: header.totalDrops,
totalCoins: header.totalDrops,
transaction_hash: header.transactionHash
};
}
}
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader);
return hashes.computeLedgerHash(header);
const header = convertLedgerHeader(ledgerHeader)
return hashes.computeLedgerHash(header)
}
function computeTransactionHash(ledger) {
function computeTransactionHash(ledger, version) {
if (ledger.rawTransactions === undefined) {
return ledger.transactionHash;
return ledger.transactionHash
}
const transactions = JSON.parse(ledger.rawTransactions);
const transactions = JSON.parse(ledger.rawTransactions)
const txs = _.map(transactions, tx => {
const mergeTx = _.assign({}, _.omit(tx, 'tx'), tx.tx || {});
const mergeTx = _.assign({}, _.omit(tx, 'tx'), tx.tx || {})
const renameMeta = _.assign({}, _.omit(mergeTx, 'meta'),
tx.meta ? {metaData: tx.meta} : {});
return renameMeta;
});
const transactionHash = hashes.computeTransactionTreeHash(txs);
tx.meta ? {metaData: tx.meta} : {})
return renameMeta
})
const transactionHash = hashes.computeTransactionTreeHash(txs, version)
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
+ ' does not match computed hash of transactions');
+ ' does not match computed hash of transactions')
}
return transactionHash;
return transactionHash
}
function computeStateHash(ledger) {
function computeStateHash(ledger, version) {
if (ledger.rawState === undefined) {
return ledger.stateHash;
return ledger.stateHash
}
const state = JSON.parse(ledger.rawState);
const stateHash = hashes.computeStateTreeHash(state);
const state = JSON.parse(ledger.rawState)
const stateHash = hashes.computeStateTreeHash(state, version)
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state');
+ ' does not match computed hash of state')
}
return stateHash;
return stateHash
}
const sLCF_SHAMapV2 = 0x02
function computeLedgerHash(ledger: Object): string {
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
const subhashes = {
transactionHash: computeTransactionHash(ledger),
stateHash: computeStateHash(ledger)
};
return hashLedgerHeader(_.assign({}, ledger, subhashes));
transactionHash: computeTransactionHash(ledger, version),
stateHash: computeStateHash(ledger, version)
}
return hashLedgerHeader(_.assign({}, ledger, subhashes))
}
module.exports = computeLedgerHash;
module.exports = computeLedgerHash

View File

@@ -1,31 +1,31 @@
/* @flow */
'use strict';
const common = require('../common');
import type {GetServerInfoResponse} from '../common/serverinfo';
'use strict' // eslint-disable-line strict
const common = require('../common')
import type {GetServerInfoResponse} from '../common/serverinfo'
function isConnected(): boolean {
return this.connection.isConnected();
return this.connection.isConnected()
}
function getLedgerVersion(): Promise<number> {
return this.connection.getLedgerVersion();
return this.connection.getLedgerVersion()
}
function connect(): Promise<void> {
return this.connection.connect();
return this.connection.connect()
}
function disconnect(): Promise<void> {
return this.connection.disconnect();
return this.connection.disconnect()
}
function getServerInfo(): Promise<GetServerInfoResponse> {
return common.serverInfo.getServerInfo(this.connection);
return common.serverInfo.getServerInfo(this.connection)
}
function getFee(): Promise<number> {
const cushion = this._feeCushion || 1.2;
return common.serverInfo.getFee(this.connection, cushion);
const cushion = this._feeCushion || 1.2
return common.serverInfo.getFee(this.connection, cushion)
}
function formatLedgerClose(ledgerClose: Object): Object {
@@ -38,7 +38,7 @@ function formatLedgerClose(ledgerClose: Object): Object {
reserveIncrementXRP: common.dropsToXrp(ledgerClose.reserve_inc),
transactionCount: ledgerClose.txn_count,
validatedLedgerVersions: ledgerClose.validated_ledgers
};
}
}
module.exports = {
@@ -49,4 +49,4 @@ module.exports = {
getFee,
getLedgerVersion,
formatLedgerClose
};
}

View File

@@ -1,39 +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');
'use strict' // eslint-disable-line 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);
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));
.comparedTo(addressToBigNumber(b.Signer.Account))
}
function combine(signedTransactions: Array<string>): Object {
validate.combine({signedTransactions});
validate.combine({signedTransactions})
const txs = _.map(signedTransactions, binary.decode);
const tx = _.omit(txs[0], 'Signers');
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');
'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};
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;
module.exports = combine

View File

@@ -1,17 +1,17 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const offerFlags = utils.common.txFlags.OfferCreate;
const {validate, iso8601ToRippleTime} = utils.common;
import type {Instructions, Prepare} from './types.js';
import type {Order} from '../ledger/transaction-types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const offerFlags = utils.common.txFlags.OfferCreate
const {validate, iso8601ToRippleTime} = utils.common
import type {Instructions, Prepare} from './types.js'
import type {Order} from '../ledger/transaction-types.js'
function createOrderTransaction(account: string, order: Order): Object {
const takerPays = utils.common.toRippledAmount(order.direction === 'buy' ?
order.quantity : order.totalPrice);
order.quantity : order.totalPrice)
const takerGets = utils.common.toRippledAmount(order.direction === 'buy' ?
order.totalPrice : order.quantity);
order.totalPrice : order.quantity)
const txJSON: Object = {
TransactionType: 'OfferCreate',
@@ -19,34 +19,37 @@ function createOrderTransaction(account: string, order: Order): Object {
TakerGets: takerGets,
TakerPays: takerPays,
Flags: 0
};
}
if (order.direction === 'sell') {
txJSON.Flags |= offerFlags.Sell;
txJSON.Flags |= offerFlags.Sell
}
if (order.passive === true) {
txJSON.Flags |= offerFlags.Passive;
txJSON.Flags |= offerFlags.Passive
}
if (order.immediateOrCancel === true) {
txJSON.Flags |= offerFlags.ImmediateOrCancel;
txJSON.Flags |= offerFlags.ImmediateOrCancel
}
if (order.fillOrKill === true) {
txJSON.Flags |= offerFlags.FillOrKill;
txJSON.Flags |= offerFlags.FillOrKill
}
if (order.expirationTime !== undefined) {
txJSON.Expiration = iso8601ToRippleTime(order.expirationTime);
txJSON.Expiration = iso8601ToRippleTime(order.expirationTime)
}
if (order.orderToReplace !== undefined) {
txJSON.OfferSequence = order.orderToReplace
}
if (order.memos !== undefined) {
txJSON.Memos = _.map(order.memos, utils.convertMemo);
txJSON.Memos = _.map(order.memos, utils.convertMemo)
}
return txJSON;
return txJSON
}
function prepareOrder(address: string, order: Order,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareOrder({address, order, instructions});
const txJSON = createOrderTransaction(address, order);
return utils.prepareTransaction(txJSON, this, instructions);
validate.prepareOrder({address, order, instructions})
const txJSON = createOrderTransaction(address, order)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareOrder;
module.exports = prepareOrder

View File

@@ -1,9 +1,9 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
import type {Instructions, Prepare} from './types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const validate = utils.common.validate
import type {Instructions, Prepare} from './types.js'
function createOrderCancellationTransaction(account: string,
orderCancellation: Object
@@ -12,19 +12,19 @@ function createOrderCancellationTransaction(account: string,
TransactionType: 'OfferCancel',
Account: account,
OfferSequence: orderCancellation.orderSequence
};
if (orderCancellation.memos !== undefined) {
txJSON.Memos = _.map(orderCancellation.memos, utils.convertMemo);
}
return txJSON;
if (orderCancellation.memos !== undefined) {
txJSON.Memos = _.map(orderCancellation.memos, utils.convertMemo)
}
return txJSON
}
function prepareOrderCancellation(address: string, orderCancellation: Object,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareOrderCancellation({address, orderCancellation, instructions});
const txJSON = createOrderCancellationTransaction(address, orderCancellation);
return utils.prepareTransaction(txJSON, this, instructions);
validate.prepareOrderCancellation({address, orderCancellation, instructions})
const txJSON = createOrderCancellationTransaction(address, orderCancellation)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareOrderCancellation;
module.exports = prepareOrderCancellation

View File

@@ -1,14 +1,14 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const toRippledAmount = utils.common.toRippledAmount;
const paymentFlags = utils.common.txFlags.Payment;
const ValidationError = utils.common.errors.ValidationError;
import type {Instructions, Prepare} from './types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const validate = utils.common.validate
const toRippledAmount = utils.common.toRippledAmount
const paymentFlags = utils.common.txFlags.Payment
const ValidationError = utils.common.errors.ValidationError
import type {Instructions, Prepare} from './types.js'
import type {Amount, Adjustment, MaxAdjustment,
MinAdjustment, Memo} from '../common/types.js';
MinAdjustment, Memo} from '../common/types.js'
type Payment = {
@@ -34,15 +34,15 @@ type Payment = {
function isXRPToXRPPayment(payment: Payment): boolean {
const sourceCurrency = _.get(payment, 'source.maxAmount.currency',
_.get(payment, 'source.amount.currency'));
_.get(payment, 'source.amount.currency'))
const destinationCurrency = _.get(payment, 'destination.amount.currency',
_.get(payment, 'destination.minAmount.currency'));
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP';
_.get(payment, 'destination.minAmount.currency'))
return sourceCurrency === 'XRP' && destinationCurrency === 'XRP'
}
function isIOUWithoutCounterparty(amount: Amount): boolean {
return amount && amount.currency !== 'XRP'
&& amount.counterparty === undefined;
&& amount.counterparty === undefined
}
function applyAnyCounterpartyEncoding(payment: Payment): void {
@@ -51,35 +51,35 @@ function applyAnyCounterpartyEncoding(payment: Payment): void {
// https://ripple.com/build/transactions/
// #special-issuer-values-for-sendmax-and-amount
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
_.forEach([payment.source, payment.destination], (adjustment) => {
_.forEach(['amount', 'minAmount', 'maxAmount'], (key) => {
_.forEach([payment.source, payment.destination], adjustment => {
_.forEach(['amount', 'minAmount', 'maxAmount'], key => {
if (isIOUWithoutCounterparty(adjustment[key])) {
adjustment[key].counterparty = adjustment.address;
adjustment[key].counterparty = adjustment.address
}
});
});
})
})
}
function createMaximalAmount(amount: Amount): Amount {
const maxXRPValue = '100000000000';
const maxIOUValue = '9999999999999999e80';
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue;
return _.assign({}, amount, {value: maxValue});
const maxXRPValue = '100000000000'
const maxIOUValue = '9999999999999999e80'
const maxValue = amount.currency === 'XRP' ? maxXRPValue : maxIOUValue
return _.assign({}, amount, {value: maxValue})
}
function createPaymentTransaction(address: string, paymentArgument: Payment
): Object {
const payment = _.cloneDeep(paymentArgument);
applyAnyCounterpartyEncoding(payment);
const payment = _.cloneDeep(paymentArgument)
applyAnyCounterpartyEncoding(payment)
if (address !== payment.source.address) {
throw new ValidationError('address must match payment.source.address');
throw new ValidationError('address must match payment.source.address')
}
if ((payment.source.maxAmount && payment.destination.minAmount) ||
(payment.source.amount && payment.destination.amount)) {
throw new ValidationError('payment must specify either (source.maxAmount '
+ 'and destination.amount) or (source.amount and destination.minAmount)');
+ 'and destination.amount) or (source.amount and destination.minAmount)')
}
// when using destination.minAmount, rippled still requires that we set
@@ -90,7 +90,7 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
// cap could be hit before the source cap.
const amount = payment.destination.minAmount && !isXRPToXRPPayment(payment) ?
createMaximalAmount(payment.destination.minAmount) :
(payment.destination.amount || payment.destination.minAmount);
(payment.destination.amount || payment.destination.minAmount)
const txJSON: Object = {
TransactionType: 'Payment',
@@ -98,25 +98,25 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
Destination: payment.destination.address,
Amount: toRippledAmount(amount),
Flags: 0
};
}
if (payment.invoiceID !== undefined) {
txJSON.InvoiceID = payment.invoiceID;
txJSON.InvoiceID = payment.invoiceID
}
if (payment.source.tag !== undefined) {
txJSON.SourceTag = payment.source.tag;
txJSON.SourceTag = payment.source.tag
}
if (payment.destination.tag !== undefined) {
txJSON.DestinationTag = payment.destination.tag;
txJSON.DestinationTag = payment.destination.tag
}
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo);
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
}
if (payment.noDirectRipple === true) {
txJSON.Flags |= paymentFlags.NoRippleDirect;
txJSON.Flags |= paymentFlags.NoRippleDirect
}
if (payment.limitQuality === true) {
txJSON.Flags |= paymentFlags.LimitQuality;
txJSON.Flags |= paymentFlags.LimitQuality
}
if (!isXRPToXRPPayment(payment)) {
// Don't set SendMax for XRP->XRP payment
@@ -125,32 +125,32 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
if (payment.allowPartialPayment === true
|| payment.destination.minAmount !== undefined) {
txJSON.Flags |= paymentFlags.PartialPayment;
txJSON.Flags |= paymentFlags.PartialPayment
}
txJSON.SendMax = toRippledAmount(
payment.source.maxAmount || payment.source.amount);
payment.source.maxAmount || payment.source.amount)
if (payment.destination.minAmount !== undefined) {
txJSON.DeliverMin = toRippledAmount(payment.destination.minAmount);
txJSON.DeliverMin = toRippledAmount(payment.destination.minAmount)
}
if (payment.paths !== undefined) {
txJSON.Paths = JSON.parse(payment.paths);
txJSON.Paths = JSON.parse(payment.paths)
}
} else if (payment.allowPartialPayment === true) {
throw new ValidationError('XRP to XRP payments cannot be partial payments');
throw new ValidationError('XRP to XRP payments cannot be partial payments')
}
return txJSON;
return txJSON
}
function preparePayment(address: string, payment: Payment,
instructions: Instructions = {}
): Promise<Prepare> {
validate.preparePayment({address, payment, instructions});
const txJSON = createPaymentTransaction(address, payment);
return utils.prepareTransaction(txJSON, this, instructions);
validate.preparePayment({address, payment, instructions})
const txJSON = createPaymentTransaction(address, payment)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = preparePayment;
module.exports = preparePayment

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
type SettingPasswordSpent = {
passwordSpent?: boolean,

View File

@@ -1,55 +1,55 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const BigNumber = require('bignumber.js');
const utils = require('./utils');
const validate = utils.common.validate;
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
const AccountFields = utils.common.constants.AccountFields;
import type {Instructions, Prepare} from './types.js';
import type {Settings} from './settings-types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const assert = require('assert')
const BigNumber = require('bignumber.js')
const utils = require('./utils')
const validate = utils.common.validate
const AccountFlagIndices = utils.common.constants.AccountFlagIndices
const AccountFields = utils.common.constants.AccountFields
import type {Instructions, Prepare} from './types.js'
import type {Settings} from './settings-types.js'
// Emptry string passed to setting will clear it
const CLEAR_SETTING = null;
const CLEAR_SETTING = null
function setTransactionFlags(txJSON: Object, values: Settings) {
const keys = Object.keys(values);
assert(keys.length === 1, 'ERROR: can only set one setting per transaction');
const flagName = keys[0];
const value = values[flagName];
const index = AccountFlagIndices[flagName];
const keys = Object.keys(values)
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
const flagName = keys[0]
const value = values[flagName]
const index = AccountFlagIndices[flagName]
if (index !== undefined) {
if (value) {
txJSON.SetFlag = index;
txJSON.SetFlag = index
} else {
txJSON.ClearFlag = index;
txJSON.ClearFlag = index
}
}
}
function setTransactionFields(txJSON: Object, input: Settings) {
const fieldSchema = AccountFields;
const fieldSchema = AccountFields
for (const fieldName in fieldSchema) {
const field = fieldSchema[fieldName];
let value = input[field.name];
const field = fieldSchema[fieldName]
let value = input[field.name]
if (value === undefined) {
continue;
continue
}
// The value required to clear an account root field varies
if (value === CLEAR_SETTING && field.hasOwnProperty('defaults')) {
value = field.defaults;
value = field.defaults
}
if (field.encoding === 'hex' && !field.length) {
// This is currently only used for Domain field
value = new Buffer(value, 'ascii').toString('hex').toUpperCase();
value = new Buffer(value, 'ascii').toString('hex').toUpperCase()
}
txJSON[fieldName] = value;
txJSON[fieldName] = value
}
}
@@ -67,7 +67,7 @@ function setTransactionFields(txJSON: Object, input: Settings) {
*/
function convertTransferRate(transferRate: number | string): number | string {
return (new BigNumber(transferRate)).shift(9).toNumber();
return (new BigNumber(transferRate)).shift(9).toNumber()
}
function formatSignerEntry(signer: Object): Object {
@@ -76,7 +76,7 @@ function formatSignerEntry(signer: Object): Object {
Account: signer.address,
SignerWeight: signer.weight
}
};
}
}
function createSettingsTransactionWithoutMemos(
@@ -86,11 +86,11 @@ function createSettingsTransactionWithoutMemos(
const removeRegularKey = {
TransactionType: 'SetRegularKey',
Account: account
};
if (settings.regularKey === null) {
return removeRegularKey;
}
return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey});
if (settings.regularKey === null) {
return removeRegularKey
}
return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey})
}
if (settings.signers !== undefined) {
@@ -99,38 +99,38 @@ function createSettingsTransactionWithoutMemos(
Account: account,
SignerQuorum: settings.signers.threshold,
SignerEntries: _.map(settings.signers.weights, formatSignerEntry)
};
}
}
const txJSON: Object = {
TransactionType: 'AccountSet',
Account: account
};
}
setTransactionFlags(txJSON, _.omit(settings, 'memos'));
setTransactionFields(txJSON, settings);
setTransactionFlags(txJSON, _.omit(settings, 'memos'))
setTransactionFields(txJSON, settings)
if (txJSON.TransferRate !== undefined) {
txJSON.TransferRate = convertTransferRate(txJSON.TransferRate);
txJSON.TransferRate = convertTransferRate(txJSON.TransferRate)
}
return txJSON;
return txJSON
}
function createSettingsTransaction(account: string, settings: Settings
): Object {
const txJSON = createSettingsTransactionWithoutMemos(account, settings);
const txJSON = createSettingsTransactionWithoutMemos(account, settings)
if (settings.memos !== undefined) {
txJSON.Memos = _.map(settings.memos, utils.convertMemo);
txJSON.Memos = _.map(settings.memos, utils.convertMemo)
}
return txJSON;
return txJSON
}
function prepareSettings(address: string, settings: Settings,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareSettings({address, settings, instructions});
const txJSON = createSettingsTransaction(address, settings);
return utils.prepareTransaction(txJSON, this, instructions);
validate.prepareSettings({address, settings, instructions})
const txJSON = createSettingsTransaction(address, settings)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareSettings;
module.exports = prepareSettings

View File

@@ -1,48 +1,48 @@
/* @flow */
'use strict';
const utils = require('./utils');
const keypairs = require('ripple-keypairs');
const binary = require('ripple-binary-codec');
const {computeBinaryTransactionHash} = require('ripple-hashes');
const validate = utils.common.validate;
'use strict' // eslint-disable-line strict
const utils = require('./utils')
const keypairs = require('ripple-keypairs')
const binary = require('ripple-binary-codec')
const {computeBinaryTransactionHash} = require('ripple-hashes')
const validate = utils.common.validate
function computeSignature(tx: Object, privateKey: string, signAs: ?string) {
const signingData = signAs ?
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx);
return keypairs.sign(signingData, privateKey);
binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx)
return keypairs.sign(signingData, privateKey)
}
function sign(txJSON: string, secret: string, options: Object = {}
): {signedTransaction: string; id: string} {
validate.sign({txJSON, secret});
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 tx = JSON.parse(txJSON)
if (tx.TxnSignature || tx.Signers) {
throw new utils.common.errors.ValidationError(
'txJSON must not contain "TxnSignature" or "Signers" properties');
'txJSON must not contain "TxnSignature" or "Signers" properties')
}
const keypair = keypairs.deriveKeypair(secret);
tx.SigningPubKey = options.signAs ? '' : keypair.publicKey;
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}];
}
tx.Signers = [{Signer: signer}]
} else {
tx.TxnSignature = computeSignature(tx, keypair.privateKey);
tx.TxnSignature = computeSignature(tx, keypair.privateKey)
}
const serialized = binary.encode(tx);
const serialized = binary.encode(tx)
return {
signedTransaction: serialized,
id: computeBinaryTransactionHash(serialized)
};
}
}
module.exports = sign;
module.exports = sign

View File

@@ -1,9 +1,9 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const {validate} = utils.common;
import type {Submit} from './types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const {validate} = utils.common
import type {Submit} from './types.js'
function isImmediateRejection(engineResult: string): boolean {
// note: "tel" errors mean the local server refused to process the
@@ -12,28 +12,28 @@ function isImmediateRejection(engineResult: string): boolean {
// if the required fee changes (this does not occur at the time of
// this writing, but it could change in the future)
// all other error classes can potentially result in transcation validation
return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej');
return _.startsWith(engineResult, 'tem')
}
function formatSubmitResponse(response) {
const data = {
resultCode: response.engine_result,
resultMessage: response.engine_result_message
};
if (isImmediateRejection(response.engine_result)) {
throw new utils.common.errors.RippledError('Submit failed', data);
}
return data;
if (isImmediateRejection(response.engine_result)) {
throw new utils.common.errors.RippledError('Submit failed', data)
}
return data
}
function submit(signedTransaction: string): Promise<Submit> {
validate.submit({signedTransaction});
validate.submit({signedTransaction})
const request = {
command: 'submit',
tx_blob: signedTransaction
};
return this.connection.request(request).then(formatSubmitResponse);
}
return this.connection.request(request).then(formatSubmitResponse)
}
module.exports = submit;
module.exports = submit

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
import type {Instructions, Prepare} from './types.js';
import type {Memo} from '../common/types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const validate = utils.common.validate
import type {Instructions, Prepare} from './types.js'
import type {Memo} from '../common/types.js'
type SuspendedPaymentCancellation = {
owner: string,
@@ -20,11 +20,11 @@ function createSuspendedPaymentCancellationTransaction(account: string,
Account: account,
Owner: payment.owner,
OfferSequence: payment.suspensionSequence
};
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo);
}
return txJSON;
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
}
return txJSON
}
function prepareSuspendedPaymentCancellation(address: string,
@@ -32,10 +32,10 @@ function prepareSuspendedPaymentCancellation(address: string,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareSuspendedPaymentCancellation(
{address, suspendedPaymentCancellation, instructions});
{address, suspendedPaymentCancellation, instructions})
const txJSON = createSuspendedPaymentCancellationTransaction(
address, suspendedPaymentCancellation);
return utils.prepareTransaction(txJSON, this, instructions);
address, suspendedPaymentCancellation)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareSuspendedPaymentCancellation;
module.exports = prepareSuspendedPaymentCancellation

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const {validate, iso8601ToRippleTime, toRippledAmount} = utils.common;
import type {Instructions, Prepare} from './types.js';
import type {Adjustment, MaxAdjustment, Memo} from '../common/types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const {validate, iso8601ToRippleTime, toRippledAmount} = utils.common
import type {Instructions, Prepare} from './types.js'
import type {Adjustment, MaxAdjustment, Memo} from '../common/types.js'
type SuspendedPaymentCreation = {
source: MaxAdjustment,
@@ -23,27 +23,27 @@ function createSuspendedPaymentCreationTransaction(account: string,
Account: account,
Destination: payment.destination.address,
Amount: toRippledAmount(payment.destination.amount)
};
}
if (payment.digest !== undefined) {
txJSON.Digest = payment.digest;
txJSON.Digest = payment.digest
}
if (payment.allowCancelAfter !== undefined) {
txJSON.CancelAfter = iso8601ToRippleTime(payment.allowCancelAfter);
txJSON.CancelAfter = iso8601ToRippleTime(payment.allowCancelAfter)
}
if (payment.allowExecuteAfter !== undefined) {
txJSON.FinishAfter = iso8601ToRippleTime(payment.allowExecuteAfter);
txJSON.FinishAfter = iso8601ToRippleTime(payment.allowExecuteAfter)
}
if (payment.source.tag !== undefined) {
txJSON.SourceTag = payment.source.tag;
txJSON.SourceTag = payment.source.tag
}
if (payment.destination.tag !== undefined) {
txJSON.DestinationTag = payment.destination.tag;
txJSON.DestinationTag = payment.destination.tag
}
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo);
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
}
return txJSON;
return txJSON
}
function prepareSuspendedPaymentCreation(address: string,
@@ -51,10 +51,10 @@ function prepareSuspendedPaymentCreation(address: string,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareSuspendedPaymentCreation(
{address, suspendedPaymentCreation, instructions});
{address, suspendedPaymentCreation, instructions})
const txJSON = createSuspendedPaymentCreationTransaction(
address, suspendedPaymentCreation);
return utils.prepareTransaction(txJSON, this, instructions);
address, suspendedPaymentCreation)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareSuspendedPaymentCreation;
module.exports = prepareSuspendedPaymentCreation

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
import type {Instructions, Prepare} from './types.js';
import type {Memo} from '../common/types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const validate = utils.common.validate
import type {Instructions, Prepare} from './types.js'
import type {Memo} from '../common/types.js'
type SuspendedPaymentExecution = {
owner: string,
@@ -23,21 +23,21 @@ function createSuspendedPaymentExecutionTransaction(account: string,
Account: account,
Owner: payment.owner,
OfferSequence: payment.suspensionSequence
};
}
if (payment.method !== undefined) {
txJSON.Method = payment.method;
txJSON.Method = payment.method
}
if (payment.digest !== undefined) {
txJSON.Digest = payment.digest;
txJSON.Digest = payment.digest
}
if (payment.proof !== undefined) {
txJSON.Proof = utils.convertStringToHex(payment.proof);
txJSON.Proof = utils.convertStringToHex(payment.proof)
}
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo);
txJSON.Memos = _.map(payment.memos, utils.convertMemo)
}
return txJSON;
return txJSON
}
function prepareSuspendedPaymentExecution(address: string,
@@ -45,10 +45,10 @@ function prepareSuspendedPaymentExecution(address: string,
instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareSuspendedPaymentExecution(
{address, suspendedPaymentExecution, instructions});
{address, suspendedPaymentExecution, instructions})
const txJSON = createSuspendedPaymentExecutionTransaction(
address, suspendedPaymentExecution);
return utils.prepareTransaction(txJSON, this, instructions);
address, suspendedPaymentExecution)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareSuspendedPaymentExecution;
module.exports = prepareSuspendedPaymentExecution

View File

@@ -1,15 +1,15 @@
/* @flow */
'use strict';
const _ = require('lodash');
const utils = require('./utils');
const validate = utils.common.validate;
const trustlineFlags = utils.common.txFlags.TrustSet;
const BigNumber = require('bignumber.js');
import type {Instructions, Prepare} from './types.js';
import type {TrustLineSpecification} from '../ledger/trustlines-types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const utils = require('./utils')
const validate = utils.common.validate
const trustlineFlags = utils.common.txFlags.TrustSet
const BigNumber = require('bignumber.js')
import type {Instructions, Prepare} from './types.js'
import type {TrustLineSpecification} from '../ledger/trustlines-types.js'
function convertQuality(quality) {
return (new BigNumber(quality)).shift(9).truncated().toNumber();
return (new BigNumber(quality)).shift(9).truncated().toNumber()
}
function createTrustlineTransaction(account: string,
@@ -19,43 +19,43 @@ function createTrustlineTransaction(account: string,
currency: trustline.currency,
issuer: trustline.counterparty,
value: trustline.limit
};
}
const txJSON: Object = {
TransactionType: 'TrustSet',
Account: account,
LimitAmount: limit,
Flags: 0
};
}
if (trustline.qualityIn !== undefined) {
txJSON.QualityIn = convertQuality(trustline.qualityIn);
txJSON.QualityIn = convertQuality(trustline.qualityIn)
}
if (trustline.qualityOut !== undefined) {
txJSON.QualityOut = convertQuality(trustline.qualityOut);
txJSON.QualityOut = convertQuality(trustline.qualityOut)
}
if (trustline.authorized === true) {
txJSON.Flags |= trustlineFlags.SetAuth;
txJSON.Flags |= trustlineFlags.SetAuth
}
if (trustline.ripplingDisabled !== undefined) {
txJSON.Flags |= trustline.ripplingDisabled ?
trustlineFlags.NoRipple : trustlineFlags.ClearNoRipple;
trustlineFlags.NoRipple : trustlineFlags.ClearNoRipple
}
if (trustline.frozen !== undefined) {
txJSON.Flags |= trustline.frozen ?
trustlineFlags.SetFreeze : trustlineFlags.ClearFreeze;
trustlineFlags.SetFreeze : trustlineFlags.ClearFreeze
}
if (trustline.memos !== undefined) {
txJSON.Memos = _.map(trustline.memos, utils.convertMemo);
txJSON.Memos = _.map(trustline.memos, utils.convertMemo)
}
return txJSON;
return txJSON
}
function prepareTrustline(address: string,
trustline: TrustLineSpecification, instructions: Instructions = {}
): Promise<Prepare> {
validate.prepareTrustline({address, trustline, instructions});
const txJSON = createTrustlineTransaction(address, trustline);
return utils.prepareTransaction(txJSON, this, instructions);
validate.prepareTrustline({address, trustline, instructions})
const txJSON = createTrustlineTransaction(address, trustline)
return utils.prepareTransaction(txJSON, this, instructions)
}
module.exports = prepareTrustline;
module.exports = prepareTrustline

View File

@@ -1,5 +1,5 @@
/* @flow */
'use strict';
'use strict' // eslint-disable-line strict
export type Instructions = {
sequence?: number,

View File

@@ -1,10 +1,10 @@
/* @flow */
'use strict';
const _ = require('lodash');
const BigNumber = require('bignumber.js');
const common = require('../common');
const txFlags = common.txFlags;
import type {Instructions, Prepare} from './types.js';
'use strict' // eslint-disable-line strict
const _ = require('lodash')
const BigNumber = require('bignumber.js')
const common = require('../common')
const txFlags = common.txFlags
import type {Instructions, Prepare} from './types.js'
function formatPrepareResponse(txJSON: Object): Object {
const instructions = {
@@ -12,94 +12,94 @@ function formatPrepareResponse(txJSON: Object): Object {
sequence: txJSON.Sequence,
maxLedgerVersion: txJSON.LastLedgerSequence === undefined ?
null : txJSON.LastLedgerSequence
};
}
return {
txJSON: JSON.stringify(txJSON),
instructions: _.omit(instructions, _.isUndefined)
};
}
}
function setCanonicalFlag(txJSON) {
txJSON.Flags |= txFlags.Universal.FullyCanonicalSig;
txJSON.Flags |= txFlags.Universal.FullyCanonicalSig
// JavaScript converts operands to 32-bit signed ints before doing bitwise
// operations. We need to convert it back to an unsigned int.
txJSON.Flags = txJSON.Flags >>> 0;
txJSON.Flags = txJSON.Flags >>> 0
}
function scaleValue(value, multiplier) {
return (new BigNumber(value)).times(multiplier).toString();
return (new BigNumber(value)).times(multiplier).toString()
}
function prepareTransaction(txJSON: Object, api: Object,
instructions: Instructions
): Promise<Prepare> {
common.validate.instructions(instructions);
common.validate.instructions(instructions)
const account = txJSON.Account;
setCanonicalFlag(txJSON);
const account = txJSON.Account
setCanonicalFlag(txJSON)
function prepareMaxLedgerVersion(): Promise<Object> {
if (instructions.maxLedgerVersion !== undefined) {
if (instructions.maxLedgerVersion !== null) {
txJSON.LastLedgerSequence = instructions.maxLedgerVersion;
txJSON.LastLedgerSequence = instructions.maxLedgerVersion
}
return Promise.resolve(txJSON);
return Promise.resolve(txJSON)
}
const offset = instructions.maxLedgerVersionOffset !== undefined ?
instructions.maxLedgerVersionOffset : 3;
instructions.maxLedgerVersionOffset : 3
return api.connection.getLedgerVersion().then(ledgerVersion => {
txJSON.LastLedgerSequence = ledgerVersion + offset;
return txJSON;
});
txJSON.LastLedgerSequence = ledgerVersion + offset
return txJSON
})
}
function prepareFee(): Promise<Object> {
const multiplier = instructions.signersCount === undefined ? 1 :
instructions.signersCount + 1;
instructions.signersCount + 1
if (instructions.fee !== undefined) {
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier);
return Promise.resolve(txJSON);
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
return Promise.resolve(txJSON)
}
const cushion = api._feeCushion;
const cushion = api._feeCushion
return common.serverInfo.getFee(api.connection, cushion).then(fee => {
const feeDrops = common.xrpToDrops(fee);
const feeDrops = common.xrpToDrops(fee)
if (instructions.maxFee !== undefined) {
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
const normalFee = BigNumber.min(feeDrops, maxFeeDrops).toString();
txJSON.Fee = scaleValue(normalFee, multiplier);
const maxFeeDrops = common.xrpToDrops(instructions.maxFee)
const normalFee = BigNumber.min(feeDrops, maxFeeDrops).toString()
txJSON.Fee = scaleValue(normalFee, multiplier)
} else {
txJSON.Fee = scaleValue(feeDrops, multiplier);
txJSON.Fee = scaleValue(feeDrops, multiplier)
}
return txJSON;
});
return txJSON
})
}
function prepareSequence(): Promise<Object> {
if (instructions.sequence !== undefined) {
txJSON.Sequence = instructions.sequence;
return Promise.resolve(txJSON);
txJSON.Sequence = instructions.sequence
return Promise.resolve(txJSON)
}
const request = {
command: 'account_info',
account: account
};
}
return api.connection.request(request).then(response => {
txJSON.Sequence = response.account_data.Sequence;
return txJSON;
});
txJSON.Sequence = response.account_data.Sequence
return txJSON
})
}
return Promise.all([
prepareMaxLedgerVersion(),
prepareFee(),
prepareSequence()
]).then(() => formatPrepareResponse(txJSON));
]).then(() => formatPrepareResponse(txJSON))
}
function convertStringToHex(string: string) {
return string ? (new Buffer(string, 'utf8')).toString('hex').toUpperCase() :
undefined;
undefined
}
function convertMemo(memo: Object): Object {
@@ -109,7 +109,7 @@ function convertMemo(memo: Object): Object {
MemoType: convertStringToHex(memo.type),
MemoFormat: convertStringToHex(memo.format)
})
};
}
}
module.exports = {
@@ -117,4 +117,4 @@ module.exports = {
convertMemo,
prepareTransaction,
common
};
}

View File

@@ -1,5 +1,5 @@
/* eslint-disable max-nested-callbacks */
'use strict';
'use strict'; // eslint-disable-line
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
@@ -14,9 +14,11 @@ 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;
const TIMEOUT = 10000; // how long before each test case times out
// how long before each test case times out
const TIMEOUT = process.browser ? 25000 : 10000;
function unused() {
}
@@ -97,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'
@@ -325,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);
@@ -359,6 +370,16 @@ describe('RippleAPI', function() {
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() {
@@ -687,6 +708,21 @@ describe('RippleAPI', function() {
});
});
it('getTransaction - amendment', function() {
const hash =
'A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F';
return this.api.getTransaction(hash).then(result => {
assert.deepEqual(result, responses.getTransaction.amendment);
});
});
it('getTransaction - feeUpdate', function() {
const hash =
'C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF';
return this.api.getTransaction(hash).then(result => {
assert.deepEqual(result, responses.getTransaction.feeUpdate);
});
});
});
it('getTransactions', function() {
@@ -960,6 +996,19 @@ describe('RippleAPI', function() {
});
});
it('getServerInfo - no validated ledger', function() {
this.api.connection._send(JSON.stringify({
command: 'config',
data: {serverInfoWithoutValidated: true}
}));
return this.api.getServerInfo().then(info => {
assert.strictEqual(info.networkLedger, 'waiting');
}).catch(error => {
assert(false, 'Should not throw Error, got ' + String(error));
});
});
it('getFee', function() {
return this.api.getFee().then(fee => {
assert.strictEqual(fee, '0.000012');
@@ -1081,7 +1130,7 @@ describe('RippleAPI', function() {
});
it('getLedgerVersion', function(done) {
this.api.getLedgerVersion().then((ver) => {
this.api.getLedgerVersion().then(ver => {
assert.strictEqual(ver, 8819951);
done();
}, done);
@@ -1122,6 +1171,28 @@ describe('RippleAPI', function() {
_.partial(checkResult, responses.getLedger.withSettingsTx, 'getLedger'));
});
it('getLedger - with partial payment', function() {
const request = {
includeTransactions: true,
includeAllData: true,
ledgerVersion: 100000
};
return this.api.getLedger(request).then(
_.partial(checkResult, responses.getLedger.withPartial, 'getLedger'));
});
it('getLedger - pre 2014 with partial payment', function() {
const request = {
includeTransactions: true,
includeAllData: true,
ledgerVersion: 100001
};
return this.api.getLedger(request).then(
_.partial(checkResult,
responses.getLedger.pre2014withPartial,
'getLedger'));
});
it('getLedger - full, then computeLedgerHash', function() {
const request = {
includeTransactions: true,

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,6 +23,7 @@ function checkResult(expected, schemaName, response) {
}
describe('RippleAPIBroadcast', function() {
this.timeout(TIMEOUT);
beforeEach(setupAPI.setupBroadcast);
afterEach(setupAPI.teardown);
@@ -50,7 +53,7 @@ describe('RippleAPIBroadcast', function() {
setTimeout(() => {
assert.strictEqual(gotLedger, 1);
done();
}, 250);
}, 1250);
});
it('error propagation', function(done) {

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');
@@ -10,7 +10,7 @@ const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClose = require('./fixtures/rippled/ledger-close.json');
const TIMEOUT = 10000; // how long before each test case times out
const TIMEOUT = 200000; // how long before each test case times out
function unused() {
}
@@ -64,11 +64,11 @@ describe('Connection', function() {
done();
return;
}
createServer().then((server) => {
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();
@@ -106,6 +106,28 @@ 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(JSON.stringify({
command: 'config',
@@ -172,6 +194,114 @@ describe('Connection', function() {
}, 1);
});
describe('reconnection test', function() {
beforeEach(function() {
this.api.connection.__workingUrl = this.api.connection._url;
this.api.connection.__doReturnBad = function() {
this._url = this.__badUrl;
const self = this;
function onReconnect(num) {
if (num >= 2) {
self._url = self.__workingUrl;
self.removeListener('reconnecting', onReconnect);
}
}
this.on('reconnecting', onReconnect);
};
});
afterEach(function() {
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
const phantomTest = /PhantomJS/;
if (phantomTest.test(navigator.userAgent)) {
// inside PhantomJS this one just hangs, so skip as not very relevant
done();
return;
}
}
this.timeout(70001);
const self = this;
self.api.connection.__badUrl = 'ws://testripple.circleci.com:129';
function breakConnection() {
self.api.connection.__doReturnBad();
self.api.connection._send(JSON.stringify({
command: 'test_command',
data: {disconnectIn: 10}
}));
}
let connectsCount = 0;
let disconnectsCount = 0;
let reconnectsCount = 0;
let code = 0;
this.api.connection.on('reconnecting', () => {
reconnectsCount += 1;
});
this.api.connection.on('disconnected', _code => {
code = _code;
disconnectsCount += 1;
});
const num = 3;
this.api.connection.on('connected', () => {
connectsCount += 1;
if (connectsCount < num) {
breakConnection();
}
if (connectsCount === num) {
if (disconnectsCount !== num) {
done(new Error('disconnectsCount must be equal to ' + num +
'(got ' + disconnectsCount + ' instead)'));
} else if (reconnectsCount !== num * 2) {
done(new Error('reconnectsCount must be equal to ' + num * 2 +
' (got ' + reconnectsCount + ' 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
) {
this.api.once('error', error => {
done(new Error('should not throw error, got ' + String(error)));
});
this.api.once('disconnected', code => {
assert.strictEqual(code, 1006);
done();
});
this.api.connection._send(JSON.stringify({
command: 'test_command',
data: {disconnectIn: 10}
}));
});
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();
@@ -179,7 +309,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);
});
});
@@ -282,4 +412,50 @@ describe('Connection', function() {
});
this.api.connection._ws.emit('message', JSON.stringify(message));
});
it('should throw RippledNotInitializedError if server does not have ' +
'validated ledgers',
function() {
this.timeout(3000);
this.api.connection._send(JSON.stringify({
command: 'global_config',
data: {returnEmptySubscribeRequest: 1}
}));
const api = new RippleAPI({server: this.api.connection._url});
return api.connect().then(() => {
assert(false, 'Must have thrown!');
}, error => {
assert(error instanceof this.api.errors.RippledNotInitializedError,
'Must throw RippledNotInitializedError, got instead ' + String(error));
});
});
it('should try to reconnect on empty subscribe response on reconnect',
function(done) {
this.timeout(23000);
this.api.on('error', error => {
done(error || new Error('Should not emit error.'));
});
let disconncedCount = 0;
this.api.on('connected', () => {
done(disconncedCount !== 1 ?
new Error('Wrong number of disconnects') : undefined);
});
this.api.on('disconnected', () => {
disconncedCount++;
});
this.api.connection._send(JSON.stringify({
command: 'global_config',
data: {returnEmptySubscribeRequest: 3}
}));
this.api.connection._send(JSON.stringify({
command: 'test_command',
data: {disconnectIn: 10}
}));
});
});

View File

@@ -3,7 +3,7 @@
"address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"amount": {
"currency": "USD",
"value": "5"
"value": "5.00"
}
},
"destination": {

View File

@@ -10,6 +10,7 @@
"value": "2"
},
"immediateOrCancel": true,
"orderToReplace": 12345,
"memos": [
{
"type": "test",

View File

@@ -1,5 +1,5 @@
{
"txJSON": "{\"TransactionType\":\"SuspendedPaymentFinish\",\"Account\":\"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\"Owner\":\"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\"OfferSequence\":2,\"Method\":1,\"Digest\":\"712C36933822AD3A3D136C5DF97AA863B69F9CE88B2D6CE6BDD11BFDE290C19D\",\"Proof\":\"74686973206D757374206861766520333220636861726163746572732E2E2E2E\",\"Flags\":2147483648,\"LastLedgerSequence\":102,\"Fee\":\"12\",\"Sequence\":1}",
"txJSON": "{\"TransactionType\":\"EscrowFinish\",\"Account\":\"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\"Owner\":\"rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh\",\"OfferSequence\":2,\"Method\":1,\"Digest\":\"712C36933822AD3A3D136C5DF97AA863B69F9CE88B2D6CE6BDD11BFDE290C19D\",\"Proof\":\"74686973206D757374206861766520333220636861726163746572732E2E2E2E\",\"Flags\":2147483648,\"LastLedgerSequence\":102,\"Fee\":\"12\",\"Sequence\":1}",
"instructions": {
"fee": "0.000012",
"sequence": 1,

View File

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

View File

@@ -0,0 +1,79 @@
{
"stateHash": "334EE5F2209538C3099E133D25725E5BFEB40A198EA7028E6317F13E95D533DF",
"closeTime": "2016-07-07T16:53:51.000Z",
"closeTimeResolution": 10,
"closeFlags": 0,
"ledgerHash": "4F6C0495378FF68A15749C0D51D097EB638DA70319FDAC7A97A27CE63E0BFFED",
"ledgerVersion": 12345,
"parentLedgerHash": "4F636662B714CD9CCE965E9C23BB2E1058A2DF496F5A2416299317AE03F1CD35",
"parentCloseTime": "2016-07-07T16:53:50.000Z",
"totalDrops": "99997302532397566",
"transactionHash": "C72A2BDCB471F3AEEB917ABC6019407CAE6DA4B858903A8AB2335A0EB077125D",
"transactions": [
{
"type": "payment",
"address": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"sequence": 23295,
"id": "A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A",
"specification": {
"source": {
"address": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"maxAmount": {
"currency": "USD",
"value": "10",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
},
"destination": {
"address": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"amount": {
"currency": "USD",
"value": "10",
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
}
},
"allowPartialPayment": true
},
"outcome": {
"result": "tesSUCCESS",
"fee": "0.01",
"balanceChanges": {
"rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX": [
{
"currency": "XRP",
"value": "-0.01"
},
{
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"currency": "USD",
"value": "-10"
}
],
"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B": [
{
"counterparty": "rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4",
"currency": "USD",
"value": "-9.980039920159681"
},
{
"counterparty": "rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX",
"currency": "USD",
"value": "10"
}
],
"rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4": [
{
"counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
"currency": "USD",
"value": "9.980039920159681"
}
]
},
"orderbookChanges": {},
"ledgerVersion": 12345,
"indexInLedger": 1
}
}
],
"rawTransactions": "[{\"Account\":\"rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX\",\"Amount\":{\"currency\":\"USD\",\"issuer\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\",\"value\":\"10\"},\"Destination\":\"rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4\",\"Fee\":\"10000\",\"Flags\":131072,\"Sequence\":23295,\"SigningPubKey\":\"02B205F4B92351AC0EEB04254B636F4C49EF922CFA3CAAD03C6477DA1E04E94B53\",\"TransactionType\":\"Payment\",\"TxnSignature\":\"3045022100FAF247A836D601DE74A515B2AADE31186D8B0DA9C23DE489E09753F5CF4BB81F0220477C5B5BC3AC89F2347744F9E00CCA62267E198489D747578162C4C7D156211D\",\"hash\":\"A0A074D10355223CBE2520A42F93A52E3CC8B4D692570EB4841084F9BBB39F7A\",\"metaData\":{\"AffectedNodes\":[{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX\",\"Balance\":\"1930599790\",\"Flags\":0,\"OwnerCount\":2,\"Sequence\":23296},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"267C16D24EC42EEF8B03D5BE4E94266B1675FA54AFCE42DE795E02AB61031CBD\",\"PreviousFields\":{\"Balance\":\"1930609790\",\"Sequence\":23295},\"PreviousTxnID\":\"0F5396388E91D37BB26C8E24073A57E7C5D51E79AEE4CD855653B8499AE4E3DD\",\"PreviousTxnLgrSeq\":22419806}},{\"ModifiedNode\":{\"FinalFields\":{\"Balance\":{\"currency\":\"USD\",\"issuer\":\"rrrrrrrrrrrrrrrrrrrrBZbvji\",\"value\":\"-9.980959751659681\"},\"Flags\":2228224,\"HighLimit\":{\"currency\":\"USD\",\"issuer\":\"rNNuQMuExCiEjeZ4h9JJnj5PSWypdMXDj4\",\"value\":\"1000000\"},\"HighNode\":\"0000000000000000\",\"LowLimit\":{\"currency\":\"USD\",\"issuer\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\",\"value\":\"0\"},\"LowNode\":\"0000000000000423\"},\"LedgerEntryType\":\"RippleState\",\"LedgerIndex\":\"C66957AF25229357F9C2D2BA17CE47D88169788EDA7610AD0F29AD5BCB225EE5\",\"PreviousFields\":{\"Balance\":{\"currency\":\"USD\",\"issuer\":\"rrrrrrrrrrrrrrrrrrrrBZbvji\",\"value\":\"-0.0009198315\"}},\"PreviousTxnID\":\"2A01E994D7000000B43DD63825A081B4440A44AB2F6FA0D506158AC9CA6B2869\",\"PreviousTxnLgrSeq\":22420532}},{\"ModifiedNode\":{\"FinalFields\":{\"Balance\":{\"currency\":\"USD\",\"issuer\":\"rrrrrrrrrrrrrrrrrrrrBZbvji\",\"value\":\"-276666.975959\"},\"Flags\":131072,\"HighLimit\":{\"currency\":\"USD\",\"issuer\":\"rGFuMiw48HdbnrUbkRYuitXTmfrDBNTCnX\",\"value\":\"1000000\"},\"HighNode\":\"0000000000000000\",\"LowLimit\":{\"currency\":\"USD\",\"issuer\":\"rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B\",\"value\":\"0\"},\"LowNode\":\"00000000000002D7\"},\"LedgerEntryType\":\"RippleState\",\"LedgerIndex\":\"FFD710AE2074A98D920D00CC352F25744899F069A6C1B9E31DD32D2C6606E615\",\"PreviousFields\":{\"Balance\":{\"currency\":\"USD\",\"issuer\":\"rrrrrrrrrrrrrrrrrrrrBZbvji\",\"value\":\"-276676.975959\"}},\"PreviousTxnID\":\"BB9DFC87E9D4ED09CA2726DDFE83A4A396ED0D6545536322DE17CDACF45C0D5B\",\"PreviousTxnLgrSeq\":22419307}}],\"TransactionIndex\":1,\"TransactionResult\":\"tesSUCCESS\"}}]"
}

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