diff --git a/README.md b/README.md index f6abff83..6bb6348f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #ripple-lib -A JavaScript API for interacting with Ripple in Node.js and the browser +A JavaScript API for interacting with Ripple in Node.js [![Circle CI](https://circleci.com/gh/ripple/ripple-lib/tree/develop.svg?style=svg)](https://circleci.com/gh/ripple/ripple-lib/tree/develop) [![Coverage Status](https://coveralls.io/repos/ripple/ripple-lib/badge.png?branch=develop)](https://coveralls.io/r/ripple/ripple-lib?branch=develop) @@ -8,7 +8,7 @@ A JavaScript API for interacting with Ripple in Node.js and the browser ###Features -+ Connect to a rippled server in JavaScript (Node.js or browser) ++ Connect to a rippled server in Node.js + Issue [rippled API](https://ripple.com/build/rippled-apis/) requests + Listen to events on the Ripple network (transaction, ledger, etc.) + Sign and submit transactions to the Ripple network diff --git a/circle.yml b/circle.yml index 80f2f5ca..0f7df69c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,7 +1,16 @@ machine: node: version: 0.12.0 +dependencies: + pre: + - wget https://s3-us-west-2.amazonaws.com/ripple-debs/rippled_0.30.1-b11-1.deb + - sudo dpkg -i rippled_0.30.1-b11-1.deb test: + pre: + - rippled -a --start --conf "$HOME/$CIRCLE_PROJECT_REPONAME/test/integration/rippled.cfg": + background: true override: - scripts/ci.sh "$CIRCLE_NODE_INDEX" "$CIRCLE_NODE_TOTAL": parallel: true + post: + - killall /usr/bin/rippled diff --git a/docs/index.md b/docs/index.md index fb641066..cc3f735d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,6 +54,7 @@ - [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation) - [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution) - [sign](#sign) + - [combine](#combine) - [submit](#submit) - [generateAddress](#generateaddress) - [computeLedgerHash](#computeledgerhash) @@ -269,7 +270,7 @@ Executing a transaction with `RippleAPI` requires the following four steps: * [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation) * [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation) * [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution) -2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. +2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method. 3. [Submit](#submit) - Submit the transaction to the connected server. 4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted. @@ -290,6 +291,7 @@ maxFee | [value](#value) | *Optional* The maximum fee to pay for the transaction maxLedgerVersion | integer,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version. maxLedgerVersionOffset | integer | *Optional* Offset from current validated legder version to highest ledger version that the transaction can be included in. sequence | [sequence](#account-sequence-number) | *Optional* The initiating account's sequence number for this transaction. +signersCount | integer | *Optional* Number of signers that will be signing this transaction. We recommended that you specify a `maxLedgerVersion` so that you can quickly determine that a failed transaction will never succeeed in the future. It is impossible for a transaction to succeed after the network ledger version exceeds the transaction's `maxLedgerVersion`. If you omit `maxLedgerVersion`, the "prepare*" method automatically supplies a `maxLedgerVersion` equal to the current ledger plus 3, which it includes in the return value from the "prepare*" method. @@ -480,6 +482,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key. requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this account’s issuances. requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag. +signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning. +*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`. +*signers.* weights | array | *Optional* Weights of signatures for each signer. +*signers.* weights[] | object | An association of an address and a weight. +*signers.weights[].* address | [address](#ripple-address) | A Ripple account address +*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold. transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee. ### Example @@ -1599,18 +1607,18 @@ paths | string | The paths of trustlines and orders to use in executing the paym ### Example ```javascript -const pathfind = { - "source": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" - }, - "destination": { - "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "amount": { - "currency": "USD", - "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", - "value": "100" - } - } +const pathfind = { + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "amount": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "100" + } + } }; return api.getPaths(pathfind) .then(paths => {/* ... */}); @@ -2644,6 +2652,12 @@ passwordSpent | boolean | *Optional* Indicates that the account has used its fre regularKey | [address](#ripple-address),null | *Optional* The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key. requireAuthorization | boolean | *Optional* If set, this account must individually approve other users in order for those users to hold this account’s issuances. requireDestinationTag | boolean | *Optional* Requires incoming payments to specify a destination tag. +signers | object | *Optional* Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning. +*signers.* threshold | integer | *Optional* A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`. +*signers.* weights | array | *Optional* Weights of signatures for each signer. +*signers.* weights[] | object | An association of an address and a weight. +*signers.weights[].* address | [address](#ripple-address) | A Ripple account address +*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold. transferRate | number,null | *Optional* The fee to charge when users transfer this account’s issuances, represented as billionths of a unit. Use `null` to set no fee. ### Example @@ -3282,7 +3296,7 @@ return api.prepareSuspendedPaymentExecution(address, suspendedPaymentExecution). ## sign -`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}` +`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}` Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit). @@ -3292,6 +3306,8 @@ Name | Type | Description ---- | ---- | ----------- txJSON | string | Transaction represented as a JSON string in rippled format. secret | secret string | The secret of the account that is initiating the transaction. +options | object | *Optional* Options that control the type of signature that will be generated. +*options.* signAs | [address](#ripple-address) | *Optional* The account that the signature should count for in multisigning. ### Return Value @@ -3319,6 +3335,44 @@ return api.sign(txJSON, secret); ``` +## combine + +`combine(signedTransactions: Array): {signedTransaction: string, id: string}` + +Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit). + +### Parameters + +Name | Type | Description +---- | ---- | ----------- +signedTransactions | array\ | An array of signed transactions (from the output of [sign](#sign)) to combine. + +### Return Value + +This method returns an object with the following structure: + +Name | Type | Description +---- | ---- | ----------- +signedTransaction | string | The signed transaction represented as an uppercase hexadecimal string. +id | [id](#transaction-id) | The [Transaction ID](#transaction-id) of the signed transaction. + +### Example + +```javascript +const signedTransactions = [ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1", + "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ]; +return api.combine(signedTransactions); +``` + + +```json +{ + "signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1", + "id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC" +} +``` + + ## submit `submit(signedTransaction: string): Promise` diff --git a/docs/samples/cancelall.js b/docs/samples/cancelall.js new file mode 100644 index 00000000..6f7f1388 --- /dev/null +++ b/docs/samples/cancelall.js @@ -0,0 +1,38 @@ +'use strict'; +const RippleAPI = require('../../dist/npm').RippleAPI; // require('ripple-lib') + +const address = 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K'; +const secret = ''; + +const api = new RippleAPI({server: 'wss://s1.ripple.com:443'}); +const instructions = {maxLedgerVersionOffset: 5}; + +function fail(message) { + console.error(message); + process.exit(1); +} + +function cancelOrder(orderSequence) { + console.log('Cancelling order: ' + orderSequence.toString()); + return api.prepareOrderCancellation(address, {orderSequence}, instructions) + .then(prepared => { + const signing = api.sign(prepared.txJSON, secret); + return api.submit(signing.signedTransaction); + }); +} + +function cancelAllOrders(orderSequences) { + if (orderSequences.length === 0) { + return Promise.resolve(); + } + const orderSequence = orderSequences.pop(); + return cancelOrder(orderSequence).then(() => cancelAllOrders(orderSequences)); +} + +api.connect().then(() => { + console.log('Connected...'); + return api.getOrders(address).then(orders => { + const orderSequences = orders.map(order => order.properties.sequence); + return cancelAllOrders(orderSequences); + }).then(() => process.exit(0)); +}).catch(fail); diff --git a/docs/src/combine.md.ejs b/docs/src/combine.md.ejs new file mode 100644 index 00000000..f65ac7a2 --- /dev/null +++ b/docs/src/combine.md.ejs @@ -0,0 +1,24 @@ +## combine + +`combine(signedTransactions: Array): {signedTransaction: string, id: string}` + +Combines signed transactions from multiple accounts for a multisignature transaction. The signed transaction must subsequently be [submitted](#submit). + +### Parameters + +<%- renderSchema("input/combine.json") %> + +### Return Value + +This method returns an object with the following structure: + +<%- renderSchema("output/sign.json") %> + +### Example + +```javascript +const signedTransactions = <%- importFile('test/fixtures/requests/combine.json') %>; +return api.combine(signedTransactions); +``` + +<%- renderFixture("responses/combine.json") %> diff --git a/docs/src/index.md.ejs b/docs/src/index.md.ejs index 40ec5aa8..9d854bb9 100644 --- a/docs/src/index.md.ejs +++ b/docs/src/index.md.ejs @@ -31,6 +31,7 @@ <% include prepareSuspendedPaymentCancellation.md.ejs %> <% include prepareSuspendedPaymentExecution.md.ejs %> <% include sign.md.ejs %> +<% include combine.md.ejs %> <% include submit.md.ejs %> <% include generateAddress.md.ejs %> <% include computeLedgerHash.md.ejs %> diff --git a/docs/src/sign.md.ejs b/docs/src/sign.md.ejs index be5e40a6..26247cf3 100644 --- a/docs/src/sign.md.ejs +++ b/docs/src/sign.md.ejs @@ -1,6 +1,6 @@ ## sign -`sign(txJSON: string, secret: string): {signedTransaction: string, id: string}` +`sign(txJSON: string, secret: string, options: Object): {signedTransaction: string, id: string}` Sign a prepared transaction. The signed transaction must subsequently be [submitted](#submit). diff --git a/docs/src/transactions.md.ejs b/docs/src/transactions.md.ejs index 3c1394f3..5e389447 100644 --- a/docs/src/transactions.md.ejs +++ b/docs/src/transactions.md.ejs @@ -30,7 +30,7 @@ Executing a transaction with `RippleAPI` requires the following four steps: * [prepareSuspendedPaymentCreation](#preparesuspendedpaymentcreation) * [prepareSuspendedPaymentCancellation](#preparesuspendedpaymentcancellation) * [prepareSuspendedPaymentExecution](#preparesuspendedpaymentexecution) -2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. +2. [Sign](#sign) - Cryptographically sign the transaction locally and save the [transaction ID](#transaction-id). Signing is how the owner of an account authorizes a transaction to take place. For multisignature transactions, the `signedTransaction` fields returned by `sign` must be collected and passed to the [combine](#combine) method. 3. [Submit](#submit) - Submit the transaction to the connected server. 4. Verify - Verify that the transaction got validated by querying with [getTransaction](#gettransaction). This is necessary because transactions may fail even if they were successfully submitted. diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 7f9b5d49..4d2fe361 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -4,17 +4,17 @@ "dependencies": { "ajv": { "version": "1.4.10", - "from": "ajv@>=1.4.8 <2.0.0", + "from": "https://registry.npmjs.org/ajv/-/ajv-1.4.10.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-1.4.10.tgz", "dependencies": { "json-stable-stringify": { "version": "1.0.0", - "from": "json-stable-stringify@>=1.0.0 <2.0.0", + "from": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz", "dependencies": { "jsonify": { "version": "0.0.0", - "from": "jsonify@>=0.0.0 <0.1.0", + "from": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" } } @@ -23,155 +23,155 @@ }, "ajv-i18n": { "version": "0.1.1", - "from": "ajv-i18n@>=0.1.0 <0.2.0", + "from": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-0.1.1.tgz", "resolved": "https://registry.npmjs.org/ajv-i18n/-/ajv-i18n-0.1.1.tgz" }, "babel-polyfill": { "version": "6.3.14", - "from": "babel-polyfill@>=6.2.0 <7.0.0", + "from": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.3.14.tgz", "dependencies": { "core-js": { "version": "1.2.6", - "from": "core-js@>=1.0.1 <2.0.0", + "from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" }, "babel-regenerator-runtime": { "version": "6.3.13", - "from": "babel-regenerator-runtime@>=6.3.13 <7.0.0", + "from": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz", "resolved": "https://registry.npmjs.org/babel-regenerator-runtime/-/babel-regenerator-runtime-6.3.13.tgz" } } }, "babel-runtime": { "version": "5.8.34", - "from": "babel-runtime@>=5.5.4 <6.0.0", + "from": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz", "dependencies": { "core-js": { "version": "1.2.6", - "from": "core-js@>=1.0.0 <2.0.0", + "from": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" } } }, "bignumber.js": { "version": "2.1.2", - "from": "bignumber.js@>=2.0.3 <3.0.0", + "from": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.2.tgz", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.1.2.tgz" }, "https-proxy-agent": { "version": "1.0.0", - "from": "https-proxy-agent@>=1.0.0 <2.0.0", + "from": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", "dependencies": { "agent-base": { "version": "2.0.1", - "from": "agent-base@>=2.0.0 <3.0.0", + "from": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.1.tgz", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.1.tgz", "dependencies": { "semver": { "version": "5.0.3", - "from": "semver@>=5.0.1 <5.1.0", + "from": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz" } } }, "debug": { "version": "2.2.0", - "from": "debug@>=2.0.0 <3.0.0", + "from": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "dependencies": { "ms": { "version": "0.7.1", - "from": "ms@0.7.1", + "from": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" } } }, "extend": { "version": "3.0.0", - "from": "extend@>=3.0.0 <4.0.0", + "from": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" } } }, "jayson": { "version": "1.2.2", - "from": "jayson@>=1.2.2 <2.0.0", + "from": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz", "resolved": "https://registry.npmjs.org/jayson/-/jayson-1.2.2.tgz", "dependencies": { "JSONStream": { "version": "1.0.3", - "from": "JSONStream@1.0.3", + "from": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.0.3.tgz", "dependencies": { "jsonparse": { "version": "1.0.0", - "from": "jsonparse@>=1.0.0 <1.1.0", + "from": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.0.0.tgz" }, "through": { "version": "2.3.8", - "from": "through@>=2.2.7 <3.0.0", + "from": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" } } }, "commander": { "version": "1.3.2", - "from": "commander@1.3.2", + "from": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-1.3.2.tgz", "dependencies": { "keypress": { "version": "0.1.0", - "from": "keypress@>=0.1.0 <0.2.0", + "from": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz" } } }, "eyes": { "version": "0.1.8", - "from": "eyes@0.1.8", + "from": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz" }, "lodash": { "version": "3.6.0", - "from": "lodash@3.6.0", + "from": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.6.0.tgz" } } }, "lodash": { "version": "3.10.1", - "from": "lodash@>=3.1.0 <4.0.0", + "from": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" }, "ripple-address-codec": { "version": "2.0.1", - "from": "ripple-address-codec@>=2.0.1 <3.0.0", + "from": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz", "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-2.0.1.tgz", "dependencies": { "hash.js": { "version": "1.0.3", - "from": "hash.js@>=1.0.3 <2.0.0", + "from": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", "dependencies": { "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <3.0.0", + "from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "x-address-codec": { "version": "0.7.2", - "from": "x-address-codec@>=0.7.0 <0.8.0", + "from": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.2.tgz", "resolved": "https://registry.npmjs.org/x-address-codec/-/x-address-codec-0.7.2.tgz", "dependencies": { "base-x": { "version": "1.0.1", - "from": "base-x@>=1.0.1 <2.0.0", + "from": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz", "resolved": "https://registry.npmjs.org/base-x/-/base-x-1.0.1.tgz" } } @@ -180,76 +180,76 @@ }, "ripple-binary-codec": { "version": "0.1.1", - "from": "ripple-binary-codec@>=0.1.1 <0.2.0", + "from": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.1.1.tgz", "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.1.1.tgz", "dependencies": { "bn.js": { "version": "3.3.0", - "from": "bn.js@>=3.2.0 <4.0.0", + "from": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz" }, "create-hash": { "version": "1.1.2", - "from": "create-hash@>=1.1.2 <2.0.0", + "from": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "dependencies": { "cipher-base": { "version": "1.0.2", - "from": "cipher-base@>=1.0.1 <2.0.0", + "from": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz" }, "ripemd160": { "version": "1.0.1", - "from": "ripemd160@>=1.0.0 <2.0.0", + "from": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz" }, "sha.js": { "version": "2.4.4", - "from": "sha.js@>=2.3.6 <3.0.0", + "from": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz" } } }, "decimal.js": { "version": "4.0.3", - "from": "decimal.js@>=4.0.2 <5.0.0", + "from": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "ripple-hashes": { "version": "0.1.0", - "from": "ripple-hashes@>=0.1.0 <0.2.0", + "from": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.1.0.tgz", "resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.1.0.tgz", "dependencies": { "create-hash": { "version": "1.1.2", - "from": "create-hash@>=1.1.2 <2.0.0", + "from": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", "dependencies": { "cipher-base": { "version": "1.0.2", - "from": "cipher-base@>=1.0.1 <2.0.0", + "from": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.2.tgz" }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <3.0.0", + "from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, "ripemd160": { "version": "1.0.1", - "from": "ripemd160@>=1.0.0 <2.0.0", + "from": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz" }, "sha.js": { "version": "2.4.4", - "from": "sha.js@>=2.3.6 <3.0.0", + "from": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz" } } @@ -258,39 +258,39 @@ }, "ripple-keypairs": { "version": "0.10.0", - "from": "ripple-keypairs@>=0.10.0 <0.11.0", + "from": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz", "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz", "dependencies": { "bn.js": { "version": "3.3.0", - "from": "bn.js@>=3.1.1 <4.0.0", + "from": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.3.0.tgz" }, "brorand": { "version": "1.0.5", - "from": "brorand@>=1.0.5 <2.0.0", + "from": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.0.5.tgz" }, "elliptic": { "version": "5.2.1", - "from": "elliptic@>=5.1.0 <6.0.0", + "from": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-5.2.1.tgz", "dependencies": { "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "hash.js": { "version": "1.0.3", - "from": "hash.js@>=1.0.3 <2.0.0", + "from": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.0.3.tgz", "dependencies": { "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <3.0.0", + "from": "inherits@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -299,13 +299,13 @@ }, "ripple-lib-transactionparser": { "version": "0.6.0", - "from": "ripple-lib-transactionparser@>=0.6.0 <0.7.0", + "from": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.0.tgz", "resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.6.0.tgz" }, "ws": { - "version": "0.7.2", - "from": "ws@>=0.7.1 <0.8.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz", + "version": "1.0.1", + "from": "ws@1.0.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-1.0.1.tgz", "dependencies": { "options": { "version": "0.0.6", diff --git a/package.json b/package.json index bfcf83a8..2c1a79f7 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "ripple-hashes": "^0.1.0", "ripple-keypairs": "^0.10.0", "ripple-lib-transactionparser": "^0.6.0", - "ws": "^0.7.1" + "ws": "^1.0.1" }, "devDependencies": { "assert-diff": "^1.0.1", diff --git a/scripts/checkeol.sh b/scripts/checkeol.sh new file mode 100755 index 00000000..6926fa4f --- /dev/null +++ b/scripts/checkeol.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +function checkEOL { + local changedFiles=$(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD) + local result=0 + for name in $changedFiles; do + grep -c -U -q $'\r' $name + if [ $? -eq 0 ]; then + echo "windows eol found in $name" >&2 + result=1 + fi + done + if [ $result -eq 1 ]; then + false + fi +} + +checkEOL diff --git a/scripts/ci.sh b/scripts/ci.sh index 1dfdf9ef..a9aebbdb 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -3,6 +3,10 @@ NODE_INDEX="$1" TOTAL_NODES="$2" +function checkEOL { + ./scripts/checkeol.sh +} + typecheck() { npm install -g flow-bin flow --version @@ -47,6 +51,7 @@ doctest() { } oneNode() { + checkEOL doctest lint typecheck @@ -57,7 +62,7 @@ oneNode() { twoNodes() { case "$NODE_INDEX" in 0) doctest; lint; integrationtest;; - 1) typecheck; unittest;; + 1) checkEOL; typecheck; unittest;; *) echo "ERROR: invalid usage"; exit 2;; esac } @@ -65,7 +70,7 @@ twoNodes() { threeNodes() { case "$NODE_INDEX" in 0) doctest; lint; integrationtest;; - 1) typecheck;; + 1) checkEOL; typecheck;; 2) unittest;; *) echo "ERROR: invalid usage"; exit 2;; esac diff --git a/scripts/publish b/scripts/publish index 2b3a5df7..e574593b 100644 --- a/scripts/publish +++ b/scripts/publish @@ -16,28 +16,3 @@ echo "" echo "publish to npm" npm publish exit_on_error - -rm -rf dist/bower -echo "" -echo "publish to bower" - -git clone git@github.com:ripple/bower-ripple.git dist/bower -gulp bower -exit_on_error - -cd dist/bower -version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?') -echo "version: $version" -git add ripple.js ripple-debug.js ripple-min.js bower.json -exit_on_error - -git commit -m "[TASK] add v$version" -exit_on_error - -git tag "v$version" -exit_on_error - -git push origin master -git push --tags origin master - -cd ../.. diff --git a/scripts/publish_rc b/scripts/publish_rc index 61a3df22..1428edcb 100644 --- a/scripts/publish_rc +++ b/scripts/publish_rc @@ -16,28 +16,3 @@ echo "" echo "publish rc to npm" npm publish --tag beta exit_on_error - -rm -rf dist/bower -echo "" -echo "publish to bower" - -git clone git@github.com:ripple/bower-ripple.git dist/bower -gulp bower -exit_on_error - -cd dist/bower -version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc([0-9])+)?') -echo "version: $version" -git add ripple.js ripple-debug.js ripple-min.js bower.json -exit_on_error - -git commit -m "[TASK] add v$version" -exit_on_error - -git tag "v$version" -exit_on_error - -git push origin master -git push --tags origin master - -cd ../.. diff --git a/scripts/publish_to_bower b/scripts/publish_to_bower deleted file mode 100644 index 280eb663..00000000 --- a/scripts/publish_to_bower +++ /dev/null @@ -1,12 +0,0 @@ -rm -rf dist/bower -git clone git@github.com:ripple/bower-ripple.git dist/bower -gulp bower -cd dist/bower -version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?') -echo "version: $version" -git add ripple.js ripple-debug.js ripple-min.js bower.json -git commit -m "[TASK] add v$version" -git tag "v$version" -git push origin master -git push --tags origin master -cd .. diff --git a/src/api.js b/src/api.js index adeef139..b8556b6e 100644 --- a/src/api.js +++ b/src/api.js @@ -44,6 +44,7 @@ const prepareSuspendedPaymentCancellation = require('./transaction/suspended-payment-cancellation'); const prepareSettings = require('./transaction/settings'); const sign = require('./transaction/sign'); +const combine = require('./transaction/combine'); const submit = require('./transaction/submit'); const errors = require('./common').errors; const generateAddress = @@ -125,6 +126,7 @@ _.assign(RippleAPI.prototype, { prepareSuspendedPaymentCancellation, prepareSettings, sign, + combine, submit, generateAddress, diff --git a/src/common/connection.js b/src/common/connection.js index cb87dbef..f116e741 100644 --- a/src/common/connection.js +++ b/src/common/connection.js @@ -38,6 +38,17 @@ class Connection extends EventEmitter { this._nextRequestID = 1; } + _updateLedgerVersions(data) { + this._ledgerVersion = Number(data.ledger_index); + if (data.validated_ledgers) { + this._availableLedgerVersions.reset(); + this._availableLedgerVersions.parseAndAddRanges( + data.validated_ledgers); + } else { + this._availableLedgerVersions.addValue(this._ledgerVersion); + } + } + // return value is array of arguments to Connection.emit _parseMessage(message) { const data = JSON.parse(message); @@ -48,10 +59,7 @@ class Connection extends EventEmitter { return [data.id.toString(), data]; } else if (isStreamMessageType(data.type)) { if (data.type === 'ledgerClosed') { - this._ledgerVersion = Number(data.ledger_index); - this._availableLedgerVersions.reset(); - this._availableLedgerVersions.parseAndAddRanges( - data.validated_ledgers); + this._updateLedgerVersions(data); } return [data.type, data]; } else if (data.type === undefined && data.error) { @@ -99,10 +107,8 @@ class Connection extends EventEmitter { command: 'subscribe', streams: ['ledger'] }; - return this.request(request).then(response => { - this._ledgerVersion = Number(response.ledger_index); - this._availableLedgerVersions.parseAndAddRanges( - response.validated_ledgers); + return this.request(request).then(data => { + this._updateLedgerVersions(data); this._isReady = true; this.emit('connected'); }); diff --git a/src/common/schema-validator.js b/src/common/schema-validator.js index abf45948..76eeb831 100644 --- a/src/common/schema-validator.js +++ b/src/common/schema-validator.js @@ -94,7 +94,8 @@ function loadSchemas() { require('./schemas/input/compute-ledger-hash'), require('./schemas/input/sign.json'), require('./schemas/input/submit.json'), - require('./schemas/input/generate-address.json') + require('./schemas/input/generate-address.json'), + require('./schemas/input/combine.json') ]; const titles = _.map(schemas, schema => schema.title); const duplicates = _.keys(_.pick(_.countBy(titles), count => count > 1)); diff --git a/src/common/schemas/input/combine.json b/src/common/schemas/input/combine.json new file mode 100644 index 00000000..fb487476 --- /dev/null +++ b/src/common/schemas/input/combine.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "combineParameters", + "type": "object", + "properties": { + "signedTransactions": { + "type": "array", + "description": "An array of signed transactions (from the output of [sign](#sign)) to combine.", + "items": { + "type": "string", + "pattern": "^[A-F0-9]+$", + "description": "A single-signed transaction represented as an uppercase hexadecimal string (from the output of [sign](#sign))" + }, + "minLength": 1 + } + }, + "additionalProperties": false, + "required": ["signedTransactions"] +} diff --git a/src/common/schemas/input/sign.json b/src/common/schemas/input/sign.json index 4e9d6db9..883906df 100644 --- a/src/common/schemas/input/sign.json +++ b/src/common/schemas/input/sign.json @@ -11,6 +11,17 @@ "type": "string", "format": "secret", "description": "The secret of the account that is initiating the transaction." + }, + "options": { + "type": "object", + "description": "Options that control the type of signature that will be generated.", + "properties": { + "signAs": { + "$ref": "address", + "description": "The account that the signature should count for in multisigning." + } + }, + "additionalProperties": false } }, "additionalProperties": false, diff --git a/src/common/schemas/objects/instructions.json b/src/common/schemas/objects/instructions.json index a267e0f7..81992090 100644 --- a/src/common/schemas/objects/instructions.json +++ b/src/common/schemas/objects/instructions.json @@ -28,6 +28,11 @@ "description": "Offset from current validated legder version to highest ledger version that the transaction can be included in.", "type": "integer", "minimum": 0 + }, + "signersCount": { + "description": "Number of signers that will be signing this transaction.", + "type": "integer", + "minimum": 1 } }, "additionalProperties": false, diff --git a/src/common/schemas/objects/settings.json b/src/common/schemas/objects/settings.json index 5fb97c31..2b4cad16 100644 --- a/src/common/schemas/objects/settings.json +++ b/src/common/schemas/objects/settings.json @@ -68,6 +68,35 @@ ], "description": "The public key of a new keypair, to use as the regular key to this account, as a base-58-encoded string in the same format as an account address. Use `null` to remove the regular key." }, + "signers": { + "type": "object", + "description": "Settings that determine what sets of accounts can be used to sign a transaction on behalf of this account using multisigning.", + "properties": { + "threshold": { + "$ref": "uint32", + "description": "A target number for the signer weights. A multi-signature from this list is valid only if the sum weights of the signatures provided is equal or greater than this value. To delete the signers setting, use the value `0`." + }, + "weights": { + "type": "array", + "description": "Weights of signatures for each signer.", + "items": { + "type": "object", + "description": "An association of an address and a weight.", + "properties": { + "address": {"$ref": "address"}, + "weight": { + "$ref": "uint32", + "description": "The weight that the signature of this account counts as towards the threshold." + } + }, + "required": ["address", "weight"], + "additionalProperties": false + }, + "minItems": 1, + "maxItems": 8 + } + } + }, "memos": {"$ref": "memos"} }, "additionalProperties": false diff --git a/src/common/types.js b/src/common/types.js index becbe0ae..0d7ad5a9 100644 --- a/src/common/types.js +++ b/src/common/types.js @@ -1,55 +1,55 @@ -/* @flow */ -'use strict'; - -export type RippledAmountIOU = { - currency: string, - value: string, - issuer?: string -} - -export type RippledAmount = string | RippledAmountIOU - - -export type Amount = { - value: string, - currency: string, - counterparty?: string -} - - -// Amount where counterparty and value are optional -export type LaxLaxAmount = { - currency: string, - value?: string, - counterparty?: string -} - -// A currency-counterparty pair, or just currency if it's XRP -export type Issue = { - currency: string, - counterparty?: string -} - -export type Adjustment = { - address: string, - amount: Amount, - tag?: number -} - -export type MaxAdjustment = { - address: string, - maxAmount: Amount, - tag?: number -} - -export type MinAdjustment = { - address: string, - minAmount: Amount, - tag?: number -} - -export type Memo = { - type?: string, - format?: string, - data?: string -} +/* @flow */ +'use strict'; + +export type RippledAmountIOU = { + currency: string, + value: string, + issuer?: string +} + +export type RippledAmount = string | RippledAmountIOU + + +export type Amount = { + value: string, + currency: string, + counterparty?: string +} + + +// Amount where counterparty and value are optional +export type LaxLaxAmount = { + currency: string, + value?: string, + counterparty?: string +} + +// A currency-counterparty pair, or just currency if it's XRP +export type Issue = { + currency: string, + counterparty?: string +} + +export type Adjustment = { + address: string, + amount: Amount, + tag?: number +} + +export type MaxAdjustment = { + address: string, + maxAmount: Amount, + tag?: number +} + +export type MinAdjustment = { + address: string, + minAmount: Amount, + tag?: number +} + +export type Memo = { + type?: string, + format?: string, + data?: string +} diff --git a/src/common/validate.js b/src/common/validate.js index bff6620b..7600cfe0 100644 --- a/src/common/validate.js +++ b/src/common/validate.js @@ -47,6 +47,7 @@ module.exports = { prepareSuspendedPaymentExecution: _.partial(schemaValidate, 'prepareSuspendedPaymentExecutionParameters'), sign: _.partial(schemaValidate, 'signParameters'), + combine: _.partial(schemaValidate, 'combineParameters'), submit: _.partial(schemaValidate, 'submitParameters'), computeLedgerHash: _.partial(schemaValidate, 'computeLedgerHashParameters'), generateAddress: _.partial(schemaValidate, 'generateAddressParameters'), diff --git a/src/ledger/parse/fields.js b/src/ledger/parse/fields.js index fb04704c..022a23a0 100644 --- a/src/ledger/parse/fields.js +++ b/src/ledger/parse/fields.js @@ -1,5 +1,6 @@ /* @flow */ 'use strict'; +const _ = require('lodash'); const BigNumber = require('bignumber.js'); const AccountFields = require('./utils').constants.AccountFields; @@ -22,6 +23,26 @@ function parseFields(data: Object): Object { settings[info.name] = parseField(info, fieldValue); } } + + if (data.RegularKey) { + settings.regularKey = data.RegularKey; + } + + // TODO: this isn't implemented in rippled yet, may have to change this later + if (data.SignerQuorum || data.SignerEntries) { + settings.signers = {}; + if (data.SignerQuorum) { + settings.signers.threshold = data.SignerQuorum; + } + if (data.SignerEntries) { + settings.signers.weights = _.map(data.SignerEntries, entry => { + return { + address: entry.SignerEntry.Account, + weight: entry.SignerEntry.SignerWeight + }; + }); + } + } return settings; } diff --git a/src/ledger/parse/settings.js b/src/ledger/parse/settings.js index d8eccc39..36b48aa7 100644 --- a/src/ledger/parse/settings.js +++ b/src/ledger/parse/settings.js @@ -52,10 +52,10 @@ function parseFlags(tx: Object) { function parseSettings(tx: Object) { const txType = tx.TransactionType; - assert(txType === 'AccountSet' || txType === 'SetRegularKey'); + assert(txType === 'AccountSet' || txType === 'SetRegularKey' || + txType === 'SignerListSet'); - const regularKey = tx.RegularKey ? {regularKey: tx.RegularKey} : {}; - return _.assign(regularKey, parseFlags(tx), parseFields(tx)); + return _.assign({}, parseFlags(tx), parseFields(tx)); } module.exports = parseSettings; diff --git a/src/ledger/parse/transaction.js b/src/ledger/parse/transaction.js index 0043ebd3..ee56795f 100644 --- a/src/ledger/parse/transaction.js +++ b/src/ledger/parse/transaction.js @@ -22,7 +22,8 @@ function parseTransactionType(type) { SetRegularKey: 'settings', SuspendedPaymentCreate: 'suspendedPaymentCreation', SuspendedPaymentFinish: 'suspendedPaymentExecution', - SuspendedPaymentCancel: 'suspendedPaymentCancellation' + SuspendedPaymentCancel: 'suspendedPaymentCancellation', + SignerListSet: 'settings' }; return mapping[type] || null; } diff --git a/src/ledger/transaction-types.js b/src/ledger/transaction-types.js index 496dbd73..7f6e71ac 100644 --- a/src/ledger/transaction-types.js +++ b/src/ledger/transaction-types.js @@ -1,135 +1,135 @@ -/* @flow */ -'use strict'; - -import type {Amount, Memo} from '../common/types.js'; - -type Outcome = { - result: string, - ledgerVersion: number, - indexInLedger: number, - fee: string, - balanceChanges: { - [key: string]: [{ - currency: string, - counterparty?: string, - value: string - }] - }, - orderbookChanges: Object, - timestamp?: string -} - -type Adjustment = { - address: string, - amount: { - currency: string, - counterparty?: string, - value: string - }, - tag?: number -} - -type Trustline = { - currency: string, - counterparty: string, - limit: string, - qualityIn?: number, - qualityOut?: number, - ripplingDisabled?: boolean, - authorized?: boolean, - frozen?: boolean -} - -type Settings = { - passwordSpent?: boolean, - requireDestinationTag?: boolean, - requireAuthorization?: boolean, - disallowIncomingXRP?: boolean, - disableMasterKey?: boolean, - enableTransactionIDTracking?: boolean, - noFreeze?: boolean, - globalFreeze?: boolean, - defaultRipple?: boolean, - emailHash?: string, - messageKey?: string, - domain?: string, - transferRate?: number, - regularKey?: string -} - -type OrderCancellation = { - orderSequence: number -} - -type Payment = { - source: Adjustment, - destination: Adjustment, - paths?: string, - memos?: Array, - invoiceID?: string, - allowPartialPayment?: boolean, - noDirectRipple?: boolean, - limitQuality?: boolean -} - -type PaymentTransaction = { - type: string, - specification: Payment, - outcome: Outcome, - id: string, - address: string, - sequence: number -} - -export type Order = { - direction: string, - quantity: Amount, - totalPrice: Amount, - immediateOrCancel?: boolean, - fillOrKill?: boolean, - passive?: boolean -} - -type OrderTransaction = { - type: string, - specification: Order, - outcome: Outcome, - id: string, - address: string, - sequence: number -} - -type OrderCancellationTransaction = { - type: string, - specification: OrderCancellation, - outcome: Outcome, - id: string, - address: string, - sequence: number -} - -type TrustlineTransaction = { - type: string, - specification: Trustline, - outcome: Outcome, - id: string, - address: string, - sequence: number -} - -type SettingsTransaction = { - type: string, - specification: Settings, - outcome: Outcome, - id: string, - address: string, - sequence: number -} - -export type TransactionOptions = { - minLedgerVersion?: number, - maxLedgerVersion?: number -} - -export type TransactionType = PaymentTransaction | OrderTransaction | - OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction +/* @flow */ +'use strict'; + +import type {Amount, Memo} from '../common/types.js'; + +type Outcome = { + result: string, + ledgerVersion: number, + indexInLedger: number, + fee: string, + balanceChanges: { + [key: string]: [{ + currency: string, + counterparty?: string, + value: string + }] + }, + orderbookChanges: Object, + timestamp?: string +} + +type Adjustment = { + address: string, + amount: { + currency: string, + counterparty?: string, + value: string + }, + tag?: number +} + +type Trustline = { + currency: string, + counterparty: string, + limit: string, + qualityIn?: number, + qualityOut?: number, + ripplingDisabled?: boolean, + authorized?: boolean, + frozen?: boolean +} + +type Settings = { + passwordSpent?: boolean, + requireDestinationTag?: boolean, + requireAuthorization?: boolean, + disallowIncomingXRP?: boolean, + disableMasterKey?: boolean, + enableTransactionIDTracking?: boolean, + noFreeze?: boolean, + globalFreeze?: boolean, + defaultRipple?: boolean, + emailHash?: string, + messageKey?: string, + domain?: string, + transferRate?: number, + regularKey?: string +} + +type OrderCancellation = { + orderSequence: number +} + +type Payment = { + source: Adjustment, + destination: Adjustment, + paths?: string, + memos?: Array, + invoiceID?: string, + allowPartialPayment?: boolean, + noDirectRipple?: boolean, + limitQuality?: boolean +} + +type PaymentTransaction = { + type: string, + specification: Payment, + outcome: Outcome, + id: string, + address: string, + sequence: number +} + +export type Order = { + direction: string, + quantity: Amount, + totalPrice: Amount, + immediateOrCancel?: boolean, + fillOrKill?: boolean, + passive?: boolean +} + +type OrderTransaction = { + type: string, + specification: Order, + outcome: Outcome, + id: string, + address: string, + sequence: number +} + +type OrderCancellationTransaction = { + type: string, + specification: OrderCancellation, + outcome: Outcome, + id: string, + address: string, + sequence: number +} + +type TrustlineTransaction = { + type: string, + specification: Trustline, + outcome: Outcome, + id: string, + address: string, + sequence: number +} + +type SettingsTransaction = { + type: string, + specification: Settings, + outcome: Outcome, + id: string, + address: string, + sequence: number +} + +export type TransactionOptions = { + minLedgerVersion?: number, + maxLedgerVersion?: number +} + +export type TransactionType = PaymentTransaction | OrderTransaction | + OrderCancellationTransaction | TrustlineTransaction | SettingsTransaction diff --git a/src/ledger/trustlines-types.js b/src/ledger/trustlines-types.js index ee2558da..853b56ab 100644 --- a/src/ledger/trustlines-types.js +++ b/src/ledger/trustlines-types.js @@ -1,33 +1,33 @@ -/* @flow */ -'use strict'; - -export type TrustLineSpecification = { - currency: string, - counterparty: string, - limit: string, - qualityIn?: number, - qualityOut?: number, - ripplingDisabled?: boolean, - authorized?: boolean, - frozen?: boolean -} - -export type Trustline = { - specification: TrustLineSpecification, - counterparty: { - limit: string, - ripplingDisabled?: boolean, - frozen?: boolean, - authorized?: boolean - }, - state: { - balance: string - } -} - -export type TrustlinesOptions = { - counterparty?: string, - currency?: string, - limit?: number, - ledgerVersion?: number -} +/* @flow */ +'use strict'; + +export type TrustLineSpecification = { + currency: string, + counterparty: string, + limit: string, + qualityIn?: number, + qualityOut?: number, + ripplingDisabled?: boolean, + authorized?: boolean, + frozen?: boolean +} + +export type Trustline = { + specification: TrustLineSpecification, + counterparty: { + limit: string, + ripplingDisabled?: boolean, + frozen?: boolean, + authorized?: boolean + }, + state: { + balance: string + } +} + +export type TrustlinesOptions = { + counterparty?: string, + currency?: string, + limit?: number, + ledgerVersion?: number +} diff --git a/src/ledger/types.js b/src/ledger/types.js index 66c2e0ea..05f0a18e 100644 --- a/src/ledger/types.js +++ b/src/ledger/types.js @@ -1,50 +1,50 @@ -/* @flow */ -'use strict'; - -import type {Amount} from '../common/types.js'; - -export type OrdersOptions = { - limit?: number, - ledgerVersion?: number -} - -export type OrderSpecification = { - direction: string, - quantity: Amount, - totalPrice: Amount, - immediateOrCancel?: boolean, - fillOrKill?: boolean, - // If enabled, the offer will not consume offers that exactly match it, and - // instead becomes an Offer node in the ledger. It will still consume offers - // that cross it. - passive?: boolean -} - -export type Order = { - specification: OrderSpecification, - properties: { - maker: string, - sequence: number, - makerExchangeRate: string - } -} - -export type GetLedger = { - accepted: boolean, - closed: boolean, - stateHash: string, - closeTime: number, - closeTimeResolution: number, - closeFlags: number, - ledgerHash: string, - ledgerVersion: number, - parentLedgerHash: string, - parentCloseTime: number, - totalDrops: string, - transactionHash: string, - transactions?: Array, - rawTransactions?: string, - transactionHashes?: Array, - rawState?: string, - stateHashes?: Array -} +/* @flow */ +'use strict'; + +import type {Amount} from '../common/types.js'; + +export type OrdersOptions = { + limit?: number, + ledgerVersion?: number +} + +export type OrderSpecification = { + direction: string, + quantity: Amount, + totalPrice: Amount, + immediateOrCancel?: boolean, + fillOrKill?: boolean, + // If enabled, the offer will not consume offers that exactly match it, and + // instead becomes an Offer node in the ledger. It will still consume offers + // that cross it. + passive?: boolean +} + +export type Order = { + specification: OrderSpecification, + properties: { + maker: string, + sequence: number, + makerExchangeRate: string + } +} + +export type GetLedger = { + accepted: boolean, + closed: boolean, + stateHash: string, + closeTime: number, + closeTimeResolution: number, + closeFlags: number, + ledgerHash: string, + ledgerVersion: number, + parentLedgerHash: string, + parentCloseTime: number, + totalDrops: string, + transactionHash: string, + transactions?: Array, + rawTransactions?: string, + transactionHashes?: Array, + rawState?: string, + stateHashes?: Array +} diff --git a/src/transaction/combine.js b/src/transaction/combine.js new file mode 100644 index 00000000..d2098f53 --- /dev/null +++ b/src/transaction/combine.js @@ -0,0 +1,39 @@ +/* @flow */ +'use strict'; +const _ = require('lodash'); +const binary = require('ripple-binary-codec'); +const utils = require('./utils'); +const BigNumber = require('bignumber.js'); +const {decodeAddress} = require('ripple-address-codec'); +const {validate} = utils.common; +const {computeBinaryTransactionHash} = require('ripple-hashes'); + +function addressToBigNumber(address) { + const hex = (new Buffer(decodeAddress(address))).toString('hex'); + return new BigNumber(hex, 16); +} + +function compareSigners(a, b) { + return addressToBigNumber(a.Signer.Account) + .comparedTo(addressToBigNumber(b.Signer.Account)); +} + +function combine(signedTransactions: Array): Object { + validate.combine({signedTransactions}); + + const txs = _.map(signedTransactions, binary.decode); + const tx = _.omit(txs[0], 'Signers'); + if (!_.every(txs, _tx => _.isEqual(tx, _.omit(_tx, 'Signers')))) { + throw new utils.common.errors.ValidationError( + 'txJSON is not the same for all signedTransactions'); + } + const unsortedSigners = _.reduce(txs, (accumulator, _tx) => + accumulator.concat(_tx.Signers || []), []); + const signers = unsortedSigners.sort(compareSigners); + const signedTx = _.assign({}, tx, {Signers: signers}); + const signedTransaction = binary.encode(signedTx); + const id = computeBinaryTransactionHash(signedTransaction); + return {signedTransaction, id}; +} + +module.exports = combine; diff --git a/src/transaction/settings-types.js b/src/transaction/settings-types.js index 695dea2f..18567891 100644 --- a/src/transaction/settings-types.js +++ b/src/transaction/settings-types.js @@ -1,53 +1,53 @@ -/* @flow */ -'use strict'; - -type SettingPasswordSpent = { - passwordSpent?: boolean, -} -type SettingRequireDestinationTag = { - requireDestinationTag?: boolean, -} -type SettingRequireAuthorization = { - requireAuthorization?: boolean, -} -type SettingDisallowIncomingXRP = { - disallowIncomingXRP?: boolean, -} -type SettingDisableMasterKey = { - disableMasterKey?: boolean, -} -type SettingEnableTransactionIDTracking = { - enableTransactionIDTracking?: boolean, -} -type SettingNoFreeze = { - noFreeze?: boolean, -} -type SettingGlobalFreeze = { - globalFreeze?: boolean, -} -type SettingDefaultRipple = { - defaultRipple?: boolean, -} -type SettingEmailHash = { - emailHash?: ?string, -} -type SettingMessageKey = { - messageKey?: string, -} -type SettingDomain = { - domain?: string, -} -type SettingTransferRate = { - transferRate?: ?number, -} -type SettingRegularKey = { - regularKey?: string -} - -export type Settings = SettingRegularKey | - SettingTransferRate | SettingDomain | SettingMessageKey | - SettingEmailHash | SettingDefaultRipple | - SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking | - SettingDisableMasterKey | SettingDisallowIncomingXRP | - SettingRequireAuthorization | SettingRequireDestinationTag | - SettingPasswordSpent +/* @flow */ +'use strict'; + +type SettingPasswordSpent = { + passwordSpent?: boolean, +} +type SettingRequireDestinationTag = { + requireDestinationTag?: boolean, +} +type SettingRequireAuthorization = { + requireAuthorization?: boolean, +} +type SettingDisallowIncomingXRP = { + disallowIncomingXRP?: boolean, +} +type SettingDisableMasterKey = { + disableMasterKey?: boolean, +} +type SettingEnableTransactionIDTracking = { + enableTransactionIDTracking?: boolean, +} +type SettingNoFreeze = { + noFreeze?: boolean, +} +type SettingGlobalFreeze = { + globalFreeze?: boolean, +} +type SettingDefaultRipple = { + defaultRipple?: boolean, +} +type SettingEmailHash = { + emailHash?: ?string, +} +type SettingMessageKey = { + messageKey?: string, +} +type SettingDomain = { + domain?: string, +} +type SettingTransferRate = { + transferRate?: ?number, +} +type SettingRegularKey = { + regularKey?: string +} + +export type Settings = SettingRegularKey | + SettingTransferRate | SettingDomain | SettingMessageKey | + SettingEmailHash | SettingDefaultRipple | + SettingGlobalFreeze | SettingNoFreeze | SettingEnableTransactionIDTracking | + SettingDisableMasterKey | SettingDisallowIncomingXRP | + SettingRequireAuthorization | SettingRequireDestinationTag | + SettingPasswordSpent diff --git a/src/transaction/settings.js b/src/transaction/settings.js index b4d09c3b..d6682821 100644 --- a/src/transaction/settings.js +++ b/src/transaction/settings.js @@ -70,7 +70,17 @@ function convertTransferRate(transferRate: number | string): number | string { return (new BigNumber(transferRate)).shift(9).toNumber(); } -function createSettingsTransaction(account: string, settings: Settings +function formatSignerEntry(signer: Object): Object { + return { + SignerEntry: { + Account: signer.address, + SignerWeight: signer.weight + } + }; +} + +function createSettingsTransactionWithoutMemos( + account: string, settings: Settings ): Object { if (settings.regularKey !== undefined) { const removeRegularKey = { @@ -83,15 +93,20 @@ function createSettingsTransaction(account: string, settings: Settings return _.assign({}, removeRegularKey, {RegularKey: settings.regularKey}); } + if (settings.signers !== undefined) { + return { + TransactionType: 'SignerListSet', + Account: account, + SignerQuorum: settings.signers.threshold, + SignerEntries: _.map(settings.signers.weights, formatSignerEntry) + }; + } + const txJSON: Object = { TransactionType: 'AccountSet', Account: account }; - if (settings.memos !== undefined) { - txJSON.Memos = _.map(settings.memos, utils.convertMemo); - } - setTransactionFlags(txJSON, _.omit(settings, 'memos')); setTransactionFields(txJSON, settings); @@ -101,6 +116,15 @@ function createSettingsTransaction(account: string, settings: Settings return txJSON; } +function createSettingsTransaction(account: string, settings: Settings +): Object { + const txJSON = createSettingsTransactionWithoutMemos(account, settings); + if (settings.memos !== undefined) { + txJSON.Memos = _.map(settings.memos, utils.convertMemo); + } + return txJSON; +} + function prepareSettings(address: string, settings: Settings, instructions: Instructions = {} ): Promise { diff --git a/src/transaction/sign.js b/src/transaction/sign.js index aeba262c..92be4342 100644 --- a/src/transaction/sign.js +++ b/src/transaction/sign.js @@ -6,23 +6,38 @@ const binary = require('ripple-binary-codec'); const {computeBinaryTransactionHash} = require('ripple-hashes'); const validate = utils.common.validate; -function computeSignature(txJSON, privateKey) { - const signingData = binary.encodeForSigning(txJSON); +function computeSignature(tx: Object, privateKey: string, signAs: ?string) { + const signingData = signAs ? + binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx); return keypairs.sign(signingData, privateKey); } -function sign(txJSON: string, secret: string +function sign(txJSON: string, secret: string, options: Object = {} ): {signedTransaction: string; id: string} { validate.sign({txJSON, secret}); // we can't validate that the secret matches the account because // the secret could correspond to the regular key const tx = JSON.parse(txJSON); - const keypair = keypairs.deriveKeypair(secret); - if (tx.SigningPubKey === undefined) { - tx.SigningPubKey = keypair.publicKey; + if (tx.TxnSignature || tx.Signers) { + throw new utils.common.errors.ValidationError( + 'txJSON must not contain "TxnSignature" or "Signers" properties'); } - tx.TxnSignature = computeSignature(tx, keypair.privateKey); + + const keypair = keypairs.deriveKeypair(secret); + tx.SigningPubKey = options.signAs ? '' : keypair.publicKey; + + if (options.signAs) { + const signer = { + Account: options.signAs, + SigningPubKey: keypair.publicKey, + TxnSignature: computeSignature(tx, keypair.privateKey, options.signAs) + }; + tx.Signers = [{Signer: signer}]; + } else { + tx.TxnSignature = computeSignature(tx, keypair.privateKey); + } + const serialized = binary.encode(tx); return { signedTransaction: serialized, diff --git a/src/transaction/submit.js b/src/transaction/submit.js index 21cd7a45..5b438d24 100644 --- a/src/transaction/submit.js +++ b/src/transaction/submit.js @@ -3,15 +3,7 @@ const _ = require('lodash'); const utils = require('./utils'); const {validate} = utils.common; - -type Submit = { - success: boolean, - engineResult: string, - engineResultCode: number, - engineResultMessage?: string, - txBlob?: string, - txJson?: Object -} +import type {Submit} from './types.js'; function isImmediateRejection(engineResult: string): boolean { // note: "tel" errors mean the local server refused to process the @@ -23,7 +15,7 @@ function isImmediateRejection(engineResult: string): boolean { return _.startsWith(engineResult, 'tem') || _.startsWith(engineResult, 'tej'); } -function formatResponse(response) { +function formatSubmitResponse(response) { const data = { resultCode: response.engine_result, resultMessage: response.engine_result_message @@ -36,11 +28,12 @@ function formatResponse(response) { function submit(signedTransaction: string): Promise { validate.submit({signedTransaction}); + const request = { command: 'submit', tx_blob: signedTransaction }; - return this.connection.request(request).then(formatResponse); + return this.connection.request(request).then(formatSubmitResponse); } module.exports = submit; diff --git a/src/transaction/types.js b/src/transaction/types.js index 9093548f..31eaa5e9 100644 --- a/src/transaction/types.js +++ b/src/transaction/types.js @@ -1,19 +1,29 @@ -/* @flow */ -'use strict'; - -export type Instructions = { - sequence?: number, - fee?: string, - maxFee?: string, - maxLedgerVersion?: number, - maxLedgerVersionOffset?: number -} - -export type Prepare = { - txJSON: string, - instructions: { - fee: string, - sequence: number, - maxLedgerVersion?: number - } -} +/* @flow */ +'use strict'; + +export type Instructions = { + sequence?: number, + fee?: string, + maxFee?: string, + maxLedgerVersion?: number, + maxLedgerVersionOffset?: number, + signersCount?: number +} + +export type Prepare = { + txJSON: string, + instructions: { + fee: string, + sequence: number, + maxLedgerVersion?: number + } +} + +export type Submit = { + success: boolean, + engineResult: string, + engineResultCode: number, + engineResultMessage?: string, + txBlob?: string, + txJson?: Object +} diff --git a/src/transaction/utils.js b/src/transaction/utils.js index 532e67d9..1dfe8de8 100644 --- a/src/transaction/utils.js +++ b/src/transaction/utils.js @@ -27,6 +27,10 @@ function setCanonicalFlag(txJSON) { txJSON.Flags = txJSON.Flags >>> 0; } +function scaleValue(value, multiplier) { + return (new BigNumber(value)).times(multiplier).toString(); +} + function prepareTransaction(txJSON: Object, api: Object, instructions: Instructions ): Promise { @@ -51,8 +55,10 @@ function prepareTransaction(txJSON: Object, api: Object, } function prepareFee(): Promise { + const multiplier = instructions.signersCount === undefined ? 1 : + instructions.signersCount + 1; if (instructions.fee !== undefined) { - txJSON.Fee = common.xrpToDrops(instructions.fee); + txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier); return Promise.resolve(txJSON); } const cushion = api._feeCushion; @@ -60,9 +66,10 @@ function prepareTransaction(txJSON: Object, api: Object, const feeDrops = common.xrpToDrops(fee); if (instructions.maxFee !== undefined) { const maxFeeDrops = common.xrpToDrops(instructions.maxFee); - txJSON.Fee = BigNumber.min(feeDrops, maxFeeDrops).toString(); + const normalFee = BigNumber.min(feeDrops, maxFeeDrops).toString(); + txJSON.Fee = scaleValue(normalFee, multiplier); } else { - txJSON.Fee = feeDrops; + txJSON.Fee = scaleValue(feeDrops, multiplier); } return txJSON; }); diff --git a/test/api-test.js b/test/api-test.js index 84462a2e..4f84e84c 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -183,20 +183,20 @@ describe('RippleAPI', function() { it('prepareSettings', function() { return this.api.prepareSettings( - address, requests.prepareSettings, instructions).then( + address, requests.prepareSettings.domain, instructions).then( _.partial(checkResult, responses.prepareSettings.flags, 'prepare')); }); it('prepareSettings - no maxLedgerVersion', function() { return this.api.prepareSettings( - address, requests.prepareSettings, {maxLedgerVersion: null}).then( + address, requests.prepareSettings.domain, {maxLedgerVersion: null}).then( _.partial(checkResult, responses.prepareSettings.noMaxLedgerVersion, 'prepare')); }); it('prepareSettings - no instructions', function() { return this.api.prepareSettings( - address, requests.prepareSettings).then( + address, requests.prepareSettings.domain).then( _.partial( checkResult, responses.prepareSettings.noInstructions, @@ -244,6 +244,23 @@ describe('RippleAPI', function() { 'prepare')); }); + it('prepareSettings - set signers', function() { + const settings = requests.prepareSettings.signers; + return this.api.prepareSettings(address, settings, instructions).then( + _.partial(checkResult, responses.prepareSettings.signers, + 'prepare')); + }); + + it('prepareSettings - fee for multisign', function() { + const localInstructions = _.defaults({ + signersCount: 4 + }, instructions); + return this.api.prepareSettings( + address, requests.prepareSettings.domain, localInstructions).then( + _.partial(checkResult, responses.prepareSettings.flagsMultisign, + 'prepare')); + }); + it('prepareSuspendedPaymentCreation', function() { const localInstructions = _.defaults({ maxFee: '0.000012' @@ -312,6 +329,14 @@ describe('RippleAPI', function() { schemaValidator.schemaValidate('sign', result); }); + it('sign - signAs', function() { + const txJSON = requests.sign.signAs; + const secret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'; + const signature = this.api.sign(JSON.stringify(txJSON), secret, + {signAs: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'}); + assert.deepEqual(signature, responses.sign.signAs); + }); + it('submit', function() { return this.api.submit(responses.sign.normal.signedTransaction).then( _.partial(checkResult, responses.submit, 'submit')); @@ -326,6 +351,11 @@ describe('RippleAPI', function() { }); }); + it('combine', function() { + const combined = this.api.combine(requests.combine.setDomain); + checkResult(responses.combine.single, 'sign', combined); + }); + describe('RippleAPI', function() { it('getBalances', function() { @@ -1255,7 +1285,7 @@ describe('RippleAPI - offline', function() { it('prepareSettings and sign', function() { const api = new RippleAPI(); const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'; - const settings = requests.prepareSettings; + const settings = requests.prepareSettings.domain; const instructions = { sequence: 23, maxLedgerVersion: 8820051, diff --git a/test/broadcast-api-test.js b/test/broadcast-api-test.js index 993d3f3d..bdf6bbd4 100644 --- a/test/broadcast-api-test.js +++ b/test/broadcast-api-test.js @@ -42,7 +42,6 @@ describe('RippleAPIBroadcast', function() { this.mocks.forEach(mock => mock.socket.send(JSON.stringify(ledgerNext))); setTimeout(() => { - console.log('-- ledgerVersion', this.api.ledgerVersion); assert.strictEqual(gotLedger, 1); done(); }, 50); diff --git a/test/connection-test.js b/test/connection-test.js index 24f2dd88..0e14cbc7 100644 --- a/test/connection-test.js +++ b/test/connection-test.js @@ -7,6 +7,7 @@ const assert = require('assert-diff'); const setupAPI = require('./setup-api'); const RippleAPI = require('ripple-api').RippleAPI; const utils = RippleAPI._PRIVATE.ledgerUtils; +const ledgerClose = require('./fixtures/rippled/ledger-close.json'); function unused() { @@ -261,4 +262,13 @@ describe('Connection', function() { this.api.connection._onMessage(JSON.stringify({type: 'unknown'})); }); + + it('ledger close without validated_ledgers', function(done) { + const message = _.omit(ledgerClose, 'validated_ledgers'); + this.api.on('ledger', function(ledger) { + assert.strictEqual(ledger.ledgerVersion, 8819951); + done(); + }); + this.api.connection._ws.emit('message', JSON.stringify(message)); + }); }); diff --git a/test/fixtures/requests/combine.json b/test/fixtures/requests/combine.json new file mode 100644 index 00000000..480ebbc9 --- /dev/null +++ b/test/fixtures/requests/combine.json @@ -0,0 +1,2 @@ +[ "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1", + "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1F1" ] diff --git a/test/fixtures/requests/get-orderbook-with-xrp.json b/test/fixtures/requests/get-orderbook-with-xrp.json index d8016465..308c9191 100644 --- a/test/fixtures/requests/get-orderbook-with-xrp.json +++ b/test/fixtures/requests/get-orderbook-with-xrp.json @@ -1,9 +1,9 @@ -{ - "base": { - "currency": "USD", - "counterparty": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw" - }, - "counter": { - "currency": "XRP" - } +{ + "base": { + "currency": "USD", + "counterparty": "rp8rJYTpodf8qbSCHVTNacf8nSW8mRakFw" + }, + "counter": { + "currency": "XRP" + } } \ No newline at end of file diff --git a/test/fixtures/requests/getpaths/normal.json b/test/fixtures/requests/getpaths/normal.json index 01c4980a..d3aca127 100644 --- a/test/fixtures/requests/getpaths/normal.json +++ b/test/fixtures/requests/getpaths/normal.json @@ -1,13 +1,13 @@ -{ - "source": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" - }, - "destination": { - "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "amount": { - "currency": "USD", - "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", - "value": "100" - } - } +{ + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "amount": { + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "value": "100" + } + } } \ No newline at end of file diff --git a/test/fixtures/requests/getpaths/send-all.json b/test/fixtures/requests/getpaths/send-all.json index e0bd51f9..b6321e71 100644 --- a/test/fixtures/requests/getpaths/send-all.json +++ b/test/fixtures/requests/getpaths/send-all.json @@ -1,15 +1,15 @@ -{ - "source": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", - "amount": { - "currency": "USD", - "value": "5" - } - }, - "destination": { - "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", - "amount": { - "currency": "USD" - } - } -} +{ + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "5" + } + }, + "destination": { + "address": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX", + "amount": { + "currency": "USD" + } + } +} diff --git a/test/fixtures/requests/getpaths/usd2usd.json b/test/fixtures/requests/getpaths/usd2usd.json index 30af3080..6d7fd415 100644 --- a/test/fixtures/requests/getpaths/usd2usd.json +++ b/test/fixtures/requests/getpaths/usd2usd.json @@ -1,20 +1,20 @@ -{ - "source": { - "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "currencies": [ - { - "currency": "LTC" - }, - { - "currency": "USD" - } - ] - }, - "destination": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", - "amount": { - "currency": "USD", - "value": "0.000001" - } - } +{ + "source": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "currencies": [ + { + "currency": "LTC" + }, + { + "currency": "USD" + } + ] + }, + "destination": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "currency": "USD", + "value": "0.000001" + } + } } \ No newline at end of file diff --git a/test/fixtures/requests/getpaths/xrp2xrp.json b/test/fixtures/requests/getpaths/xrp2xrp.json index 51709a93..5c515989 100644 --- a/test/fixtures/requests/getpaths/xrp2xrp.json +++ b/test/fixtures/requests/getpaths/xrp2xrp.json @@ -1,12 +1,12 @@ -{ - "source": { - "address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J" - }, - "destination": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", - "amount": { - "value": "0.000002", - "currency": "XRP" - } - } -} +{ + "source": { + "address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J" + }, + "destination": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "value": "0.000002", + "currency": "XRP" + } + } +} diff --git a/test/fixtures/requests/index.js b/test/fixtures/requests/index.js index 84b9ca16..199a5c0c 100644 --- a/test/fixtures/requests/index.js +++ b/test/fixtures/requests/index.js @@ -20,7 +20,10 @@ module.exports = { allOptions: require('./prepare-payment-all-options'), noCounterparty: require('./prepare-payment-no-counterparty') }, - prepareSettings: require('./prepare-settings'), + prepareSettings: { + domain: require('./prepare-settings'), + signers: require('./prepare-settings-signers') + }, prepareSuspendedPaymentCreation: { normal: require('./prepare-suspended-payment-creation'), full: require('./prepare-suspended-payment-creation-full') @@ -40,7 +43,8 @@ module.exports = { }, sign: { normal: require('./sign'), - suspended: require('./sign-suspended.json') + suspended: require('./sign-suspended.json'), + signAs: require('./sign-as') }, getPaths: { normal: require('./getpaths/normal'), @@ -61,5 +65,8 @@ module.exports = { computeLedgerHash: { header: require('./compute-ledger-hash'), transactions: require('./compute-ledger-hash-transactions') + }, + combine: { + setDomain: require('./combine.json') } }; diff --git a/test/fixtures/requests/prepare-payment-wrong-address.json b/test/fixtures/requests/prepare-payment-wrong-address.json index 0533a0b9..4be603e1 100644 --- a/test/fixtures/requests/prepare-payment-wrong-address.json +++ b/test/fixtures/requests/prepare-payment-wrong-address.json @@ -1,17 +1,17 @@ -{ - "source": { - "address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", - "amount": { - "value": "0.01", - "currency": "USD", - "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM" - } - }, - "destination": { - "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "minAmount": { - "value": "0.01", - "currency": "XRP" - } - } -} +{ + "source": { + "address": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM", + "amount": { + "value": "0.01", + "currency": "USD", + "counterparty": "rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM" + } + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "minAmount": { + "value": "0.01", + "currency": "XRP" + } + } +} diff --git a/test/fixtures/requests/prepare-payment-wrong-partial.json b/test/fixtures/requests/prepare-payment-wrong-partial.json index d94e7b62..de630b82 100644 --- a/test/fixtures/requests/prepare-payment-wrong-partial.json +++ b/test/fixtures/requests/prepare-payment-wrong-partial.json @@ -1,17 +1,17 @@ -{ - "source": { - "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", - "amount": { - "value": "0.01", - "currency": "XRP" - } - }, - "destination": { - "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", - "minAmount": { - "value": "0.01", - "currency": "XRP" - } - }, - "allowPartialPayment": true -} +{ + "source": { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "amount": { + "value": "0.01", + "currency": "XRP" + } + }, + "destination": { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "minAmount": { + "value": "0.01", + "currency": "XRP" + } + }, + "allowPartialPayment": true +} diff --git a/test/fixtures/requests/prepare-settings-signers.json b/test/fixtures/requests/prepare-settings-signers.json new file mode 100644 index 00000000..abb8723a --- /dev/null +++ b/test/fixtures/requests/prepare-settings-signers.json @@ -0,0 +1,19 @@ +{ + "signers": { + "threshold": 2, + "weights": [ + { + "address": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59", + "weight": 1 + }, + { + "address": "rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo", + "weight": 1 + }, + { + "address": "rwBYyfufTzk77zUSKEu4MvixfarC35av1J", + "weight": 1 + } + ] + } +} diff --git a/test/fixtures/requests/sign-as.json b/test/fixtures/requests/sign-as.json new file mode 100644 index 00000000..df1e9e04 --- /dev/null +++ b/test/fixtures/requests/sign-as.json @@ -0,0 +1,8 @@ +{ + "Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", + "Amount": "1000000000", + "Destination": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", + "Fee": "50", + "Sequence": 2, + "TransactionType": "Payment" +} diff --git a/test/fixtures/responses/combine.json b/test/fixtures/responses/combine.json new file mode 100644 index 00000000..151ac1e5 --- /dev/null +++ b/test/fixtures/responses/combine.json @@ -0,0 +1,4 @@ +{ + "signedTransaction": "12000322800000002400000004201B000000116840000000000F42407300770B6578616D706C652E636F6D811407C532442A675C881BA1235354D4AB9D023243A6F3E01073210287AAAB8FBE8C4C4A47F6F1228C6E5123A7ED844BFE88A9B22C2F7CC34279EEAA74473045022100B09DDF23144595B5A9523B20E605E138DC6549F5CA7B5984D7C32B0E3469DF6B022018845CA6C203D4B6288C87DDA439134C83E7ADF8358BD41A8A9141A9B631419F8114517D9B9609229E0CDFE2428B586738C5B2E84D45E1E0107321026C784C1987F83BACBF02CD3E484AFC84ADE5CA6B36ED4DCA06D5BA233B9D382774473045022100E484F54FF909469FA2033E22EFF3DF8EDFE62217062680BB2F3EDF2F185074FE0220350DB29001C710F0450DAF466C5D819DC6D6A3340602DE9B6CB7DA8E17C90F798114FE9337B0574213FA5BCC0A319DBB4A7AC0CCA894E1F1", + "id": "8A3BFD2214B4C8271ED62648FCE9ADE4EE82EF01827CF7D1F7ED497549A368CC" +} diff --git a/test/fixtures/responses/get-transaction-suspended-payment-execution-simple.json b/test/fixtures/responses/get-transaction-suspended-payment-execution-simple.json index 437499cd..8c9d0b6f 100644 --- a/test/fixtures/responses/get-transaction-suspended-payment-execution-simple.json +++ b/test/fixtures/responses/get-transaction-suspended-payment-execution-simple.json @@ -31,4 +31,4 @@ "ledgerVersion": 14, "indexInLedger": 0 } -} +} diff --git a/test/fixtures/responses/get-transaction-suspended-payment-execution.json b/test/fixtures/responses/get-transaction-suspended-payment-execution.json index ee6ceeff..12e40ad9 100644 --- a/test/fixtures/responses/get-transaction-suspended-payment-execution.json +++ b/test/fixtures/responses/get-transaction-suspended-payment-execution.json @@ -32,4 +32,4 @@ "ledgerVersion": 14, "indexInLedger": 0 } -} +} diff --git a/test/fixtures/responses/get-trustlines-all.json b/test/fixtures/responses/get-trustlines-all.json index 2ce58fbf..12a7141d 100644 --- a/test/fixtures/responses/get-trustlines-all.json +++ b/test/fixtures/responses/get-trustlines-all.json @@ -331,4 +331,4 @@ "balance": "0" } } -] +] diff --git a/test/fixtures/responses/index.js b/test/fixtures/responses/index.js index c17e8751..2281f43a 100644 --- a/test/fixtures/responses/index.js +++ b/test/fixtures/responses/index.js @@ -81,13 +81,15 @@ module.exports = { regularKey: require('./prepare-settings-regular-key.json'), removeRegularKey: require('./prepare-settings-remove-regular-key.json'), flags: require('./prepare-settings.json'), + flagsMultisign: require('./prepare-settings-multisign.json'), flagSet: require('./prepare-settings-flag-set.json'), flagClear: require('./prepare-settings-flag-clear.json'), setTransferRate: require('./prepare-settings-set-transfer-rate.json'), fieldClear: require('./prepare-settings-field-clear.json'), noInstructions: require('./prepare-settings-no-instructions.json'), signed: require('./prepare-settings-signed.json'), - noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json') + noMaxLedgerVersion: require('./prepare-settings-no-maxledgerversion.json'), + signers: require('./prepare-settings-signers.json') }, prepareSuspendedPaymentCreation: { normal: require('./prepare-suspended-payment-creation'), @@ -108,7 +110,11 @@ module.exports = { }, sign: { normal: require('./sign.json'), - suspended: require('./sign-suspended.json') + suspended: require('./sign-suspended.json'), + signAs: require('./sign-as') + }, + combine: { + single: require('./combine.json') }, submit: require('./submit.json'), ledgerEvent: require('./ledger-event.json') diff --git a/test/fixtures/responses/prepare-payment-no-counterparty.json b/test/fixtures/responses/prepare-payment-no-counterparty.json index 056eb34c..27f522e2 100644 --- a/test/fixtures/responses/prepare-payment-no-counterparty.json +++ b/test/fixtures/responses/prepare-payment-no-counterparty.json @@ -1,8 +1,8 @@ -{ - "txJSON": "{\"Flags\":2147942400,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"LTC\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\"},\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"Paths\":[[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"},{\"account\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", - "instructions": { - "fee": "0.000012", - "sequence": 23, - "maxLedgerVersion": 8820051 - } -} +{ + "txJSON": "{\"Flags\":2147942400,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"LTC\",\"issuer\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\"},\"InvoiceID\":\"A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A\",\"SourceTag\":14,\"DestinationTag\":58,\"Memos\":[{\"Memo\":{\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\",\"MemoData\":\"7465787465642064617461\"}}],\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\"},\"Paths\":[[{\"account\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"issuer\":\"rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q\",\"currency\":\"USD\"},{\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"},{\"account\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"issuer\":\"rfYv1TXnwgDDK4WQNbFALykYuEBnrR4pDX\",\"currency\":\"LTC\"}]],\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/responses/prepare-settings-field-clear.json b/test/fixtures/responses/prepare-settings-field-clear.json index 99eb63b2..4f0be207 100644 --- a/test/fixtures/responses/prepare-settings-field-clear.json +++ b/test/fixtures/responses/prepare-settings-field-clear.json @@ -1,8 +1,8 @@ -{ - "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"WalletLocator\":\"0\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", - "instructions": { - "fee": "0.000012", - "sequence": 23, - "maxLedgerVersion": 8820051 - } -} +{ + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"WalletLocator\":\"0\",\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/responses/prepare-settings-multisign.json b/test/fixtures/responses/prepare-settings-multisign.json new file mode 100644 index 00000000..506127cd --- /dev/null +++ b/test/fixtures/responses/prepare-settings-multisign.json @@ -0,0 +1,8 @@ +{ + "txJSON": "{\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Memos\":[{\"Memo\":{\"MemoData\":\"7465787465642064617461\",\"MemoType\":\"74657374\",\"MemoFormat\":\"706C61696E2F74657874\"}}],\"Domain\":\"726970706C652E636F6D\",\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"60\",\"Sequence\":23}", + "instructions": { + "fee": "0.00006", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/responses/prepare-settings-set-transfer-rate.json b/test/fixtures/responses/prepare-settings-set-transfer-rate.json index 104c4338..387409d1 100644 --- a/test/fixtures/responses/prepare-settings-set-transfer-rate.json +++ b/test/fixtures/responses/prepare-settings-set-transfer-rate.json @@ -1,8 +1,8 @@ -{ - "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TransferRate\":1000000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", - "instructions": { - "fee": "0.000012", - "sequence": 23, - "maxLedgerVersion": 8820051 - } -} +{ + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"TransferRate\":1000000000,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/responses/prepare-settings-signed.json b/test/fixtures/responses/prepare-settings-signed.json index be63f9cd..8d421d46 100644 --- a/test/fixtures/responses/prepare-settings-signed.json +++ b/test/fixtures/responses/prepare-settings-signed.json @@ -1,4 +1,4 @@ -{ +{ "signedTransaction": "12000322800000002400000017201B0086955368400000000000000C732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402202FBF6A6F74DFDA17C7341D532B66141206BC71A147C08DBDA6A950AA9A1741DC022055859A39F2486A46487F8DA261E3D80B4FDD26178A716A929F26377D1BEC7E43770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304F9EA7C04746573747D0B74657874656420646174617E0A706C61696E2F74657874E1F1", "id": "4755D26FAC39E3E477870D4E03CC6783DDDF967FFBE240606755D3D03702FC16" -} \ No newline at end of file +} diff --git a/test/fixtures/responses/prepare-settings-signers.json b/test/fixtures/responses/prepare-settings-signers.json new file mode 100644 index 00000000..88c891f4 --- /dev/null +++ b/test/fixtures/responses/prepare-settings-signers.json @@ -0,0 +1,8 @@ +{ + "txJSON": "{\"TransactionType\":\"SignerListSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerQuorum\":2,\"SignerEntries\":[{\"SignerEntry\":{\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"SignerWeight\":1}},{\"SignerEntry\":{\"Account\":\"rwBYyfufTzk77zUSKEu4MvixfarC35av1J\",\"SignerWeight\":1}}],\"Flags\":2147483648,\"LastLedgerSequence\":8820051,\"Fee\":\"12\",\"Sequence\":23}", + "instructions": { + "fee": "0.000012", + "sequence": 23, + "maxLedgerVersion": 8820051 + } +} diff --git a/test/fixtures/responses/sign-as.json b/test/fixtures/responses/sign-as.json new file mode 100644 index 00000000..afe039b8 --- /dev/null +++ b/test/fixtures/responses/sign-as.json @@ -0,0 +1,4 @@ +{ + "signedTransaction": "120000240000000261400000003B9ACA00684000000000000032730081142E244E6F20104E57C0C60BD823CB312BF10928C78314B5F762798A53D543A014CAF8B297CFF8F2F937E8F3E01073210330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD02074473045022100BB6FC77F26BC88587204CAA79B2230C420D7EC937B8AC3A0CF9B0BE988BAB0D002203BF86893BA3B764375FFFAD9D54A4AAEDABD07C4D72ADB9C1B20C10B4DD712898114B5F762798A53D543A014CAF8B297CFF8F2F937E8E1F1", + "id": "AB7632D7C07E591658635CED6A5DDE832B22CA066907CB131DEFAAA925B98185" +} diff --git a/test/fixtures/rippled/book-offers-xrp-usd.json b/test/fixtures/rippled/book-offers-xrp-usd.json index cb75a9f8..298127d3 100644 --- a/test/fixtures/rippled/book-offers-xrp-usd.json +++ b/test/fixtures/rippled/book-offers-xrp-usd.json @@ -29,4 +29,4 @@ }, "status": "success", "type": "response" -} +} diff --git a/test/fixtures/rippled/ledger-close-newer.json b/test/fixtures/rippled/ledger-close-newer.json index 8899692e..57b00e91 100644 --- a/test/fixtures/rippled/ledger-close-newer.json +++ b/test/fixtures/rippled/ledger-close-newer.json @@ -1,12 +1,12 @@ -{ - "fee_base": 10, - "fee_ref": 10, - "ledger_hash": "9141FA171F2C0CE63E609466AF728FF66C12F7ACD4B4B50B0947A7F3409D593A", - "ledger_index": 14804627, - "ledger_time": 490945840, - "reserve_base": 20000000, - "reserve_inc": 5000000, - "txn_count": 19, - "type": "ledgerClosed", - "validated_ledgers": "13983423-14804627" +{ + "fee_base": 10, + "fee_ref": 10, + "ledger_hash": "9141FA171F2C0CE63E609466AF728FF66C12F7ACD4B4B50B0947A7F3409D593A", + "ledger_index": 14804627, + "ledger_time": 490945840, + "reserve_base": 20000000, + "reserve_inc": 5000000, + "txn_count": 19, + "type": "ledgerClosed", + "validated_ledgers": "13983423-14804627" } \ No newline at end of file diff --git a/test/fixtures/rippled/ledger-not-found.json b/test/fixtures/rippled/ledger-not-found.json index 89d1be47..2514f0af 100644 --- a/test/fixtures/rippled/ledger-not-found.json +++ b/test/fixtures/rippled/ledger-not-found.json @@ -1,13 +1,13 @@ -{ - "id": 0, - "status": "error", - "type": "response", - "error": "lgrNotFound", - "error_code": 20, - "error_message": "ledgerNotFound", - "request": { - "command": "ledger", - "id": 3, - "ledger_index": 34 - } +{ + "id": 0, + "status": "error", + "type": "response", + "error": "lgrNotFound", + "error_code": 20, + "error_message": "ledgerNotFound", + "request": { + "command": "ledger", + "id": 3, + "ledger_index": 34 + } } \ No newline at end of file diff --git a/test/fixtures/rippled/tx/offer-cancel.json b/test/fixtures/rippled/tx/offer-cancel.json index 7ecab3ea..0aa0cf89 100644 --- a/test/fixtures/rippled/tx/offer-cancel.json +++ b/test/fixtures/rippled/tx/offer-cancel.json @@ -1,95 +1,95 @@ -{ - "id": 0, - "status": "success", - "type": "response", - "result": { - "TransactionType": "OfferCancel", - "Flags": 0, - "Sequence": 466, - "OfferSequence": 465, - "LastLedgerSequence": 14661888, - "Fee": "12000", - "SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025", - "TxnSignature": "3045022100E4148E9809C5CE13BC5583E8CA665614D9FF02D6589D13BA7FBB67CF45EAC0BF02201B84DC18A921260BCEE685908260888BC20D4375DB4A8702F25B346CAD7F3387", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b", - "hash": "809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E", - "ledger_index": 14661789, - "inLedger": 14661789, - "meta": { - "TransactionIndex": 4, - "AffectedNodes": [ - { - "ModifiedNode": { - "LedgerEntryType": "AccountRoot", - "PreviousTxnLgrSeq": 14661788, - "PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", - "LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9", - "PreviousFields": { - "Sequence": 466, - "OwnerCount": 4, - "Balance": "71827095" - }, - "FinalFields": { - "Flags": 0, - "Sequence": 467, - "OwnerCount": 3, - "Balance": "71815095", - "Domain": "726970706C652E636F6D", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - }, - { - "ModifiedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", - "FinalFields": { - "Flags": 0, - "RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", - "Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - }, - { - "DeletedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "FinalFields": { - "Flags": 0, - "ExchangeRate": "550435C0500F1000", - "RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "TakerPaysCurrency": "0000000000000000000000005553440000000000", - "TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3", - "TakerGetsCurrency": "0000000000000000000000000000000000000000", - "TakerGetsIssuer": "0000000000000000000000000000000000000000" - } - } - }, - { - "DeletedNode": { - "LedgerEntryType": "Offer", - "LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C", - "FinalFields": { - "Flags": 0, - "Sequence": 465, - "PreviousTxnLgrSeq": 14661788, - "BookNode": "0000000000000000", - "OwnerNode": "0000000000000000", - "PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", - "BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "TakerPays": { - "value": "237", - "currency": "USD", - "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" - }, - "TakerGets": "200", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - } - ], - "TransactionResult": "tesSUCCESS" - }, - "validated": true - } -} +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "TransactionType": "OfferCancel", + "Flags": 0, + "Sequence": 466, + "OfferSequence": 465, + "LastLedgerSequence": 14661888, + "Fee": "12000", + "SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025", + "TxnSignature": "3045022100E4148E9809C5CE13BC5583E8CA665614D9FF02D6589D13BA7FBB67CF45EAC0BF02201B84DC18A921260BCEE685908260888BC20D4375DB4A8702F25B346CAD7F3387", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b", + "hash": "809335DD3B0B333865096217AA2F55A4DF168E0198080B3A090D12D88880FF0E", + "ledger_index": 14661789, + "inLedger": 14661789, + "meta": { + "TransactionIndex": 4, + "AffectedNodes": [ + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 14661788, + "PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", + "LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9", + "PreviousFields": { + "Sequence": 466, + "OwnerCount": 4, + "Balance": "71827095" + }, + "FinalFields": { + "Flags": 0, + "Sequence": 467, + "OwnerCount": 3, + "Balance": "71815095", + "Domain": "726970706C652E636F6D", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", + "FinalFields": { + "Flags": 0, + "RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", + "Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + }, + { + "DeletedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "FinalFields": { + "Flags": 0, + "ExchangeRate": "550435C0500F1000", + "RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "TakerPaysCurrency": "0000000000000000000000005553440000000000", + "TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3", + "TakerGetsCurrency": "0000000000000000000000000000000000000000", + "TakerGetsIssuer": "0000000000000000000000000000000000000000" + } + } + }, + { + "DeletedNode": { + "LedgerEntryType": "Offer", + "LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C", + "FinalFields": { + "Flags": 0, + "Sequence": 465, + "PreviousTxnLgrSeq": 14661788, + "BookNode": "0000000000000000", + "OwnerNode": "0000000000000000", + "PreviousTxnID": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", + "BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "TakerPays": { + "value": "237", + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" + }, + "TakerGets": "200", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + } + ], + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/fixtures/rippled/tx/offer-create-sell.json b/test/fixtures/rippled/tx/offer-create-sell.json index a3e2c483..2eca4534 100644 --- a/test/fixtures/rippled/tx/offer-create-sell.json +++ b/test/fixtures/rippled/tx/offer-create-sell.json @@ -48,4 +48,4 @@ }, "status": "success", "type": "response" -} +} diff --git a/test/fixtures/rippled/tx/offer-create.json b/test/fixtures/rippled/tx/offer-create.json index 97cf87d1..de48413f 100644 --- a/test/fixtures/rippled/tx/offer-create.json +++ b/test/fixtures/rippled/tx/offer-create.json @@ -1,92 +1,92 @@ -{ - "id": 0, - "status": "success", - "type": "response", - "result": { - "TransactionType": "OfferCreate", - "Flags": 0, - "Sequence": 465, - "LastLedgerSequence": 14661886, - "TakerPays": { - "value": "237", - "currency": "USD", - "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" - }, - "TakerGets": "200", - "Fee": "12000", - "SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025", - "TxnSignature": "3045022100FA4CBD0A54A38906F8D4C18FBA4DBCE45B98F9C5A33BC9102CB5911E9E20E88F022032C47AC74E60042FF1517C866680A41B396D61146FBA9E60B4CF74E373CA7AD2", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b", - "hash": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", - "ledger_index": 14661788, - "inLedger": 14661788, - "meta": { - "TransactionIndex": 2, - "AffectedNodes": [ - { - "ModifiedNode": { - "LedgerEntryType": "AccountRoot", - "PreviousTxnLgrSeq": 14660978, - "PreviousTxnID": "566D4DE22972C5BAD2506CFFA928B21D2BD33FA52FE16712D17D727681FAA4B1", - "LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9", - "PreviousFields": { - "Sequence": 465, - "OwnerCount": 3, - "Balance": "71839095" - }, - "FinalFields": { - "Flags": 0, - "Sequence": 466, - "OwnerCount": 4, - "Balance": "71827095", - "Domain": "726970706C652E636F6D", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - }, - { - "ModifiedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", - "FinalFields": { - "Flags": 0, - "RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", - "Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - }, - { - "CreatedNode": { - "LedgerEntryType": "DirectoryNode", - "LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "NewFields": { - "ExchangeRate": "550435C0500F1000", - "RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "TakerPaysCurrency": "0000000000000000000000005553440000000000", - "TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3" - } - } - }, - { - "CreatedNode": { - "LedgerEntryType": "Offer", - "LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C", - "NewFields": { - "Sequence": 465, - "BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", - "TakerPays": { - "value": "237", - "currency": "USD", - "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" - }, - "TakerGets": "200", - "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" - } - } - } - ], - "TransactionResult": "tesSUCCESS" - }, - "validated": true - } -} +{ + "id": 0, + "status": "success", + "type": "response", + "result": { + "TransactionType": "OfferCreate", + "Flags": 0, + "Sequence": 465, + "LastLedgerSequence": 14661886, + "TakerPays": { + "value": "237", + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" + }, + "TakerGets": "200", + "Fee": "12000", + "SigningPubKey": "036A749E3B7187E43E8936E3D83A7030989325249E03803F12B7F64BAACABA6025", + "TxnSignature": "3045022100FA4CBD0A54A38906F8D4C18FBA4DBCE45B98F9C5A33BC9102CB5911E9E20E88F022032C47AC74E60042FF1517C866680A41B396D61146FBA9E60B4CF74E373CA7AD2", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b", + "hash": "5D9B0B246255815B63983C188B4C23325B3544F605CDBE3004769EE9E990D2F2", + "ledger_index": 14661788, + "inLedger": 14661788, + "meta": { + "TransactionIndex": 2, + "AffectedNodes": [ + { + "ModifiedNode": { + "LedgerEntryType": "AccountRoot", + "PreviousTxnLgrSeq": 14660978, + "PreviousTxnID": "566D4DE22972C5BAD2506CFFA928B21D2BD33FA52FE16712D17D727681FAA4B1", + "LedgerIndex": "4AD70690C6FF8A069F8AE00B09F70E9B732360026E8085050D314432091A59C9", + "PreviousFields": { + "Sequence": 465, + "OwnerCount": 3, + "Balance": "71839095" + }, + "FinalFields": { + "Flags": 0, + "Sequence": 466, + "OwnerCount": 4, + "Balance": "71827095", + "Domain": "726970706C652E636F6D", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + }, + { + "ModifiedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", + "FinalFields": { + "Flags": 0, + "RootIndex": "6FCB8B0AF9F22ACF762B7712BF44C6CF172FD2BECD849509604EB7DB3AD2C250", + "Owner": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + }, + { + "CreatedNode": { + "LedgerEntryType": "DirectoryNode", + "LedgerIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "NewFields": { + "ExchangeRate": "550435C0500F1000", + "RootIndex": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "TakerPaysCurrency": "0000000000000000000000005553440000000000", + "TakerPaysIssuer": "DD39C650A96EDA48334E70CC4A85B8B2E8502CD3" + } + } + }, + { + "CreatedNode": { + "LedgerEntryType": "Offer", + "LedgerIndex": "D0BEA7E310CDCEED282911314B0D6D00BB7E3B985EAA275AE2AC2DE3763AAF0C", + "NewFields": { + "Sequence": 465, + "BookDirectory": "CF8D13399C6ED20BA82740CFA78E928DC8D498255249BA63550435C0500F1000", + "TakerPays": { + "value": "237", + "currency": "USD", + "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q" + }, + "TakerGets": "200", + "Account": "r9UHu5CWni1qRY7Q4CfFZLGvXo2pGQy96b" + } + } + } + ], + "TransactionResult": "tesSUCCESS" + }, + "validated": true + } +} diff --git a/test/fixtures/rippled/tx/suspended-payment-execution-simple.json b/test/fixtures/rippled/tx/suspended-payment-execution-simple.json index 3d76a2e9..3753a2f1 100644 --- a/test/fixtures/rippled/tx/suspended-payment-execution-simple.json +++ b/test/fixtures/rippled/tx/suspended-payment-execution-simple.json @@ -96,4 +96,4 @@ }, "status": "success", "type": "response" -} +} diff --git a/test/fixtures/rippled/tx/suspended-payment-execution.json b/test/fixtures/rippled/tx/suspended-payment-execution.json index 26b4c957..d9dd9170 100644 --- a/test/fixtures/rippled/tx/suspended-payment-execution.json +++ b/test/fixtures/rippled/tx/suspended-payment-execution.json @@ -97,4 +97,4 @@ }, "status": "success", "type": "response" -} +} diff --git a/test/integration/fixtures/get-transaction.json b/test/integration/fixtures/get-transaction.json deleted file mode 100644 index df18a520..00000000 --- a/test/integration/fixtures/get-transaction.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "2", - "result": { - "type": "order", - "address": "rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV", - "sequence": 7973823, - "id": "4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466", - "specification": { - "direction": "buy", - "quantity": { - "currency": "USD", - "value": "0.000709756467", - "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B" - }, - "totalPrice": { - "currency": "JPY", - "value": "0.086630181788", - "counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN" - } - }, - "outcome": { - "result": "tesSUCCESS", - "timestamp": "2015-12-03T00:53:21.000Z", - "fee": "0.010001", - "balanceChanges": { - "rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [ - { - "currency": "XRP", - "value": "-0.010001" - } - ] - }, - "orderbookChanges": { - "rK5j9n8baXfL4gzUoZsfxBvvsv97P5swaV": [ - { - "direction": "buy", - "quantity": { - "currency": "USD", - "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "value": "0.000709260645" - }, - "totalPrice": { - "currency": "JPY", - "counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN", - "value": "0.086665436143" - }, - "sequence": 7973725, - "status": "cancelled", - "makerExchangeRate": "0.008183892870852266" - }, - { - "direction": "buy", - "quantity": { - "currency": "USD", - "counterparty": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", - "value": "0.000709756467" - }, - "totalPrice": { - "currency": "JPY", - "counterparty": "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN", - "value": "0.086630181788" - }, - "sequence": 7973823, - "status": "created", - "makerExchangeRate": "0.008192946757712049" - } - ] - }, - "ledgerVersion": 17445469, - "indexInLedger": 2 - } - } -} diff --git a/test/integration/fixtures/get-transactions.json b/test/integration/fixtures/get-transactions.json deleted file mode 100644 index c5555cba..00000000 --- a/test/integration/fixtures/get-transactions.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "3", - "result": [ - { - "type": "order", - "address": "rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM", - "sequence": 3372206, - "id": "848397FA686BD4A59F91EC1F4DE717360470EE8BD67CAA01D5FD333EDA8D97B3", - "specification": { - "direction": "buy", - "quantity": { - "currency": "JPY", - "value": "27865.90216965619", - "counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg" - }, - "totalPrice": { - "currency": "XRP", - "value": "40000" - } - }, - "outcome": { - "result": "tesSUCCESS", - "fee": "0.011", - "balanceChanges": { - "rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [ - { - "currency": "XRP", - "value": "-0.011" - } - ] - }, - "orderbookChanges": { - "rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM": [ - { - "direction": "buy", - "quantity": { - "currency": "JPY", - "counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg", - "value": "27880.6855384734" - }, - "totalPrice": { - "currency": "XRP", - "value": "40000" - }, - "sequence": 3372204, - "status": "cancelled", - "makerExchangeRate": "0.697017138461835" - }, - { - "direction": "buy", - "quantity": { - "currency": "JPY", - "counterparty": "rJRi8WW24gt9X85PHAxfWNPCizMMhqUQwg", - "value": "27865.90216965619" - }, - "totalPrice": { - "currency": "XRP", - "value": "40000" - }, - "sequence": 3372206, - "status": "created", - "makerExchangeRate": "0.6966475542414048" - } - ] - }, - "ledgerVersion": 17533547, - "indexInLedger": 12 - } - } - ] -} diff --git a/test/integration/fixtures/index.js b/test/integration/fixtures/index.js deleted file mode 100644 index 2e119520..00000000 --- a/test/integration/fixtures/index.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -module.exports = { - getTransaction: require('./get-transaction'), - getTransactions: require('./get-transactions') -}; diff --git a/test/integration/http-integration-test.js b/test/integration/http-integration-test.js index 6b96e9da..55fc797c 100644 --- a/test/integration/http-integration-test.js +++ b/test/integration/http-integration-test.js @@ -4,18 +4,19 @@ const assert = require('assert-diff'); const _ = require('lodash'); const jayson = require('jayson'); +const RippleAPI = require('../../src').RippleAPI; const createHTTPServer = require('../../src/index').createHTTPServer; +const {payTo, ledgerAccept} = require('./utils'); const apiFixtures = require('../fixtures'); const apiRequests = apiFixtures.requests; const apiResponses = apiFixtures.responses; -const fixtures = require('./fixtures'); - const TIMEOUT = 20000; // how long before each test case times out +const serverUri = 'ws://127.0.0.1:6006'; const apiOptions = { - server: 'wss://s1.ripple.com' + server: serverUri }; const httpPort = 3000; @@ -38,11 +39,14 @@ function random() { return _.fill(Array(16), 0); } + describe('http server integration tests', function() { this.timeout(TIMEOUT); let server = null; let client = null; + let paymentId = null; + let newWallet = null; function createTestInternal(testName, methodName, params, testFunc, id) { it(testName, function() { @@ -60,6 +64,21 @@ describe('http server integration tests', function() { makeNamedParams(params), testFunc, id); } + before(() => { + this.api = new RippleAPI({server: serverUri}); + console.log('CONNECTING...'); + return this.api.connect().then(() => { + console.log('CONNECTED...'); + }) + .then(() => ledgerAccept(this.api)) + .then(() => newWallet = this.api.generateAddress()) + .then(() => ledgerAccept(this.api)) + .then(() => payTo(this.api, newWallet.address)) + .then(paymentId_ => { + paymentId = paymentId_; + }); + }); + beforeEach(function() { server = createHTTPServer(apiOptions, httpPort); return server.start().then(() => { @@ -85,32 +104,49 @@ describe('http server integration tests', function() { result => assert(_.isNumber(result.result.validatedLedger.ledgerVersion)) ); - createTest( - 'getTransaction', - [{id: '4EB6B76237DEEE99F1EA16FAACED2D1E69C5F9CB54F727A4ECA51A08AD3AF466'}], - result => assert.deepEqual(result, fixtures.getTransaction), - '2' - ); + it('getTransaction', function() { + const params = [{id: paymentId}]; + return new Promise((resolve, reject) => { + client.request('getTransaction', makePositionalParams(params), + (err, result) => { + if (err) { + reject(err); + } + assert.strictEqual(result.result.id, paymentId); + const outcome = result.result.outcome; + assert.strictEqual(outcome.result, 'tesSUCCESS'); + assert.strictEqual(outcome.balanceChanges + .rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh[0].value, '-4003218.000012'); + resolve(result); + }); + }); + }); - createTest( - 'getTransactions', - [{address: 'rpP2JgiMyTF5jR5hLG3xHCPi1knBb1v9cM'}, { + it('getTransactions', function() { + const params = [{address: newWallet.address}, { options: { binary: true, - limit: 1, - start: - 'FBAAC31D6BAEEFA9E501266FD62DA7A7982662BC19BC42F49BB41405C2F820DB' + limit: 1 } - }], - result => assert.deepEqual(result, fixtures.getTransactions), - '3' - ); + }]; + return new Promise((resolve, reject) => { + client.request('getTransactions', makeNamedParams(params), + (err, result) => { + if (err) { + reject(err); + } + assert.strictEqual(result.result.length, 1); + assert.strictEqual(result.result[0].id, paymentId); + resolve(result); + }); + }); + }); createTest( 'prepareSettings', [ {address}, - {settings: apiRequests.prepareSettings}, + {settings: apiRequests.prepareSettings.domain}, {instructions: { maxFee: '0.000012', sequence: 23, diff --git a/test/integration/integration-test.js b/test/integration/integration-test.js index 06719b6f..7c880abb 100644 --- a/test/integration/integration-test.js +++ b/test/integration/integration-test.js @@ -9,73 +9,179 @@ const requests = require('../fixtures/requests'); const RippleAPI = require('../../src').RippleAPI; const {isValidAddress} = require('ripple-address-codec'); const {isValidSecret} = require('../../src/common'); +const {payTo, ledgerAccept} = require('./utils'); -const TIMEOUT = 30000; // how long before each test case times out +const TIMEOUT = 10000; // how long before each test case times out const INTERVAL = 1000; // how long to wait between checks for validated ledger +const serverUrl = 'ws://127.0.0.1:6006'; -function verifyTransaction(testcase, hash, type, options, txData) { +function acceptLedger(api) { + return api.connection.request({command: 'ledger_accept'}); +} + +function verifyTransaction(testcase, hash, type, options, txData, address) { console.log('VERIFY...'); return testcase.api.getTransaction(hash, options).then(data => { assert(data && data.outcome); assert.strictEqual(data.type, type); - assert.strictEqual(data.address, wallet.getAddress()); + assert.strictEqual(data.address, address); assert.strictEqual(data.outcome.result, 'tesSUCCESS'); - testcase.transactions.push(hash); + if (testcase.transactions !== undefined) { + testcase.transactions.push(hash); + } return {txJSON: JSON.stringify(txData), id: hash, tx: data}; }).catch(error => { if (error instanceof errors.PendingLedgerVersionError) { console.log('NOT VALIDATED YET...'); return new Promise((resolve, reject) => { setTimeout(() => verifyTransaction(testcase, hash, type, - options, txData).then(resolve, reject), INTERVAL); + options, txData, address).then(resolve, reject), INTERVAL); }); } + console.log(error.stack); assert(false, 'Transaction not successful: ' + error.message); }); } -function testTransaction(testcase, type, lastClosedLedgerVersion, prepared) { +function testTransaction(testcase, type, lastClosedLedgerVersion, prepared, + address = wallet.getAddress(), secret = wallet.getSecret()) { const txJSON = prepared.txJSON; assert(txJSON, 'missing txJSON'); const txData = JSON.parse(txJSON); - assert.strictEqual(txData.Account, wallet.getAddress()); - const signedData = testcase.api.sign(txJSON, wallet.getSecret()); + assert.strictEqual(txData.Account, address); + const signedData = testcase.api.sign(txJSON, secret); console.log('PREPARED...'); - return testcase.api.submit(signedData.signedTransaction).then(data => { + return testcase.api.submit(signedData.signedTransaction) + .then(data => testcase.test.title.indexOf('multisign') !== -1 ? + acceptLedger(testcase.api).then(() => data) : data).then(data => { console.log('SUBMITTED...'); assert.strictEqual(data.resultCode, 'tesSUCCESS'); const options = { minLedgerVersion: lastClosedLedgerVersion, maxLedgerVersion: txData.LastLedgerSequence }; + ledgerAccept(testcase.api); return new Promise((resolve, reject) => { setTimeout(() => verifyTransaction(testcase, signedData.id, type, - options, txData).then(resolve, reject), INTERVAL); + options, txData, address).then(resolve, reject), INTERVAL); }); }); } -function setup() { - this.api = new RippleAPI({server: 'wss://s1.ripple.com'}); +function setup(server = 'wss://s1.ripple.com') { + this.api = new RippleAPI({server}); console.log('CONNECTING...'); return this.api.connect().then(() => { console.log('CONNECTED...'); }); } +const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; +const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'; + +function makeTrustLine(testcase, address, secret) { + const api = testcase.api; + const specification = { + currency: 'USD', + counterparty: masterAccount, + limit: '1341.1', + ripplingDisabled: true + }; + const trust = api.prepareTrustline(address, specification, {}) + .then(data => { + const signed = api.sign(data.txJSON, secret); + if (address === wallet.getAddress()) { + testcase.transactions.push(signed.id); + } + return api.submit(signed.signedTransaction); + }) + .then(() => ledgerAccept(api)); + return trust; +} + +function makeOrder(api, address, specification, secret) { + return api.prepareOrder(address, specification) + .then(data => api.sign(data.txJSON, secret)) + .then(signed => api.submit(signed.signedTransaction)) + .then(() => ledgerAccept(api)); +} + +function setupAccounts(testcase) { + const api = testcase.api; + + const promise = payTo(api, 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM') + .then(() => payTo(api, wallet.getAddress())) + .then(() => payTo(api, testcase.newWallet.address)) + .then(() => payTo(api, 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc')) + .then(() => payTo(api, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q')) + .then(() => { + return api.prepareSettings(masterAccount, {defaultRipple: true}) + .then(data => api.sign(data.txJSON, masterSecret)) + .then(signed => api.submit(signed.signedTransaction)) + .then(() => ledgerAccept(api)); + }) + .then(() => makeTrustLine(testcase, wallet.getAddress(), + wallet.getSecret())) + .then(() => makeTrustLine(testcase, testcase.newWallet.address, + testcase.newWallet.secret)) + .then(() => payTo(api, wallet.getAddress(), '123', 'USD', masterAccount)) + .then(() => payTo(api, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q')) + .then(() => { + const orderSpecification = { + direction: 'buy', + quantity: { + currency: 'USD', + value: '432', + counterparty: masterAccount + }, + totalPrice: { + currency: 'XRP', + value: '432' + } + }; + return makeOrder(testcase.api, testcase.newWallet.address, + orderSpecification, testcase.newWallet.secret); + }) + .then(() => { + const orderSpecification = { + direction: 'buy', + quantity: { + currency: 'XRP', + value: '1741' + }, + totalPrice: { + currency: 'USD', + value: '171', + counterparty: masterAccount + } + }; + return makeOrder(testcase.api, masterAccount, orderSpecification, + masterSecret); + }); + return promise; +} + function teardown() { return this.api.disconnect(); } function suiteSetup() { this.transactions = []; - return setup.bind(this)().then(() => { - return this.api.getLedgerVersion().then(ledgerVersion => { + + return setup.bind(this)(serverUrl) + .then(() => ledgerAccept(this.api)) + .then(() => this.newWallet = this.api.generateAddress()) + // two times to give time to server to send `ledgerClosed` event + // so getLedgerVersion will return right value + .then(() => ledgerAccept(this.api)) + .then(() => this.api.getLedgerVersion()) + .then(ledgerVersion => { this.startLedgerVersion = ledgerVersion; - }); - }).then(teardown.bind(this)); + }) + .then(() => setupAccounts(this)) + .then(() => teardown.bind(this)()); } describe('integration tests', function() { @@ -84,14 +190,14 @@ describe('integration tests', function() { this.timeout(TIMEOUT); before(suiteSetup); - beforeEach(setup); + beforeEach(_.partial(setup, serverUrl)); afterEach(teardown); it('settings', function() { return this.api.getLedgerVersion().then(ledgerVersion => { return this.api.prepareSettings(address, - requests.prepareSettings, instructions).then(prepared => + requests.prepareSettings.domain, instructions).then(prepared => testTransaction(this, 'settings', ledgerVersion, prepared)); }); }); @@ -140,10 +246,10 @@ describe('integration tests', function() { } }; return this.api.getLedgerVersion().then(ledgerVersion => { - return this.api.prepareOrder(address, - orderSpecification, instructions).then(prepared => - testTransaction(this, 'order', ledgerVersion, prepared) - ).then(result => { + return this.api.prepareOrder(address, orderSpecification, instructions) + .then(prepared => + testTransaction(this, 'order', ledgerVersion, prepared)) + .then(result => { const txData = JSON.parse(result.txJSON); return this.api.getOrders(address).then(orders => { assert(orders && orders.length > 0); @@ -155,8 +261,9 @@ describe('integration tests', function() { assert.deepEqual(createdOrder.specification, orderSpecification); return txData; }); - }).then(txData => this.api.prepareOrderCancellation( - address, {orderSequence: txData.Sequence}, instructions) + }) + .then(txData => this.api.prepareOrderCancellation( + address, {orderSequence: txData.Sequence}, instructions) .then(prepared => testTransaction(this, 'orderCancellation', ledgerVersion, prepared)) ); @@ -232,7 +339,7 @@ describe('integration tests', function() { it('getSettings', function() { return this.api.getSettings(address).then(data => { assert(data); - assert.strictEqual(data.domain, requests.prepareSettings.domain); + assert.strictEqual(data.domain, requests.prepareSettings.domain.domain); }); }); @@ -244,7 +351,7 @@ describe('integration tests', function() { }, counter: { currency: 'USD', - counterparty: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q' + counterparty: masterAccount } }; return this.api.getOrderbook(address, orderbook).then(book => { @@ -272,11 +379,11 @@ describe('integration tests', function() { address: address }, destination: { - address: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc', + address: this.newWallet.address, amount: { - value: '0.000001', + value: '1', currency: 'USD', - counterparty: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q' + counterparty: masterAccount } } }; @@ -289,8 +396,24 @@ describe('integration tests', function() { }); }); + it('getPaths - send all', function() { - const pathfind = requests.getPaths.sendAll; + const pathfind = { + source: { + address: address, + amount: { + currency: 'USD', + value: '0.005' + } + }, + destination: { + address: this.newWallet.address, + amount: { + currency: 'USD' + } + } + }; + return this.api.getPaths(pathfind).then(data => { assert(data && data.length > 0); assert(_.every(data, path => { @@ -313,3 +436,63 @@ describe('integration tests', function() { }); }); + +describe('integration tests - standalone rippled', function() { + const instructions = {maxLedgerVersionOffset: 10}; + this.timeout(TIMEOUT); + + beforeEach(_.partial(setup, serverUrl)); + afterEach(teardown); + const address = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs'; + const secret = 'ss6F8381Br6wwpy9p582H8sBt19J3'; + const signer1address = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2'; + const signer1secret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx'; + const signer2address = 'r3RtUvGw9nMoJ5FuHxuoVJvcENhKtuF9ud'; + const signer2secret = 'shUHQnL4EH27V4EiBrj6EfhWvZngF'; + + it('submit multisigned transaction', function() { + const signers = { + threshold: 2, + weights: [ + {address: signer1address, weight: 1}, + {address: signer2address, weight: 1} + ] + }; + let minLedgerVersion = null; + return payTo(this.api, address).then(() => { + return this.api.getLedgerVersion().then(ledgerVersion => { + minLedgerVersion = ledgerVersion; + return this.api.prepareSettings(address, {signers}, instructions) + .then(prepared => { + return testTransaction(this, 'settings', ledgerVersion, prepared, + address, secret); + }); + }); + }).then(() => { + const multisignInstructions = + _.assign({}, instructions, {signersCount: 2}); + return this.api.prepareSettings( + address, {domain: 'example.com'}, multisignInstructions) + .then(prepared => { + const signed1 = this.api.sign( + prepared.txJSON, signer1secret, {signAs: signer1address}); + const signed2 = this.api.sign( + prepared.txJSON, signer2secret, {signAs: signer2address}); + const combined = this.api.combine([ + signed1.signedTransaction, signed2.signedTransaction + ]); + return this.api.submit(combined.signedTransaction) + .then(response => acceptLedger(this.api).then(() => response)) + .then(response => { + assert.strictEqual(response.resultCode, 'tesSUCCESS'); + const options = {minLedgerVersion}; + return verifyTransaction(this, combined.id, 'settings', + options, {}, address); + }).catch(error => { + console.log(error.message); + throw error; + }); + }); + }); + }); +}); diff --git a/test/integration/rippled.cfg b/test/integration/rippled.cfg new file mode 100644 index 00000000..4269de08 --- /dev/null +++ b/test/integration/rippled.cfg @@ -0,0 +1,961 @@ +#------------------------------------------------------------------------------- +# +# Rippled Server Instance Configuration Example +# +#------------------------------------------------------------------------------- +# +# Contents +# +# 1. Server +# +# 2. Peer Protocol +# +# 3. Ripple Protocol +# +# 4. HTTPS Client +# +# 5. Database +# +# 6. Diagnostics +# +# 7. Voting +# +# 8. Example Settings +# +#------------------------------------------------------------------------------- +# +# Purpose +# +# This file documents and provides examples of all rippled server process +# configuration options. When the rippled server instance is launched, it +# looks for a file with the following name: +# +# rippled.cfg +# +# For more information on where the rippled server instance searches for +# the file please visit the Ripple wiki. Specifically, the section explaining +# the --conf command line option: +# +# https://ripple.com/wiki/Rippled#--conf.3Dpath +# +# This file should be named rippled.cfg. This file is UTF-8 with Dos, UNIX, +# or Mac style end of lines. Blank lines and lines beginning with '#' are +# ignored. Undefined sections are reserved. No escapes are currently defined. +# +# Notation +# +# In this document a simple BNF notation is used. Angle brackets denote +# required elements, square brackets denote optional elements, and single +# quotes indicate string literals. A vertical bar separating 1 or more +# elements is a logical "or"; Any one of the elements may be chosen. +# Parenthesis are notational only, and used to group elements, they are not +# part of the syntax unless they appear in quotes. White space may always +# appear between elements, it has no effect on values. +# +# A required identifier +# '=' The equals sign character +# | Logical "or" +# ( ) Used for grouping +# +# +# An identifier is a string of upper or lower case letters, digits, or +# underscores subject to the requirement that the first character of an +# identifier must be a letter. Identifiers are not case sensitive (but +# values may be). +# +# Some configuration sections contain key/value pairs. A line containing +# a key/value pair has this syntax: +# +# '=' +# +# Depending on the section and key, different value types are possible: +# +# A signed integer +# An unsigned integer +# A boolean. 1 = true/yes/on, 0 = false/no/off. +# +# Consult the documentation on the key in question to determine the possible +# value types. +# +# +# +#------------------------------------------------------------------------------- +# +# 1. Server +# +#---------- +# +# +# +# rippled offers various server protocols to clients making inbound +# connections. The listening ports rippled uses are "universal" ports +# which may be configured to handshake in one or more of the available +# supported protocols. These universal ports simplify administration: +# A single open port can be used for multiple protocols. +# +# NOTE At least one server port must be defined in order +# to accept incoming network connections. +# +# +# [server] +# +# A list of port names and key/value pairs. A port name must start with a +# letter and contain only letters and numbers. The name is not case-sensitive. +# For each name in this list, rippled will look for a configuration file +# section with the same name and use it to create a listening port. The +# name is informational only; the choice of name does not affect the function +# of the listening port. +# +# Key/value pairs specified in this section are optional, and apply to all +# listening ports unless the port overrides the value in its section. They +# may be considered default values. +# +# Suggestion: +# +# To avoid a conflict with port names and future configuration sections, +# we recommend prepending "port_" to the port name. This prefix is not +# required, but suggested. +# +# This example defines two ports with different port numbers and settings: +# +# [server] +# port_public +# port_private +# port = 80 +# +# [port_public] +# ip=0.0.0.0 +# port = 443 +# protocol=peer,https +# +# [port_private] +# ip=127.0.0.1 +# protocol=http +# +# When rippled is used as a command line client (for example, issuing a +# server stop command), the first port advertising the http or https +# protocol will be used to make the connection. +# +# +# +# [] +# +# A series of key/value pairs that define the settings for the port with +# the corresponding name. These keys are possible: +# +# ip = +# +# Required. Determines the IP address of the network interface to bind +# to. To bind to all available interfaces, uses 0.0.0.0 +# +# port = +# +# Required. Sets the port number to use for this port. +# +# protocol = [ http, https, peer ] +# +# Required. A comma-separated list of protocols to support: +# +# http JSON-RPC over HTTP +# https JSON-RPC over HTTPS +# ws Websockets +# wss Secure Websockets +# peer Peer Protocol +# +# Restrictions: +# +# Only one port may be configured to support the peer protocol. +# A port cannot have websocket and non websocket protocols at the +# same time. It is possible have both Websockets and Secure Websockets +# together in one port. +# +# NOTE If no ports support the peer protocol, rippled cannot +# receive incoming peer connections or become a superpeer. +# +# user = +# password = +# +# When set, these credentials will be required on HTTP/S requests. +# The credentials must be provided using HTTP's Basic Authentication +# headers. If either or both fields are empty, then no credentials are +# required. IP address restrictions, if any, will be checked in addition +# to the credentials specified here. +# +# When acting in the client role, rippled will supply these credentials +# using HTTP's Basic Authentication headers when making outbound HTTP/S +# requests. +# +# admin = [ IP, IP, IP, ... ] +# +# A comma-separated list of IP addresses. +# +# When set, grants administrative command access to the specified IP +# addresses. These commands may be issued over http, https, ws, or wss +# if configured on the port. If unspecified, the default is to not allow +# administrative commands. +# +# *SECURITY WARNING* +# 0.0.0.0 may be specified to allow access from any IP address. It must +# be the only address specified and cannot be combined with other IPs. +# Use of this address can compromise server security, please consider its +# use carefully. +# +# admin_user = +# admin_password = +# +# When set, clients must provide these credentials in the submitted +# JSON for any administrative command requests submitted to the HTTP/S, +# WS, or WSS protocol interfaces. If administrative commands are +# disabled for a port, these credentials have no effect. +# +# When acting in the client role, rippled will supply these credentials +# in the submitted JSON for any administrative command requests when +# invoking JSON-RPC commands on remote servers. +# +# ssl_key = +# ssl_cert = +# ssl_chain = +# +# Use the specified files when configuring SSL on the port. +# +# NOTE If no files are specified and secure protocols are selected, +# rippled will generate an internal self-signed certificate. +# +# The files have these meanings: +# +# ssl_key +# +# Specifies the filename holding the SSL key in PEM format. +# +# ssl_cert +# +# Specifies the path to the SSL certificate file in PEM format. +# This is not needed if the chain includes it. +# +# ssl_chain +# +# If you need a certificate chain, specify the path to the +# certificate chain here. The chain may include the end certificate. +# +# +# +# [rpc_startup] +# +# Specify a list of RPC commands to run at startup. +# +# Examples: +# { "command" : "server_info" } +# { "command" : "log_level", "partition" : "ripplecalc", "severity" : "trace" } +# +# +# +# [websocket_ping_frequency] +# +# +# +# The amount of time to wait in seconds, before sending a websocket 'ping' +# message. Ping messages are used to determine if the remote end of the +# connection is no longer available. +# +# +# +#------------------------------------------------------------------------------- +# +# 2. Peer Protocol +# +#----------------- +# +# These settings control security and access attributes of the Peer to Peer +# server section of the rippled process. Peer Protocol implements the +# Ripple Payment protocol. It is over peer connections that transactions +# and validations are passed from to machine to machine, to determine the +# contents of validated ledgers. +# +# +# +# [ips] +# +# List of hostnames or ips where the Ripple protocol is served. For a starter +# list, you can either copy entries from: https://ripple.com/ripple.txt or if +# you prefer you can specify r.ripple.com 51235 +# +# One IPv4 address or domain names per line is allowed. A port may must be +# specified after adding a space to the address. By convention, if known, +# IPs are listed in from most to least trusted. +# +# Examples: +# 192.168.0.1 +# 192.168.0.1 3939 +# r.ripple.com 51235 +# +# This will give you a good, up-to-date list of addresses: +# +# [ips] +# r.ripple.com 51235 +# +# The default is: [ips_fixed] addresses (if present) or r.ripple.com 51235 +# +# +# [ips_fixed] +# +# List of IP addresses or hostnames to which rippled should always attempt to +# maintain peer connections with. This is useful for manually forming private +# networks, for example to configure a validation server that connects to the +# Ripple network through a public-facing server, or for building a set +# of cluster peers. +# +# One IPv4 address or domain names per line is allowed. A port must be +# specified after adding a space to the address. +# +# +# +# [peer_private] +# +# 0 or 1. +# +# 0: Request peers to broadcast your address. Normal outbound peer connections [default] +# 1: Request peers not broadcast your address. Only connect to configured peers. +# +# +# +# [peers_max] +# +# The largest number of desired peer connections (incoming or outgoing). +# Cluster and fixed peers do not count towards this total. There are +# implementation-defined lower limits imposed on this value for security +# purposes. +# +# +# +# [node_seed] +# +# This is used for clustering. To force a particular node seed or key, the +# key can be set here. The format is the same as the validation_seed field. +# To obtain a validation seed, use the validation_create command. +# +# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE +# shfArahZT9Q9ckTf3s1psJ7C7qzVN +# +# +# +# [cluster_nodes] +# +# To extend full trust to other nodes, place their node public keys here. +# Generally, you should only do this for nodes under common administration. +# Node public keys start with an 'n'. To give a node a name for identification +# place a space after the public key and then the name. +# +# +# +# [sntp_servers] +# +# IP address or domain of NTP servers to use for time synchronization. +# +# These NTP servers are suitable for rippled servers located in the United +# States: +# time.windows.com +# time.apple.com +# time.nist.gov +# pool.ntp.org +# +# +# +# [overlay] +# +# Controls settings related to the peer to peer overlay. +# +# A set of key/value pair parameters to configure the overlay. +# +# public_ip = +# +# If the server has a known, fixed public IPv4 address, +# specify that IP address here in dotted decimal notation. +# Peers will use this information to reject attempt to proxy +# connections to or from this server. +# +# ip_limit = +# +# The maximum number of incoming peer connections allowed by a single +# IP that isn't classified as "private" in RFC1918. The implementation +# imposes some hard and soft upper limits on this value to prevent a +# single host from consuming all inbound slots. If the value is not +# present the server will autoconfigure an appropriate limit. +# +# +# +# [transaction_queue] EXPERIMENTAL +# +# This section is EXPERIMENTAL, and should not be +# present for production configuration settings. +# +# A set of key/value pair parameters to tune the performance of the +# transaction queue. +# +# ledgers_in_queue = +# +# The queue will be limited to this of average ledgers' +# worth of transactions. If the queue fills up, the transactions +# with the lowest fees will be dropped from the queue any time a +# transaction with a higher fee level is added. Default: 20. +# +# retry_sequence_percent = +# +# If a client resubmits a transaction, the new transaction's fee +# must be more than percent higher than the original +# transaction's fee, or meet the current open ledger fee to be +# considered. Default: 125. +# +# +# +#------------------------------------------------------------------------------- +# +# 3. Ripple Protocol +# +#------------------- +# +# These settings affect the behavior of the server instance with respect +# to Ripple payment protocol level activities such as validating and +# closing ledgers, establishing a quorum, or adjusting fees in response +# to server overloads. +# +# +# +# [node_size] +# +# Tunes the servers based on the expected load and available memory. Legal +# sizes are "tiny", "small", "medium", "large", and "huge". We recommend +# you start at the default and raise the setting if you have extra memory. +# The default is "tiny". +# +# +# +# [validation_quorum] +# +# Sets the minimum number of trusted validations a ledger must have before +# the server considers it fully validated. Note that if you are validating, +# your validation counts. +# +# +# +# [ledger_history] +# +# The number of past ledgers to acquire on server startup and the minimum to +# maintain while running. +# +# To serve clients, servers need historical ledger data. Servers that don't +# need to serve clients can set this to "none". Servers that want complete +# history can set this to "full". +# +# This must be less than or equal to online_delete (if online_delete is used) +# +# The default is: 256 +# +# +# +# [fetch_depth] +# +# The number of past ledgers to serve to other peers that request historical +# ledger data (or "full" for no limit). +# +# Servers that require low latency and high local performance may wish to +# restrict the historical ledgers they are willing to serve. Setting this +# below 32 can harm network stability as servers require easy access to +# recent history to stay in sync. Values below 128 are not recommended. +# +# The default is: full +# +# +# +# [validation_seed] +# +# To perform validation, this section should contain either a validation seed +# or key. The validation seed is used to generate the validation +# public/private key pair. To obtain a validation seed, use the +# validation_create command. +# +# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE +# shfArahZT9Q9ckTf3s1psJ7C7qzVN +# +# +# +# [validators] +# +# List of nodes to always accept as validators. Nodes are specified by domain +# or public key. +# +# For domains, rippled will probe for https web servers at the specified +# domain in the following order: ripple.DOMAIN, www.DOMAIN, DOMAIN +# +# For public key entries, a comment may optionally be specified after adding +# a space to the public key. +# +# Examples: +# ripple.com +# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5 +# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe +# +# +# +# [validators_file] +# +# Path to file contain a list of nodes to always accept as validators. Use +# this to specify a file other than this file to manage your validators list. +# +# If this entry is not present or empty and no nodes from previous runs were +# found in the database, rippled will look for a validators.txt in the config +# directory. If not found there, it will attempt to retrieve the file from +# the [validators_site] web site. +# +# After specifying a different [validators_file] or changing the contents of +# the validators file, issue a RPC unl_load command to have rippled load the +# file. +# +# Specify the file by specifying its full path. +# +# Examples: +# C:/home/johndoe/ripple/validators.txt +# /home/johndoe/ripple/validators.txt +# +# +# +# [validators_site] +# +# Specifies where to find validators.txt for UNL boostrapping and RPC +# unl_network command. +# +# Example: ripple.com +# +# +# +# [path_search] +# When searching for paths, the default search aggressiveness. This can take +# exponentially more resources as the size is increased. +# +# The default is: 7 +# +# [path_search_fast] +# [path_search_max] +# When searching for paths, the minimum and maximum search aggressiveness. +# +# If you do not need pathfinding, you can set path_search_max to zero to +# disable it and avoid some expensive bookkeeping. +# +# The default for 'path_search_fast' is 2. The default for 'path_search_max' is 10. +# +# [path_search_old] +# +# For clients that use the legacy path finding interfaces, the search +# aggressiveness to use. The default is 7. +# +# +# +# [fee_default] +# +# Sets the base cost of a transaction in drops. Used when the server has +# no other source of fee information, such as signing transactions offline. +# +# +# +#------------------------------------------------------------------------------- +# +# 4. HTTPS Client +# +#---------------- +# +# The rippled server instance uses HTTPS GET requests in a variety of +# circumstances, including but not limited to contacting trusted domains to +# fetch information such as mapping an email address to a Ripple Payment +# Network address. +# +# [ssl_verify] +# +# 0 or 1. +# +# 0. HTTPS client connections will not verify certificates. +# 1. Certificates will be checked for HTTPS client connections. +# +# If not specified, this parameter defaults to 1. +# +# +# +# [ssl_verify_file] +# +# +# +# A file system path leading to the certificate verification file for +# HTTPS client requests. +# +# +# +# [ssl_verify_dir] +# +# +# +# +# A file system path leading to a file or directory containing the root +# certificates that the server will accept for verifying HTTP servers. +# Used only for outbound HTTPS client connections. +# +# +# +#------------------------------------------------------------------------------- +# +# 5. Database +# +#------------ +# +# rippled creates 4 SQLite database to hold bookkeeping information +# about transactions, local credentials, and various other things. +# It also creates the NodeDB, which holds all the objects that +# make up the current and historical ledgers. +# +# The size of the NodeDB grows in proportion to the amount of new data and the +# amount of historical data (a configurable setting) so the performance of the +# underlying storage media where the NodeDB is placed can significantly affect +# the performance of the server. +# +# Partial pathnames will be considered relative to the location of +# the rippled.cfg file. +# +# [node_db] Settings for the Node Database (required) +# +# Format (without spaces): +# One or more lines of case-insensitive key / value pairs: +# '=' +# ... +# +# Example: +# type=nudb +# path=db/nudb +# +# The "type" field must be present and controls the choice of backend: +# +# type = NuDB +# +# NuDB is a high-performance database written by Ripple Labs and optimized +# for rippled and solid-state drives. +# +# NuDB maintains its high speed regardless of the amount of history +# stored. Online delete may be selected, but is not required. NuDB is +# available on all platforms that rippled runs on. +# +# type = RocksDB +# +# RocksDB is an open-source, general-purpose key/value store - see +# http://rocksdb.org/ for more details. +# +# RocksDB is an alternative backend for systems that don't use solid-state +# drives. Because RocksDB's performance degrades as it stores more data, +# keeping full history is not advised, and using online delete is +# recommended. RocksDB is not available on Windows. +# +# The RocksDB backend also provides these optional parameters: +# +# compression 0 for none, 1 for Snappy compression +# +# +# +# Required keys: +# path Location to store the database (all types) +# +# Optional keys: +# +# These keys are possible for any type of backend: +# +# online_delete Minimum value of 256. Enable automatic purging +# of older ledger information. Maintain at least this +# number of ledger records online. Must be greater +# than or equal to ledger_history. +# +# advisory_delete 0 for disabled, 1 for enabled. If set, then +# require administrative RPC call "can_delete" +# to enable online deletion of ledger records. +# +# Notes: +# The 'node_db' entry configures the primary, persistent storage. +# +# The 'import_db' is used with the '--import' command line option to +# migrate the specified database into the current database given +# in the [node_db] section. +# +# [import_db] Settings for performing a one-time import (optional) +# [database_path] Path to the book-keeping databases. +# +# There are 4 bookkeeping SQLite database that the server creates and +# maintains. If you omit this configuration setting, it will default to +# creating a directory called "db" located in the same place as your +# rippled.cfg file. Partial pathnames will be considered relative to +# the location of the rippled executable. +# +# +# +# +#------------------------------------------------------------------------------- +# +# 6. Diagnostics +# +#--------------- +# +# These settings are designed to help server administrators diagnose +# problems, and obtain detailed information about the activities being +# performed by the rippled process. +# +# +# +# [debug_logfile] +# +# Specifies where a debug logfile is kept. By default, no debug log is kept. +# Unless absolute, the path is relative the directory containing this file. +# +# Example: debug.log +# +# +# +# [insight] +# +# Configuration parameters for the Beast. Insight stats collection module. +# +# Insight is a module that collects information from the areas of rippled +# that have instrumentation. The configuration parameters control where the +# collection metrics are sent. The parameters are expressed as key = value +# pairs with no white space. The main parameter is the choice of server: +# +# "server" +# +# Choice of server to send metrics to. Currently the only choice is +# "statsd" which sends UDP packets to a StatsD daemon, which must be +# running while rippled is running. More information on StatsD is +# available here: +# https://github.com/b/statsd_spec +# +# When server=statsd, these additional keys are used: +# +# "address" The UDP address and port of the listening StatsD server, +# in the format, n.n.n.n:port. +# +# "prefix" A string prepended to each collected metric. This is used +# to distinguish between different running instances of rippled. +# +# If this section is missing, or the server type is unspecified or unknown, +# statistics are not collected or reported. +# +# Example: +# +# [insight] +# server=statsd +# address=192.168.0.95:4201 +# prefix=my_validator +# +#------------------------------------------------------------------------------- +# +# 7. Voting +# +#---------- +# +# The vote settings configure settings for the entire Ripple network. +# While a single instance of rippled cannot unilaterally enforce network-wide +# settings, these choices become part of the instance's vote during the +# consensus process for each voting ledger. +# +# [voting] +# +# A set of key/value pair parameters used during voting ledgers. +# +# reference_fee = +# +# The cost of the reference transaction fee, specified in drops. +# The reference transaction is the simplest form of transaction. +# It represents an XRP payment between two parties. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# reference_fee = 10 # 10 drops +# +# account_reserve = +# +# The account reserve requirement is specified in drops. The portion of an +# account's XRP balance that is at or below the reserve may only be +# spent on transaction fees, and not transferred out of the account. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# account_reserve = 20000000 # 20 XRP +# +# owner_reserve = +# +# The owner reserve is the amount of XRP reserved in the account for +# each ledger item owned by the account. Ledger items an account may +# own include trust lines, open orders, and tickets. +# +# If this parameter is unspecified, rippled will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# owner_reserve = 5000000 # 5 XRP +# +#------------------------------------------------------------------------------- +# +# 8. Example Settings +# +#-------------------- +# +# Administrators can use these values as a starting point for configuring +# their instance of rippled, but each value should be checked to make sure +# it meets the business requirements for the organization. +# +# Server +# +# These example configuration settings create these ports: +# +# "peer" +# +# Peer protocol open to everyone. This is required to accept +# incoming rippled connections. This does not affect automatic +# or manual outgoing Peer protocol connections. +# +# "rpc" +# +# Administrative RPC commands over HTTPS, when originating from +# the same machine (via the loopback adapter at 127.0.0.1). +# +# "wss_admin" +# +# Admin level API commands over Secure Websockets, when originating +# from the same machine (via the loopback adapter at 127.0.0.1). +# +# This port is commented out but can be enabled by removing +# the '#' from each corresponding line including the entry under [server] +# +# "wss_public" +# +# Guest level API commands over Secure Websockets, open to everyone. +# +# For HTTPS and Secure Websockets ports, if no certificate and key file +# are specified then a self-signed certificate will be generated on startup. +# If you have a certificate and key file, uncomment the corresponding lines +# and ensure the paths to the files are correct. +# +# NOTE +# +# To accept connections on well known ports such as 80 (HTTP) or +# 443 (HTTPS), most operating systems will require rippled to +# run with administrator privileges, or else rippled will not start. + +[server] +port_rpc_admin_local +#port_peer +port_ws_admin_local +#port_ws_public +#port_ws_admin_public +#ssl_key = /etc/ssl/private/server.key +#ssl_cert = /etc/ssl/certs/server.crt + +[port_rpc_admin_local] +port = 5005 +ip = 127.0.0.1 +admin = 127.0.0.1 +protocol = http + +[port_peer] +port = 51235 +ip = 0.0.0.0 +protocol = peer + +[port_ws_admin_local] +port = 6006 +ip = 127.0.0.1 +admin = 127.0.0.1 +protocol = ws + +[port_ws_admin_public] +port = 5007 +ip = 0.0.0.0 +admin = 192.168.50.1 +protocol = ws + +[port_ws_public] +port = 5006 +ip = 0.0.0.0 +protocol = ws + +#[port_ws_public] +#port = 5005 +#ip = 127.0.0.1 +#protocol = wss + +#------------------------------------------------------------------------------- + +[node_size] +medium + +# This is primary persistent datastore for rippled. This includes transaction +# metadata, account states, and ledger headers. Helpful information can be +# found here: https://ripple.com/wiki/NodeBackEnd +# delete old ledgers while maintaining at least 2000. Do not require an +# external administrative command to initiate deletion. +[node_db] +type=RocksDB +path=/var/lib/rippled/db/rocksdb +open_files=2000 +filter_bits=12 +cache_mb=256 +file_size_mb=8 +file_size_mult=2 +online_delete=2000 +advisory_delete=0 + +[database_path] +/var/lib/rippled/db + +# This needs to be an absolute directory reference, not a relative one. +# Modify this value as required. +[debug_logfile] +/var/log/rippled.debug.log + +[sntp_servers] +time.windows.com +time.apple.com +time.nist.gov +pool.ntp.org + +# Where to find some other servers speaking the Ripple protocol. +# +#[ips] +#r.ripple.com 51235 + +# Public keys of the validators that this rippled instance trusts. The latest +# list of validators can be obtained from https://ripple.com/ripple.txt +# +# See also https://wiki.ripple.com/Ripple.txt +# +[validators] +n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7 RL1 +n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj RL2 +n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C RL3 +n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS RL4 +n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA RL5 + +# The number of validators rippled needs to accept a consensus. +# Don't change this unless you know what you're doing. +[validation_quorum] +3 + +# Turn down default logging to save disk space in the long run. +# Valid values here are trace, debug, info, warning, error, and fatal +[rpc_startup] +{ "command": "log_level", "severity": "trace" } +#{ "command": "log_level", "severity": "warning" } + +# If ssl_verify is 1, certificates will be validated. +# To allow the use of self-signed certificates for development or internal use, +# set to ssl_verify to 0. +[ssl_verify] +1 + +[features] +SusPay +MultiSign diff --git a/test/integration/utils.js b/test/integration/utils.js new file mode 100644 index 00000000..e35830d7 --- /dev/null +++ b/test/integration/utils.js @@ -0,0 +1,56 @@ +'use strict'; + +const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; +const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'; + +function ledgerAccept(api) { + const request = {command: 'ledger_accept'}; + return api.connection.request(request); +} + +function pay(api, from, to, amount, secret, currency = 'XRP', counterparty) { + const paymentSpecification = { + source: { + address: from, + maxAmount: { + value: amount, + currency: currency + } + }, + destination: { + address: to, + amount: { + value: amount, + currency: currency + } + } + }; + + if (counterparty !== undefined) { + paymentSpecification.source.maxAmount.counterparty = counterparty; + paymentSpecification.destination.amount.counterparty = counterparty; + } + + let id = null; + return api.preparePayment(from, paymentSpecification, {}) + .then(data => api.sign(data.txJSON, secret)) + .then(signed => { + id = signed.id; + return api.submit(signed.signedTransaction); + }) + .then(() => ledgerAccept(api)) + .then(() => id); +} + + +function payTo(api, to, amount = '4003218', currency = 'XRP', counterparty) { + return pay(api, masterAccount, to, amount, masterSecret, currency, + counterparty); +} + + +module.exports = { + pay, + payTo, + ledgerAccept +}; diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 3b41b636..2ab30abe 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -249,6 +249,11 @@ module.exports = function(port) { } }); + mock.on('request_submit_multisigned', function(request, conn) { + assert.strictEqual(request.command, 'submit_multisigned'); + conn.send(createResponse(request, fixtures.submit.success)); + }); + mock.on('request_account_lines', function(request, conn) { if (request.account === addresses.ACCOUNT) { conn.send(accountLinesResponse.normal(request));