Compare commits

...

54 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
109 changed files with 7415 additions and 1696 deletions

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

@@ -5,6 +5,7 @@ machine:
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(() => {
@@ -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
@@ -1501,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
---- | ---- | -----------
@@ -2674,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
@@ -3041,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 => {/* ... */});
```
@@ -3593,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(() => {

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

4332
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.8",
"version": "0.17.5",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"files": [
@@ -15,7 +15,8 @@
"test": "test"
},
"dependencies": {
"ajv": "^1.4.8",
"ajv": "^4.0.5",
"ajv-i18n": "^1.2.0",
"babel-polyfill": "^6.3.14",
"babel-runtime": "^6.3.19",
"bignumber.js": "^2.0.3",
@@ -23,8 +24,8 @@
"jayson": "^1.2.2",
"lodash": "^3.1.0",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "^0.1.2",
"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"
@@ -33,31 +34,29 @@
"assert-diff": "^1.0.1",
"babel-cli": "^6.4.0",
"babel-core": "^6.4.0",
"babel-eslint": "^4.1.6",
"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.10.0",
"coveralls": "^2.11.9",
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eslint": "^2.1.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",
"http-server": "^0.8.5",
"isparta": "^4.0.0",
"istanbul": "^1.1.0-alpha.1",
"json-loader": "^0.5.2",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "^2.1.0",
"mocha-junit-reporter": "^1.9.1",
"mocha-phantomjs": "^4.0.1",
"mocha-in-sauce": "^0.0.1",
"mocha-junit-reporter": "^1.9.1",
"null-loader": "^0.1.1",
"webpack": "^1.5.3",
"yargs": "^1.3.1"
@@ -72,7 +71,7 @@
"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": "babel-node node_modules/isparta/lib/cli cover ./node_modules/mocha/bin/_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",

View File

@@ -35,21 +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-min build-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 using minified version in PhantomJS"
mocha-phantomjs test/localrunnermin.html
#echo "Running tests in PhantomJS"
#mocha-phantomjs test/localrunner.html
#echo "Running tests using minified version in PhantomJS"
#mocha-phantomjs test/localrunnermin.html
echo "Running tests in SauceLabs"
http-server &
npm run sauce
#echo "Running tests in SauceLabs"
#http-server &
#npm run sauce
pkill -f mocked-server.js
pkill -f http-server
#pkill -f mocked-server.js
#pkill -f http-server
rm -rf test-compiled
}
@@ -58,9 +58,9 @@ integrationtest() {
mocha test/integration/http-integration-test.js
# run integration tests in PhantomJS
gulp build-tests build-min
echo "Running integragtion tests in PhantomJS"
mocha-phantomjs test/localintegrationrunner.html
#gulp build-tests build-min
#echo "Running integragtion tests in PhantomJS"
#mocha-phantomjs test/localintegrationrunner.html
}
doctest() {
@@ -76,7 +76,6 @@ oneNode() {
checkEOL
doctest
lint
typecheck
unittest
integrationtest
}
@@ -84,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
}
@@ -92,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

View File

@@ -1,4 +1,3 @@
'use strict';
const _ = require('lodash');
const MochaSauce = require('mocha-in-sauce');
@@ -18,7 +17,6 @@ function main() {
maxDuration: 180000,
// the current build name (optional)
build: Date.now(),
seleniumVersion: '2.50.1',
url: testUrl,
runSauceConnect: true
};
@@ -42,6 +40,10 @@ function main() {
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',

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

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

View File

@@ -1,140 +1,224 @@
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(...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(resolve = function() {}, reject = function() {}) {
_onUnexpectedClose(beforeOpen, resolve, reject, code) {
if (this._onOpenErrorBound) {
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
this._ws.removeListener('error', this._onOpenErrorBound)
this._onOpenErrorBound = null
}
this._ws = null;
this._isReady = false;
this.connect().then(resolve, reject);
// 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() {
this._ws.removeListener('close', this._onUnexpectedCloseBound);
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.removeListener('error', this._onOpenErrorBound);
this._onOpenErrorBound = null;
this._ws.on('error', error =>
this.emit('error', 'websocket', error.message, error));
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) {
reject(new NotConnectedError(error && error.message));
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:'),
@@ -143,48 +227,49 @@ 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
@@ -192,142 +277,156 @@ class Connection extends EventEmitter {
// In case if there is connection error (say, server is not responding)
// we must return this error to connection's caller. After successful
// opening, we will forward all errors to main api object.
this._onOpenErrorBound = this._onOpenError.bind(this, reject);
this._ws.once('error', this._onOpenErrorBound);
this._ws.on('message', this._onMessage.bind(this));
this._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,
resolve, reject);
this._ws.once('close', this._onUnexpectedCloseBound);
this._ws.once('open', () => this._onOpen().then(resolve, reject));
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,43 +1,43 @@
'use strict';
const util = require('util');
const browserHacks = require('./browser-hacks');
'use strict' // eslint-disable-line strict
const util = require('util')
const browserHacks = require('./browser-hacks')
// this is needed because extending builtins doesn't work in babel 6.x
function extendableBuiltin(cls) {
function ExtendableBuiltin() {
cls.apply(this, arguments);
cls.apply(this, arguments)
}
ExtendableBuiltin.prototype = Object.create(cls.prototype);
browserHacks.setPrototypeOf(ExtendableBuiltin, cls);
return ExtendableBuiltin;
ExtendableBuiltin.prototype = Object.create(cls.prototype)
browserHacks.setPrototypeOf(ExtendableBuiltin, cls)
return ExtendableBuiltin
}
class RippleError extends extendableBuiltin(Error) {
constructor(message, data) {
super(message);
super(message)
this.name = browserHacks.getConstructorName(this);
this.message = message;
this.data = data;
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()
}
}
@@ -53,6 +53,8 @@ class NotConnectedError extends ConnectionError {}
class DisconnectedError extends ConnectionError {}
class RippledNotInitializedError extends ConnectionError {}
class TimeoutError extends ConnectionError {}
class ResponseFormatError extends ConnectionError {}
@@ -61,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')
}
}
@@ -85,6 +87,7 @@ module.exports = {
RippledError,
NotConnectedError,
DisconnectedError,
RippledNotInitializedError,
TimeoutError,
ResponseFormatError,
ValidationError,
@@ -92,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

@@ -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

@@ -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,50 +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 => {
this.emit('error', error);
};
this.emit('error', error)
}
this._ws.onmessage = message => {
this.emit('message', message.data);
};
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,86 +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,7 +1,7 @@
/* @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 {
@@ -9,13 +9,13 @@ function parseAmount(amount: RippledAmount): Amount {
return {
currency: 'XRP',
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,39 +1,39 @@
/* @flow */
'use strict';
const _ = require('lodash');
const assert = require('assert');
const utils = require('./utils');
const parseAmount = require('./amount');
const txFlags = utils.txFlags;
'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),
@@ -44,7 +44,7 @@ function parsePayment(tx: Object): Object {
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,85 +1,109 @@
/* @flow */
'use strict';
const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const utils = require('../utils');
const BigNumber = require('bignumber.js');
const parseAmount = require('./amount');
'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';
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(changes, removeEmptyCounterparty)
})
}
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
_.forEach(orderbookChanges, changes => {
_.forEach(changes, change => {
_.forEach(change, removeEmptyCounterparty);
});
});
_.forEach(change, removeEmptyCounterparty)
})
})
}
function isPartialPayment(tx) {
return (tx.Flags & utils.common.txFlags.Payment.PartialPayment) !== 0;
return (tx.Flags & utils.common.txFlags.Payment.PartialPayment) !== 0
}
function parseDeliveredAmount(tx: Object): Amount | void {
let deliveredAmount;
// TODO: Workaround for existing rippled bug where delivered_amount may not be
// provided for account_tx
if (tx.TransactionType === 'Payment') {
if (tx.meta.delivered_amount) {
deliveredAmount = parseAmount(tx.meta.delivered_amount);
} else if (tx.Amount && !isPartialPayment(tx)) {
deliveredAmount = parseAmount(tx.Amount);
}
if (tx.TransactionType !== 'Payment' ||
tx.meta.TransactionResult !== 'tesSUCCESS') {
return undefined
}
return deliveredAmount;
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 utils.common.removeUndefined({
result: tx.meta.TransactionResult,
@@ -90,24 +114,24 @@ function parseOutcome(tx: Object): ?Object {
ledgerVersion: tx.ledger_index,
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 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 = {
@@ -123,4 +147,4 @@ module.exports = {
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');
@@ -708,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() {
@@ -981,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');
@@ -1143,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

@@ -1,3 +1,4 @@
'use strict'; // eslint-disable-line
/* eslint-disable max-nested-callbacks */
const _ = require('lodash');
@@ -117,7 +118,9 @@ describe('Connection', function() {
}
}
const connection = new utils.common.Connection('ws://127.0.0.1:321');
// 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);
@@ -191,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();
@@ -301,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

@@ -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\"}}]"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
{
"type": "amendment",
"address": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"sequence": 0,
"id": "A971B83ABED51D83749B73F3C1AAA627CD965AFF74BE8CD98299512D6FB0658F",
"specification": {
"amendment": "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE"
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2016-05-05T16:33:30.000Z",
"fee": "0",
"balanceChanges": {},
"orderbookChanges": {},
"indexInLedger": 0
}
}

View File

@@ -0,0 +1,21 @@
{
"type": "feeUpdate",
"address": "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
"sequence": 0,
"id": "C6A40F56127436DCD830B1B35FF939FD05B5747D30D6542572B7A835239817AF",
"specification": {
"baseFeeXRP": "0.00001",
"referenceFeeUnits": 10,
"reserveBaseXRP": "50",
"reserveIncrementXRP": "12.5"
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2014-08-08T16:57:50.000Z",
"fee": "0",
"balanceChanges": {},
"orderbookChanges": {},
"ledgerVersion": 3717633,
"indexInLedger": 3
}
}

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line strict
module.exports = {
generateAddress: require('./generate-address.json'),
@@ -43,7 +43,9 @@ module.exports = {
suspendedPaymentExecution:
require('./get-transaction-suspended-payment-execution.json'),
suspendedPaymentExecutionSimple:
require('./get-transaction-suspended-payment-execution-simple.json')
require('./get-transaction-suspended-payment-execution-simple.json'),
amendment: require('./get-transaction-amendment.json'),
feeUpdate: require('./get-transaction-fee-update.json')
},
getTransactions: {
normal: require('./get-transactions.json'),
@@ -57,7 +59,9 @@ module.exports = {
header: require('./get-ledger'),
full: require('./get-ledger-full'),
withSettingsTx: require('./get-ledger-with-settings-tx'),
withStateAsHashes: require('./get-ledger-with-state-as-hashes')
withStateAsHashes: require('./get-ledger-with-state-as-hashes'),
withPartial: require('./get-ledger-with-partial-payment'),
pre2014withPartial: require('./get-ledger-pre2014-with-partial')
},
prepareOrder: {
buy: require('./prepare-order.json'),

View File

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

View File

@@ -1,4 +1,4 @@
{
"signedTransaction": "12000222800000002400000001201900000002201B000000665015712C36933822AD3A3D136C5DF97AA863B69F9CE88B2D6CE6BDD11BFDE290C19D68400000000000000C73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020744630440220690785C69BCF4CDA2DD5E82D2F9A9C7BDCBAB68A1E927DC03C0C32900938AC1702202A25E42E9920DB2F46E378F6F6598828D441E99F7D828010AD91674A5E7C0AED70112074686973206D757374206861766520333220636861726163746572732E2E2E2E8114B5F762798A53D543A014CAF8B297CFF8F2F937E88214B5F762798A53D543A014CAF8B297CFF8F2F937E8021001",
"id": "B0ADFCC7925588AB2C224A7693222B3397158EA9206FBD614763FB6D00C1F6C1"
"signedTransaction": "12000222800000002400000001201900000002201B000000665015712C36933822AD3A3D136C5DF97AA863B69F9CE88B2D6CE6BDD11BFDE290C19D68400000000000000C73210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100E1DAAD071DC0E556FEC472E0C6CABBAB064901D97FEAD61CA9990DCC17326FFC022051DF3331D340B3E635024D7B9684602EFDEE2D178AAC9E4824722B4DAB6036718114B5F762798A53D543A014CAF8B297CFF8F2F937E88214B5F762798A53D543A014CAF8B297CFF8F2F937E8021001",
"id": "D40E312D9CD2EE8C5940D1D4918E9F5778870CB2FBF1D188061EB21B2E7CF371"
}

View File

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

7
test/fixtures/rippled/empty.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
}
}

View File

@@ -1,4 +1,4 @@
'use strict';
'use strict'; // eslint-disable-line
module.exports = {
submit: {
@@ -10,8 +10,11 @@ module.exports = {
notFound: require('./ledger-not-found'),
withoutCloseTime: require('./ledger-without-close-time'),
withSettingsTx: require('./ledger-with-settings-tx'),
withStateAsHashes: require('./ledger-with-state-as-hashes')
withStateAsHashes: require('./ledger-with-state-as-hashes'),
withPartialPayment: require('./ledger-with-partial-payment'),
pre2014withPartial: require('./ledger-pre2014-with-partial')
},
empty: require('./empty'),
subscribe: require('./subscribe'),
unsubscribe: require('./unsubscribe'),
account_info: {
@@ -31,6 +34,8 @@ module.exports = {
},
server_info: {
normal: require('./server-info'),
noValidated: require('./server-info-no-validated'),
syncing: require('./server-info-syncing'),
error: require('./server-info-error')
},
path_find: {
@@ -69,6 +74,8 @@ module.exports = {
require('./tx/suspended-payment-execution-simple.json'),
Unrecognized: require('./tx/unrecognized.json'),
NoMeta: require('./tx/no-meta.json'),
LedgerZero: require('./tx/ledger-zero.json')
LedgerZero: require('./tx/ledger-zero.json'),
Amendment: require('./tx/amendment.json'),
SetFee: require('./tx/set-fee.json')
}
};

View File

@@ -0,0 +1,141 @@
{
"id": 1,
"status": "success",
"type": "response",
"result": {
"ledger": {
"accepted": true,
"account_hash": "334EE5F2209538C3099E133D25725E5BFEB40A198EA7028E6317F13E95D533DF",
"close_flags": 0,
"close_time": 521225631,
"close_time_human": "2016-Jul-07 16:53:51",
"close_time_resolution": 10,
"closed": true,
"hash": "4F6C0495378FF68A15749C0D51D097EB638DA70319FDAC7A97A27CE63E0BFFED",
"ledger_hash": "4F6C0495378FF68A15749C0D51D097EB638DA70319FDAC7A97A27CE63E0BFFED",
"ledger_index": "12345",
"parent_close_time": 521225630,
"parent_hash": "4F636662B714CD9CCE965E9C23BB2E1058A2DF496F5A2416299317AE03F1CD35",
"seqNum": "22420574",
"totalCoins": "99997302532397566",
"total_coins": "99997302532397566",
"transaction_hash": "C72A2BDCB471F3AEEB917ABC6019407CAE6DA4B858903A8AB2335A0EB077125D",
"transactions": [
{
"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"
}
}
]
},
"ledger_hash": "4F6C0495378FF68A15749C0D51D097EB638DA70319FDAC7A97A27CE63E0BFFED",
"ledger_index": 12345,
"validated": true
}
}

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