Compare commits

...

51 Commits
1.3.1 ... 1.4.0

Author SHA1 Message Date
Elliot Lee
e4b245104a Release 1.4.0 2019-10-28 17:10:03 -07:00
Elliot Lee
789497b07e Fix lint errors 2019-10-28 17:01:32 -07:00
FKSRipple
5cf01ba099 Merge pull request #1051 from FredKSchott/tests-to-ts-02
Move tests to TypeScript
2019-10-28 10:54:39 -07:00
Fred K. Schott
e8669891f8 small typo fixes 2019-10-28 10:54:06 -07:00
Fred K. Schott
ac0f265a5b move tests to TypeScript 2019-10-28 10:53:36 -07:00
Elliot Lee
fcd6b430e1 Reduce dependency size (#1057)
- Remove .npmignore since it is overridden by package.json 'files'
- Include build/ripple-latest-min.js, not everything in build/
2019-10-25 13:57:45 -07:00
Elliot Lee
f3ad8a9b80 Merge pull request #1060 from fix-test
Fix broken tests
2019-10-25 13:56:43 -07:00
Fred K. Schott
43ff824da1 fix broken tests 2019-10-25 10:18:46 -07:00
Fairy
b8022610ca Fix error in Safari (#869)
* Safari minify fix

* Update browser-hacks.ts
2019-10-24 10:41:21 -07:00
FKSRipple
03defe203a Merge pull request #773 from aMoniker/develop
unref timer so it doesnt hang the node process
2019-10-23 18:29:20 -07:00
FKSRipple
aedcbe56b3 Added small style changes 2019-10-23 18:29:07 -07:00
FKSRipple
c365db460a Merge pull request #1044 from ripple/connection-timeout
Add a 2-second timeout for connect()
2019-10-23 18:19:39 -07:00
Elliot Lee
cfdc4752d0 let -> const and reject with error 2019-10-23 12:25:02 -07:00
Elliot Lee
e1964ac5ed Add support for the X-address format (#1041)
* Update schema-validator

* Update to ripple-address-codec 4.0.0

* Update ./src/common/hashes/index.ts

* Add generateXAddress method

* Deprecate generateAddress method

* Add unit tests

* Add documentation
2019-10-23 12:03:59 -07:00
James Greenleaf
e17ab9cd8f Add conditional to timer.unref 2019-10-23 14:44:20 -04:00
James Greenleaf
edc15b8727 Merge remote-tracking branch 'upstream/develop' into develop 2019-10-23 14:42:05 -04:00
Fred K. Schott
034f8d41fc remove bad semicolon 2019-10-23 00:33:09 -07:00
Fred K. Schott
0fa70db1e1 Merge branch 'develop' into connection-timeout 2019-10-23 00:32:03 -07:00
FKSRipple
fa7ba9b72b Update connection.ts 2019-10-23 00:27:17 -07:00
FKSRipple
3a20123e0f Merge pull request #1046 from ripple/improve-tx-not-found
Improve getTransaction() error when tx has not been validated yet
2019-10-23 00:22:07 -07:00
FKSRipple
03510d1bc4 Update index.ts 2019-10-23 00:21:35 -07:00
Elliot Lee
8b116f637a Update yarn.lock 2019-10-22 00:57:26 -07:00
Elliot Lee
9c49de6552 Merge pull request #1054 from tylerlevine/update-https-proxy-agent
Update https-proxy-agent to 3.0.0
2019-10-22 00:56:33 -07:00
Elliot Lee
842347bcab Merge branch 'develop' into update-https-proxy-agent 2019-10-22 00:56:07 -07:00
Elliot Lee
628b9c4853 Merge pull request #1049 from FredKSchott/eslint
Replace tslint (since deprecated) with eslint
2019-10-21 11:01:53 -07:00
Fred K. Schott
d60b6ee33f Merge remote-tracking branch 'myfork/eslint' into eslint 2019-10-21 10:03:27 -07:00
Fred K. Schott
4f4fcbbc70 add tests to linting 2019-10-21 10:02:14 -07:00
Tyler Levine
cc896670dc Update https-proxy-agent to 3.0.0 2019-10-18 16:16:54 -07:00
Elliot Lee
eb2a497dee Release 1.3.4 2019-10-18 14:41:58 -07:00
Fred K. Schott
0cf5ce1416 Merge branch 'develop' into eslint 2019-10-17 11:23:26 -07:00
Elliot Lee
988381d584 Merge pull request #1048 from FredKSchott/tsc-update
Update TypeScript
2019-10-16 23:04:20 -07:00
Elliot Lee
0b163eae23 XRP Ledger Hashes: create README.md 2019-10-15 16:34:48 -07:00
Fred K. Schott
3a3ff8a65e Update utils.ts 2019-10-15 13:49:03 -07:00
Fred K. Schott
9f183a6dfc replace tslint (deprecated) with eslint 2019-10-13 16:44:22 -07:00
Fred K. Schott
fadfd4e06c update typescript 2019-10-13 16:19:45 -07:00
Sohail Salehi
eb521faa8d Doc update for sign() method (#1010)
* Add multi-signing example to the sign() method

Explains why the `signAs` option is compulsory for multi-signing scenarios.
2019-10-08 11:59:12 -07:00
Elliot Lee
6b572ca862 Improve getTransaction() error when tx has not been validated yet 2019-10-04 23:56:09 -07:00
Elliot Lee
d075ec6716 Add a 2-second timeout for connect() 2019-10-03 00:02:55 -07:00
Tyler Storm
14e6bf5ef9 Integrate ripple-hashes (#1039)
* Update TxParser and Mocha

* Convert ripple-hashes to TypeScript and add into ripple-lib along with unit tests

* Switch casing to camelCase

* Document intToVuc

* Convert Slots to numbers, add exception if number is outside 0-15

* Remove Shamap v2 support

* Improve naming
2019-10-01 23:35:11 -07:00
Elliot Lee
b6bddd3b0e [MINOR] Remove unnecessary semicolon 2019-09-26 03:18:54 -07:00
Elliot Lee
d5ed9b6cf5 Improve error message when signing fails
When there are multiple representations of the same value
(for example, trailing zeros) the verification will fail.

This points users to the error.data object for details about
the failure, and adds a diff so that the cause of the
discrepancy can be seen at a glance.
2019-09-24 09:58:24 -07:00
Tyler Storm
7a9912d4e0 Update TxParser and Mocha 2019-09-24 09:57:56 -07:00
Mo Morsi
4d2ddceb4e Change "wipple" to "xrp1ntel" (#1036)
The Wipple Analytics Engine is now accessed through the xrp1ntel website
2019-09-12 13:15:26 -07:00
Elliot Lee
ae2aed675a v1.3.3 2019-09-10 19:30:11 -07:00
Elliot Lee
1d7cb41218 Add rippleTimeToISO8601.md.ejs 2019-09-04 00:31:02 -07:00
Elliot Lee
76db002eb5 Expand node version compatibility
Previously the library could not be installed with node 12:

https://github.com/ripple/ripple-binary-codec/pull/33
2019-09-04 00:17:53 -07:00
Elliot Lee
ebb64ba177 v1.3.2 2019-09-03 16:43:31 -07:00
Elliot Lee
1a685e2b68 Fix #1032 (#1033)
* Update ripple-address-codec@latest

* Export and document rippleTimeToISO8601
2019-09-03 16:38:56 -07:00
Rome Reginelli
9c561885a1 Docs: update recommended Node ver. (#1031) 2019-08-29 20:34:25 -07:00
Elliot Lee
612e98b198 Release 1.3.1 2019-08-26 13:58:09 -07:00
Jim Greenleaf
0c98082b25 unref timer so it doesnt hang the node process 2017-06-19 15:30:49 -04:00
89 changed files with 6746 additions and 2565 deletions

25
.eslintrc.json Normal file
View File

@@ -0,0 +1,25 @@
{
"env": {
"browser": true,
"es6": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"no-useless-constructor": 0,
"no-unused-vars": 0,
"no-prototype-builtins": 0,
"require-atomic-updates": 0
}
}

View File

@@ -1,4 +0,0 @@
lib-cov
coverage.html
src
dist/bower

View File

@@ -12,7 +12,7 @@ Warning: Use at your own risk.
## Data and visualizations
- **[Wipple - XRP Intelligence](https://wipple.devnull.network/)**
- **[xrp1ntel - XRP Intelligence](https://xrp1ntel.com/)**
Monitor the XRP Network in real time and explore historical statistics.

View File

@@ -1,5 +1,54 @@
# ripple-lib Release History
## 1.4.0 (2019-10-28)
* Unref timer so it does not hang the Node.js process
* Add a 2-second timeout for connect()
* Improve getTransaction() error when tx has not been validated yet
* Add support for the new X-address format
* Fix error in Safari, Chrome 78, Firefox 70
* Some error messages have changed slightly. For example:
* `-instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format`
* `+instance.Account is not of a type(s) string,instance.Account is not exactly one from <xAddress>,<classicAddress>`
### Internal improvements
* Reduce dependency size
* Move tests to TypeScript
* Replace tslint with eslint
* Update https-proxy-agent
* Add tests
## 1.3.4 (2019-10-18)
* Update ripple-lib-transactionparser
* Improve error message when signing fails (e.g. due to trailing zeros)
* Integrate ripple-hashes (in TypeScript with improved naming and docs)
* Add multi-signing example to sign() method docs
* Update TypeScript
## 1.3.3 (2019-09-10)
* Expand node version compatibility to support Node.js 12 ([ripple-binary-codec#32](https://github.com/ripple/ripple-binary-codec/issues/32))
## 1.3.2 (2019-09-03)
* Export and document `rippleTimeToISO8601()` method
* Add type for isValidAddress (fixes error TS7016, #1032)
* Docs: update recommended Node.js version (#1031)
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1) or later.
## 1.3.1 (2019-08-26)
* Upgrade to gulp 4 (#1030)
* Remove http server (unused)
* Update dependencies
There are no changes in the browser version with this release. The npm package for Node.js should be slightly smaller.
When using this release with [rippled](https://github.com/ripple/rippled), we recommend using [rippled version 1.3.1](https://github.com/ripple/rippled/releases/tag/1.3.1).
## 1.3.0 (2019-08-16)
### Bug fixes:

View File

@@ -80,6 +80,7 @@
- [sign](#sign)
- [combine](#combine)
- [submit](#submit)
- [generateXAddress](#generatexaddress)
- [generateAddress](#generateaddress)
- [isValidAddress](#isvalidaddress)
- [isValidSecret](#isvalidsecret)
@@ -91,6 +92,7 @@
- [xrpToDrops](#xrptodrops)
- [dropsToXrp](#dropstoxrp)
- [iso8601ToRippleTime](#iso8601torippletime)
- [rippleTimeToISO8601](#rippletimetoiso8601)
- [txFlags](#txflags)
- [schemaValidator](#schemavalidator)
- [schemaValidate](#schemavalidate)
@@ -141,7 +143,7 @@ api.connect().then(() => {
}).catch(console.error);
```
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
@@ -224,7 +226,19 @@ Methods that depend on the state of the XRP Ledger are unavailable in offline mo
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
```
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
```json
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
```
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
### Classic Address
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
### X-address
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
## Account Sequence Number
@@ -376,12 +390,12 @@ Name | Type | Description
source | object | The source of the funds to be sent.
*source.* address | [address](#address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
destination | object | The destination of the funds to be sent.
*destination.* address | [address](#address) | An address representing the destination of the transaction.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
allowPartialPayment | boolean | *Optional* If true, this payment should proceed even if the whole amount cannot be delivered due to a lack of liquidity or a lack of funds in the source account.
invoiceID | string | *Optional* A 256-bit hash that can be used to identify a particular payment.
@@ -537,7 +551,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
*signers.* threshold | integer | 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](#address) | A Ripple account address
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
@@ -850,7 +864,7 @@ Returns the response from invoking the specified command, with the specified opt
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
### Return Value
@@ -2288,12 +2302,12 @@ Name | Type | Description
source | object | Properties of the source of the payment.
*source.* address | [address](#address) | The address to send from.
*source.* amount | [laxAmount](#amount) | An exact amount to send. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with source.maxAmount)
*source.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*source.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
*source.* maxAmount | [laxAmount](#amount) | The maximum amount to send. (This field cannot be used with source.amount)
destination | object | Properties of the destination of the payment.
*destination.* address | [address](#address) | An address representing the destination of the transaction.
*destination.* amount | [laxAmount](#amount) | An exact amount to deliver to the recipient. If the counterparty is not specified, amounts with any counterparty may be used. (This field cannot be used with `destination.minAmount`.)
*destination.* tag | integer | *Optional* An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.
*destination.* tag | integer | *Optional* An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.
*destination.* minAmount | [laxAmount](#amount) | The minimum amount to be delivered. (This field cannot be used with destination.amount)
paths | string | The paths of trustlines and orders to use in executing the payment.
@@ -3905,7 +3919,7 @@ signers | object | *Optional* Settings that determine what sets of accounts can
*signers.* threshold | integer | 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](#address) | A Ripple account address
*signers.weights[].* address | [address](#address) | An account address on the XRP Ledger
*signers.weights[].* weight | integer | The weight that the signature of this account counts as towards the threshold.
transferRate | number,null | *Optional* The fee to charge when users transfer this accounts issuances, as the decimal amount that must be sent to deliver 1 unit. Has precision up to 9 digits beyond the decimal point. Use `null` to set no fee.
@@ -5402,6 +5416,8 @@ options | object | *Optional* Options that control the type of signature that wi
*options.* signAs | [address](#address) | *Optional* The account that the signature should count for in multisigning.
secret | secret string | *Optional* The secret of the account that is initiating the transaction. (This field cannot be used with keypair).
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
### Return Value
This method returns an object with the following structure:
@@ -5429,6 +5445,94 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
```
### Example (multisigning)
```javascript
const RippleAPI = require('ripple-lib').RippleAPI;
// jon's address will have a multi-signing setup with a quorum of 2
const jon = {
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
};
const aya = {
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
};
const bran = {
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
};
// Setup the signers list with a quorum of 2
const multiSignSetupTransaction = {
"Flags": 0,
"TransactionType": "SignerListSet",
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
"Fee": "120",
"SignerQuorum": 2,
"SignerEntries": [
{
"SignerEntry": {
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
"SignerWeight": 2
}
},
{
"SignerEntry": {
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
"SignerWeight": 1
}
},
]
};
// a transaction which requires multi signing
const multiSignPaymentTransaction = {
TransactionType: 'Payment',
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
Amount: '88000000'
};
const api = new RippleAPI({
server: 'wss://s.altnet.rippletest.net:51233'
});
api.connect().then(() => {
// adding the multi signing feature to jon's account
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
console.log(prepared);
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
api.submit(jonSign).then( response => {
console.log(response.resultCode, response.resultMessage);
// multi sign a transaction
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
console.log(prepared);
// Aya and Bran sign it too but with 'signAs' set to their own account
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
// signatures are combined and submitted
let combinedTx = api.combine([ayaSign, branSign]);
api.submit(combinedTx.signedTransaction).then(response => {
console.log(response.tx_json.hash);
return api.disconnect();
}).catch(console.error);
}).catch(console.error);
}).catch(console.error)
}).catch(console.error);
}).catch(console.error);
```
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
```
[ValidationError(txJSON is not the same for all signedTransactions)]
```
## combine
`combine(signedTransactions: Array<string>): {signedTransaction: string, id: string}`
@@ -5530,9 +5634,9 @@ return api.submit(signedTransaction)
```
## generateAddress
## generateXAddress
`generateAddress(): {address: string, secret: string}`
`generateXAddress(options?: object): {address: string, secret: string}`
Generate a new XRP Ledger address and corresponding secret.
@@ -5543,6 +5647,7 @@ Name | Type | Description
options | object | *Optional* Options to control how the address and secret are generated.
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
### Return Value
@@ -5550,8 +5655,8 @@ This method returns an object with the following structure:
Name | Type | Description
---- | ---- | -----------
address | [address](#address) | A randomly generated Ripple account address.
secret | secret string | The secret corresponding to the `address`.
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
secret | secret string | The secret corresponding to the address.
### Example
@@ -5562,6 +5667,52 @@ return api.generateAddress();
```json
{
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}
```
## generateAddress
`generateAddress(options?: object): {address: string, secret: string}`
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
Generate a new XRP Ledger address and corresponding secret.
### Parameters
Name | Type | Description
---- | ---- | -----------
options | object | *Optional* Options to control how the address and secret are generated.
*options.* algorithm | string | *Optional* The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
*options.* entropy | array\<integer\> | *Optional* The entropy to use to generate the seed.
*options.* includeClassicAddress | boolean | *Optional* If `true`, return the classic address, in addition to the X-address.
*options.* test | boolean | *Optional* Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`.
### Return Value
This method returns an object with the following structure:
Name | Type | Description
---- | ---- | -----------
xAddress | [xAddress](#x-address) | A randomly generated XRP Ledger address in X-address format.
classicAddress | [classicAddress](#classic-address) | A randomly generated XRP Ledger Account ID (classic address).
address | [classicAddress](#classic-address) | Deprecated: Use `classicAddress` instead.
secret | secret string | The secret corresponding to the address.
### Example
```javascript
return api.generateAddress();
```
```json
{
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}
@@ -5572,7 +5723,7 @@ return api.generateAddress();
`isValidAddress(address: string): boolean`
Checks if the specified string contains a valid address.
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
### Parameters
@@ -5860,6 +6011,34 @@ api.iso8601ToRippleTime('2017-02-17T15:04:57Z');
540659097
```
## rippleTimeToISO8601
`rippleTimeToISO8601(rippleTime: number): string`
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
### Parameters
`rippleTime`: A number of seconds since the Ripple Epoch.
### Return Value
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
### Example
```javascript
api.rippleTimeToISO8601(540659097);
```
```json
'2017-02-17T15:04:57.000Z'
```
## txFlags
`txFlags.TRANSACTION_TYPE.FLAG`

View File

@@ -6,7 +6,19 @@
"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59"
```
Every XRP Ledger account has an *address*, which is a base58-encoding of a hash of the account's public key. XRP Ledger addresses always start with the lowercase letter `r`.
```json
"X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ"
```
An *address* refers to a specific XRP Ledger account. It is a base-58 encoding of a hash of the account's public key. There are two kinds of addresses in common use:
### Classic Address
A *classic address* encodes a hash of the account's public key and a checksum. It has no other data. This kind of address always starts with the lowercase letter `r`.
### X-address
An *X-address* encodes a hash of the account's public key, a tag, and a checksum. This kind of address starts with the uppercase letter `X` if it is intended for use on the production XRP Ledger (mainnet). It starts with the uppercase letter `T` if it is intended for use on a test network such as Testnet or Devnet.
## Account Sequence Number

View File

@@ -26,7 +26,7 @@ api.connect().then(() => {
}).catch(console.error);
```
RippleAPI is designed to work in [Node.js](https://nodejs.org) version **6.11.3**. RippleAPI may work on older Node.js versions if you use [Babel](https://babeljs.io/) for [ECMAScript 6](https://babeljs.io/docs/learn-es2015/) support.
RippleAPI is designed to work in [Node.js](https://nodejs.org) version 6 or higher. Ripple recommends Node.js v10 LTS.
The code samples in this documentation are written with ECMAScript 6 (ES6) features, but `RippleAPI` also works with ECMAScript 5 (ES5). Regardless of whether you use ES5 or ES6, the methods that return Promises return [ES6-style promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

View File

@@ -1,6 +1,8 @@
## generateAddress
`generateAddress(): {address: string, secret: string}`
`generateAddress(options?: object): {address: string, secret: string}`
Deprecated: This method returns a classic address. If you do not need the classic address, use `generateXAddress` instead.
Generate a new XRP Ledger address and corresponding secret.

View File

@@ -0,0 +1,23 @@
## generateXAddress
`generateXAddress(options?: object): {address: string, secret: string}`
Generate a new XRP Ledger address and corresponding secret.
### Parameters
<%- renderSchema('input/generate-x-address.json') %>
### Return Value
This method returns an object with the following structure:
<%- renderSchema('output/generate-x-address.json') %>
### Example
```javascript
return api.generateAddress();
```
<%- renderFixture('responses/generate-x-address.json') %>

View File

@@ -52,6 +52,7 @@
<% include sign.md.ejs %>
<% include combine.md.ejs %>
<% include submit.md.ejs %>
<% include generateXAddress.md.ejs %>
<% include generateAddress.md.ejs %>
<% include isValidAddress.md.ejs %>
<% include isValidSecret.md.ejs %>
@@ -62,6 +63,7 @@
<% include computeLedgerHash.md.ejs %>
<% include xrpToDropsAndDropsToXrp.md.ejs %>
<% include iso8601ToRippleTime.md.ejs %>
<% include rippleTimeToISO8601.md.ejs %>
<% include txFlags.md.ejs %>
<% include schemaValidator.md.ejs %>
<% include events.md.ejs %>

View File

@@ -2,7 +2,7 @@
`isValidAddress(address: string): boolean`
Checks if the specified string contains a valid address.
Checks if the specified string contains a valid address. X-addresses are considered valid with ripple-lib v1.4.0 and higher.
### Parameters

View File

@@ -6,7 +6,7 @@ Returns the response from invoking the specified command, with the specified opt
Refer to [rippled APIs](https://ripple.com/build/rippled-apis/) for commands and options. All XRP amounts must be specified in drops. One drop is equal to 0.000001 XRP. See [Specifying Currency Amounts](https://ripple.com/build/rippled-apis/#specifying-currency-amounts).
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://ripple.com/build/rippled-apis/#specifying-ledgers).
Most commands return data for the `current` (in-progress, open) ledger by default. Do not rely on this. Always specify a ledger version in your request. In the example below, the 'validated' ledger is requested, which is the most recent ledger that has been validated by the whole network. See [Specifying Ledgers](https://xrpl.org/basic-data-types.html#specifying-ledgers).
### Return Value

View File

@@ -0,0 +1,27 @@
## rippleTimeToISO8601
`rippleTimeToISO8601(rippleTime: number): string`
This method takes the number of seconds since the "Ripple Epoch" of January 1, 2000 (00:00 UTC) and returns a string representation of a date.
The Ripple Epoch is 946684800 seconds after the Unix Epoch.
This method is useful for interpreting timestamps returned by the rippled APIs. The rippled APIs represent time as an unsigned integer of the number of seconds since the Ripple Epoch.
### Parameters
`rippleTime`: A number of seconds since the Ripple Epoch.
### Return Value
A string representing a date and time, created by calling a `Date` object's `toISOString()` method.
### Example
```javascript
api.rippleTimeToISO8601(540659097);
```
```json
'2017-02-17T15:04:57.000Z'
```

View File

@@ -15,6 +15,8 @@ This method can sign any of [the transaction types supported by ripple-binary-co
<%- renderSchema("input/sign.json") %>
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
### Return Value
This method returns an object with the following structure:
@@ -31,3 +33,91 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
```
<%- renderFixture("responses/sign.json") %>
### Example (multisigning)
```javascript
const RippleAPI = require('ripple-lib').RippleAPI;
// jon's address will have a multi-signing setup with a quorum of 2
const jon = {
account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
secret: 'sh4Va7b1wQof8knHFV2sxwX12fSgK'
};
const aya = {
account: 'rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN',
secret: 'snaMuMrXeVc2Vd4NYvHofeGNjgYoe'
};
const bran = {
account: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
secret: 'shQtQ8Um5MS218yvEU3Ehy1eZQKqH'
};
// Setup the signers list with a quorum of 2
const multiSignSetupTransaction = {
"Flags": 0,
"TransactionType": "SignerListSet",
"Account": "rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj",
"Fee": "120",
"SignerQuorum": 2,
"SignerEntries": [
{
"SignerEntry": {
"Account": "rnrPdBjs98fFFfmRpL6hM7exT788SWQPFN",
"SignerWeight": 2
}
},
{
"SignerEntry": {
"Account": "rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH",
"SignerWeight": 1
}
},
]
};
// a transaction which requires multi signing
const multiSignPaymentTransaction = {
TransactionType: 'Payment',
Account: 'rJKpme4m2zBQceBuU89d7vLMzgoUw2Ptj',
Destination: 'rJ93RLnT1t5A8fCr7HTScw7WtfKJMRXodH',
Amount: '88000000'
};
const api = new RippleAPI({
server: 'wss://s.altnet.rippletest.net:51233'
});
api.connect().then(() => {
// adding the multi signing feature to jon's account
api.prepareTransaction(multiSignSetupTransaction).then((prepared) => {
console.log(prepared);
jonSign = api.sign(prepared.txJSON, jon.secret).signedTransaction;
api.submit(jonSign).then( response => {
console.log(response.resultCode, response.resultMessage);
// multi sign a transaction
api.prepareTransaction(multiSignPaymentTransaction).then(prepared => {
console.log(prepared);
// Aya and Bran sign it too but with 'signAs' set to their own account
let ayaSign = api.sign(prepared.txJSON, aya.secret, {'signAs': aya.account}).signedTransaction;
let branSign = api.sign(prepared.txJSON, bran.secret, {'signAs': bran.account}).signedTransaction;
// signatures are combined and submitted
let combinedTx = api.combine([ayaSign, branSign]);
api.submit(combinedTx.signedTransaction).then(response => {
console.log(response.tx_json.hash);
return api.disconnect();
}).catch(console.error);
}).catch(console.error);
}).catch(console.error)
}).catch(console.error);
}).catch(console.error);
```
Assuming the multisigning account was setup properly, the above example will respond with `resultCode: 'tesSUCCESS'` and the hash for the transaction.
If any of `{signAs: some_address}` options were missing the code will return a validation error as follow:
```
[ValidationError(txJSON is not the same for all signedTransactions)]
```

View File

@@ -1,11 +1,11 @@
{
"name": "ripple-lib",
"version": "1.3.0",
"version": "1.4.0",
"license": "ISC",
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
"files": [
"dist/npm/*",
"build/*"
"build/ripple-latest-min.js"
],
"main": "dist/npm/",
"types": "dist/npm/index.d.ts",
@@ -20,35 +20,37 @@
"@types/lodash": "^4.14.136",
"@types/ws": "^3.2.0",
"bignumber.js": "^4.1.0",
"https-proxy-agent": "2.2.1",
"https-proxy-agent": "^3.0.0",
"jsonschema": "1.2.2",
"lodash": "^4.17.4",
"ripple-address-codec": "^2.0.1",
"ripple-binary-codec": "0.2.2",
"ripple-hashes": "0.3.2",
"ripple-address-codec": "^4.0.0",
"lodash.isequal": "^4.5.0",
"ripple-binary-codec": "^0.2.4",
"ripple-keypairs": "^0.10.1",
"ripple-lib-transactionparser": "0.7.1",
"ripple-lib-transactionparser": "0.8.0",
"ws": "^3.3.1"
},
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/node": "11.13.0",
"@typescript-eslint/eslint-plugin": "^2.3.3",
"@typescript-eslint/parser": "^2.3.3",
"assert-diff": "^1.0.1",
"doctoc": "^0.15.0",
"ejs": "^2.3.4",
"eslint": "^6.5.1",
"eventemitter2": "^0.4.14",
"gulp": "^4.0.2",
"json-loader": "^0.5.2",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "6.1.3",
"mocha": "6.2.0",
"mocha-junit-reporter": "^1.9.1",
"null-loader": "^0.1.1",
"nyc": "^14.1.1",
"source-map-support": "0.5.12",
"ts-loader": "^3.2.0",
"ts-node": "8.0.3",
"tslint": "^5.8.0",
"tslint-eslint-rules": "^4.1.1",
"typescript": "3.4.2",
"typescript": "^3.6.4",
"uglifyjs-webpack-plugin": "^1.1.4",
"webpack": "3.12.0"
},
@@ -57,11 +59,11 @@
"doctoc": "doctoc docs/index.md --title '# RippleAPI Reference' --github --maxlevel 2",
"docgen": "node --harmony scripts/build_docs.js",
"clean": "rm -rf dist/npm",
"compile": "mkdir -p dist/npm/common/js && cp -r src/common/js/ dist/npm/common/js/ && mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc --build",
"compile": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/ && tsc --build",
"watch": "tsc -w",
"prepublish": "npm run clean && npm run compile && npm run build",
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --exit",
"lint": "tslint -p ./",
"test": "TS_NODE_PROJECT=test/tsconfig.json nyc mocha --exit",
"lint": "eslint src/**/*.ts 'test/*-test.{ts,js}'",
"perf": "./scripts/perf_test.sh",
"start": "node scripts/http.js"
},

View File

@@ -5,8 +5,10 @@ import {
validate,
xrpToDrops,
dropsToXrp,
rippleTimeToISO8601,
iso8601ToRippleTime,
txFlags
txFlags,
ensureClassicAddress
} from './common'
import {
connect,
@@ -45,8 +47,8 @@ import prepareSettings from './transaction/settings'
import sign from './transaction/sign'
import combine from './transaction/combine'
import submit from './transaction/submit'
import {generateAddressAPI} from './offline/generate-address'
import {deriveKeypair, deriveAddress} from './offline/derive'
import {generateAddressAPI, GenerateAddressOptions, GeneratedAddress} from './offline/generate-address'
import {deriveKeypair, deriveAddress, deriveXAddress} from './offline/derive'
import computeLedgerHash from './offline/ledgerhash'
import signPaymentChannelClaim from './offline/sign-payment-channel-claim'
import verifyPaymentChannelClaim from './offline/verify-payment-channel-claim'
@@ -73,6 +75,7 @@ import {getServerInfo, getFee} from './common/serverinfo'
import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
import {ConnectionOptions} from './common/connection'
import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec'
export interface APIOptions extends ConnectionOptions {
server?: string,
@@ -174,7 +177,8 @@ class RippleAPI extends EventEmitter {
async request(command: string, params: any = {}): Promise<any> {
return this.connection.request({
...params,
command
command,
account: params.account ? ensureClassicAddress(params.account) : undefined
})
}
@@ -287,6 +291,15 @@ class RippleAPI extends EventEmitter {
return results
}
// @deprecated Use X-addresses instead
generateAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
return generateAddressAPI({...options, includeClassicAddress: true})
}
generateXAddress(options: GenerateAddressOptions = {}): GeneratedAddress {
return generateAddressAPI(options)
}
connect = connect
disconnect = disconnect
isConnected = isConnected
@@ -327,7 +340,6 @@ class RippleAPI extends EventEmitter {
combine = combine
submit = submit
generateAddress = generateAddressAPI
deriveKeypair = deriveKeypair
deriveAddress = deriveAddress
computeLedgerHash = computeLedgerHash
@@ -335,8 +347,17 @@ class RippleAPI extends EventEmitter {
verifyPaymentChannelClaim = verifyPaymentChannelClaim
errors = errors
static deriveXAddress = deriveXAddress
// RippleAPI.deriveClassicAddress (static) is a new name for api.deriveAddress
static deriveClassicAddress = deriveAddress
static isValidXAddress = isValidXAddress
static isValidClassicAddress = isValidClassicAddress
xrpToDrops = xrpToDrops
dropsToXrp = dropsToXrp
rippleTimeToISO8601 = rippleTimeToISO8601
iso8601ToRippleTime = iso8601ToRippleTime
txFlags = txFlags

View File

@@ -7,11 +7,14 @@ function setPrototypeOf(object, prototype) {
}
function getConstructorName(object: object): string {
// hack for internet explorer
if (!object.constructor.name) {
return object.constructor.toString().match(/^function\s+([^(]*)/)![1]
if (object.constructor.name) {
return object.constructor.name
}
return object.constructor.name
// try to guess it on legacy browsers (ie)
const constructorString = object.constructor.toString()
const functionConstructor = constructorString.match(/^function\s+([^(]*)/)
const classConstructor = constructorString.match(/^class\s([^\s]*)/)
return functionConstructor ? functionConstructor[1] : classConstructor[1]
}
export {

View File

@@ -1,14 +1,14 @@
import * as _ from 'lodash'
import {EventEmitter} from 'events'
import {parse as parseUrl} from 'url'
import * as WebSocket from 'ws'
import WebSocket from 'ws'
import RangeSet from './rangeset'
import {RippledError, DisconnectedError, NotConnectedError,
TimeoutError, ResponseFormatError, ConnectionError,
RippledNotInitializedError} from './errors'
export interface ConnectionOptions {
trace?: boolean,
trace?: boolean
proxy?: string
proxyAuthorization?: string
authorization?: string
@@ -16,7 +16,8 @@ export interface ConnectionOptions {
key?: string
passphrase?: string
certificate?: string
timeout?: number
timeout?: number,
connectionTimeout?: number
}
class Connection extends EventEmitter {
@@ -38,11 +39,13 @@ class Connection extends EventEmitter {
private _availableLedgerVersions = new RangeSet()
private _nextRequestID: number = 1
private _retry: number = 0
private _connectTimer: null|NodeJS.Timer = null
private _retryTimer: null|NodeJS.Timer = null
private _onOpenErrorBound: null| null|((...args: any[]) => void) = null
private _onUnexpectedCloseBound: null|((...args: any[]) => void) = null
private _fee_base: null|number = null
private _fee_ref: null|number = null
private _connectionTimeout: number
constructor(url, options: ConnectionOptions = {}) {
super()
@@ -61,6 +64,7 @@ class Connection extends EventEmitter {
this._passphrase = options.passphrase
this._certificate = options.certificate
this._timeout = options.timeout || (20 * 1000)
this._connectionTimeout = options.connectionTimeout || 2000
}
_updateLedgerVersions(data) {
@@ -179,6 +183,13 @@ class Connection extends EventEmitter {
}
}
_clearConnectTimer() {
if (this._connectTimer !== null) {
clearTimeout(this._connectTimer)
this._connectTimer = null
}
}
_onOpen() {
if (!this._ws) {
return Promise.reject(new DisconnectedError())
@@ -282,8 +293,13 @@ class Connection extends EventEmitter {
}
connect(): Promise<void> {
this._clearConnectTimer()
this._clearReconnectTimer()
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
this._connectTimer = setTimeout(() => {
reject(new ConnectionError(`Error: connect() timed out after ${this._connectionTimeout} ms. ` +
`If your internet connection is working, the rippled server may be blocked or inaccessible.`))
}, this._connectionTimeout)
if (!this._url) {
reject(new ConnectionError(
'Cannot connect because no server was specified'))
@@ -291,7 +307,7 @@ class Connection extends EventEmitter {
if (this._state === WebSocket.OPEN) {
resolve()
} else if (this._state === WebSocket.CONNECTING) {
this._ws.once('open', resolve)
this._ws.once('open', () => resolve)
} else {
this._ws = this._createWebSocket()
// when an error causes the connection to close, the close event
@@ -311,9 +327,20 @@ class Connection extends EventEmitter {
this._onUnexpectedCloseBound = this._onUnexpectedClose.bind(this, true,
resolve, reject)
this._ws.once('close', this._onUnexpectedCloseBound)
this._ws.once('open', () => this._onOpen().then(resolve, reject))
this._ws.once('open', () => {
return this._onOpen().then(resolve, reject)
})
}
})
// Once we have a resolution or rejection, clear the timeout timer as no
// longer needed.
.then(() => {
this._clearConnectTimer()
})
.catch((err) => {
this._clearConnectTimer()
throw err;
})
}
disconnect(): Promise<void> {
@@ -322,6 +349,7 @@ class Connection extends EventEmitter {
_disconnect(calledByUser): Promise<void> {
if (calledByUser) {
this._clearConnectTimer()
this._clearReconnectTimer()
this._retry = 0
}
@@ -456,6 +484,10 @@ class Connection extends EventEmitter {
this._whenReady(this._send(message)).then(() => {
const delay = timeout || this._timeout
timer = setTimeout(() => _reject(new TimeoutError()), delay)
// Node.js won't exit if a timer is still running, so we tell Node to ignore (Node will still wait for the request to complete)
if (timer.unref) {
timer.unref()
}
}).catch(_reject)
})
}

View File

@@ -0,0 +1,49 @@
# XRP Ledger Hashes
Methods to hash XRP Ledger objects
## Methods
### computeBinaryTransactionHash = (txBlobHex: string): string
Compute the hash of a binary transaction blob.
### computeTransactionHash = (txJSON: any): string
Compute the hash of a transaction in txJSON format.
### computeBinaryTransactionSigningHash = (txBlobHex: string): string
### computeTransactionSigningHash = (txJSON: any): string
### computeAccountHash = (address: string): string
Compute the hash of an account, given the account's classic address (starting with `r`).
### computeSignerListHash = (address: string): string
Compute the hash of an account's SignerList.
### computeOrderHash = (address: string, sequence: number): string
Compute the hash of an order, given the owner's classic address (starting with `r`) and the account sequence number of the `OfferCreate` order transaction.
### computeTrustlineHash = (address1: string, address2: string, currency: string): string
Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code.
### computeTransactionTreeHash = (transactions: any[]): string
### computeStateTreeHash = (entries: any[]): string
### computeLedgerHash = (ledgerHeader): string
Compute the hash of a ledger.
### computeEscrowHash = (address, sequence): string
Compute the hash of an escrow, given the owner's classic address (starting with `r`) and the account sequence number of the `EscrowCreate` escrow transaction.
### computePaymentChannelHash = (address, dstAddress, sequence): string
Compute the hash of a payment channel, given the owner's classic address (starting with `r`), the classic address of the destination, and the account sequence number of the `PaymentChannelCreate` payment channel transaction.

View File

@@ -0,0 +1,40 @@
/**
* Prefix for hashing functions.
*
* These prefixes are inserted before the source material used to
* generate various hashes. This is done to put each hash in its own
* "space." This way, two different types of objects with the
* same binary data will produce different hashes.
*
* Each prefix is a 4-byte value with the last byte set to zero
* and the first three bytes formed from the ASCII equivalent of
* some arbitrary string. For example "TXN".
*/
enum HashPrefix {
// transaction plus signature to give transaction ID
TRANSACTION_ID = 0x54584E00, // 'TXN'
// transaction plus metadata
TRANSACTION_NODE = 0x534E4400, // 'TND'
// inner node in tree
INNER_NODE = 0x4D494E00, // 'MIN'
// leaf node in tree
LEAF_NODE = 0x4D4C4E00, // 'MLN'
// inner transaction to sign
TRANSACTION_SIGN = 0x53545800, // 'STX'
// inner transaction to sign (TESTNET)
TRANSACTION_SIGN_TESTNET = 0x73747800, // 'stx'
// inner transaction to multisign
TRANSACTION_MULTISIGN = 0x534D5400, // 'SMT'
// ledger
LEDGER = 0x4C575200 // 'LWR'
}
export default HashPrefix

155
src/common/hashes/index.ts Normal file
View File

@@ -0,0 +1,155 @@
import BigNumber from 'bignumber.js'
import {decodeAccountID} from 'ripple-address-codec'
import sha512Half from './sha512Half'
import HashPrefix from './hash-prefix'
import {SHAMap, NodeType} from './shamap'
import {encode} from 'ripple-binary-codec'
import ledgerspaces from './ledgerspaces'
const padLeftZero = (string: string, length: number): string => {
return Array(length - string.length + 1).join('0') + string
}
const intToHex = (integer: number, byteLength: number): string => {
return padLeftZero(Number(integer).toString(16), byteLength * 2)
}
const bytesToHex = (bytes: number[]): string => {
return Buffer.from(bytes).toString('hex')
}
const bigintToHex = (integerString: string | number | BigNumber, byteLength: number): string => {
const hex = (new BigNumber(integerString)).toString(16)
return padLeftZero(hex, byteLength * 2)
}
const ledgerSpaceHex = (name: string): string => {
return intToHex(ledgerspaces[name].charCodeAt(0), 2)
}
const addressToHex = (address: string): string => {
return (Buffer.from(decodeAccountID(address))).toString('hex')
}
const currencyToHex = (currency: string): string => {
if (currency.length === 3) {
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
bytes[12] = currency.charCodeAt(0) & 0xff
bytes[13] = currency.charCodeAt(1) & 0xff
bytes[14] = currency.charCodeAt(2) & 0xff
return bytesToHex(bytes)
}
return currency
}
const addLengthPrefix = (hex: string): string => {
const length = hex.length / 2
if (length <= 192) {
return bytesToHex([length]) + hex
} else if (length <= 12480) {
const x = length - 193
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex
} else if (length <= 918744) {
const x = length - 12481
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex
}
throw new Error('Variable integer overflow.')
}
export const computeBinaryTransactionHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_ID.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}
export const computeTransactionHash = (txJSON: any): string => {
return computeBinaryTransactionHash(encode(txJSON))
}
export const computeBinaryTransactionSigningHash = (txBlobHex: string): string => {
const prefix = HashPrefix.TRANSACTION_SIGN.toString(16).toUpperCase()
return sha512Half(prefix + txBlobHex)
}
export const computeTransactionSigningHash = (txJSON: any): string => {
return computeBinaryTransactionSigningHash(encode(txJSON))
}
export const computeAccountHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
}
export const computeSignerListHash = (address: string): string => {
return sha512Half(ledgerSpaceHex('signerList') +
addressToHex(address) +
'00000000') // uint32(0) signer list index
}
export const computeOrderHash = (address: string, sequence: number): string => {
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
}
export const computeTrustlineHash = (address1: string, address2: string, currency: string): string => {
const address1Hex = addressToHex(address1)
const address2Hex = addressToHex(address2)
const swap = (new BigNumber(address1Hex, 16)).greaterThan(
new BigNumber(address2Hex, 16))
const lowAddressHex = swap ? address2Hex : address1Hex
const highAddressHex = swap ? address1Hex : address2Hex
const prefix = ledgerSpaceHex('rippleState')
return sha512Half(prefix + lowAddressHex + highAddressHex +
currencyToHex(currency))
}
export const computeTransactionTreeHash = (transactions: any[]): string => {
const shamap = new SHAMap()
transactions.forEach(txJSON => {
const txBlobHex = encode(txJSON)
const metaHex = encode(txJSON.metaData)
const txHash = computeBinaryTransactionHash(txBlobHex)
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex)
shamap.addItem(txHash, data, NodeType.TRANSACTION_METADATA)
})
return shamap.hash
}
export const computeStateTreeHash = (entries: any[]): string => {
const shamap = new SHAMap()
entries.forEach(ledgerEntry => {
const data = encode(ledgerEntry)
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
})
return shamap.hash
}
// see rippled Ledger::updateHash()
export const computeLedgerHash = (ledgerHeader): string => {
const prefix = HashPrefix.LEDGER.toString(16).toUpperCase()
return sha512Half(prefix +
intToHex(ledgerHeader.ledger_index, 4) +
bigintToHex(ledgerHeader.total_coins, 8) +
ledgerHeader.parent_hash +
ledgerHeader.transaction_hash +
ledgerHeader.account_hash +
intToHex(ledgerHeader.parent_close_time, 4) +
intToHex(ledgerHeader.close_time, 4) +
intToHex(ledgerHeader.close_time_resolution, 1) +
intToHex(ledgerHeader.close_flags, 1)
)
}
export const computeEscrowHash = (address, sequence): string => {
return sha512Half(ledgerSpaceHex('escrow') + addressToHex(address) +
intToHex(sequence, 4))
}
export const computePaymentChannelHash = (address, dstAddress, sequence): string => {
return sha512Half(ledgerSpaceHex('paychan') + addressToHex(address) +
addressToHex(dstAddress) + intToHex(sequence, 4))
}

View File

@@ -0,0 +1,25 @@
/**
* Ripple ledger namespace prefixes.
*
* The Ripple ledger is a key-value store. In order to avoid name collisions,
* names are partitioned into namespaces.
*
* Each namespace is just a single character prefix.
*/
export default {
account : 'a',
dirNode : 'd',
generatorMap : 'g',
rippleState : 'r',
offer : 'o', // Entry for an offer.
ownerDir : 'O', // Directory of things owned by an account.
bookDir : 'B', // Directory of order books.
contract : 'c',
skipList : 's',
amendment : 'f',
feeSettings : 'e',
signerList : 'S',
escrow : 'u',
paychan : 'x'
}

View File

@@ -0,0 +1,7 @@
import {createHash} from 'crypto'
const sha512Half = (hex: string): string => {
return createHash('sha512').update(Buffer.from(hex, 'hex')).digest('hex').toUpperCase().slice(0, 64)
}
export default sha512Half

173
src/common/hashes/shamap.ts Normal file
View File

@@ -0,0 +1,173 @@
import hashPrefix from './hash-prefix'
import sha512Half from './sha512Half'
const HEX_ZERO = '0000000000000000000000000000000000000000000000000000000000000000'
export enum NodeType {
INNER = 1,
TRANSACTION_NO_METADATA = 2,
TRANSACTION_METADATA = 3,
ACCOUNT_STATE = 4
}
export abstract class Node {
/**
* Abstract constructor representing a node in a SHAMap tree.
* Can be either InnerNode or Leaf.
* @constructor
*/
public constructor() {}
public addItem(_tag: string, _node: Node): void {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#addItem.')
}
public get hash(): string|void {
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
}
}
export class InnerNode extends Node {
public leaves: { [slot: number]: Node }
public type: NodeType
public depth: number
public empty: boolean
/**
* Define an Inner (non-leaf) node in a SHAMap tree.
* @param {number} depth i.e. how many parent inner nodes
* @constructor
*/
public constructor(depth: number = 0) {
super()
this.leaves = {}
this.type = NodeType.INNER
this.depth = depth
this.empty = true
}
/**
* @param {string} tag equates to a ledger entry `index`
* @param {Node} node to add
* @return {void}
*/
public addItem(tag: string, node: Node): void {
const existingNode = this.getNode(parseInt(tag[this.depth],16))
if (existingNode) {
// A node already exists in this slot
if (existingNode instanceof InnerNode) {
// There is an inner node, so we need to go deeper
existingNode.addItem(tag, node)
} else if (existingNode instanceof Leaf) {
if (existingNode.tag === tag) {
// Collision
throw new Error(
'Tried to add a node to a SHAMap that was already in there.')
} else {
// Turn it into an inner node
const newInnerNode = new InnerNode(this.depth + 1)
// Parent new and existing node
newInnerNode.addItem(existingNode.tag, existingNode)
newInnerNode.addItem(tag, node)
// And place the newly created inner node in the slot
this.setNode(parseInt(tag[this.depth], 16), newInnerNode)
}
}
} else {
// Neat, we have a nice open spot for the new node
this.setNode(parseInt(tag[this.depth], 16), node)
}
}
/**
* Overwrite the node that is currently in a given slot.
* @param {number} slot a number 0-15
* @param {Node} node to place
* @return {void}
*/
public setNode(slot: number, node: Node): void {
if (slot < 0 || slot > 15) {
throw new Error ('Invalid slot: slot must be between 0-15.')
}
this.leaves[slot] = node
this.empty = false
}
/**
* Get the node that is currently in a given slot.
* @param {number} slot a number 0-15
* @return {Node}
*/
public getNode(slot: number): Node {
if (slot < 0 || slot > 15) {
throw new Error ('Invalid slot: slot must be between 0-15.')
}
return this.leaves[slot]
}
public get hash(): string {
if (this.empty) return HEX_ZERO
let hex = ''
for (let i = 0; i < 16; i++) {
hex += this.leaves[i] ? this.leaves[i].hash : HEX_ZERO
}
const prefix = hashPrefix.INNER_NODE.toString(16)
return sha512Half(prefix + hex)
}
}
export class Leaf extends Node {
public tag: string
public type: NodeType
public data: string
/**
* Leaf node in a SHAMap tree.
* @param {string} tag equates to a ledger entry `index`
* @param {string} data hex of account state, transaction etc
* @param {number} type one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc
* @constructor
*/
public constructor(tag: string, data: string, type: NodeType) {
super()
this.tag = tag
this.type = type
this.data = data
}
public get hash(): string|void {
switch (this.type) {
case NodeType.ACCOUNT_STATE:
const leafPrefix = hashPrefix.LEAF_NODE.toString(16)
return sha512Half(leafPrefix + this.data + this.tag)
case NodeType.TRANSACTION_NO_METADATA:
const txIDPrefix = hashPrefix.TRANSACTION_ID.toString(16)
return sha512Half(txIDPrefix + this.data)
case NodeType.TRANSACTION_METADATA:
const txNodePrefix = hashPrefix.TRANSACTION_NODE.toString(16)
return sha512Half(txNodePrefix + this.data + this.tag)
default:
throw new Error('Tried to hash a SHAMap node of unknown type.')
}
}
}
export class SHAMap {
public root: InnerNode
/**
* SHAMap tree.
* @constructor
*/
public constructor() {
this.root = new InnerNode(0)
}
public addItem(tag: string, data: string, type: NodeType): void {
this.root.addItem(tag, new Leaf(tag, data, type))
}
public get hash(): string {
return this.root.hash
}
}

View File

@@ -2,13 +2,35 @@ import * as constants from './constants'
import * as errors from './errors'
import * as validate from './validate'
import * as serverInfo from './serverinfo'
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
export function ensureClassicAddress(account: string): string {
if (isValidXAddress(account)) {
const {
classicAddress,
tag
} = xAddressToClassicAddress(account)
// Except for special cases, X-addresses used for requests
// must not have an embedded tag. In other words,
// `tag` should be `false`.
if (tag !== false) {
throw new Error('This command does not support the use of a tag. Use an address without a tag.')
}
// For rippled requests that use an account, always use a classic address.
return classicAddress
} else {
return account
}
}
export {
constants,
errors,
validate,
serverInfo
}
export {
dropsToXrp,
xrpToDrops,
@@ -20,4 +42,3 @@ export {
} from './utils'
export {default as Connection} from './connection'
export {txFlags} from './txflags'

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@ class RangeSet {
}
addRange(start: number, end: number) {
assert(start <= end, `invalid range ${start} <= ${end}`)
assert.ok(start <= end, `invalid range ${start} <= ${end}`)
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]))
}

View File

@@ -2,7 +2,7 @@ import * as _ from 'lodash'
import * as assert from 'assert'
const {Validator} = require('jsonschema')
import {ValidationError} from './errors'
import {isValidAddress} from 'ripple-address-codec'
import {isValidClassicAddress, isValidXAddress} from 'ripple-address-codec'
import {isValidSecret} from './utils'
function loadSchemas() {
@@ -34,6 +34,8 @@ function loadSchemas() {
require('./schemas/objects/destination-address-tag.json'),
require('./schemas/objects/transaction-hash.json'),
require('./schemas/objects/address.json'),
require('./schemas/objects/x-address.json'),
require('./schemas/objects/classic-address.json'),
require('./schemas/objects/adjustment.json'),
require('./schemas/objects/quality.json'),
require('./schemas/objects/amount.json'),
@@ -121,17 +123,28 @@ function loadSchemas() {
]
const titles = schemas.map(schema => schema.title)
const duplicates = _.keys(_.pickBy(_.countBy(titles), count => count > 1))
assert(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
assert.ok(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
const validator = new Validator()
// Register custom format validators that ignore undefined instances
// since jsonschema will still call the format validator on a missing
// (optional) property
validator.customFormats.address = function(instance) {
// This relies on "format": "xAddress" in `x-address.json`!
validator.customFormats.xAddress = function(instance) {
if (instance === undefined) {
return true
}
return isValidXAddress(instance)
}
// This relies on "format": "classicAddress" in `classic-address.json`!
validator.customFormats.classicAddress = function(instance) {
if (instance === undefined) {
return true
}
return isValidAddress(instance)
}
validator.customFormats.secret = function(instance) {
if (instance === undefined) {
return true
@@ -158,6 +171,10 @@ function schemaValidate(schemaName: string, object: any): void {
}
}
function isValidAddress(address: string): boolean {
return isValidXAddress(address) || isValidClassicAddress(address)
}
export {
schemaValidate,
isValidSecret,

View File

@@ -20,6 +20,14 @@
"type": "string",
"enum": ["ecdsa-secp256k1", "ed25519"],
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
},
"test": {
"type": "boolean",
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
},
"includeClassicAddress": {
"type": "boolean",
"description": "If `true`, return the classic address, in addition to the X-address."
}
},
"additionalProperties": false

View File

@@ -0,0 +1,33 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "generateXAddressParameters",
"type": "object",
"properties": {
"options": {
"type": "object",
"description": "Options to control how the address and secret are generated.",
"properties": {
"entropy": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 255
},
"description": "The entropy to use to generate the seed."
},
"algorithm": {
"type": "string",
"enum": ["ecdsa-secp256k1", "ed25519"],
"description": "The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`."
},
"test": {
"type": "boolean",
"description": "Specifies whether the address is intended for use on a test network such as Testnet or Devnet. If `true`, the address should only be used for testing, and will start with `T`. If `false`, the address should only be used on mainnet, and will start with `X`."
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}

View File

@@ -1,9 +1,12 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "address",
"description": "A Ripple account address",
"description": "An account address on the XRP Ledger",
"type": "string",
"format": "address",
"link": "address",
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{25,34}$"
"oneOf": [
{"$ref": "xAddress"},
{"$ref": "classicAddress"}
]
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "classicAddress",
"description": "A classic address (Account ID) for the XRP Ledger",
"type": "string",
"format": "classicAddress",
"link": "classic-address",
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{24,34}$"
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "tag",
"description": "An arbitrary unsigned 32-bit integer that identifies a reason for payment or a non-Ripple account.",
"description": "An arbitrary 32-bit unsigned integer. It typically maps to an off-ledger account; for example, a hosted wallet or exchange account.",
"type": "integer",
"$ref": "uint32"
}

View File

@@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "xAddress",
"description": "An XRP Ledger address in X-address format",
"type": "string",
"format": "xAddress",
"link": "x-address",
"pattern": "^[XT][1-9A-HJ-NP-Za-km-z]{46}$"
}

View File

@@ -3,16 +3,24 @@
"title": "generateAddress",
"type": "object",
"properties": {
"xAddress": {
"$ref": "xAddress",
"description": "A randomly generated XRP Ledger address in X-address format."
},
"classicAddress": {
"$ref": "classicAddress",
"description": "A randomly generated XRP Ledger Account ID (classic address)."
},
"address": {
"$ref": "address",
"description": "A randomly generated Ripple account address."
"$ref": "classicAddress",
"description": "Deprecated: Use `classicAddress` instead."
},
"secret": {
"type": "string",
"format": "secret",
"description": "The secret corresponding to the `address`."
"description": "The secret corresponding to the address."
}
},
"required": ["address", "secret"],
"required": ["xAddress", "classicAddress", "address", "secret"],
"additionalProperties": false
}

View File

@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "generateXAddress",
"type": "object",
"properties": {
"xAddress": {
"$ref": "xAddress",
"description": "A randomly generated XRP Ledger address in X-address format."
},
"secret": {
"type": "string",
"format": "secret",
"description": "The secret corresponding to the address."
}
},
"required": ["xAddress", "secret"],
"additionalProperties": false
}

View File

@@ -17,7 +17,7 @@ function dropsToXrp(drops: string | BigNumber): string {
if (typeof drops === 'string') {
if (!drops.match(/^-?[0-9]*\.?[0-9]*$/)) {
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
} else if (drops === '.') {
throw new ValidationError(`dropsToXrp: invalid value '${drops}',` +
` should be a BigNumber or string-encoded number.`)
@@ -51,7 +51,7 @@ function xrpToDrops(xrp: string | BigNumber): string {
if (typeof xrp === 'string') {
if (!xrp.match(/^-?[0-9]*\.?[0-9]*$/)) {
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
` should be a number matching (^-?[0-9]*\.?[0-9]*$).`)
` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`)
} else if (xrp === '.') {
throw new ValidationError(`xrpToDrops: invalid value '${xrp}',` +
` should be a BigNumber or string-encoded number.`)

View File

@@ -1,4 +1,4 @@
import {validate, removeUndefined, dropsToXrp} from '../common'
import {validate, removeUndefined, dropsToXrp, ensureClassicAddress} from '../common'
import {RippleAPI} from '..'
import {AccountInfoResponse} from '../common/types/commands/account_info'
@@ -34,6 +34,11 @@ export default async function getAccountInfo(
): Promise<FormattedGetAccountInfoResponse> {
// 1. Validate
validate.getAccountInfo({address, options})
// Only support retrieving account info without a tag,
// since account info is not distinguished by tag.
address = ensureClassicAddress(address)
// 2. Make Request
const response = await this.request('account_info', {
account: address,

View File

@@ -1,11 +1,10 @@
import * as utils from './utils'
import {validate} from '../common'
import {validate, ensureClassicAddress} from '../common'
import {Connection} from '../common'
import {GetTrustlinesOptions} from './trustlines'
import {FormattedTrustline} from '../common/types/objects/trustlines'
import {RippleAPI} from '..'
export type Balance = {
value: string,
currency: string,
@@ -52,6 +51,13 @@ function getBalances(this: RippleAPI, address: string, options: GetTrustlinesOpt
): Promise<GetBalances> {
validate.getTrustlines({address, options})
// Only support retrieving balances without a tag,
// since we currently do not calculate balances
// on a per-tag basis. Apps must interpret and
// use tags independent of the XRP Ledger, comparing
// with the XRP Ledger's balance as an accounting check.
address = ensureClassicAddress(address)
return Promise.all([
getLedgerVersionHelper(this.connection, options.ledgerVersion).then(
ledgerVersion =>

View File

@@ -33,6 +33,6 @@ export default async function getOrders(
ledger_index: options.ledgerVersion || await this.getLedgerVersion(),
limit: options.limit
})
// 3. Return Formatted Response
// 3. Return Formatted Response, from the perspective of `address`
return formatResponse(address, responses)
}

View File

@@ -1,7 +1,7 @@
import * as assert from 'assert'
function parseOrderCancellation(tx: any): object {
assert(tx.TransactionType === 'OfferCancel')
assert.ok(tx.TransactionType === 'OfferCancel')
return {
orderSequence: tx.OfferSequence
}

View File

@@ -8,7 +8,7 @@ export type FormattedCheckCancel = {
}
function parseCheckCancel(tx: any): FormattedCheckCancel {
assert(tx.TransactionType === 'CheckCancel')
assert.ok(tx.TransactionType === 'CheckCancel')
return removeUndefined({
checkID: tx.CheckID

View File

@@ -23,7 +23,7 @@ export type FormattedCheckCash = {
}
function parseCheckCash(tx: any): FormattedCheckCash {
assert(tx.TransactionType === 'CheckCash')
assert.ok(tx.TransactionType === 'CheckCash')
return removeUndefined({
checkID: tx.CheckID,

View File

@@ -24,7 +24,7 @@ export type FormattedCheckCreate = {
}
function parseCheckCreate(tx: any): FormattedCheckCreate {
assert(tx.TransactionType === 'CheckCreate')
assert.ok(tx.TransactionType === 'CheckCreate')
return removeUndefined({
destination: tx.Destination,

View File

@@ -10,7 +10,7 @@ export type FormattedDepositPreauth = {
}
function parseDepositPreauth(tx: any): FormattedDepositPreauth {
assert(tx.TransactionType === 'DepositPreauth')
assert.ok(tx.TransactionType === 'DepositPreauth')
return removeUndefined({
authorize: tx.Authorize,

View File

@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowCancellation(tx: any): object {
assert(tx.TransactionType === 'EscrowCancel')
assert.ok(tx.TransactionType === 'EscrowCancel')
return removeUndefined({
memos: parseMemos(tx),

View File

@@ -4,7 +4,7 @@ import {parseTimestamp, parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowCreation(tx: any): object {
assert(tx.TransactionType === 'EscrowCreate')
assert.ok(tx.TransactionType === 'EscrowCreate')
return removeUndefined({
amount: parseAmount(tx.Amount).value,

View File

@@ -3,7 +3,7 @@ import {parseMemos} from './utils'
import {removeUndefined} from '../../common'
function parseEscrowExecution(tx: any): object {
assert(tx.TransactionType === 'EscrowFinish')
assert.ok(tx.TransactionType === 'EscrowFinish')
return removeUndefined({
memos: parseMemos(tx),

View File

@@ -10,7 +10,7 @@ import {
const flags = txFlags.OfferCreate
function parseOrder(tx: OfferCreateTransaction): FormattedOrderSpecification {
assert(tx.TransactionType === 'OfferCreate')
assert.ok(tx.TransactionType === 'OfferCreate')
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(tx.TakerGets)

View File

@@ -4,7 +4,7 @@ import parseAmount from './amount'
const claimFlags = txFlags.PaymentChannelClaim
function parsePaymentChannelClaim(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelClaim')
assert.ok(tx.TransactionType === 'PaymentChannelClaim')
return removeUndefined({
channel: tx.Channel,

View File

@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
import parseAmount from './amount'
function parsePaymentChannelCreate(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelCreate')
assert.ok(tx.TransactionType === 'PaymentChannelCreate')
return removeUndefined({
amount: parseAmount(tx.Amount).value,

View File

@@ -4,7 +4,7 @@ import {removeUndefined} from '../../common'
import parseAmount from './amount'
function parsePaymentChannelFund(tx: any): object {
assert(tx.TransactionType === 'PaymentChannelFund')
assert.ok(tx.TransactionType === 'PaymentChannelFund')
return removeUndefined({
channel: tx.Channel,

View File

@@ -19,7 +19,7 @@ function removeGenericCounterparty(amount, address) {
// Payment specification
function parsePayment(tx: any): object {
assert(tx.TransactionType === 'Payment')
assert.ok(tx.TransactionType === 'Payment')
const source = {
address: tx.Account,

View File

@@ -7,7 +7,7 @@ import parseFields from './fields'
function getAccountRootModifiedNode(tx: any) {
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
node.ModifiedNode.LedgerEntryType === 'AccountRoot')
assert(modifiedNodes.length === 1)
assert.ok(modifiedNodes.length === 1)
return modifiedNodes[0].ModifiedNode
}
@@ -51,7 +51,7 @@ function parseFlags(tx: any): any {
function parseSettings(tx: any) {
const txType = tx.TransactionType
assert(txType === 'AccountSet' || txType === 'SetRegularKey' ||
assert.ok(txType === 'AccountSet' || txType === 'SetRegularKey' ||
txType === 'SignerListSet')
return _.assign({}, parseFlags(tx), parseFields(tx))

View File

@@ -14,7 +14,7 @@ function parseFlag(flagsValue, trueValue, falseValue) {
}
function parseTrustline(tx: any): object {
assert(tx.TransactionType === 'TrustSet')
assert.ok(tx.TransactionType === 'TrustSet')
return removeUndefined({
limit: tx.LimitAmount.value,

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash'
import transactionParser = require('ripple-lib-transactionparser')
import transactionParser from 'ripple-lib-transactionparser'
import BigNumber from 'bignumber.js'
import * as common from '../../common'
import parseAmount from './amount'

View File

@@ -1,9 +1,10 @@
import * as _ from 'lodash'
import parseFields from './parse/fields'
import {validate, constants} from '../common'
import {validate, constants, ensureClassicAddress} from '../common'
import {FormattedSettings} from '../common/types/objects'
import {AccountInfoResponse} from '../common/types/commands'
import {RippleAPI} from '..'
const AccountFlags = constants.AccountFlags
export type SettingsOptions = {
@@ -38,6 +39,11 @@ export async function getSettings(
): Promise<FormattedSettings> {
// 1. Validate
validate.getSettings({address, options})
// Only support retrieving settings without a tag,
// since settings do not distinguish by tag.
address = ensureClassicAddress(address)
// 2. Make Request
const response = await this.request('account_info', {
account: address,

View File

@@ -30,8 +30,12 @@ function attachTransactionDate(connection: Connection, tx: any
if (!ledgerVersion) {
return new Promise(() => {
throw new errors.NotFoundError(
'ledger_index and LedgerSequence not found in tx')
const error = new errors.NotFoundError(
'Transaction has not been validated yet; try again later')
error.data = {
details: '(ledger_index and LedgerSequence not found in tx)'
}
throw error
})
}

View File

@@ -1,14 +1,13 @@
import * as _ from 'lodash'
import binary = require('ripple-binary-codec')
const {computeTransactionHash} = require('ripple-hashes')
import binary from 'ripple-binary-codec';
import {computeTransactionHash} from '../common/hashes'
import * as utils from './utils'
import parseTransaction from './parse/transaction'
import getTransaction from './transaction'
import {validate, errors, Connection} from '../common'
import {validate, errors, Connection, ensureClassicAddress} from '../common'
import {FormattedTransactionType} from '../transaction/types'
import {RippleAPI} from '..'
export type TransactionsOptions = {
start?: string,
limit?: number,
@@ -167,6 +166,12 @@ function getTransactions(this: RippleAPI, address: string, options: Transactions
): Promise<GetTransactionsResponse> {
validate.getTransactions({address, options})
// Only support retrieving transactions without a tag,
// since we currently do not filter transactions based
// on tag. Apps must interpret and use tags
// independently, filtering transactions if needed.
address = ensureClassicAddress(address)
const defaults = {maxLedgerVersion: -1}
if (options.start) {
return getTransaction.call(this, options.start).then(tx => {

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash'
import {validate} from '../common'
import {validate, ensureClassicAddress} from '../common'
import parseAccountTrustline from './parse/account-trustline'
import {RippleAPI} from '..'
import {FormattedTrustline} from '../common/types/objects/trustlines'
@@ -20,11 +20,16 @@ async function getTrustlines(
): Promise<FormattedTrustline[]> {
// 1. Validate
validate.getTrustlines({address, options})
const ledgerVersion = await this.getLedgerVersion()
// Only support retrieving trustlines without a tag,
// since it does not make sense to filter trustlines
// by tag.
address = ensureClassicAddress(address)
// 2. Make Request
const responses = await this._requestAll('account_lines', {
account: address,
ledger_index: ledgerVersion,
ledger_index: await this.getLedgerVersion(),
limit: options.limit,
peer: options.counterparty
})

View File

@@ -4,7 +4,7 @@ import * as common from '../common'
import {Connection} from '../common'
import {FormattedTransactionType} from '../transaction/types'
import {Issue} from '../common/types/objects'
import {RippleAPI} from '..'
import {RippleAPI} from '..'
export type RecursiveData = {
marker: string,
@@ -14,7 +14,7 @@ export type RecursiveData = {
export type Getter = (marker?: string, limit?: number) => Promise<RecursiveData>
function clamp(value: number, min: number, max: number): number {
assert(min <= max, 'Illegal clamp bounds')
assert.ok(min <= max, 'Illegal clamp bounds')
return Math.min(Math.max(value, min), max)
}

View File

@@ -1,6 +1,13 @@
import {deriveKeypair, deriveAddress} from 'ripple-keypairs'
import {classicAddressToXAddress} from 'ripple-address-codec'
function deriveXAddress(options: {publicKey: string, tag: number | false, test: boolean}): string {
const classicAddress = deriveAddress(options.publicKey)
return classicAddressToXAddress(classicAddress, options.tag, options.test)
}
export {
deriveKeypair,
deriveAddress
deriveAddress,
deriveXAddress
}

View File

@@ -1,19 +1,45 @@
import keypairs = require('ripple-keypairs')
import * as common from '../common'
const {errors, validate} = common
import {classicAddressToXAddress} from 'ripple-address-codec'
import keypairs from 'ripple-keypairs'
import {errors, validate} from '../common'
export type GeneratedAddress = {
secret: string,
address: string
xAddress: string,
classicAddress?: string,
address?: string, // @deprecated Use `classicAddress` instead.
secret: string
}
function generateAddressAPI(options?: any): GeneratedAddress {
export interface GenerateAddressOptions {
// The entropy to use to generate the seed.
entropy?: Uint8Array,
// The digital signature algorithm to generate an address for. Can be `ecdsa-secp256k1` (default) or `ed25519`.
algorithm?: 'ecdsa-secp256k1' | 'ed25519',
// Specifies whether the address is intended for use on a test network such as Testnet or Devnet.
// If `true`, the address should only be used for testing, and will start with `T`.
// If `false` (default), the address should only be used on mainnet, and will start with `X`.
test?: boolean,
// If `true`, return the classic address, in addition to the X-address.
includeClassicAddress?: boolean
}
function generateAddressAPI(options: GenerateAddressOptions): GeneratedAddress {
validate.generateAddress({options})
try {
const secret = keypairs.generateSeed(options)
const keypair = keypairs.deriveKeypair(secret)
const address = keypairs.deriveAddress(keypair.publicKey)
return {secret, address}
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
const returnValue: any = {
xAddress: classicAddressToXAddress(classicAddress, false, options && options.test),
secret
}
if (options.includeClassicAddress) {
returnValue.classicAddress = classicAddress
returnValue.address = classicAddress
}
return returnValue
} catch (error) {
throw new errors.UnexpectedError(error.message)
}

View File

@@ -1,5 +1,5 @@
import * as _ from 'lodash'
import hashes = require('ripple-hashes')
import {computeLedgerHash, computeTransactionTreeHash, computeStateTreeHash} from '../common/hashes'
import * as common from '../common'
function convertLedgerHeader(header): any {
@@ -22,11 +22,11 @@ function convertLedgerHeader(header): any {
function hashLedgerHeader(ledgerHeader) {
const header = convertLedgerHeader(ledgerHeader)
return hashes.computeLedgerHash(header)
return computeLedgerHash(header)
}
function computeTransactionHash(ledger, version,
options: ComputeLedgerHashOptions) {
function computeTransactionHash(ledger,
options: ComputeLedgerHeaderHashOptions) {
let transactions: any[]
if (ledger.rawTransactions) {
transactions = JSON.parse(ledger.rawTransactions)
@@ -56,7 +56,7 @@ function computeTransactionHash(ledger, version,
tx.meta ? {metaData: tx.meta} : {})
return renameMeta
})
const transactionHash = hashes.computeTransactionTreeHash(txs, version)
const transactionHash = computeTransactionTreeHash(txs)
if (ledger.transactionHash !== undefined
&& ledger.transactionHash !== transactionHash) {
throw new common.errors.ValidationError('transactionHash in header'
@@ -68,8 +68,8 @@ function computeTransactionHash(ledger, version,
return transactionHash
}
function computeStateHash(ledger, version,
options: ComputeLedgerHashOptions) {
function computeStateHash(ledger,
options: ComputeLedgerHeaderHashOptions) {
if (ledger.rawState === undefined) {
if (options.computeTreeHashes) {
throw new common.errors.ValidationError('rawState'
@@ -78,7 +78,7 @@ function computeStateHash(ledger, version,
return ledger.stateHash
}
const state = JSON.parse(ledger.rawState)
const stateHash = hashes.computeStateTreeHash(state, version)
const stateHash = computeStateTreeHash(state)
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
throw new common.errors.ValidationError('stateHash in header'
+ ' does not match computed hash of state')
@@ -86,20 +86,17 @@ function computeStateHash(ledger, version,
return stateHash
}
const sLCF_SHAMapV2 = 0x02
export type ComputeLedgerHashOptions = {
export type ComputeLedgerHeaderHashOptions = {
computeTreeHashes?: boolean
}
function computeLedgerHash(ledger: any,
options: ComputeLedgerHashOptions = {}): string {
const version = ((ledger.closeFlags & sLCF_SHAMapV2) === 0) ? 1 : 2
function computeLedgerHeaderHash(ledger: any,
options: ComputeLedgerHeaderHashOptions = {}): string {
const subhashes = {
transactionHash: computeTransactionHash(ledger, version, options),
stateHash: computeStateHash(ledger, version, options)
transactionHash: computeTransactionHash(ledger, options),
stateHash: computeStateHash(ledger, options)
}
return hashLedgerHeader(_.assign({}, ledger, subhashes))
}
export default computeLedgerHash
export default computeLedgerHeaderHash

View File

@@ -1,6 +1,6 @@
import * as common from '../common'
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import keypairs from 'ripple-keypairs'
import binary from 'ripple-binary-codec'
const {validate, xrpToDrops} = common
function signPaymentChannelClaim(channel: string, amount: string,

View File

@@ -1,5 +1,5 @@
import keypairs = require('ripple-keypairs')
import binary = require('ripple-binary-codec')
import keypairs from 'ripple-keypairs'
import binary from 'ripple-binary-codec'
import {validate, xrpToDrops} from '../common'
function verifyPaymentChannelClaim(channel: string, amount: string,

View File

@@ -1,13 +1,13 @@
import * as _ from 'lodash'
import binary = require('ripple-binary-codec')
import binary from 'ripple-binary-codec'
import * as utils from './utils'
import BigNumber from 'bignumber.js'
import {decodeAddress} from 'ripple-address-codec'
import {decodeAccountID} from 'ripple-address-codec'
import {validate} from '../common'
import {computeBinaryTransactionHash} from 'ripple-hashes'
import {computeBinaryTransactionHash} from '../common/hashes'
function addressToBigNumber(address) {
const hex = (Buffer.from(decodeAddress(address))).toString('hex')
const hex = (Buffer.from(decodeAccountID(address))).toString('hex')
return new BigNumber(hex, 16)
}

View File

@@ -9,6 +9,7 @@ import {Amount, Adjustment, MaxAdjustment,
MinAdjustment, Memo} from '../common/types/objects'
import {xrpToDrops} from '../common'
import {RippleAPI} from '..'
import {getClassicAccountAndTag, ClassicAccountAndTag} from './utils'
export interface Payment {
@@ -84,15 +85,49 @@ function createMaximalAmount(amount: Amount): Amount {
return _.assign({}, amount, {value: maxValue})
}
/**
* Given an address and tag:
* 1. Get the classic account and tag;
* 2. If a tag is provided:
* 2a. If the address was an X-address, validate that the X-address has the expected tag;
* 2b. If the address was a classic address, return `expectedTag` as the tag.
* 3. If we do not want to use a tag in this case,
* set the tag in the return value to `undefined`.
*
* @param address The address to parse.
* @param expectedTag If provided, and the `Account` is an X-address,
* this method throws an error if `expectedTag`
* does not match the tag of the X-address.
* @returns {ClassicAccountAndTag}
* The classic account and tag.
*/
function validateAndNormalizeAddress(address: string, expectedTag: number | undefined): ClassicAccountAndTag {
const classicAddress = getClassicAccountAndTag(address, expectedTag)
classicAddress.tag = classicAddress.tag === false ? undefined : classicAddress.tag
return classicAddress
}
function createPaymentTransaction(address: string, paymentArgument: Payment
): TransactionJSON {
const payment = _.cloneDeep(paymentArgument)
applyAnyCounterpartyEncoding(payment)
if (address !== payment.source.address) {
const sourceAddressAndTag = validateAndNormalizeAddress(payment.source.address, payment.source.tag)
const addressToVerifyAgainst = validateAndNormalizeAddress(address, undefined)
if (addressToVerifyAgainst.classicAccount !== sourceAddressAndTag.classicAccount) {
throw new ValidationError('address must match payment.source.address')
}
if (addressToVerifyAgainst.tag !== undefined &&
sourceAddressAndTag.tag !== undefined &&
addressToVerifyAgainst.tag !== sourceAddressAndTag.tag) {
throw new ValidationError(
'address includes a tag that does not match payment.source.tag')
}
const destinationAddressAndTag = validateAndNormalizeAddress(payment.destination.address, payment.destination.tag)
if (
(isMaxAdjustment(payment.source) && isMinAdjustment(payment.destination))
||
@@ -119,8 +154,8 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
const txJSON: any = {
TransactionType: 'Payment',
Account: payment.source.address,
Destination: payment.destination.address,
Account: sourceAddressAndTag.classicAccount,
Destination: destinationAddressAndTag.classicAccount,
Amount: toRippledAmount(amount),
Flags: 0
}
@@ -128,11 +163,11 @@ function createPaymentTransaction(address: string, paymentArgument: Payment
if (payment.invoiceID !== undefined) {
txJSON.InvoiceID = payment.invoiceID
}
if (payment.source.tag !== undefined) {
txJSON.SourceTag = payment.source.tag
if (sourceAddressAndTag.tag !== undefined) {
txJSON.SourceTag = sourceAddressAndTag.tag
}
if (payment.destination.tag !== undefined) {
txJSON.DestinationTag = payment.destination.tag
if (destinationAddressAndTag.tag !== undefined) {
txJSON.DestinationTag = destinationAddressAndTag.tag
}
if (payment.memos !== undefined) {
txJSON.Memos = _.map(payment.memos, utils.convertMemo)

View File

@@ -10,7 +10,7 @@ import {RippleAPI} from '..'
function setTransactionFlags(txJSON: utils.TransactionJSON, values: FormattedSettings) {
const keys = Object.keys(values)
assert(keys.length === 1, 'ERROR: can only set one setting per transaction')
assert.ok(keys.length === 1, 'ERROR: can only set one setting per transaction')
const flagName = keys[0]
const value = values[flagName]
const index = AccountFlagIndices[flagName]

View File

@@ -1,13 +1,13 @@
import * as isEqual from '../common/js/lodash.isequal'
import isEqual from 'lodash.isequal'
import * as utils from './utils'
import keypairs = require('ripple-keypairs')
import binaryCodec = require('ripple-binary-codec')
import {computeBinaryTransactionHash} from 'ripple-hashes'
import keypairs from 'ripple-keypairs'
import binaryCodec from 'ripple-binary-codec'
import {computeBinaryTransactionHash} from '../common/hashes'
import {SignOptions, KeyPair} from './types'
import {BigNumber} from 'bignumber.js'
import {xrpToDrops} from '../common'
import {RippleAPI} from '..'
const validate = utils.common.validate
const validate = utils.common.validate
function computeSignature(tx: object, privateKey: string, signAs?: string) {
const signingData = signAs
@@ -60,6 +60,72 @@ function signWithKeypair(
}
}
/**
* Compares two objects and creates a diff.
*
* @param a An object to compare.
* @param b The other object to compare with.
*
* @returns An object containing the differences between the two objects.
*/
function objectDiff(a: object, b: object): object {
const diffs = {}
// Compare two items and push non-matches to object
const compare = function (i1: any, i2: any, k: string): void {
const type1 = Object.prototype.toString.call(i1)
const type2 = Object.prototype.toString.call(i2)
if (type2 === '[object Undefined]') {
diffs[k] = null // Indicate that the item has been removed
return
}
if (type1 !== type2) {
diffs[k] = i2 // Indicate that the item has changed types
return
}
if (type1 === '[object Object]') {
const objDiff = objectDiff(i1, i2)
if (Object.keys(objDiff).length > 0) {
diffs[k] = objDiff
}
return
}
if (type1 === '[object Array]') {
if (!isEqual(i1, i2)) {
diffs[k] = i2 // If arrays do not match, add second item to diffs
}
return
}
if (type1 === '[object Function]') {
if (i1.toString() !== i2.toString()) {
diffs[k] = i2 // If functions differ, add second one to diffs
}
return
}
if (i1 !== i2) {
diffs[k] = i2
}
}
// Check items in first object
for (const key in a) {
if (a.hasOwnProperty(key)) {
compare(a[key], b[key], key)
}
}
// Get items that are in the second object but not the first
for (const key in b) {
if (b.hasOwnProperty(key)) {
if (!a[key] && a[key] !== b[key]) {
diffs[key] = b[key]
}
}
}
return diffs
}
/**
* Decode a serialized transaction, remove the fields that are added during the signing process,
* and verify that it matches the transaction prior to signing.
@@ -93,11 +159,12 @@ function checkTxSerialization(serialized: string, tx: utils.TransactionJSON): vo
if (!isEqual(decoded, tx)) {
const error = new utils.common.errors.ValidationError(
'Serialized transaction does not match original txJSON'
'Serialized transaction does not match original txJSON. See `error.data`'
)
error.data = {
decoded,
tx
tx,
diff: objectDiff(tx, decoded)
}
throw error
}

View File

@@ -1,10 +1,13 @@
import BigNumber from 'bignumber.js'
import * as common from '../common'
import {Memo, RippledAmount} from '../common/types/objects'
const txFlags = common.txFlags
import {Instructions, Prepare} from './types'
import {RippleAPI} from '..'
import {ValidationError} from '../common/errors'
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
const txFlags = common.txFlags
const TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD = ['Payment', 'CheckCreate', 'EscrowCreate', 'PaymentChannelCreate']
export type ApiMemo = {
MemoData?: string,
@@ -56,6 +59,49 @@ function scaleValue(value, multiplier, extra = 0) {
return (new BigNumber(value)).times(multiplier).plus(extra).toString()
}
/**
* @typedef {Object} ClassicAccountAndTag
* @property {string} classicAccount - The classic account address.
* @property {number | false | undefined } tag - The destination tag;
* `false` if no tag should be used;
* `undefined` if the input could not specify whether a tag should be used.
*/
export interface ClassicAccountAndTag {
classicAccount: string,
tag: number | false | undefined
}
/**
* Given an address (account), get the classic account and tag.
* If an `expectedTag` is provided:
* 1. If the `Account` is an X-address, validate that the tags match.
* 2. If the `Account` is a classic address, return `expectedTag` as the tag.
*
* @param Account The address to parse.
* @param expectedTag If provided, and the `Account` is an X-address,
* this method throws an error if `expectedTag`
* does not match the tag of the X-address.
* @returns {ClassicAccountAndTag}
* The classic account and tag.
*/
function getClassicAccountAndTag(Account: string, expectedTag?: number): ClassicAccountAndTag {
if (isValidXAddress(Account)) {
const classic = xAddressToClassicAddress(Account)
if (expectedTag !== undefined && classic.tag !== expectedTag) {
throw new ValidationError('address includes a tag that does not match the tag specified in the transaction')
}
return {
classicAccount: classic.classicAddress,
tag: classic.tag
}
} else {
return {
classicAccount: Account,
tag: expectedTag
}
}
}
function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
instructions: Instructions
): Promise<Prepare> {
@@ -68,56 +114,105 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
'" exists in instance when not allowed'))
}
// To remove the signer list, SignerEntries field should be omitted.
const newTxJSON = Object.assign({}, txJSON)
// To remove the signer list, `SignerEntries` field should be omitted.
if (txJSON['SignerQuorum'] === 0) {
delete txJSON.SignerEntries;
delete newTxJSON.SignerEntries
}
const account = txJSON.Account
setCanonicalFlag(txJSON)
// Sender:
const {classicAccount, tag: sourceTag} = getClassicAccountAndTag(txJSON.Account)
newTxJSON.Account = classicAccount
if (sourceTag !== undefined) {
if (txJSON.SourceTag && txJSON.SourceTag !== sourceTag) {
return Promise.reject(new ValidationError(
'The `SourceTag`, if present, must match the tag of the `Account` X-address'))
}
if (sourceTag) {
newTxJSON.SourceTag = sourceTag
}
}
function prepareMaxLedgerVersion(): Promise<TransactionJSON> {
// Destination:
if (typeof txJSON.Destination === 'string') {
const {classicAccount: destinationAccount, tag: destinationTag} = getClassicAccountAndTag(txJSON.Destination)
newTxJSON.Destination = destinationAccount
if (destinationTag !== undefined) {
if (TRANSACTION_TYPES_WITH_DESTINATION_TAG_FIELD.includes(txJSON.TransactionType)) {
if (txJSON.DestinationTag && txJSON.DestinationTag !== destinationTag) {
return Promise.reject(new ValidationError(
'The Payment `DestinationTag`, if present, must match the tag of the `Destination` X-address'))
}
if (destinationTag) {
newTxJSON.DestinationTag = destinationTag
}
}
}
}
function convertToClassicAccountIfPresent(fieldName: string): void {
const account = txJSON[fieldName]
if (typeof account === 'string') {
const {classicAccount: ca} = getClassicAccountAndTag(account)
newTxJSON[fieldName] = ca
}
}
// DepositPreauth:
convertToClassicAccountIfPresent('Authorize')
convertToClassicAccountIfPresent('Unauthorize')
// EscrowCancel, EscrowFinish:
convertToClassicAccountIfPresent('Owner')
// SetRegularKey:
convertToClassicAccountIfPresent('RegularKey')
setCanonicalFlag(newTxJSON)
function prepareMaxLedgerVersion(): Promise<void> {
// Up to one of the following is allowed:
// txJSON.LastLedgerSequence
// instructions.maxLedgerVersion
// instructions.maxLedgerVersionOffset
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersion) {
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersion`' +
' in `instructions` cannot both be set'))
}
if (txJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
if (newTxJSON.LastLedgerSequence && instructions.maxLedgerVersionOffset) {
return Promise.reject(new ValidationError('`LastLedgerSequence` in txJSON and `maxLedgerVersionOffset`' +
' in `instructions` cannot both be set'))
}
if (txJSON.LastLedgerSequence) {
return Promise.resolve(txJSON)
if (newTxJSON.LastLedgerSequence) {
return Promise.resolve()
}
if (instructions.maxLedgerVersion !== undefined) {
if (instructions.maxLedgerVersion !== null) {
txJSON.LastLedgerSequence = instructions.maxLedgerVersion
newTxJSON.LastLedgerSequence = instructions.maxLedgerVersion
}
return Promise.resolve(txJSON)
return Promise.resolve()
}
const offset = instructions.maxLedgerVersionOffset !== undefined ?
instructions.maxLedgerVersionOffset : 3
return api.connection.getLedgerVersion().then(ledgerVersion => {
txJSON.LastLedgerSequence = ledgerVersion + offset
return txJSON
newTxJSON.LastLedgerSequence = ledgerVersion + offset
return
})
}
function prepareFee(): Promise<TransactionJSON> {
function prepareFee(): Promise<void> {
// instructions.fee is scaled (for multi-signed transactions) while txJSON.Fee is not.
// Due to this difference, we do NOT allow both to be set, as the behavior would be complex and
// potentially ambiguous.
// Furthermore, txJSON.Fee is in drops while instructions.fee is in XRP, which would just add to
// the confusion. It is simpler to require that only one is used.
if (txJSON.Fee && instructions.fee) {
if (newTxJSON.Fee && instructions.fee) {
return Promise.reject(new ValidationError('`Fee` in txJSON and `fee` in `instructions` cannot both be set'))
}
if (txJSON.Fee) {
if (newTxJSON.Fee) {
// txJSON.Fee is set. Use this value and do not scale it.
return Promise.resolve(txJSON)
return Promise.resolve()
}
const multiplier = instructions.signersCount === undefined ? 1 :
instructions.signersCount + 1
@@ -128,50 +223,50 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
`max of ${api._maxFeeXRP} XRP. To use this fee, increase ` +
'`maxFeeXRP` in the RippleAPI constructor.'))
}
txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
return Promise.resolve(txJSON)
newTxJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier)
return Promise.resolve()
}
const cushion = api._feeCushion
return api.getFee(cushion).then(fee => {
return api.connection.getFeeRef().then(feeRef => {
const extraFee =
(txJSON.TransactionType !== 'EscrowFinish' ||
txJSON.Fulfillment === undefined) ? 0 :
(newTxJSON.TransactionType !== 'EscrowFinish' ||
newTxJSON.Fulfillment === undefined) ? 0 :
(cushion * feeRef * (32 + Math.floor(
Buffer.from(txJSON.Fulfillment, 'hex').length / 16)))
Buffer.from(newTxJSON.Fulfillment, 'hex').length / 16)))
const feeDrops = common.xrpToDrops(fee)
const maxFeeXRP = instructions.maxFee ?
BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP
const maxFeeDrops = common.xrpToDrops(maxFeeXRP)
const normalFee = scaleValue(feeDrops, multiplier, extraFee)
txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
newTxJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10)
return txJSON
return
})
})
}
async function prepareSequence(): Promise<TransactionJSON> {
async function prepareSequence(): Promise<void> {
if (instructions.sequence !== undefined) {
if (txJSON.Sequence === undefined || instructions.sequence === txJSON.Sequence) {
txJSON.Sequence = instructions.sequence
return Promise.resolve(txJSON)
if (newTxJSON.Sequence === undefined || instructions.sequence === newTxJSON.Sequence) {
newTxJSON.Sequence = instructions.sequence
return Promise.resolve()
} else {
// Both txJSON.Sequence and instructions.sequence are defined, and they are NOT equal
return Promise.reject(new ValidationError('`Sequence` in txJSON must match `sequence` in `instructions`'))
}
}
if (txJSON.Sequence !== undefined) {
return Promise.resolve(txJSON)
if (newTxJSON.Sequence !== undefined) {
return Promise.resolve()
}
try {
// Consider requesting from the 'current' ledger (instead of 'validated').
const response = await api.request('account_info', {
account
account: classicAccount
})
txJSON.Sequence = response.account_data.Sequence
return Promise.resolve(txJSON)
newTxJSON.Sequence = response.account_data.Sequence
return Promise.resolve()
} catch (e) {
return Promise.reject(e)
}
@@ -181,7 +276,7 @@ function prepareTransaction(txJSON: TransactionJSON, api: RippleAPI,
prepareMaxLedgerVersion(),
prepareFee(),
prepareSequence()
]).then(() => formatPrepareResponse(txJSON))
]).then(() => formatPrepareResponse(newTxJSON))
}
function convertStringToHex(string: string): string {
@@ -203,5 +298,6 @@ export {
convertMemo,
prepareTransaction,
common,
setCanonicalFlag
setCanonicalFlag,
getClassicAccountAndTag
}

View File

@@ -1,29 +1,22 @@
/* eslint-disable max-nested-callbacks */
'use strict'; // eslint-disable-line
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
const validate = RippleAPI._PRIVATE.validate;
const fixtures = require('./fixtures');
const requests = fixtures.requests;
const responses = fixtures.responses;
const addresses = require('./fixtures/addresses');
const hashes = require('./fixtures/hashes');
import assert from 'assert-diff';
import BigNumber from 'bignumber.js';
import _ from 'lodash';
import { RippleAPI } from 'ripple-api';
import { RecursiveData } from 'ripple-api/ledger/utils';
import binary from 'ripple-binary-codec';
import { requests, responses } from './fixtures';
import addresses from './fixtures/addresses';
import hashes from './fixtures/hashes';
import ledgerClosed from './fixtures/rippled/ledger-close-newer.json';
import setupAPI from './setup-api';
const {validate, schemaValidator} = RippleAPI._PRIVATE;
const address = addresses.ACCOUNT;
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClosed = require('./fixtures/rippled/ledger-close-newer');
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const binary = require('ripple-binary-codec');
const BigNumber = require('bignumber.js');
assert.options.strict = true;
// how long before each test case times out
const TIMEOUT = 20000;
function unused() {
}
function closeLedger(connection) {
connection._ws.emit('message', JSON.stringify(ledgerClosed));
}
@@ -147,17 +140,17 @@ describe('RippleAPI', function () {
assert.throws(() => {
this.api.xrpToDrops('.')
}, /xrpToDrops\: invalid value '\.', should be a BigNumber or string-encoded number\./)
}, /xrpToDrops: invalid value '\.', should be a BigNumber or string-encoded number\./)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
this.api.xrpToDrops('1.0.0')
}, /xrpToDrops:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
}, /xrpToDrops: invalid value '1\.0\.0'/)
assert.throws(() => {
this.api.xrpToDrops('...')
}, /xrpToDrops:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
}, /xrpToDrops: invalid value '\.\.\.'/)
})
})
@@ -261,17 +254,17 @@ describe('RippleAPI', function () {
assert.throws(() => {
this.api.dropsToXrp('.')
}, /dropsToXrp\: invalid value '\.', should be a BigNumber or string-encoded number\./)
}, /dropsToXrp: invalid value '\.', should be a BigNumber or string-encoded number\./)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
this.api.dropsToXrp('1.0.0')
}, /dropsToXrp:\ invalid\ value\ '1\.0\.0'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
}, /dropsToXrp: invalid value '1\.0\.0'/)
assert.throws(() => {
this.api.dropsToXrp('...')
}, /dropsToXrp:\ invalid\ value\ '\.\.\.'\,\ should\ be\ a\ number\ matching\ \(\^\-\?\[0\-9\]\*\.\?\[0\-9\]\*\$\)\./)
}, /dropsToXrp: invalid value '\.\.\.'/)
})
})
@@ -311,7 +304,7 @@ describe('RippleAPI', function () {
it('throws with an invalid secret', function (){
assert.throws(() => {
this.api.deriveKeypair('...');
}, /^Error\: Non\-base58 character$/)
}, /^Error: Non-base58 character$/)
})
})
@@ -996,7 +989,7 @@ describe('RippleAPI', function () {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account does not conform to the "address" format');
assert.strictEqual(err.message, 'instance.Account is not of a type(s) string,instance.Account is not exactly one from <xAddress>,<classicAddress>');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
@@ -1020,7 +1013,7 @@ describe('RippleAPI', function () {
done(new Error('Expected method to reject. Prepared transaction: ' + JSON.stringify(response)));
}).catch(err => {
assert.strictEqual(err.name, 'ValidationError');
assert.strictEqual(err.message, 'instance.Account does not conform to the "address" format');
assert.strictEqual(err.message, 'instance.Account is not exactly one from <xAddress>,<classicAddress>');
done();
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
@@ -1364,7 +1357,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('rejects promise and does not throw when field is missing', function (done) {
@@ -1393,7 +1386,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('rejects promise and does not throw when fee exceeds maxFeeXRP', function (done) {
@@ -1427,7 +1420,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('preparePayment - XRP to XRP no partial', function (done) {
@@ -1442,7 +1435,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('preparePayment - address must match payment.source.address', function (done) {
@@ -1457,7 +1450,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('preparePayment - wrong amount', function (done) {
@@ -1472,7 +1465,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('preparePayment - throws when fee exceeds 2 XRP', function (done) {
@@ -1492,7 +1485,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
});
@@ -1581,7 +1574,7 @@ describe('RippleAPI', function () {
});
it('prepareOrder - invalid', function (done) {
const request = requests.prepareOrder.sell;
const request = Object.assign({}, requests.prepareOrder.sell);
delete request.direction; // Make invalid
try {
this.api.prepareOrder(address, request, instructionsWithMaxLedgerVersionOffset).then(prepared => {
@@ -1593,7 +1586,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareOrderCancellation', function () {
@@ -1620,7 +1613,7 @@ describe('RippleAPI', function () {
});
it('prepareOrderCancellation - invalid', function (done) {
const request = requests.prepareOrderCancellation.withMemos;
const request = Object.assign({}, requests.prepareOrderCancellation.withMemos);
delete request.orderSequence; // Make invalid
try {
this.api.prepareOrderCancellation(address, request).then(prepared => {
@@ -1632,7 +1625,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareTrustline - simple', function () {
@@ -1654,7 +1647,7 @@ describe('RippleAPI', function () {
});
it('prepareTrustline - invalid', function (done) {
const trustline = requests.prepareTrustline.complex;
const trustline = Object.assign({}, requests.prepareTrustline.complex);
delete trustline.limit; // Make invalid
try {
this.api.prepareTrustline(
@@ -1667,7 +1660,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareSettings', function () {
@@ -1764,7 +1757,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareSettings - signers no weights', function () {
@@ -1820,7 +1813,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareEscrowCreation', function () {
@@ -1855,7 +1848,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareEscrowExecution', function () {
@@ -1888,7 +1881,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareEscrowExecution - no fulfillment', function (done) {
@@ -1903,7 +1896,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('prepareEscrowCancellation', function () {
@@ -2507,7 +2500,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('rejects Promise on preparePaymentChannelClaim with no signature', function (done) {
@@ -2522,7 +2515,7 @@ describe('RippleAPI', function () {
}).catch(done); // Finish test with assertion failure immediately instead of waiting for timeout.
} catch (err) {
done(new Error('Expected method to reject, but method threw. Thrown: ' + err));
};
}
});
it('sign', function () {
@@ -2679,6 +2672,37 @@ describe('RippleAPI', function () {
);
});
it('sign - throws when encoded tx does not match decoded tx - prepared order', async function () {
const order = {
direction: 'sell',
quantity: {
currency: 'USD',
counterparty: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
value: '3.140000'
},
totalPrice: {
currency: 'XRP',
value: '31415'
}
};
const prepared = await this.api.prepareOrder('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', order, {
sequence: 123
});
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
try {
this.api.sign(prepared.txJSON, secret);
return Promise.reject(new Error('api.sign should have thrown'));
} catch (error) {
assert.equal(error.name, 'ValidationError');
assert.equal(error.message, 'Serialized transaction does not match original txJSON. See `error.data`');
assert.deepEqual(error.data.diff, {
TakerGets: {
value: '3.14'
}
});
}
});
it('sign - throws when encoded tx does not match decoded tx - AccountSet', function () {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV';
const request = {
@@ -2927,7 +2951,7 @@ describe('RippleAPI', function () {
const options = {
includeRawTransaction: true
}
const expected = responses.getTransaction.settings
const expected = Object.assign({}, responses.getTransaction.settings) // Avoid mutating test fixture
expected.rawTransaction = "{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Fee\":\"10\",\"Flags\":2147483648,\"Sequence\":1,\"SetFlag\":2,\"SigningPubKey\":\"03EA3ADCA632F125EC2CC4F7F6A82DE0DCE2B65290CAC1F22242C5163F0DA9652D\",\"TransactionType\":\"AccountSet\",\"TxnSignature\":\"3045022100DE8B666B1A31EA65011B0F32130AB91A5747E32FA49B3054CEE8E8362DBAB98A022040CF0CF254677A8E5CD04C59CA2ED7F6F15F7E184641BAE169C561650967B226\",\"date\":460832270,\"hash\":\"4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B\",\"inLedger\":8206418,\"ledger_index\":8206418,\"meta\":{\"AffectedNodes\":[{\"ModifiedNode\":{\"FinalFields\":{\"Account\":\"rLVKsA4F9iJBbA6rX2x4wCmkj6drgtqpQe\",\"Balance\":\"29999990\",\"Flags\":786432,\"OwnerCount\":0,\"Sequence\":2},\"LedgerEntryType\":\"AccountRoot\",\"LedgerIndex\":\"3F5072C4875F32ED770DAF3610A716600ED7C7BB0348FADC7A98E011BB2CD36F\",\"PreviousFields\":{\"Balance\":\"30000000\",\"Flags\":4194304,\"Sequence\":1},\"PreviousTxnID\":\"3FB0350A3742BBCC0D8AA3C5247D1AEC01177D0A24D9C34762BAA2FEA8AD88B3\",\"PreviousTxnLgrSeq\":8206397}}],\"TransactionIndex\":5,\"TransactionResult\":\"tesSUCCESS\"},\"validated\":true}"
return this.api.getTransaction(hash, options).then(
_.partial(checkResult, expected,
@@ -3099,14 +3123,14 @@ describe('RippleAPI', function () {
});
});
it('getTransaction - ledger_index not found', function () {
it('getTransaction - transaction not validated', function () {
const hash =
'4FB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11';
return this.api.getTransaction(hash).then(() => {
assert(false, 'Should throw NotFoundError');
}).catch(error => {
assert(error instanceof this.api.errors.NotFoundError);
assert(error.message.indexOf('ledger_index') !== -1);
assert(error.message.indexOf('Transaction has not been validated yet') !== -1);
});
});
@@ -3416,6 +3440,7 @@ describe('RippleAPI', function () {
_.partial(checkResult, responses.getTrustlines.all, 'getTrustlines'));
});
// @deprecated See corresponding test in `x-address-api-test.js`
it('generateAddress', function () {
function random() {
return _.fill(Array(16), 0);
@@ -3545,9 +3570,9 @@ describe('RippleAPI', function () {
taker: address
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
assert.deepEqual(orderbook, responses.getOrderbook.normal);
});
@@ -3581,9 +3606,9 @@ describe('RippleAPI', function () {
taker: address
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
assert.deepEqual(orderbook, responses.getOrderbook.withXRP);
});
@@ -3649,9 +3674,9 @@ describe('RippleAPI', function () {
taker: myAddress
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
assert.deepStrictEqual([], orderbook.bids);
return checkSortingOfOrders(orderbook.asks);
@@ -3682,9 +3707,9 @@ describe('RippleAPI', function () {
taker: myAddress
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
return checkSortingOfOrders(orderbook.bids) && checkSortingOfOrders(orderbook.asks);
});
@@ -3719,9 +3744,9 @@ describe('RippleAPI', function () {
taker: address
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
const bidRates = orderbook.bids.map(bid => bid.properties.makerExchangeRate);
@@ -3763,9 +3788,9 @@ describe('RippleAPI', function () {
taker: address
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
const orders = _.flatten([orderbook.bids, orderbook.asks]);
@@ -3810,9 +3835,9 @@ describe('RippleAPI', function () {
taker: address
})
]
).then((directOfferResults, reverseOfferResults) => {
const directOffers = (directOfferResults ? directOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults : []).reduce((acc, res) => acc.concat(res.offers), [])
).then(([directOfferResults, reverseOfferResults]) => {
const directOffers = (directOfferResults ? directOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const reverseOffers = (reverseOfferResults ? reverseOfferResults.offers : []).reduce((acc, res) => acc.concat(res), [])
const orderbook = RippleAPI.formatBidsAndAsks(orderbookInfo, [...directOffers, ...reverseOffers]);
assert(
@@ -4517,8 +4542,7 @@ describe('RippleAPI', function () {
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44'
});
assert.throws(() => {
const hash = this.api.computeLedgerHash(ledger);
unused(hash);
this.api.computeLedgerHash(ledger);
}, /does not match computed hash of state/);
});
});
@@ -4545,20 +4569,21 @@ describe('RippleAPI', function () {
it('ledger utils - renameCounterpartyToIssuerInOrder', function () {
const order = {
taker_gets: { counterparty: '1' },
taker_pays: { counterparty: '1' }
taker_gets: { counterparty: '1', currency: 'XRP' },
taker_pays: { counterparty: '1', currency: 'XRP' }
};
const expected = {
taker_gets: { issuer: '1' },
taker_pays: { issuer: '1' }
taker_gets: { issuer: '1', currency: 'XRP' },
taker_pays: { issuer: '1', currency: 'XRP' }
};
assert.deepEqual(utils.renameCounterpartyToIssuerInOrder(order), expected);
});
it('ledger utils - compareTransactions', function () {
// @ts-ignore
assert.strictEqual(utils.compareTransactions({}, {}), 0);
let first = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
let second = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
let first: any = { outcome: { ledgerVersion: 1, indexInLedger: 100 } };
let second: any = { outcome: { ledgerVersion: 1, indexInLedger: 200 } };
assert.strictEqual(utils.compareTransactions(first, second), -1);
@@ -4574,13 +4599,13 @@ describe('RippleAPI', function () {
});
it('ledger utils - getRecursive', function () {
function getter(marker, limit) {
return new Promise((resolve, reject) => {
if (marker === undefined) {
resolve({ marker: 'A', limit: limit, results: [1] });
} else {
function getter(marker) {
return new Promise<RecursiveData>((resolve, reject) => {
if (marker !== undefined) {
reject(new Error());
return;
}
resolve({ marker: 'A', results: [1] });
});
}
return utils.getRecursive(getter, 10).then(() => {
@@ -4722,22 +4747,26 @@ describe('RippleAPI - offline', function () {
assert.throws(() => api.computeLedgerHash(header));
});
/* eslint-disable no-unused-vars */
it('RippleAPI - implicit server port', function () {
const api = new RippleAPI({ server: 'wss://s1.ripple.com' });
new RippleAPI({ server: 'wss://s1.ripple.com' });
});
/* eslint-enable no-unused-vars */
it('RippleAPI invalid options', function () {
assert.throws(() => new RippleAPI({ invalid: true }));
assert.throws(() => new RippleAPI({ invalid: true } as any));
});
it('RippleAPI valid options', function () {
const api = new RippleAPI({ server: 'wss://s:1' });
assert.deepEqual(api.connection._url, 'wss://s:1');
const privateConnectionUrl = (api.connection as any)._url;
assert.deepEqual(privateConnectionUrl, 'wss://s:1');
});
it('RippleAPI invalid server uri', function () {
assert.throws(() => new RippleAPI({ server: 'wss//s:1' }));
});
xit('RippleAPI connect() times out after 2 seconds', function () {
// TODO: Use a timer mock like https://jestjs.io/docs/en/timer-mocks
// to test that connect() times out after 2 seconds.
});
});

View File

@@ -1,11 +1,9 @@
/* eslint-disable max-nested-callbacks */
'use strict';
const _ = require('lodash');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const responses = require('./fixtures').responses;
const ledgerClosed = require('./fixtures/rippled/ledger-close');
const RippleAPI = require('ripple-api').RippleAPI;
import _ from 'lodash';
import assert from 'assert-diff';
import setupAPI from './setup-api';
import {responses} from './fixtures';
import ledgerClosed from './fixtures/rippled/ledger-close.json';
import {RippleAPI} from 'ripple-api';
const schemaValidator = RippleAPI._PRIVATE.schemaValidator;
const TIMEOUT = 20000;

View File

@@ -1,19 +1,14 @@
'use strict'; // eslint-disable-line
/* eslint-disable max-nested-callbacks */
const _ = require('lodash');
const net = require('net');
const assert = require('assert-diff');
const setupAPI = require('./setup-api');
const RippleAPI = require('ripple-api').RippleAPI;
import _ from 'lodash';
import net from 'net';
import assert from 'assert-diff';
import setupAPI from './setup-api';
import {RippleAPI} from 'ripple-api';
import ledgerClose from './fixtures/rippled/ledger-close.json';
const utils = RippleAPI._PRIVATE.ledgerUtils;
const ledgerClose = require('./fixtures/rippled/ledger-close.json');
const TIMEOUT = 200000; // how long before each test case times out
function unused() {
}
const isBrowser = (process as any).browser;
function createServer() {
return new Promise((resolve, reject) => {
@@ -34,14 +29,14 @@ describe('Connection', function() {
afterEach(setupAPI.teardown);
it('default options', function() {
const connection = new utils.common.Connection('url');
const connection: any = new utils.common.Connection('url');
assert.strictEqual(connection._url, 'url');
assert(_.isUndefined(connection._proxyURL));
assert(_.isUndefined(connection._authorization));
});
it('trace', function() {
const connection = new utils.common.Connection('url', {trace: true});
const connection: any = new utils.common.Connection('url', {trace: true});
const message1 = '{"type": "transaction"}';
const message2 = '{"type": "path_find"}';
const messages = [];
@@ -60,11 +55,11 @@ describe('Connection', function() {
});
it('with proxy', function(done) {
if (process.browser) {
if (isBrowser) {
done();
return;
}
createServer().then(server => {
createServer().then((server: any) => {
const port = server.address().port;
const expect = 'CONNECT localhost';
server.on('connection', socket => {
@@ -72,6 +67,7 @@ describe('Connection', function() {
const got = data.toString('ascii', 0, expect.length);
assert.strictEqual(got, expect);
server.close();
connection.disconnect();
done();
});
});
@@ -81,10 +77,10 @@ describe('Connection', function() {
authorization: 'authorization',
trustedCertificates: ['path/to/pem']
};
const connection =
new utils.common.Connection(this.api.connection._url, options);
connection.connect().catch(done);
connection.connect().catch(done);
const connection = new utils.common.Connection(this.api.connection._url, options);
connection.connect().catch((err) => {
assert(err instanceof this.api.errors.NotConnectedError);
});
}, done);
});
@@ -109,7 +105,7 @@ describe('Connection', function() {
it('should throw NotConnectedError if server not responding ', function(
done
) {
if (process.browser) {
if (isBrowser) {
const phantomTest = /PhantomJS/;
if (phantomTest.test(navigator.userAgent)) {
// inside PhantomJS this one just hangs, so skip as not very relevant
@@ -154,7 +150,6 @@ describe('Connection', function() {
it('DisconnectedError on send', function() {
this.api.connection._ws.send = function(message, options, callback) {
unused(message, options);
callback({message: 'not connected'});
};
return this.api.getServerInfo().then(() => {
@@ -215,7 +210,7 @@ describe('Connection', function() {
});
it('reconnect on several unexpected close', function(done) {
if (process.browser) {
if (isBrowser) {
const phantomTest = /PhantomJS/;
if (phantomTest.test(navigator.userAgent)) {
// inside PhantomJS this one just hangs, so skip as not very relevant
@@ -315,21 +310,17 @@ describe('Connection', function() {
});
it('Cannot connect because no server', function() {
const connection = new utils.common.Connection();
const connection = new utils.common.Connection(undefined as string);
return connection.connect().then(() => {
assert(false, 'Should throw ConnectionError');
}).catch(error => {
assert(error instanceof this.api.errors.ConnectionError);
assert(error instanceof this.api.errors.ConnectionError, 'Should throw ConnectionError');
});
});
it('connect multiserver error', function() {
const options = {
servers: ['wss://server1.com', 'wss://server2.com']
};
assert.throws(function() {
const api = new RippleAPI(options);
unused(api);
new RippleAPI({servers: ['wss://server1.com', 'wss://server2.com']} as any);
}, this.api.errors.RippleError);
});

View File

@@ -1,6 +1,8 @@
'use strict';
module.exports = {
ACCOUNT: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
ACCOUNT_X: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
ACCOUNT_T: 'T719a5UwUCnEs54UsxG9CJYYDhwmFCqkr7wxCcNcfZ6p5GZ',
OTHER_ACCOUNT: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
THIRD_ACCOUNT: 'rwBYyfufTzk77zUSKEu4MvixfarC35av1J',
FOURTH_ACCOUNT: 'rJnZ4YHCUsHvQu7R6mZohevKJDHFzVD6Zr',

View File

@@ -1,4 +1,6 @@
{
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}

View File

@@ -0,0 +1,4 @@
{
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
}

View File

@@ -5,6 +5,7 @@ function buildList(options) {
}
module.exports = {
generateXAddress: require('./generate-x-address.json'),
generateAddress: require('./generate-address.json'),
getAccountInfo: require('./get-account-info.json'),
getAccountObjects: require('./get-account-objects.json'),

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

123
test/hashes-test.ts Normal file
View File

@@ -0,0 +1,123 @@
import assert from 'assert';
import fs from 'fs';
import * as hashes from '../src/common/hashes';
/**
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder
*/
function createLedgerTest(ledgerIndex: number) {
describe(String(ledgerIndex), function() {
var path = __dirname + '/fixtures/rippled/ledger-full-' + ledgerIndex + '.json';
var ledgerRaw = fs.readFileSync(path, {encoding: 'utf8'});
var ledgerJSON = JSON.parse(ledgerRaw);
var hasAccounts = Array.isArray(ledgerJSON.accountState)
&& ledgerJSON.accountState.length > 0;
if (hasAccounts) {
it('has account_hash of ' + ledgerJSON.account_hash, function() {
assert.equal(ledgerJSON.account_hash,
hashes.computeStateTreeHash(ledgerJSON.accountState));
});
}
it('has transaction_hash of ' + ledgerJSON.transaction_hash, function() {
assert.equal(ledgerJSON.transaction_hash,
hashes.computeTransactionTreeHash(ledgerJSON.transactions));
});
});
}
describe('Ledger', function() {
// This is the first recorded ledger with a non empty transaction set
createLedgerTest(38129);
// Because, why not.
createLedgerTest(40000);
// 1311 AffectedNodes, no accounts
createLedgerTest(7501326);
describe('calcAccountRootEntryHash', function() {
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
var actualEntryHash = hashes.computeAccountHash(account);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcRippleStateEntryHash', function() {
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
var currency = 'USD';
var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
var actualEntryHash1 = hashes.computeTrustlineHash(
account1, account2, currency);
var actualEntryHash2 = hashes.computeTrustlineHash(
account2, account1, currency);
assert.equal(actualEntryHash1, expectedEntryHash);
assert.equal(actualEntryHash2, expectedEntryHash);
});
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
var currency = 'UAM';
var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
var actualEntryHash1 = hashes.computeTrustlineHash(
account1, account2, currency);
var actualEntryHash2 = hashes.computeTrustlineHash(
account2, account1, currency);
assert.equal(actualEntryHash1, expectedEntryHash);
assert.equal(actualEntryHash2, expectedEntryHash);
});
});
describe('calcOfferEntryHash', function() {
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
var sequence = 137;
var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
var actualEntryHash = hashes.computeOrderHash(account, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('computeSignerListHash', function() {
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
var expectedEntryHash = '778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF';
var actualEntryHash = hashes.computeSignerListHash(account);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcEscrowEntryHash', function() {
it('will calculate the Escrow entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x, sequence 84', function() {
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
var sequence = 84;
var expectedEntryHash = '61E8E8ED53FA2CEBE192B23897071E9A75217BF5A410E9CB5B45AAB7AECA567A';
var actualEntryHash = hashes.computeEscrowHash(account, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
describe('calcPaymentChannelEntryHash', function() {
it('will calculate the PaymentChannel entry hash for rDx69ebzbowuqztksVDmZXjizTd12BVr4x and rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso, sequence 82', function() {
var account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x';
var dstAccount = 'rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso'
var sequence = 82;
var expectedEntryHash = 'E35708503B3C3143FB522D749AAFCC296E8060F0FB371A9A56FAE0B1ED127366';
var actualEntryHash = hashes.computePaymentChannelHash(account, dstAccount, sequence);
assert.equal(actualEntryHash, expectedEntryHash);
});
});
});

View File

@@ -3,3 +3,4 @@
--slow 500
--require ts-node/register
--require source-map-support/register
./test/*.{ts,js}

View File

@@ -1,6 +1,6 @@
'use strict';
const assert = require('assert');
const RangeSet = require('ripple-api').RippleAPI._PRIVATE.RangeSet;
import assert from 'assert';
import {RippleAPI} from 'ripple-api';
const RangeSet = RippleAPI._PRIVATE.RangeSet;
describe('RangeSet', function() {
it('addRange()/addValue()', function() {

61
test/shamap-test.ts Normal file
View File

@@ -0,0 +1,61 @@
import assert from 'assert';
import {SHAMap, NodeType} from '../src/common/hashes/shamap';
const TYPE_TRANSACTION_NO_METADATA = NodeType.TRANSACTION_NO_METADATA
var HEX_ZERO = '00000000000000000000000000000000' +
'00000000000000000000000000000000';
/**
* Generates data to hash for testing
*/
function intToVuc(v: number): string {
var ret = '';
for (var i = 0; i < 32; i++) {
ret += '0';
ret += v.toString(16).toUpperCase();
}
return ret;
}
function fillShamapTest(shamap: any, keys: string[], hashes: string[]) {
for (var i = 0; i < keys.length; i++) {
var data = intToVuc(i);
shamap.addItem(keys[i].toUpperCase(), data, TYPE_TRANSACTION_NO_METADATA);
assert.equal(shamap.hash, hashes[i]);
}
}
describe('SHAMap', function() {
describe('#addItem', function() {
it('will add new nodes to v1', function() {
var keys = [
'b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8',
'292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8'
];
var hashesv1 = [
'B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F',
'FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266',
'4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75',
'7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07',
'395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E',
'D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4',
'76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615',
'DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5'
];
var shamapv1 = new SHAMap();
assert.equal(shamapv1.hash, HEX_ZERO);
fillShamapTest(shamapv1, keys, hashesv1);
});
});
});

6
test/tsconfig.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": "../tsconfig-base.json",
"compilerOptions": {
"noEmit": true,
}
}

4034
test/x-address-api-test.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,23 @@
{
"compilerOptions": {
"pretty": true,
"lib": [
"es2017"
],
"target": "es6",
"target": "es2017",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true /* Added 2019-04-13 */,
"sourceMap": true,
"noEmitOnError": true /* Added 2019-04-13 */,
"strict": true /* Enable all strict type-checking options. */,
"strictNullChecks": false,
"noImplicitAny": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": true,
"strict": true /* Enable all strict type-checking options. */,
"preserveConstEnums": false,
"module": "commonjs",
"moduleResolution": "node",
"suppressImplicitAnyIndexErrors": false
"esModuleInterop": true,
"suppressImplicitAnyIndexErrors": false,
"resolveJsonModule": true,
"preserveSymlinks": true
}
}

View File

@@ -1,76 +0,0 @@
{
"extends": [
"tslint-eslint-rules"
],
"rules": {
"ban": [true, ["alert"]],
"no-arg": true,
"no-conditional-assignment": true,
"no-console": false,
"no-constant-condition": true,
"no-control-regex": true,
"no-debugger": true,
"no-duplicate-case": true,
"no-empty": true,
"no-empty-character-class": true,
"no-eval": true,
"no-ex-assign": true,
"no-extra-boolean-cast": true,
"no-extra-semi": true,
"no-switch-case-fall-through": true,
"no-inner-declarations": [true, "functions"],
"no-invalid-regexp": true,
// this rule would cause problems with mocha test cases,
"no-invalid-this": false,
"no-irregular-whitespace": true,
"ter-no-irregular-whitespace": true,
"label-position": true,
"indent": [true, "spaces", 2],
"linebreak-style": [true, "unix"],
"no-multi-spaces": true,
"no-consecutive-blank-lines": [true, 2],
"no-unused-expression": true,
"no-construct": true,
"no-duplicate-variable": true,
"no-regex-spaces": true,
"no-shadowed-variable": true,
"ter-no-sparse-arrays": true,
"no-trailing-whitespace": true,
"no-string-throw": true,
"no-unexpected-multiline": true,
"no-unused-variable": [true, {"ignore-pattern": "^_"}],
"no-use-before-declare": true,
"no-var-keyword": true,
"no-magic-numbers": false,
"array-bracket-spacing": [true, "never"],
"ter-arrow-body-style": false,
"ter-arrow-parens": [true, "as-needed"],
"ter-arrow-spacing": true,
"block-spacing": true,
"brace-style": [true, "1tbs", {"allowSingleLine": true}],
"variable-name": false,
"trailing-comma": [true, {"multiline": "never", "singleline": "never"}],
"cyclomatic-complexity": [false, 11],
"curly": [true, "all"],
"switch-default": false,
"eofline": true,
"triple-equals": true,
"forin": false,
"handle-callback-err": true,
"ter-max-len": [true, 120],
"new-parens": true,
"object-curly-spacing": [true, "never"],
"object-literal-shorthand": false,
"one-variable-per-declaration": [true, "ignore-for-loop"],
"ter-prefer-arrow-callback": false,
"prefer-const": true,
"object-literal-key-quotes": false,
"quotemark": [true, "single"],
"radix": true,
"semicolon": [true, "never"],
"space-in-parens": [true, "never"],
"comment-format": [true, "check-space"],
"use-isnan": true,
"valid-typeof": true
}
}

1380
yarn.lock

File diff suppressed because it is too large Load Diff