mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 13:05:49 +00:00
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
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
## 1.4.0-b1 (2019-09-26)
|
||||
|
||||
* Add support for the new X-address format.
|
||||
* 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>`
|
||||
|
||||
## 1.3.4 (2019-10-18)
|
||||
|
||||
* Update ripple-lib-transactionparser
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
- [sign](#sign)
|
||||
- [combine](#combine)
|
||||
- [submit](#submit)
|
||||
- [generateXAddress](#generatexaddress)
|
||||
- [generateAddress](#generateaddress)
|
||||
- [isValidAddress](#isvalidaddress)
|
||||
- [isValidSecret](#isvalidsecret)
|
||||
@@ -225,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
|
||||
|
||||
@@ -377,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.
|
||||
@@ -538,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 account’s 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.
|
||||
|
||||
@@ -851,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
|
||||
|
||||
@@ -2289,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.
|
||||
|
||||
@@ -3906,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 account’s 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.
|
||||
|
||||
@@ -5403,7 +5416,7 @@ 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).
|
||||
|
||||
Please note that when this method is used for multisigning, the `options` parameter is not *Optional* anymore. It will be compulsory. See the multisigning example in this section for more details.
|
||||
When this method is used for multisigning, the `options` parameter is required. See the multisigning example in this section for more details.
|
||||
|
||||
### Return Value
|
||||
|
||||
@@ -5431,6 +5444,7 @@ return api.sign(txJSON, secret); // or: api.sign(txJSON, keypair);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Example (multisigning)
|
||||
|
||||
```javascript
|
||||
@@ -5620,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.
|
||||
|
||||
@@ -5633,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
|
||||
|
||||
@@ -5640,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
|
||||
|
||||
@@ -5652,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"
|
||||
}
|
||||
@@ -5662,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
23
docs/src/generateXAddress.md.ejs
Normal file
23
docs/src/generateXAddress.md.ejs
Normal 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') %>
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)]
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.3.4",
|
||||
"version": "1.4.0-b2",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -23,8 +23,8 @@
|
||||
"https-proxy-agent": "^3.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ripple-address-codec": "^4.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ripple-address-codec": "^3.0.4",
|
||||
"ripple-binary-codec": "^0.2.4",
|
||||
"ripple-keypairs": "^0.10.1",
|
||||
"ripple-lib-transactionparser": "0.8.0",
|
||||
|
||||
29
src/api.ts
29
src/api.ts
@@ -7,7 +7,8 @@ import {
|
||||
dropsToXrp,
|
||||
rippleTimeToISO8601,
|
||||
iso8601ToRippleTime,
|
||||
txFlags
|
||||
txFlags,
|
||||
ensureClassicAddress
|
||||
} from './common'
|
||||
import {
|
||||
connect,
|
||||
@@ -46,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'
|
||||
@@ -74,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,
|
||||
@@ -175,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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -288,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
|
||||
@@ -328,7 +340,6 @@ class RippleAPI extends EventEmitter {
|
||||
combine = combine
|
||||
submit = submit
|
||||
|
||||
generateAddress = generateAddressAPI
|
||||
deriveKeypair = deriveKeypair
|
||||
deriveAddress = deriveAddress
|
||||
computeLedgerHash = computeLedgerHash
|
||||
@@ -336,6 +347,14 @@ 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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import BigNumber from 'bignumber.js'
|
||||
import {decodeAddress} from 'ripple-address-codec'
|
||||
import {decodeAccountID} from 'ripple-address-codec'
|
||||
import sha512Half from './sha512Half'
|
||||
import HashPrefix from './hash-prefix'
|
||||
import {SHAMap, NodeType} from './shamap'
|
||||
@@ -28,12 +28,12 @@ const ledgerSpaceHex = (name: string): string => {
|
||||
}
|
||||
|
||||
const addressToHex = (address: string): string => {
|
||||
return (Buffer.from(decodeAddress(address))).toString('hex')
|
||||
return (Buffer.from(decodeAccountID(address))).toString('hex')
|
||||
}
|
||||
|
||||
const currencyToHex = (currency: string): string => {
|
||||
if (currency.length === 3) {
|
||||
let bytes = new Array(20 + 1).join('0').split('').map(parseFloat)
|
||||
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
|
||||
@@ -81,7 +81,7 @@ export const computeAccountHash = (address: string): string => {
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('signerList') +
|
||||
addressToHex(address) +
|
||||
'00000000' /* uint32(0) signer list index */)
|
||||
'00000000') // uint32(0) signer list index
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
@@ -106,7 +106,7 @@ export const computeTrustlineHash = (address1: string, address2: string, currenc
|
||||
export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
transactions.forEach((txJSON) => {
|
||||
transactions.forEach(txJSON => {
|
||||
const txBlobHex = encode(txJSON)
|
||||
const metaHex = encode(txJSON.metaData)
|
||||
const txHash = computeBinaryTransactionHash(txBlobHex)
|
||||
@@ -120,7 +120,7 @@ export const computeTransactionTreeHash = (transactions: any[]): string => {
|
||||
export const computeStateTreeHash = (entries: any[]): string => {
|
||||
const shamap = new SHAMap()
|
||||
|
||||
entries.forEach((ledgerEntry) => {
|
||||
entries.forEach(ledgerEntry => {
|
||||
const data = encode(ledgerEntry)
|
||||
shamap.addItem(ledgerEntry.index, data, NodeType.ACCOUNT_STATE)
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'),
|
||||
@@ -126,12 +128,23 @@ function loadSchemas() {
|
||||
// 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,
|
||||
|
||||
@@ -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
|
||||
|
||||
33
src/common/schemas/input/generate-x-address.json
Normal file
33
src/common/schemas/input/generate-x-address.json
Normal 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
|
||||
}
|
||||
@@ -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"}
|
||||
]
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/classic-address.json
Normal file
9
src/common/schemas/objects/classic-address.json
Normal 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}$"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
9
src/common/schemas/objects/x-address.json
Normal file
9
src/common/schemas/objects/x-address.json
Normal 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}$"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
18
src/common/schemas/output/generate-x-address.json
Normal file
18
src/common/schemas/output/generate-x-address.json
Normal 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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,11 +4,10 @@ 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 => {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
import {classicAddressToXAddress} from 'ripple-address-codec'
|
||||
import keypairs from 'ripple-keypairs'
|
||||
import * as common from '../common'
|
||||
const {errors, validate} = common
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as _ from 'lodash'
|
||||
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 '../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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
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,7 @@ export {
|
||||
convertMemo,
|
||||
prepareTransaction,
|
||||
common,
|
||||
setCanonicalFlag
|
||||
setCanonicalFlag,
|
||||
getClassicAccountAndTag,
|
||||
ClassicAccountAndTag
|
||||
}
|
||||
|
||||
@@ -996,7 +996,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 +1020,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) {
|
||||
@@ -1581,7 +1581,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 => {
|
||||
@@ -1620,7 +1620,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 => {
|
||||
@@ -1654,7 +1654,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(
|
||||
@@ -2958,7 +2958,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,
|
||||
@@ -3447,6 +3447,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);
|
||||
|
||||
2
test/fixtures/addresses.js
vendored
2
test/fixtures/addresses.js
vendored
@@ -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',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"classicAddress": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"address": "rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
|
||||
4
test/fixtures/responses/generate-x-address.json
vendored
Normal file
4
test/fixtures/responses/generate-x-address.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"xAddress": "XVLcsWWNiFdUEqoDmSwgxh1abfddG1LtbGFk7omPgYpbyE8",
|
||||
"secret": "sp6JS7f14BuwFY8Mw6bTtLKWauoUs"
|
||||
}
|
||||
1
test/fixtures/responses/index.js
vendored
1
test/fixtures/responses/index.js
vendored
@@ -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'),
|
||||
|
||||
4034
test/x-address-api-test.js
Normal file
4034
test/x-address-api-test.js
Normal file
File diff suppressed because it is too large
Load Diff
16
yarn.lock
16
yarn.lock
@@ -99,6 +99,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
|
||||
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
|
||||
|
||||
"@types/base-x@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/base-x/-/base-x-3.0.0.tgz#a1365259d1d3fa3ff973ab543192a6bdd4cb2f90"
|
||||
integrity sha512-vnqSlpsv9uFX5/z8GyKWAfWHhLGJDBkrgRRsnxlsX23DHOlNyqP/eHQiv4TwnYcZULzQIxaWA/xRWU9Dyy4qzw==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/lodash@^4.14.136":
|
||||
version "4.14.144"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.144.tgz#12e57fc99064bce45e5ab3c8bc4783feb75eab8e"
|
||||
@@ -4389,6 +4396,15 @@ ripple-address-codec@^3.0.4:
|
||||
base-x "3.0.4"
|
||||
create-hash "^1.1.2"
|
||||
|
||||
ripple-address-codec@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ripple-address-codec/-/ripple-address-codec-4.0.0.tgz#c20f39eea38def43d2379462e47bff4adabece30"
|
||||
integrity sha512-PsKl9aytg6fZG2F4RtfPT0c1gj42suAQY9VvJVGz+DfQTdXQaTT9V/StVhaJ6jhVpl7oCd981BB9p2Kq+Kyrng==
|
||||
dependencies:
|
||||
"@types/base-x" "^3.0.0"
|
||||
base-x "3.0.4"
|
||||
create-hash "^1.1.2"
|
||||
|
||||
ripple-binary-codec@^0.2.4:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ripple-binary-codec/-/ripple-binary-codec-0.2.4.tgz#555d64c52182a215222af6045b5170e43f0530b1"
|
||||
|
||||
Reference in New Issue
Block a user