mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-13 09:05:49 +00:00
Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1c1c7033a | ||
|
|
bc352c4cf0 | ||
|
|
1980fa9fa4 | ||
|
|
6cabb2e935 | ||
|
|
05411527ee | ||
|
|
3c13da66b3 | ||
|
|
5d6af09508 | ||
|
|
15bf721d24 | ||
|
|
14351c9512 | ||
|
|
3b13de5310 | ||
|
|
f92eff2df8 | ||
|
|
a65ac5f8f0 | ||
|
|
0e36a1c505 | ||
|
|
f5bed635e0 | ||
|
|
905ab9f2e4 | ||
|
|
547b63b891 | ||
|
|
c26ddb497e | ||
|
|
2e81cfb56f | ||
|
|
337cb6574a | ||
|
|
abcb6bfecb | ||
|
|
797fda3363 | ||
|
|
bd920ee5bb | ||
|
|
2720970e1f | ||
|
|
b7a12d4bbb | ||
|
|
b4f6135b96 | ||
|
|
7d65bf4641 | ||
|
|
656c81a72c | ||
|
|
bfd0374ef6 | ||
|
|
bf1a772e40 | ||
|
|
8ede100594 | ||
|
|
927f1f6d9a | ||
|
|
0be4c6f233 | ||
|
|
912eea5037 | ||
|
|
06a029b89c | ||
|
|
df708a77d2 | ||
|
|
31232ad50c | ||
|
|
7abaf61e11 | ||
|
|
903a6e31b8 | ||
|
|
2eb5898e8b | ||
|
|
dc2bc0291b | ||
|
|
1357f7eeb4 | ||
|
|
b0cb0a759b | ||
|
|
19d0ca6bfc | ||
|
|
26d03fe2a5 | ||
|
|
dfa61df40a | ||
|
|
d154cced14 | ||
|
|
80b96d9bc9 | ||
|
|
8fa30f71eb | ||
|
|
804094b1ce | ||
|
|
9caf077b58 | ||
|
|
1a5ba06ca3 | ||
|
|
657cad9ffd | ||
|
|
a338a936db | ||
|
|
226e10bca2 | ||
|
|
3a5a989011 | ||
|
|
c9720ef061 | ||
|
|
b6927f178f | ||
|
|
6b40e4fe9d | ||
|
|
59ec56db4c | ||
|
|
a8119d678a | ||
|
|
8e38e313b2 | ||
|
|
b7b75d78ae | ||
|
|
824efb6b59 | ||
|
|
c151ca202c | ||
|
|
b9a64c92e7 | ||
|
|
bcaa06721a | ||
|
|
06227ef12b | ||
|
|
c17827e030 | ||
|
|
97ca0f0b21 | ||
|
|
e4e6419e50 | ||
|
|
fd8c883cf4 | ||
|
|
6b66a59673 | ||
|
|
46177338c2 | ||
|
|
6fcff9b106 | ||
|
|
4f9b6b9186 |
@@ -1,6 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 8
|
||||
- 10
|
||||
- 12
|
||||
- 13
|
||||
|
||||
@@ -39,6 +39,10 @@ Warning: Use at your own risk.
|
||||
- **[XRP Scan - XRP Ledger explorer](https://xrpscan.com)**
|
||||
|
||||
XRP Ledger explorer, metrics and analytics.
|
||||
|
||||
- **[xrplorer](https://xrplorer.com)**
|
||||
|
||||
XRP Ledger explorer, API, metrics, and analytics using a graph database that is synchronized live with the XRPL.
|
||||
|
||||
## Send and request payments
|
||||
|
||||
|
||||
41
HISTORY.md
41
HISTORY.md
@@ -1,5 +1,46 @@
|
||||
# ripple-lib Release History
|
||||
|
||||
Subscribe to [the **ripple-lib-announce** mailing list](https://groups.google.com/forum/#!forum/ripple-lib-announce) for release announcements. We recommend that ripple-lib users stay up-to-date with the latest stable release.
|
||||
|
||||
## 1.7.0 (2020-04-28)
|
||||
|
||||
* Export hashing functions (#1275)
|
||||
* Add failHard (fail_hard) option in `submit` method (#1029)
|
||||
* Add type for parseAccountFlags (#1258)
|
||||
* Add api.connection.getReserveBase() (#1259)
|
||||
* Travis: remove node 8 (#1257)
|
||||
* Dependencies
|
||||
* Update ripple-address-codec, @types/ws, @types/lodash, https-proxy-agent
|
||||
* Update devDependencies: eventemitter2, nyc, ejs, @types/node, webpack, ts-node, prettier, @typescript-eslint/eslint-plugin
|
||||
|
||||
## 1.6.5 (2020-03-23)
|
||||
|
||||
* APPLICATIONS.md: Add xrplorer.com
|
||||
* Internal: Fix typos
|
||||
* Dependencies
|
||||
* Update @types/ws, @types/node, @typescript-eslint/eslint-plugin, @types/mocha, webpack, typescript, mocha, assert-diff
|
||||
* Remove mocha-junit-reporter
|
||||
|
||||
## 1.6.4 (2020-02-18)
|
||||
|
||||
* Fix generateXAddress() and generateAddress() with no entropy (#1211, #1209)
|
||||
* Internal
|
||||
* Improve unit tests
|
||||
* Dependencies
|
||||
* Update webpack-cli, @types/node, webpack, @typescript-eslint/eslint-plugin,
|
||||
typescript, ripple-keypairs
|
||||
|
||||
## 1.6.3 (2020-02-05)
|
||||
|
||||
* Update ripple-keypairs to 1.0.0
|
||||
* Bug fix: Assign event listener to socket close event on open before attempting post-open logic (#1186)
|
||||
* Protects against possible unhandled rejection in disconnect
|
||||
* Adds the Connection `_ws.close` event listener post `_ws.open` before executing any post `_ws.open` logic, i.e. `Connection._subscribeToLedger`
|
||||
* This prevents a reconnection error loop that occurs if `Connection._ws` is never cleaned up by the unreachable `_ws.close` event listener
|
||||
* Also ensures that a possible disconnect() promise rejection is not unhandled if any `_ws.open` logic in `Connection.connect()` throws
|
||||
* Dependencies
|
||||
* Update mocha-junit-reporter, @types/node, mocha, @typescript-eslint/eslint-plugin, ripple-address-codec
|
||||
|
||||
## 1.6.2 (2020-01-17)
|
||||
|
||||
* Bug fix: Catch possible error in reconnect() on _heartbeat(), emit reconnect error (#1179)
|
||||
|
||||
@@ -6,7 +6,13 @@ A JavaScript/TypeScript API for interacting with the XRP Ledger
|
||||
|
||||
This is the recommended library for integrating a JavaScript/TypeScript app with the XRP Ledger, especially if you intend to use advanced functionality such as IOUs, payment paths, the decentralized exchange, account settings, payment channels, escrows, multi-signing, and more.
|
||||
|
||||
**What is ripple-lib used for?** Here's a [list of applications](APPLICATIONS.md) that use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
## [➡️ Reference Documentation](https://xrpl.org/rippleapi-reference.html)
|
||||
|
||||
See the full reference documentation on the XRP Ledger Dev Portal.
|
||||
|
||||
## [➡️ Applications and Projects](APPLICATIONS.md)
|
||||
|
||||
What is ripple-lib used for? The applications on the list linked above use `ripple-lib`. Open a PR to add your app or project to the list!
|
||||
|
||||
### Features
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
- [hasNextPage](#hasnextpage)
|
||||
- [requestNextPage](#requestnextpage)
|
||||
- [Static Methods](#static-methods)
|
||||
- [computeBinaryTransactionHash](#computebinarytransactionhash)
|
||||
- [renameCounterpartyToIssuer](#renamecounterpartytoissuer)
|
||||
- [formatBidsAndAsks](#formatbidsandasks)
|
||||
- [API Methods](#api-methods)
|
||||
@@ -982,6 +983,40 @@ return api.request(command, params).then(response => {
|
||||
|
||||
# Static Methods
|
||||
|
||||
ripple-lib features a number of static methods that you can access directly on the `RippleAPI` object. The most commonly-used one is `computeBinaryTransactionHash`, described below. For the full list, see the [XRP Ledger Hashes README](https://github.com/ripple/ripple-lib/blob/develop/src/common/hashes/README.md).
|
||||
|
||||
## computeBinaryTransactionHash
|
||||
|
||||
`computeBinaryTransactionHash(txBlobHex: string): string`
|
||||
|
||||
Returns the hash (id) of a binary transaction blob.
|
||||
|
||||
This is a static method on the `RippleAPI` class.
|
||||
|
||||
### Parameters
|
||||
|
||||
This method takes one parameter, a string containing a binary transaction in hex.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a string representing the transaction's id (hash).
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signed_blob = '120000228000000024000B2E5A201B0066374B61400000003B9ACA0068400000000000000C732102356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC74473045022100B3721EEB1ED6DFF29FB8B209E2DE6B54A0A6E44D52D926342F3D334BE98F08640220367A74107AD5DEAEFA3AB2984C161FC23F30B2704BB5CC984358BA262177A4568114F667B0CA50CC7709A220B0561B85E53A48461FA883142B71D8B09B4EE8DAA68FB936C23E3A974713BDAC'
|
||||
if (typeof signed_blob === 'string' && signed_blob.match(/^[A-F0-9]+$/)) {
|
||||
const id = RippleAPI.computeBinaryTransactionHash(signed_blob)
|
||||
console.log('Transaction hash:', id')
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Transaction hash: 80C5E11E1A21A626759D6CB944B33DBAAC66BD704A289C86E330B847904F5C13
|
||||
```
|
||||
|
||||
[RunKit Example: computeBinaryTransactionHash](https://runkit.com/intelliot/computebinarytransactionhash-example)
|
||||
|
||||
## renameCounterpartyToIssuer
|
||||
|
||||
`renameCounterpartyToIssuer(issue: {currency: string, counterparty: address}): {currency: string, issuer: address}`
|
||||
@@ -5600,6 +5635,7 @@ Submits a signed transaction. The transaction is not guaranteed to succeed; it m
|
||||
Name | Type | Description
|
||||
---- | ---- | -----------
|
||||
signedTransaction | string | A signed transaction as returned by [sign](#sign).
|
||||
failHard | boolean | *Optional* If `true`, and the transaction fails locally, do not retry or relay the transaction to other servers. Defaults to `false`.
|
||||
|
||||
### Return Value
|
||||
|
||||
|
||||
@@ -1 +1,35 @@
|
||||
# Static Methods
|
||||
|
||||
ripple-lib features a number of static methods that you can access directly on the `RippleAPI` object. The most commonly-used one is `computeBinaryTransactionHash`, described below. For the full list, see the [XRP Ledger Hashes README](https://github.com/ripple/ripple-lib/blob/develop/src/common/hashes/README.md).
|
||||
|
||||
## computeBinaryTransactionHash
|
||||
|
||||
`computeBinaryTransactionHash(txBlobHex: string): string`
|
||||
|
||||
Returns the hash (id) of a binary transaction blob.
|
||||
|
||||
This is a static method on the `RippleAPI` class.
|
||||
|
||||
### Parameters
|
||||
|
||||
This method takes one parameter, a string containing a binary transaction in hex.
|
||||
|
||||
### Return Value
|
||||
|
||||
This method returns a string representing the transaction's id (hash).
|
||||
|
||||
### Example
|
||||
|
||||
```javascript
|
||||
const signed_blob = '120000228000000024000B2E5A201B0066374B61400000003B9ACA0068400000000000000C732102356E89059A75438887F9FEE2056A2890DB82A68353BE9C0C0C8F89C0018B37FC74473045022100B3721EEB1ED6DFF29FB8B209E2DE6B54A0A6E44D52D926342F3D334BE98F08640220367A74107AD5DEAEFA3AB2984C161FC23F30B2704BB5CC984358BA262177A4568114F667B0CA50CC7709A220B0561B85E53A48461FA883142B71D8B09B4EE8DAA68FB936C23E3A974713BDAC'
|
||||
if (typeof signed_blob === 'string' && signed_blob.match(/^[A-F0-9]+$/)) {
|
||||
const id = RippleAPI.computeBinaryTransactionHash(signed_blob)
|
||||
console.log('Transaction hash:', id')
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
Transaction hash: 80C5E11E1A21A626759D6CB944B33DBAAC66BD704A289C86E330B847904F5C13
|
||||
```
|
||||
|
||||
[RunKit Example: computeBinaryTransactionHash](https://runkit.com/intelliot/computebinarytransactionhash-example)
|
||||
|
||||
23
package.json
23
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "1.6.2",
|
||||
"version": "1.7.0",
|
||||
"license": "ISC",
|
||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -23,34 +23,33 @@
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/ws": "^7.2.0",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"https-proxy-agent": "^4.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"jsonschema": "1.2.2",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"ripple-address-codec": "^4.0.0",
|
||||
"ripple-address-codec": "^4.1.1",
|
||||
"ripple-binary-codec": "^0.2.5",
|
||||
"ripple-keypairs": "^0.11.0",
|
||||
"ripple-keypairs": "^1.0.0",
|
||||
"ripple-lib-transactionparser": "0.8.2",
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@types/mocha": "^7.0.1",
|
||||
"@types/node": "^13.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.3.3",
|
||||
"@typescript-eslint/parser": "^2.3.3",
|
||||
"assert-diff": "^2.0.3",
|
||||
"@typescript-eslint/parser": "^2.27.0",
|
||||
"assert-diff": "^3.0.0",
|
||||
"doctoc": "^1.4.0",
|
||||
"ejs": "^3.0.1",
|
||||
"eslint": "^6.5.1",
|
||||
"eventemitter2": "^6.0.0",
|
||||
"json-schema-to-markdown-table": "^0.4.0",
|
||||
"mocha": "7.0.0",
|
||||
"mocha-junit-reporter": "^1.9.1",
|
||||
"mocha": "^7.1.1",
|
||||
"nyc": "^15.0.0",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier": "^2.0.5",
|
||||
"ts-node": "^8.4.1",
|
||||
"typescript": "^3.6.4",
|
||||
"webpack": "^4.41.2",
|
||||
"typescript": "^3.7.5",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.9"
|
||||
},
|
||||
|
||||
@@ -14,7 +14,10 @@ lint() {
|
||||
|
||||
unittest() {
|
||||
# test "src"
|
||||
mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
|
||||
|
||||
# TODO: replace/upgrade mocha-junit-reporter
|
||||
#mocha test --reporter mocha-junit-reporter --reporter-options mochaFile=$CIRCLE_TEST_REPORTS/test-results.xml
|
||||
|
||||
yarn test --coverage
|
||||
#yarn run coveralls
|
||||
|
||||
|
||||
38
src/api.ts
38
src/api.ts
@@ -89,6 +89,19 @@ import {clamp, renameCounterpartyToIssuer} from './ledger/utils'
|
||||
import {TransactionJSON, Instructions, Prepare} from './transaction/types'
|
||||
import {ConnectionUserOptions} from './common/connection'
|
||||
import {isValidXAddress, isValidClassicAddress} from 'ripple-address-codec'
|
||||
import {
|
||||
computeBinaryTransactionHash,
|
||||
computeTransactionHash,
|
||||
computeBinaryTransactionSigningHash,
|
||||
computeAccountLedgerObjectID,
|
||||
computeSignerListLedgerObjectID,
|
||||
computeOrderID,
|
||||
computeTrustlineHash,
|
||||
computeTransactionTreeHash,
|
||||
computeStateTreeHash,
|
||||
computeEscrowHash,
|
||||
computePaymentChannelHash
|
||||
} from './common/hashes'
|
||||
|
||||
export interface APIOptions extends ConnectionUserOptions {
|
||||
server?: string
|
||||
@@ -402,6 +415,31 @@ class RippleAPI extends EventEmitter {
|
||||
static isValidXAddress = isValidXAddress
|
||||
static isValidClassicAddress = isValidClassicAddress
|
||||
|
||||
/**
|
||||
* Static methods that replace functionality from the now-deprecated ripple-hashes library
|
||||
*/
|
||||
// Compute the hash of a binary transaction blob.
|
||||
static computeBinaryTransactionHash = computeBinaryTransactionHash // (txBlobHex: string): string
|
||||
// Compute the hash of a transaction in txJSON format.
|
||||
static computeTransactionHash = computeTransactionHash // (txJSON: any): string
|
||||
static computeBinaryTransactionSigningHash = computeBinaryTransactionSigningHash // (txBlobHex: string): string
|
||||
// Compute the hash of an account, given the account's classic address (starting with `r`).
|
||||
static computeAccountLedgerObjectID = computeAccountLedgerObjectID // (address: string): string
|
||||
// Compute the hash (ID) of an account's SignerList.
|
||||
static computeSignerListLedgerObjectID = computeSignerListLedgerObjectID // (address: string): 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.
|
||||
static computeOrderID = computeOrderID // (address: string, sequence: number): string
|
||||
// Compute the hash of a trustline, given the two parties' classic addresses (starting with `r`) and the currency code.
|
||||
static computeTrustlineHash = computeTrustlineHash // (address1: string, address2: string, currency: string): string
|
||||
static computeTransactionTreeHash = computeTransactionTreeHash // (transactions: any[]): string
|
||||
static computeStateTreeHash = computeStateTreeHash // (entries: any[]): string
|
||||
// Compute the hash of a ledger.
|
||||
static computeLedgerHash = computeLedgerHash // (ledgerHeader): 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.
|
||||
static computeEscrowHash = computeEscrowHash // (address, 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.
|
||||
static computePaymentChannelHash = computePaymentChannelHash // (address, dstAddress, sequence): string
|
||||
|
||||
xrpToDrops = xrpToDrops
|
||||
dropsToXrp = dropsToXrp
|
||||
rippleTimeToISO8601 = rippleTimeToISO8601
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Back off strategy that increases exponentionally. Useful with repeated
|
||||
* A Back off strategy that increases exponentially. Useful with repeated
|
||||
* setTimeout calls over a network (where the destination may be down).
|
||||
*/
|
||||
export class ExponentialBackoff {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
RippledNotInitializedError,
|
||||
RippleError
|
||||
} from './errors'
|
||||
import {ExponentialBackoff} from './backoff';
|
||||
import {ExponentialBackoff} from './backoff'
|
||||
|
||||
/**
|
||||
* ConnectionOptions is the configuration for the Connection class.
|
||||
@@ -38,6 +38,23 @@ export interface ConnectionOptions {
|
||||
*/
|
||||
export type ConnectionUserOptions = Partial<ConnectionOptions>
|
||||
|
||||
/**
|
||||
* Ledger Stream Message
|
||||
* https://xrpl.org/subscribe.html#ledger-stream
|
||||
*/
|
||||
interface LedgerStreamMessage {
|
||||
type?: 'ledgerClosed' // not present in initial `subscribe` response
|
||||
fee_base: number
|
||||
fee_ref: number
|
||||
ledger_hash: string
|
||||
ledger_index: number
|
||||
ledger_time: number
|
||||
reserve_base: number
|
||||
reserve_inc: number
|
||||
txn_count?: number // not present in initial `subscribe` response
|
||||
validated_ledgers?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an intentionally triggered web-socket disconnect code.
|
||||
* WebSocket spec allows 4xxx codes for app/library specific codes.
|
||||
@@ -118,10 +135,11 @@ function websocketSendAsync(ws: WebSocket, message: string) {
|
||||
* captured by the Connection class over time.
|
||||
*/
|
||||
class LedgerHistory {
|
||||
private availableVersions = new RangeSet()
|
||||
latestVersion: null | number = null
|
||||
feeBase: null | number = null
|
||||
feeRef: null | number = null
|
||||
latestVersion: null | number = null
|
||||
reserveBase: null | number = null
|
||||
private availableVersions = new RangeSet()
|
||||
|
||||
/**
|
||||
* Returns true if the given version exists.
|
||||
@@ -143,25 +161,22 @@ class LedgerHistory {
|
||||
* of whether ledger history data exists or not. If relevant ledger data
|
||||
* is found, we'll update our history (ex: from a "ledgerClosed" event).
|
||||
*/
|
||||
update(responseData: {
|
||||
ledger_index?: string
|
||||
validated_ledgers?: string
|
||||
fee_base?: string
|
||||
fee_ref?: string
|
||||
}) {
|
||||
this.latestVersion = Number(responseData.ledger_index)
|
||||
if (responseData.validated_ledgers) {
|
||||
update(ledgerMessage: LedgerStreamMessage) {
|
||||
// type: ignored
|
||||
this.feeBase = ledgerMessage.fee_base
|
||||
this.feeRef = ledgerMessage.fee_ref
|
||||
// ledger_hash: ignored
|
||||
this.latestVersion = ledgerMessage.ledger_index
|
||||
// ledger_time: ignored
|
||||
this.reserveBase = ledgerMessage.reserve_base
|
||||
// reserve_inc: ignored (may be useful for advanced use cases)
|
||||
// txn_count: ignored
|
||||
if (ledgerMessage.validated_ledgers) {
|
||||
this.availableVersions.reset()
|
||||
this.availableVersions.parseAndAddRanges(responseData.validated_ledgers)
|
||||
this.availableVersions.parseAndAddRanges(ledgerMessage.validated_ledgers)
|
||||
} else {
|
||||
this.availableVersions.addValue(this.latestVersion)
|
||||
}
|
||||
if (responseData.fee_base) {
|
||||
this.feeBase = Number(responseData.fee_base)
|
||||
}
|
||||
if (responseData.fee_ref) {
|
||||
this.feeRef = Number(responseData.fee_ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,10 +386,10 @@ export class Connection extends EventEmitter {
|
||||
* If this succeeds, we're good. If it fails, disconnect so that the consumer can reconnect, if desired.
|
||||
*/
|
||||
private _heartbeat = () => {
|
||||
return this.request({command: 'ping'}).catch(() => {
|
||||
this.reconnect().catch((error) => {
|
||||
this.emit('error', 'reconnect', error.message, error)
|
||||
})
|
||||
return this.request({command: 'ping'}).catch(() => {
|
||||
this.reconnect().catch(error => {
|
||||
this.emit('error', 'reconnect', error.message, error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -406,7 +421,7 @@ export class Connection extends EventEmitter {
|
||||
} catch (error) {
|
||||
// Ignore this error, propagate the root cause.
|
||||
} finally {
|
||||
// Throw the root error (takes precendence over try/catch).
|
||||
// Throw the root error (takes precedence over try/catch).
|
||||
// eslint-disable-next-line no-unsafe-finally
|
||||
throw new RippledNotInitializedError('Rippled not initialized')
|
||||
}
|
||||
@@ -489,18 +504,6 @@ export class Connection extends EventEmitter {
|
||||
this._ws.on('error', error =>
|
||||
this.emit('error', 'websocket', error.message, error)
|
||||
)
|
||||
// Finalize the connection and resolve all awaiting connect() requests
|
||||
try {
|
||||
this._retryConnectionBackoff.reset()
|
||||
await this._subscribeToLedger()
|
||||
this._startHeartbeatInterval()
|
||||
this._connectionManager.resolveAllAwaiting()
|
||||
this.emit('connected')
|
||||
} catch (error) {
|
||||
this._connectionManager.rejectAllAwaiting(error)
|
||||
this.disconnect()
|
||||
return
|
||||
}
|
||||
// Handle a closed connection: reconnect if it was unexpected
|
||||
this._ws.once('close', code => {
|
||||
this._clearHeartbeatInterval()
|
||||
@@ -524,6 +527,17 @@ export class Connection extends EventEmitter {
|
||||
}, retryTimeout)
|
||||
}
|
||||
})
|
||||
// Finalize the connection and resolve all awaiting connect() requests
|
||||
try {
|
||||
this._retryConnectionBackoff.reset()
|
||||
await this._subscribeToLedger()
|
||||
this._startHeartbeatInterval()
|
||||
this._connectionManager.resolveAllAwaiting()
|
||||
this.emit('connected')
|
||||
} catch (error) {
|
||||
this._connectionManager.rejectAllAwaiting(error)
|
||||
await this.disconnect().catch(() => {}) // Ignore this error, propagate the root cause.
|
||||
}
|
||||
})
|
||||
return this._connectionManager.awaitConnection()
|
||||
}
|
||||
@@ -536,8 +550,8 @@ export class Connection extends EventEmitter {
|
||||
* If no open websocket connection exists, resolve with no code (`undefined`).
|
||||
*/
|
||||
disconnect(): Promise<number | undefined> {
|
||||
clearTimeout(this._reconnectTimeoutID);
|
||||
this._reconnectTimeoutID = null;
|
||||
clearTimeout(this._reconnectTimeoutID)
|
||||
this._reconnectTimeoutID = null
|
||||
if (this._state === WebSocket.CLOSED || !this._ws) {
|
||||
return Promise.resolve(undefined)
|
||||
}
|
||||
@@ -564,11 +578,6 @@ export class Connection extends EventEmitter {
|
||||
await this.connect()
|
||||
}
|
||||
|
||||
async getLedgerVersion(): Promise<number> {
|
||||
await this._waitForReady()
|
||||
return this._ledger.latestVersion!
|
||||
}
|
||||
|
||||
async getFeeBase(): Promise<number> {
|
||||
await this._waitForReady()
|
||||
return this._ledger.feeBase!
|
||||
@@ -579,6 +588,16 @@ export class Connection extends EventEmitter {
|
||||
return this._ledger.feeRef!
|
||||
}
|
||||
|
||||
async getLedgerVersion(): Promise<number> {
|
||||
await this._waitForReady()
|
||||
return this._ledger.latestVersion!
|
||||
}
|
||||
|
||||
async getReserveBase(): Promise<number> {
|
||||
await this._waitForReady()
|
||||
return this._ledger.reserveBase!
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given range of ledger versions exist in history
|
||||
* (inclusive).
|
||||
|
||||
@@ -58,6 +58,18 @@ const AccountFlags = {
|
||||
defaultRipple: accountRootFlags.DefaultRipple
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
passwordSpent?: boolean
|
||||
requireDestinationTag?: boolean
|
||||
requireAuthorization?: boolean
|
||||
depositAuth?: boolean
|
||||
disallowIncomingXRP?: boolean
|
||||
disableMasterKey?: boolean
|
||||
noFreeze?: boolean
|
||||
globalFreeze?: boolean
|
||||
defaultRipple?: boolean
|
||||
}
|
||||
|
||||
const AccountFlagIndices = {
|
||||
requireDestinationTag: txFlagIndices.AccountSet.asfRequireDest,
|
||||
requireAuthorization: txFlagIndices.AccountSet.asfRequireAuth,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Methods to hash XRP Ledger objects
|
||||
|
||||
## Methods
|
||||
## Computing a transaction hash (ID)
|
||||
|
||||
### computeBinaryTransactionHash = (txBlobHex: string): string
|
||||
|
||||
@@ -12,19 +12,35 @@ Compute the hash of a binary transaction blob.
|
||||
|
||||
Compute the hash of a transaction in txJSON format.
|
||||
|
||||
## [Hash Prefixes](https://xrpl.org/basic-data-types.html#hash-prefixes)
|
||||
|
||||
In many cases, the XRP Ledger prefixes an object's binary data with a 4-byte code before calculating its hash, so that objects of different types have different hashes even if the binary data is the same. The existing 4-byte codes are structured as 3 alphabetic characters, encoded as ASCII, followed by a zero byte.
|
||||
|
||||
Some types of hashes appear in API requests and responses. Others are only calculated as the first step of signing a certain type of data, or calculating a higher-level hash. Some of following methods internally use some of the 4-byte hash prefixes in order to calculate the appropriate hash.
|
||||
|
||||
### computeBinaryTransactionSigningHash = (txBlobHex: string): string
|
||||
|
||||
### computeTransactionSigningHash = (txJSON: any): string
|
||||
In order to single-sign a transaction, you must perform these steps:
|
||||
|
||||
### computeAccountHash = (address: string): string
|
||||
1. Assuming the transaction is in JSON format (txJSON), `encode` the transaction in the XRP Ledger's binary format.
|
||||
2. Hash the data with the appropriate prefix (`0x53545800` if single-signing, or `0x534D5400` if multi-signing).
|
||||
3. After signing, you must re-serialize the transaction with the `TxnSignature` field included.
|
||||
|
||||
The `computeBinaryTransactionSigningHash` helps with step 2, automatically using the `0x53545800` prefix needed for single-signing a transaction.
|
||||
|
||||
For details, see [Serialization Format](https://xrpl.org/serialization.html).
|
||||
|
||||
_Removed:_ `computeTransactionSigningHash`, which took txJSON as a parameter. It was part of the deprecated ripple-hashes library. If you have txJSON, `encode` it with [ripple-binary-codec](https://github.com/ripple/ripple-binary-codec) first. Example: `return computeBinaryTransactionSigningHash(encode(txJSON))`
|
||||
|
||||
### computeAccountLedgerObjectID = (address: string): string
|
||||
|
||||
Compute the hash of an account, given the account's classic address (starting with `r`).
|
||||
|
||||
### computeSignerListHash = (address: string): string
|
||||
### computeSignerListLedgerObjectID = (address: string): string
|
||||
|
||||
Compute the hash of an account's SignerList.
|
||||
|
||||
### computeOrderHash = (address: string, sequence: number): string
|
||||
### computeOrderID = (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.
|
||||
|
||||
|
||||
@@ -71,6 +71,14 @@ export const computeTransactionHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionHash(encode(txJSON))
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the given binary transaction data with the single-signing prefix.
|
||||
*
|
||||
* See [Serialization Format](https://xrpl.org/serialization.html)
|
||||
*
|
||||
* @param txBlobHex The binary transaction blob as a hexadecimal string
|
||||
* @returns {string} The hash to sign
|
||||
*/
|
||||
export const computeBinaryTransactionSigningHash = (
|
||||
txBlobHex: string
|
||||
): string => {
|
||||
@@ -78,21 +86,56 @@ export const computeBinaryTransactionSigningHash = (
|
||||
return sha512Half(prefix + txBlobHex)
|
||||
}
|
||||
|
||||
export const computeTransactionSigningHash = (txJSON: any): string => {
|
||||
return computeBinaryTransactionSigningHash(encode(txJSON))
|
||||
}
|
||||
|
||||
export const computeAccountHash = (address: string): string => {
|
||||
/**
|
||||
* Compute Account Ledger Object ID
|
||||
*
|
||||
* All objects in a ledger's state tree have a unique ID.
|
||||
* The Account Ledger Object ID is derived by hashing the
|
||||
* address with a namespace identifier. This ensures every
|
||||
* ID is unique.
|
||||
*
|
||||
* See [Ledger Object IDs](https://xrpl.org/ledger-object-ids.html)
|
||||
*
|
||||
* @param address The classic account address
|
||||
* @returns {string} The Ledger Object ID for the account
|
||||
*/
|
||||
export const computeAccountLedgerObjectID = (address: string): string => {
|
||||
return sha512Half(ledgerSpaceHex('account') + addressToHex(address))
|
||||
}
|
||||
|
||||
export const computeSignerListHash = (address: string): string => {
|
||||
/**
|
||||
* [SignerList ID Format](https://xrpl.org/signerlist.html#signerlist-id-format)
|
||||
*
|
||||
* The ID of a SignerList object is the SHA-512Half of the following values, concatenated in order:
|
||||
* * The RippleState space key (0x0053)
|
||||
* * The AccountID of the owner of the SignerList
|
||||
* * The SignerListID (currently always 0)
|
||||
*
|
||||
* This method computes a SignerList Ledger Object ID.
|
||||
*
|
||||
* @param address The classic account address of the SignerList owner (starting with r)
|
||||
* @return {string} The ID of the account's SignerList object
|
||||
*/
|
||||
export const computeSignerListLedgerObjectID = (address: string): string => {
|
||||
return sha512Half(
|
||||
ledgerSpaceHex('signerList') + addressToHex(address) + '00000000'
|
||||
) // uint32(0) signer list index
|
||||
}
|
||||
|
||||
export const computeOrderHash = (address: string, sequence: number): string => {
|
||||
/**
|
||||
* [Offer ID Format](https://xrpl.org/offer.html#offer-id-format)
|
||||
*
|
||||
* The ID of a Offer object is the SHA-512Half of the following values, concatenated in order:
|
||||
* * The Offer space key (0x006F)
|
||||
* * The AccountID of the account placing the offer
|
||||
* * The Sequence number of the OfferCreate transaction that created the offer
|
||||
*
|
||||
* This method computes an Offer ID (aka Order ID).
|
||||
*
|
||||
* @param address The classic account address of the SignerList owner (starting with r)
|
||||
* @returns {string} The ID of the account's Offer object
|
||||
*/
|
||||
export const computeOrderID = (address: string, sequence: number): string => {
|
||||
const prefix = '00' + intToHex(ledgerspaces.offer.charCodeAt(0), 1)
|
||||
return sha512Half(prefix + addressToHex(address) + intToHex(sequence, 4))
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
/**
|
||||
* Ripple ledger namespace prefixes.
|
||||
* XRP Ledger namespace prefixes.
|
||||
*
|
||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
||||
* The XRP 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.
|
||||
*
|
||||
* See [LedgerNameSpace enum](https://github.com/ripple/rippled/blob/master/src/ripple/protocol/LedgerFormats.h#L100)
|
||||
*/
|
||||
export default {
|
||||
account: 'a',
|
||||
@@ -16,9 +18,12 @@ export default {
|
||||
bookDir: 'B', // Directory of order books.
|
||||
contract: 'c',
|
||||
skipList: 's',
|
||||
escrow: 'u',
|
||||
amendment: 'f',
|
||||
feeSettings: 'e',
|
||||
ticket: 'T',
|
||||
signerList: 'S',
|
||||
escrow: 'u',
|
||||
paychan: 'x'
|
||||
paychan: 'x',
|
||||
check: 'C',
|
||||
depositPreauth: 'p'
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"signedTransaction": {
|
||||
"$ref": "blob",
|
||||
"description": "A signed transaction as returned by [sign](#sign)."
|
||||
},
|
||||
"failHard": {
|
||||
"type": "boolean",
|
||||
"description": "If `true`, and the transaction fails locally, do not retry or relay the transaction to other servers. Defaults to `false`."
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {validate, constants, ensureClassicAddress} from '../common'
|
||||
import {FormattedSettings} from '../common/types/objects'
|
||||
import {AccountInfoResponse} from '../common/types/commands'
|
||||
import {RippleAPI} from '..'
|
||||
import {Settings} from '../common/constants'
|
||||
|
||||
const AccountFlags = constants.AccountFlags
|
||||
|
||||
@@ -14,7 +15,7 @@ export type SettingsOptions = {
|
||||
export function parseAccountFlags(
|
||||
value: number,
|
||||
options: {excludeFalse?: boolean} = {}
|
||||
) {
|
||||
): Settings {
|
||||
const settings = {}
|
||||
for (const flagName in AccountFlags) {
|
||||
if (value & AccountFlags[flagName]) {
|
||||
|
||||
@@ -28,7 +28,16 @@ export interface GenerateAddressOptions {
|
||||
function generateAddressAPI(options: GenerateAddressOptions): GeneratedAddress {
|
||||
validate.generateAddress({options})
|
||||
try {
|
||||
const secret = keypairs.generateSeed(options)
|
||||
const generateSeedOptions: {
|
||||
entropy?: Uint8Array
|
||||
algorithm?: 'ecdsa-secp256k1' | 'ed25519'
|
||||
} = {
|
||||
algorithm: options.algorithm
|
||||
}
|
||||
if (options.entropy) {
|
||||
generateSeedOptions.entropy = Uint8Array.from(options.entropy)
|
||||
}
|
||||
const secret = keypairs.generateSeed(generateSeedOptions)
|
||||
const keypair = keypairs.deriveKeypair(secret)
|
||||
const classicAddress = keypairs.deriveAddress(keypair.publicKey)
|
||||
const returnValue: any = {
|
||||
|
||||
@@ -7,7 +7,7 @@ function verifyPaymentChannelClaim(
|
||||
amount: string,
|
||||
signature: string,
|
||||
publicKey: string
|
||||
): string {
|
||||
): boolean {
|
||||
validate.verifyPaymentChannelClaim({channel, amount, signature, publicKey})
|
||||
|
||||
const signingData = binary.encodeForSigningClaim({
|
||||
|
||||
@@ -38,12 +38,16 @@ function formatSubmitResponse(response): FormattedSubmitResponse {
|
||||
|
||||
async function submit(
|
||||
this: RippleAPI,
|
||||
signedTransaction: string
|
||||
signedTransaction: string,
|
||||
failHard?: boolean
|
||||
): Promise<FormattedSubmitResponse> {
|
||||
// 1. Validate
|
||||
validate.submit({signedTransaction})
|
||||
// 2. Make Request
|
||||
const response = await this.request('submit', {tx_blob: signedTransaction})
|
||||
const response = await this.request('submit', {
|
||||
tx_blob: signedTransaction,
|
||||
...(failHard ? {fail_hard: failHard} : {})
|
||||
})
|
||||
// 3. Return Formatted Response
|
||||
return formatSubmitResponse(response)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import {TestSuite} from '../../utils'
|
||||
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
|
||||
const {generateAddress: RESPONSE_FIXTURES} = responses
|
||||
|
||||
/**
|
||||
@@ -9,22 +10,231 @@ const {generateAddress: RESPONSE_FIXTURES} = responses
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateAddress': async (api, address) => {
|
||||
function random(): number[] {
|
||||
'generateAddress': async api => {
|
||||
// GIVEN entropy of all zeros
|
||||
function random() {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
// WHEN generating an address
|
||||
api.generateAddress({entropy: random()}),
|
||||
|
||||
// THEN we get the expected return value
|
||||
RESPONSE_FIXTURES
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress invalid': async (api, address) => {
|
||||
'generateAddress invalid entropy': async api => {
|
||||
assert.throws(() => {
|
||||
// GIVEN entropy of 1 byte
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
api.generateAddress({entropy: random()})
|
||||
|
||||
// THEN an UnexpectedError is thrown
|
||||
// because 16 bytes of entropy are required
|
||||
}, api.errors.UnexpectedError)
|
||||
},
|
||||
|
||||
'generateAddress with no options object': async api => {
|
||||
// GIVEN no options
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress()
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
|
||||
},
|
||||
|
||||
'generateAddress with empty options object': async api => {
|
||||
// GIVEN an empty options object
|
||||
const options = {}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert(account.secret.startsWith('s'), 'Secret must start with `s`')
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1`': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 's' (not 'sEd')
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
assert.notStrictEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`secp256k1 secret ${account.secret} must not start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519`': async api => {
|
||||
// GIVEN we want to use 'ed25519'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with an address starting with 'r' and a secret starting with 'sEd'
|
||||
assert(account.address.startsWith('r'), 'Address must start with `r`')
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`Ed25519 secret ${account.secret} must start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
const response = Object.assign({}, responses.generateAddress, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'TVG3TcCD58BD6MZqsNuTihdrhZwR8SzvYS8U87zvHsAcNw4'
|
||||
})
|
||||
assert.deepEqual(account, response)
|
||||
},
|
||||
|
||||
'generateAddress with algorithm `ed25519` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
||||
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateAddress for test network use': async api => {
|
||||
// GIVEN we want an address for test network use
|
||||
const options: GenerateAddressOptions = {test: true}
|
||||
|
||||
// WHEN generating an address
|
||||
const account = api.generateAddress(options)
|
||||
|
||||
// THEN we get an object with xAddress starting with 'T' and a secret starting with 's'
|
||||
|
||||
// generateAddress return value always includes xAddress to encourage X-address adoption
|
||||
assert.deepEqual(
|
||||
account.xAddress.slice(0, 1),
|
||||
'T',
|
||||
'Test addresses start with T'
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert-diff'
|
||||
import responses from '../../fixtures/responses'
|
||||
import {TestSuite} from '../../utils'
|
||||
import {GenerateAddressOptions} from '../../../src/offline/generate-address'
|
||||
|
||||
/**
|
||||
* Every test suite exports their tests in the default object.
|
||||
@@ -8,22 +9,231 @@ import {TestSuite} from '../../utils'
|
||||
* - Check out "test/api/index.ts" for more information about the test runner.
|
||||
*/
|
||||
export default <TestSuite>{
|
||||
'generateXAddress': async (api, address) => {
|
||||
'generateXAddress': async api => {
|
||||
// GIVEN entropy of all zeros
|
||||
function random() {
|
||||
return new Array(16).fill(0)
|
||||
}
|
||||
|
||||
assert.deepEqual(
|
||||
// WHEN generating an X-address
|
||||
api.generateXAddress({entropy: random()}),
|
||||
|
||||
// THEN we get the expected return value
|
||||
responses.generateXAddress
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress invalid': async (api, address) => {
|
||||
'generateXAddress invalid entropy': async api => {
|
||||
assert.throws(() => {
|
||||
// GIVEN entropy of 1 byte
|
||||
function random() {
|
||||
return new Array(1).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
api.generateXAddress({entropy: random()})
|
||||
|
||||
// THEN an UnexpectedError is thrown
|
||||
// because 16 bytes of entropy are required
|
||||
}, api.errors.UnexpectedError)
|
||||
},
|
||||
|
||||
'generateXAddress with no options object': async api => {
|
||||
// GIVEN no options
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress()
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert(account.secret.startsWith('s'), 'Secrets start with s')
|
||||
},
|
||||
|
||||
'generateXAddress with empty options object': async api => {
|
||||
// GIVEN an empty options object
|
||||
const options = {}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert(account.secret.startsWith('s'), 'Secrets start with s')
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1`': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ecdsa-secp256k1'}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 's'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
assert.notStrictEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`secp256k1 secret ${account.secret} must not start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519`': async api => {
|
||||
// GIVEN we want to use 'ed25519'
|
||||
const options: GenerateAddressOptions = {algorithm: 'ed25519'}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with an xAddress starting with 'X' and a secret starting with 'sEd'
|
||||
assert(
|
||||
account.xAddress.startsWith('X'),
|
||||
'By default X-addresses start with X'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 3),
|
||||
'sEd',
|
||||
`Ed25519 secret ${account.secret} must start with 'sEd'`
|
||||
)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateXAddress)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0)
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, responses.generateAddress)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy; include classic address': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'X7xq1YJ4xmLSGGLhuakFQB9CebWYthQkgsvFC4LGFH871HB',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ecdsa-secp256k1` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ecdsa-secp256k1' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ecdsa-secp256k1',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
const response = Object.assign({}, responses.generateAddress, {
|
||||
xAddress: 'TVG3TcCD58BD6MZqsNuTihdrhZwR8SzvYS8U87zvHsAcNw4'
|
||||
})
|
||||
assert.deepEqual(account, response)
|
||||
},
|
||||
|
||||
'generateXAddress with algorithm `ed25519` and given entropy; include classic address; for test network use': async api => {
|
||||
// GIVEN we want to use 'ed25519' with entropy of zero
|
||||
const options: GenerateAddressOptions = {
|
||||
algorithm: 'ed25519',
|
||||
entropy: new Array(16).fill(0),
|
||||
includeClassicAddress: true,
|
||||
test: true
|
||||
}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get the expected return value
|
||||
assert.deepEqual(account, {
|
||||
xAddress: 'T7t4HeTMF5tT68agwuVbJwu23ssMPeh8dDtGysZoQiij1oo',
|
||||
secret: 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE',
|
||||
classicAddress: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7',
|
||||
address: 'r9zRhGr7b6xPekLvT6wP4qNdWMryaumZS7'
|
||||
})
|
||||
},
|
||||
|
||||
'generateXAddress for test network use': async api => {
|
||||
// GIVEN we want an X-address for test network use
|
||||
const options: GenerateAddressOptions = {test: true}
|
||||
|
||||
// WHEN generating an X-address
|
||||
const account = api.generateXAddress(options)
|
||||
|
||||
// THEN we get an object with xAddress starting with 'T' and a secret starting with 's'
|
||||
assert.deepEqual(
|
||||
account.xAddress.slice(0, 1),
|
||||
'T',
|
||||
'Test X-addresses start with T'
|
||||
)
|
||||
assert.deepEqual(
|
||||
account.secret.slice(0, 1),
|
||||
's',
|
||||
`Secret ${account.secret} must start with 's'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +355,8 @@ export default <TestSuite>{
|
||||
},
|
||||
|
||||
'AccountDelete': async (api, address) => {
|
||||
const hash = 'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
|
||||
const hash =
|
||||
'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
|
||||
const response = await api.getTransaction(hash)
|
||||
assertResultMatch(
|
||||
response,
|
||||
|
||||
@@ -98,6 +98,7 @@ describe('Connection', function() {
|
||||
)
|
||||
assert.strictEqual(await this.api.connection.getFeeBase(), 10)
|
||||
assert.strictEqual(await this.api.connection.getFeeRef(), 10)
|
||||
assert.strictEqual(await this.api.connection.getReserveBase(), 20000000) // 20 XRP
|
||||
})
|
||||
|
||||
it('with proxy', function(done) {
|
||||
@@ -223,26 +224,29 @@ describe('Connection', function() {
|
||||
it('DisconnectedError on initial _onOpen send', async function() {
|
||||
// _onOpen previously could throw PromiseRejectionHandledWarning: Promise rejection was handled asynchronously
|
||||
// do not rely on the api.setup hook to test this as it bypasses the case, disconnect api connection first
|
||||
await this.api.disconnect();
|
||||
await this.api.disconnect()
|
||||
|
||||
// stub _onOpen to only run logic relevant to test case
|
||||
this.api.connection._onOpen = () => {
|
||||
// overload websocket send on open when _ws exists
|
||||
this.api.connection._ws.send = function(data, options, cb) {
|
||||
// recent ws throws this error instead of calling back
|
||||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
|
||||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)')
|
||||
}
|
||||
const request = {command: 'subscribe', streams: ['ledger']};
|
||||
return this.api.connection.request(request);
|
||||
const request = {command: 'subscribe', streams: ['ledger']}
|
||||
return this.api.connection.request(request)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.api.connect();
|
||||
await this.api.connect()
|
||||
} catch (error) {
|
||||
assert(error instanceof this.api.errors.DisconnectedError);
|
||||
assert.strictEqual(error.message, 'WebSocket is not open: readyState 0 (CONNECTING)');
|
||||
assert(error instanceof this.api.errors.DisconnectedError)
|
||||
assert.strictEqual(
|
||||
error.message,
|
||||
'WebSocket is not open: readyState 0 (CONNECTING)'
|
||||
)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it('ResponseFormatError', function() {
|
||||
return this.api
|
||||
@@ -378,8 +382,8 @@ describe('Connection', function() {
|
||||
throw new Error('error on reconnect')
|
||||
}
|
||||
// Hook up a listener for the reconnect error event
|
||||
this.api.on('error', (error, message) => {
|
||||
if(error === 'reconnect' && message === 'error on reconnect') {
|
||||
this.api.on('error', (error, message) => {
|
||||
if (error === 'reconnect' && message === 'error on reconnect') {
|
||||
return done()
|
||||
}
|
||||
return done(new Error('Expected error on reconnect'))
|
||||
@@ -590,6 +594,24 @@ describe('Connection', function() {
|
||||
}
|
||||
)
|
||||
|
||||
it('should clean up websocket connection if error after websocket is opened', async function() {
|
||||
await this.api.disconnect()
|
||||
// fail on connection
|
||||
this.api.connection._subscribeToLedger = async () => {
|
||||
throw new Error('error on _subscribeToLedger')
|
||||
}
|
||||
try {
|
||||
await this.api.connect()
|
||||
throw new Error('expected connect() to reject, but it resolved')
|
||||
} catch (err) {
|
||||
assert(err.message === 'error on _subscribeToLedger')
|
||||
// _ws.close event listener should have cleaned up the socket when disconnect _ws.close is run on connection error
|
||||
// do not fail on connection anymore
|
||||
this.api.connection._subscribeToLedger = async () => {}
|
||||
await this.api.connection.reconnect()
|
||||
}
|
||||
})
|
||||
|
||||
it('should try to reconnect on empty subscribe response on reconnect', function(done) {
|
||||
this.timeout(23000)
|
||||
this.api.on('error', error => {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('Ledger', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
||||
var expectedEntryHash =
|
||||
'2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8'
|
||||
var actualEntryHash = hashes.computeAccountHash(account)
|
||||
var actualEntryHash = hashes.computeAccountLedgerObjectID(account)
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
@@ -105,18 +105,18 @@ describe('Ledger', function() {
|
||||
var sequence = 137
|
||||
var expectedEntryHash =
|
||||
'03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3'
|
||||
var actualEntryHash = hashes.computeOrderHash(account, sequence)
|
||||
var actualEntryHash = hashes.computeOrderID(account, sequence)
|
||||
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeSignerListHash', function() {
|
||||
describe('computeSignerListLedgerObjectID', function() {
|
||||
it('will calculate the SignerList index for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw', function() {
|
||||
var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
|
||||
var expectedEntryHash =
|
||||
'778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF'
|
||||
var actualEntryHash = hashes.computeSignerListHash(account)
|
||||
var actualEntryHash = hashes.computeSignerListLedgerObjectID(account)
|
||||
assert.equal(actualEntryHash, expectedEntryHash)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,21 +32,33 @@ describe('RippleAPI [Test Runner]', function() {
|
||||
// Run all the tests:
|
||||
for (const {name: methodName, tests, config} of allTestSuites) {
|
||||
describe(`api.${methodName}`, () => {
|
||||
// Run each test with the original-style address.
|
||||
describe(`[Original Address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
// Run each test that does not use an address.
|
||||
for (const [testName, fn] of tests) {
|
||||
if (fn.length === 1) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT)
|
||||
})
|
||||
}
|
||||
}
|
||||
// Run each test with a classic address.
|
||||
describe(`[Classic Address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
if (fn.length === 2) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
// Run each test with the newer, x-address style.
|
||||
// Run each test with an X-address.
|
||||
if (!config.skipXAddress) {
|
||||
describe(`[X-address]`, () => {
|
||||
for (const [testName, fn] of tests) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT_X)
|
||||
})
|
||||
if (fn.length === 2) {
|
||||
it(testName, function() {
|
||||
return fn(this.api, addresses.ACCOUNT_X)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"moduleResolution": "node",
|
||||
|
||||
"declaration": true,
|
||||
"declarationMap": true /* Added 2019-04-13 */,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"strict": true,
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"noUnusedLocals": true,
|
||||
|
||||
Reference in New Issue
Block a user