build: Initial linting setup (#1560)

* sets up linting config and runs `yarn lint --fix` once, so that all changes will show up correctly in future PRs.

* Note that there are still a lot of linter errors.
This commit is contained in:
Nathan Nichols
2021-08-26 21:22:40 -05:00
committed by Mayukha Vadari
parent cfa014c44b
commit 6742e2048a
286 changed files with 15508 additions and 12691 deletions

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
dist
node_modules

99
.eslintrc.js Normal file
View File

@@ -0,0 +1,99 @@
module.exports = {
root: true,
// Make ESLint compatible with TypeScript
parser: '@typescript-eslint/parser',
parserOptions: {
// Enable linting rules with type information from our tsconfig
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json'],
// Allow the use of imports / ES modules
sourceType: 'module',
ecmaFeatures: {
// Enable global strict mode
impliedStrict: true
}
},
// Specify global variables that are predefined
env: {
node: true, // Enable node global variables & Node.js scoping
es2020: true // Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020
},
plugins: [],
extends: ['@xrplf/eslint-config/base', 'plugin:mocha/recommended'],
rules: {
// Certain rippled APIs require snake_case naming
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase']
},
{
selector: 'interface',
format: ['snake_case']
}
],
// Ignore type imports when counting dependencies.
'import/max-dependencies': [
'error',
{
max: 5,
ignoreTypeImports: true
}
],
// Removes comments and blank lines from the max-line rules
'max-lines-per-function': [
'warn',
{
max: 50,
skipBlankLines: true,
skipComments: true
}
],
'max-lines': [
'warn',
{
max: 250,
skipBlankLines: true,
skipComments: true
}
]
},
overrides: [
{
files: ['test/**/*.ts'],
rules: {
// Removed the max for test files and test helper files, since tests usually need to import more things
'import/max-dependencies': 'off',
// describe blocks count as a function in Mocha tests, and can be insanely long
'max-lines-per-function': 'off',
// Tests can be very long turns off max-line count
'max-lines': 'off',
// We have lots of statements in tests
'max-statements': 'off',
// We have lots of magic numbers in tests
'no-magic-number': 'off'
}
},
{
files: ['.eslintrc.js', 'jest.config.js'],
rules: {
// Removed no-commonjs requirement as eslint must be in common js format
'import/no-commonjs': 'off',
// Removed this as eslint prevents us from doing this differently
'import/unambiguous': 'off'
}
}
]
}

View File

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

View File

@@ -10,10 +10,27 @@ on:
workflow_dispatch:
jobs:
unit:
# build-and-lint:
# runs-on: ubuntu-latest
# strategy:
# matrix:
# node-version: [14.x]
# steps:
# - uses: actions/checkout@v2
# - name: Use Node.js ${{ matrix.node-version }}
# uses: actions/setup-node@v1
# with:
# node-version: ${{ matrix.node-version }}
# - run: yarn install
# - run: yarn lint
# - run: yarn build
unit:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x]
@@ -24,10 +41,8 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn install --ignore-engines
- run: yarn test
- run: yarn lint
- run: yarn build
integration:
runs-on: ubuntu-latest
@@ -42,7 +57,7 @@ jobs:
ports:
- 6006:6006
options:
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
steps:
- uses: actions/checkout@v2
@@ -50,7 +65,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn install --ignore-engines
- run: yarn test:integration
env:
HOST: localhost
@@ -69,7 +84,7 @@ jobs:
ports:
- 6006:6006
options:
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
steps:
- uses: actions/checkout@v2

View File

@@ -1,10 +0,0 @@
{
"parser": "typescript",
"printWidth": 80,
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"quoteProps": "consistent",
"bracketSpacing": false
}

6
.vscode/extenstions.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

35
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,35 @@
{
"editor.tabSize": 2,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[javascriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"eslint.alwaysShowStatus": true,
"eslint.lintTask.enable": true,
"eslint.codeAction.showDocumentation": {
"enable": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/.hg/store/**": true
},
}

View File

@@ -12,10 +12,6 @@
"unpkg": "build/ripple-latest-min.js",
"jsdelivr": "build/ripple-latest-min.js",
"types": "dist/npm/index.d.ts",
"browser": {
"ws": "./dist/npm/client/wsWrapper.js",
"https-proxy-agent": false
},
"directories": {
"test": "test"
},
@@ -41,8 +37,10 @@
"@types/chai": "^4.2.21",
"@types/mocha": "^9.0.0",
"@types/node": "^16.4.3",
"@typescript-eslint/eslint-plugin": "^2.3.3",
"@typescript-eslint/parser": "^2.27.0",
"@types/puppeteer": "5.4.4",
"@typescript-eslint/eslint-plugin": "^3.7.0",
"@typescript-eslint/parser": "^3.7.0",
"@xrplf/eslint-config": "^1.1.0",
"assert": "^2.0.0",
"assert-diff": "^3.0.0",
"buffer": "^6.0.2",
@@ -50,21 +48,29 @@
"crypto-browserify": "^3.12.0",
"doctoc": "^2.0.0",
"ejs": "^3.0.1",
"eslint": "^6.5.1",
"eslint": "^7.5.0",
"eslint-plugin-array-func": "^3.1.7",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.24.1",
"eslint-plugin-jsdoc": "^29.0.0",
"eslint-plugin-mocha": "^9.0.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-tsdoc": "^0.2.14",
"eventemitter2": "^6.0.0",
"https-browserify": "^1.0.0",
"json-schema-to-markdown-table": "^0.4.0",
"mocha": "^9",
"nyc": "^15",
"path-browserify": "1.0.1",
"prettier": "^2.0.5",
"prettier": "^2.3.2",
"process": "^0.11.10",
"puppeteer": "10.2.0",
"stream-browserify": "^3.0.0",
"stream-http": "3.2.0",
"ts-loader": "^9.2.5",
"ts-node": "^10.1.0",
"typescript": "^3.9.9",
"typescript": "^3.9.10",
"url": "^0.11.0",
"webpack": "^5.6.0",
"webpack-bundle-analyzer": "^4.1.0",
@@ -72,21 +78,22 @@
},
"scripts": {
"build:schemas": "mkdir -p dist/npm/common && cp -r src/common/schemas dist/npm/common/",
"build:lib": "tsc --build",
"build:snippets": "tsc --build ./snippets/tsconfig.json",
"build:lib": "tsc --build tsconfig.build.json",
"build:web": "webpack",
"build": "yarn build:schemas && yarn build:lib && yarn build:web",
"build": "yarn build:schemas && yarn build:lib && yarn build:snippets && yarn build:web",
"analyze": "yarn build:web --analyze",
"watch": "yarn build:lib --watch",
"clean": "rm -rf dist/npm",
"clean": "rm -rf dist",
"doctoc": "doctoc docs/index.md --title '# RippleAPI Reference' --github --maxlevel 2",
"docgen": "node --harmony scripts/build_docs.js",
"prepublish": "yarn clean && yarn build",
"test": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha --config=test/.mocharc.json --exit",
"test:integration": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha ./test/integration/*.ts",
"test:browser": "TS_NODE_PROJECT=src/tsconfig.json nyc mocha ./test/browser/*.ts",
"test": "nyc mocha --config=test/.mocharc.json --exit",
"test:integration": "TS_NODE_PROJECT=tsconfig.build.json nyc mocha ./test/integration/*.ts",
"test:browser": "TS_NODE_PROJECT=tsconfig.build.json nyc mocha ./test/browser/*.ts",
"test:watch": "TS_NODE_PROJECT=src/tsconfig.json mocha --config=test/.mocharc.json --watch --reporter dot",
"format": "prettier --write '{src,test}/**/*.ts'",
"lint": "eslint 'src/**/*.ts' 'test/*.{ts,js}'",
"lint": "eslint . --ext .ts --max-warnings 0",
"perf": "./scripts/perf_test.sh",
"compile:snippets": "tsc -p snippets/tsconfig.json",
"start:snippet": "npm run compile:snippets && node ./snippets/dist/start.js",

View File

@@ -1,9 +1,13 @@
import * as codec from 'ripple-binary-codec'
// import * as codec from 'ripple-binary-codec'
const original = codec.decode('12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402200A693FB5CA6B21250EBDFD8CFF526EE0DF7C9E4E31EB0660692E75E6A93BF5F802203CC39463DDA21386898CA31E18AD1A6828647D65741DD637BAD71BC83E29DB9481145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900')
// const original = codec.decode(
// '12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D87446304402200A693FB5CA6B21250EBDFD8CFF526EE0DF7C9E4E31EB0660692E75E6A93BF5F802203CC39463DDA21386898CA31E18AD1A6828647D65741DD637BAD71BC83E29DB9481145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900'
// )
const test = codec.decode('12000022800200002400000017201B008694F261EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100D8B57E8E06EAE27B1343AF8CAD3F501E18260CCF8BCED08066074106F0F191A3022058FEA6CE9E7FA69D1244C3A70F18983CC2DAF0B10CBB86A6677CF2A5D2B8A68081145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900')
// const test = codec.decode(
// '12000022800200002400000017201B008694F261EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D874473045022100D8B57E8E06EAE27B1343AF8CAD3F501E18260CCF8BCED08066074106F0F191A3022058FEA6CE9E7FA69D1244C3A70F18983CC2DAF0B10CBB86A6677CF2A5D2B8A68081145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900'
// )
console.log('original:', JSON.stringify(original))
// console.log('original:', JSON.stringify(original))
console.log('test:', JSON.stringify(test))
// console.log('test:', JSON.stringify(test))

View File

@@ -1,19 +1,22 @@
import {Client} from '../../dist/npm'
import { TransactionMetadata } from '../../src/models/common/transaction'
// import {Client} from '../../dist/npm'
// import {TransactionMetadata} from '../../src/models/common/transaction'
const client = new Client('wss://s.altnet.rippletest.net:51233')
// const client = new Client('wss://s.altnet.rippletest.net:51233')
getTransaction()
// getTransaction()
async function getTransaction() {
await client.connect()
const ledger = await client.request({command: 'ledger', transactions: true})
console.log(ledger)
const tx = await client.request({
command: 'tx',
transaction: ledger.result.ledger.transactions[0] as string
})
console.log(tx)
console.log('deliveredAmount:', (tx.result.meta as TransactionMetadata).DeliveredAmount)
process.exit(0)
}
// async function getTransaction() {
// await client.connect()
// const ledger = await client.request({command: 'ledger', transactions: true})
// console.log(ledger)
// const tx = await client.request({
// command: 'tx',
// transaction: ledger.result.ledger.transactions[0] as string
// })
// console.log(tx)
// console.log(
// 'deliveredAmount:',
// (tx.result.meta as TransactionMetadata).DeliveredAmount
// )
// process.exit(0)
// }

View File

@@ -1,18 +1,21 @@
import {Client} from '../../dist/npm'
import { AccountFlags } from '../../dist/npm/common/constants'
// import {Client} from '../../dist/npm'
// import {AccountFlags} from '../../dist/npm/common/constants'
const client = new Client('wss://s.altnet.rippletest.net:51233')
// const client = new Client('wss://s.altnet.rippletest.net:51233')
parseAccountFlags()
// parseAccountFlags()
async function parseAccountFlags() {
await client.connect()
const account_info = await client.request({command: 'account_info', account: 'rKsdkGhyZH6b2Zzd5hNnEqSv2wpznn4n6N'})
const flags = account_info.result.account_data.Flags
for (const flagName in AccountFlags) {
if (flags & AccountFlags[flagName]) {
console.log(`${flagName} enabled`)
}
}
process.exit(0)
}
// async function parseAccountFlags() {
// await client.connect()
// const account_info = await client.request({
// command: 'account_info',
// account: 'rKsdkGhyZH6b2Zzd5hNnEqSv2wpznn4n6N'
// })
// const flags = account_info.result.account_data.Flags
// for (const flagName in AccountFlags) {
// if (flags & AccountFlags[flagName]) {
// console.log(`${flagName} enabled`)
// }
// }
// process.exit(0)
// }

View File

@@ -1,47 +1,53 @@
import {Client} from '../../dist/npm'
// import {Client} from '../../dist/npm'
const client = new Client(
// 'wss://s.altnet.rippletest.net:51233'
// 'ws://35.158.96.209:51233'
'ws://34.210.87.206:51233'
)
// const client = new Client(
// // 'wss://s.altnet.rippletest.net:51233'
// // 'ws://35.158.96.209:51233'
// 'ws://34.210.87.206:51233'
// )
sign()
// sign()
async function sign() {
await client.connect()
const pathfind: any = {
source: {
address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
amount: {
currency: 'drops',
value: '100'
}
},
destination: {
address: 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj',
amount: {
currency: 'USD',
counterparty: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc'
}
}
}
// async function sign() {
// await client.connect()
// const pathfind: any = {
// source: {
// address: 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
// amount: {
// currency: 'drops',
// value: '100'
// }
// },
// destination: {
// address: 'rKT4JX4cCof6LcDYRz8o3rGRu7qxzZ2Zwj',
// amount: {
// currency: 'USD',
// counterparty: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc'
// }
// }
// }
await client.getPaths(pathfind).then(async (data) => {
console.log('paths:', JSON.stringify(data))
const fakeSecret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
// await client
// .getPaths(pathfind)
// .then(async (data) => {
// console.log('paths:', JSON.stringify(data))
// const fakeSecret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
pathfind.paths = data[0].paths
pathfind.destination = data[0].destination
await client.preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', pathfind).then(ret => {
const signed = client.sign(ret.txJSON, fakeSecret)
console.log('signed:', signed)
}).catch(err => {
console.log('ERR 1:', JSON.stringify(err))
})
}).catch(err => {
console.log('ERR 2:', err)
})
// pathfind.paths = data[0].paths
// pathfind.destination = data[0].destination
// await client
// .preparePayment('r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', pathfind)
// .then((ret) => {
// const signed = client.sign(ret.txJSON, fakeSecret)
// console.log('signed:', signed)
// })
// .catch((err) => {
// console.log('ERR 1:', JSON.stringify(err))
// })
// })
// .catch((err) => {
// console.log('ERR 2:', err)
// })
client.disconnect()
}
// client.disconnect()
// }

View File

@@ -1,201 +1,215 @@
import {
Client,
AccountInfoResponse,
LedgerClosedEvent
} from '../../dist/npm'
import https = require('https')
// import https = require('https')
/**
* When implementing Reliable Transaction Submission, there are many potential solutions, each with different trade-offs. The main decision points are:
* 1) Transaction preparation:
* - How do we decide which account sequence and LastLedgerSequence numbers to use?
* (To prevent unintentional duplicate transactions, an {account, account_sequence} pair can be used as a transaction's idempotency key)
* - How do we decide how much to pay for the transaction fee? (If our transactions have been failing due to low fee, we should consider increasing this value)
* 2) Transaction status retrieval. Options include:
* - Poll for transaction status:
* - On a regular interval (e.g. every 3-5 seconds), or
* - When a new validated ledger is detected
* + (To accommodate an edge case in transaction retrieval, check the sending account's Sequence number to confirm that it has the expected value;
* alternatively, wait until a few additional ledgers have been validated before deciding that a transaction has definitively not been included in a validated ledger)
* - Listen for transaction status: scan all validated transactions to see if our transactions are among them
* 3) What do we do when a transaction fails? It is possible to implement retry logic, but caution is advised. Note that there are a few ways for a transaction to fail:
* A) `tec`: The transaction was included in a ledger but only claimed the transaction fee
* B) `tesSUCCESS` but unexpected result: The transaction was successful but did not have the expected result. This generally does not occur for XRP-to-XRP payments
* C) The transaction was not, and never will be, included in a validated ledger [3C]
*
* References:
* - https://xrpl.org/reliable-transaction-submission.html
* - https://xrpl.org/send-xrp.html
* - https://xrpl.org/look-up-transaction-results.html
* - https://xrpl.org/get-started-with-rippleapi-for-javascript.html
* - https://xrpl.org/monitor-incoming-payments-with-websocket.html
*
* For the implementation in this example, we have made the following decisions:
* 1) The script will choose the account sequence and LastLedgerSequence numbers automatically. We allow ripple-lib to choose the fee.
* Payments are defined upfront, and idempotency is not needed. If the script is run a second time, duplicate payments will result.
* 2) We will listen for notification that a new validated ledger has been found, and poll for transaction status at that time.
* Futhermore, as a precaution, we will wait until the server is 3 ledgers past the transaction's LastLedgerSequence
* (with the transaction nowhere to be seen) before deciding that it has definitively failed per [3C]
* 3) Transactions will not be automatically retried. Transactions are limited to XRP-to-XRP payments and cannot "succeed" in an unexpected way.
*/
reliableTransactionSubmissionExample()
// import {Client, AccountInfoResponse, LedgerClosedEvent} from '../../dist/npm'
async function reliableTransactionSubmissionExample() {
/**
* Array of payments to execute.
*
* For brevity, these are XRP-to-XRP payments, taking a source, destination, and an amount in drops.
*
* The script will attempt to make all of these payments as quickly as possible, and report the final status of each. Transactions that fail are NOT retried.
*/
const payments = []
// /**
// * When implementing Reliable Transaction Submission, there are many potential solutions, each with different trade-offs. The main decision points are:
// * 1) Transaction preparation:
// * - How do we decide which account sequence and LastLedgerSequence numbers to use?
// * (To prevent unintentional duplicate transactions, an {account, account_sequence} pair can be used as a transaction's idempotency key)
// * - How do we decide how much to pay for the transaction fee? (If our transactions have been failing due to low fee, we should consider increasing this value)
// * 2) Transaction status retrieval. Options include:
// * - Poll for transaction status:
// * - On a regular interval (e.g. Every 3-5 seconds), or
// * - When a new validated ledger is detected
// * + (To accommodate an edge case in transaction retrieval, check the sending account's Sequence number to confirm that it has the expected value;
// * alternatively, wait until a few additional ledgers have been validated before deciding that a transaction has definitively not been included in a validated ledger)
// * - Listen for transaction status: scan all validated transactions to see if our transactions are among them
// * 3) What do we do when a transaction fails? It is possible to implement retry logic, but caution is advised. Note that there are a few ways for a transaction to fail:
// * A) `tec`: The transaction was included in a ledger but only claimed the transaction fee
// * B) `tesSUCCESS` but unexpected result: The transaction was successful but did not have the expected result. This generally does not occur for XRP-to-XRP payments
// * C) The transaction was not, and never will be, included in a validated ledger [3C].
// *
// * References:
// * - https://xrpl.org/reliable-transaction-submission.html
// * - https://xrpl.org/send-xrp.html
// * - https://xrpl.org/look-up-transaction-results.html
// * - https://xrpl.org/get-started-with-rippleapi-for-javascript.html
// * - https://xrpl.org/monitor-incoming-payments-with-websocket.html.
// *
// * For the implementation in this example, we have made the following decisions:
// * 1) The script will choose the account sequence and LastLedgerSequence numbers automatically. We allow ripple-lib to choose the fee.
// * Payments are defined upfront, and idempotency is not needed. If the script is run a second time, duplicate payments will result.
// * 2) We will listen for notification that a new validated ledger has been found, and poll for transaction status at that time.
// * Futhermore, as a precaution, we will wait until the server is 3 ledgers past the transaction's LastLedgerSequence
// * (with the transaction nowhere to be seen) before deciding that it has definitively failed per [3C]
// * 3) Transactions will not be automatically retried. Transactions are limited to XRP-to-XRP payments and cannot "succeed" in an unexpected way.
// */
// reliableTransactionSubmissionExample()
const sourceAccount = (await generateTestnetAccount()).account
console.log(`Generated new Testnet account: ${sourceAccount.classicAddress}/${sourceAccount.secret}`)
// Send amounts from 1 drop to 10 drops
for (let i = 1; i <= 10; i++) {
payments.push({
source: sourceAccount,
destination: 'rhsoCozhUxwcyQgzFi1FVRoMVQgk7cZd4L', // Random Testnet destination
amount_drops: i.toString(),
})
}
const results = await performPayments(payments)
console.log(JSON.stringify(results, null, 2))
process.exit(0)
}
// async function reliableTransactionSubmissionExample() {
// /**
// * Array of payments to execute.
// *
// * For brevity, these are XRP-to-XRP payments, taking a source, destination, and an amount in drops.
// *
// * The script will attempt to make all of these payments as quickly as possible, and report the final status of each. Transactions that fail are NOT retried.
// */
// const payments = []
async function performPayments(payments) {
const finalResults = []
const txFinalizedPromises = []
const client = new Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
// const sourceAccount = (await generateTestnetAccount()).account
// console.log(
// `Generated new Testnet account: ${sourceAccount.classicAddress}/${sourceAccount.secret}`
// )
// // Send amounts from 1 drop to 10 drops
// for (let i = 1; i <= 10; i++) {
// payments.push({
// source: sourceAccount,
// destination: 'rhsoCozhUxwcyQgzFi1FVRoMVQgk7cZd4L', // Random Testnet destination
// amount_drops: i.toString()
// })
// }
// const results = await performPayments(payments)
// console.log(JSON.stringify(results, null, 2))
// process.exit(0)
// }
for (let i = 0; i < payments.length; i++) {
const payment = payments[i]
const account_info: AccountInfoResponse = await client.request({
command: 'account_info',
account: payment.source.classicAddress,
ledger_index: 'current'})
const sequence = account_info.result.account_data.Sequence
const preparedPayment = await client.preparePayment(payment.source.classicAddress, {
source: {
address: payment.source.classicAddress,
amount: {
value: payment.amount_drops,
currency: 'drops'
}
},
destination: {
address: payment.destination,
minAmount: {
value: payment.amount_drops,
currency: 'drops'
}
}
}, {
sequence
})
const signed = client.sign(preparedPayment.txJSON, payment.source.secret)
finalResults.push({
id: signed.id
})
const response = await client.request({command: 'submit', tx_blob: signed.signedTransaction})
// async function performPayments(payments) {
// const finalResults = []
// const txFinalizedPromises = []
// const client = new Client('wss://s.altnet.rippletest.net:51233')
// await client.connect()
// Most of the time we'll get 'tesSUCCESS' or (after many submissions) 'terQUEUED'
console.log(`tx ${i} - tentative: ${response.result.engine_result}`)
// for (let i = 0; i < payments.length; i++) {
// const payment = payments[i]
// const account_info: AccountInfoResponse = await client.request({
// command: 'account_info',
// account: payment.source.classicAddress,
// ledger_index: 'current'
// })
// const sequence = account_info.result.account_data.Sequence
// const preparedPayment = await client.preparePayment(
// payment.source.classicAddress,
// {
// source: {
// address: payment.source.classicAddress,
// amount: {
// value: payment.amount_drops,
// currency: 'drops'
// }
// },
// destination: {
// address: payment.destination,
// minAmount: {
// value: payment.amount_drops,
// currency: 'drops'
// }
// }
// },
// {
// sequence
// }
// )
// const signed = client.sign(preparedPayment.txJSON, payment.source.secret)
// finalResults.push({
// id: signed.id
// })
// const response = await client.request({
// command: 'submit',
// tx_blob: signed.signedTransaction
// })
const txFinalizedPromise = new Promise<void>((resolve) => {
const ledgerClosedCallback = async (event: LedgerClosedEvent) => {
let status
try {
status = await client.request({command: 'tx', transaction: signed.id})
} catch (e) {
// Typical error when the tx hasn't been validated yet:
if (e.name !== 'MissingLedgerHistoryError') {
console.log(e)
}
// // Most of the time we'll get 'tesSUCCESS' or (after many submissions) 'terQUEUED'
// console.log(`tx ${i} - tentative: ${response.result.engine_result}`)
if (event.ledger_index > preparedPayment.instructions.maxLedgerVersion + 3) {
// Assumptions:
// - We are still connected to the same rippled server
// - No ledger gaps occurred
// - All ledgers between the time we submitted the tx and now have been checked for the tx
status = {
finalResult: 'Transaction was not, and never will be, included in a validated ledger'
}
} else {
// Check again later:
client.connection.once('ledgerClosed', ledgerClosedCallback)
return
}
}
// const txFinalizedPromise = new Promise<void>((resolve) => {
// const ledgerClosedCallback = async (event: LedgerClosedEvent) => {
// let status
// try {
// status = await client.request({command: 'tx', transaction: signed.id})
// } catch (e) {
// // Typical error when the tx hasn't been validated yet:
// if (e.name !== 'MissingLedgerHistoryError') {
// console.log(e)
// }
for (let j = 0; j < finalResults.length; j++) {
if (finalResults[j].id === signed.id) {
finalResults[j].result = status.address ? {
source: status.address,
destination: status.specification.destination.address,
deliveredAmount: status.outcome.deliveredAmount,
result: status.outcome.result,
timestamp: status.outcome.timestamp,
ledgerVersion: status.outcome.ledgerVersion
} : status
process.stdout.write('.')
return resolve()
}
}
}
client.connection.once('ledgerClosed', ledgerClosedCallback)
})
txFinalizedPromises.push(txFinalizedPromise)
}
await Promise.all(txFinalizedPromises)
return finalResults
}
// if (
// event.ledger_index >
// preparedPayment.instructions.maxLedgerVersion + 3
// ) {
// // Assumptions:
// // - We are still connected to the same rippled server
// // - No ledger gaps occurred
// // - All ledgers between the time we submitted the tx and now have been checked for the tx
// status = {
// finalResult:
// 'Transaction was not, and never will be, included in a validated ledger'
// }
// } else {
// // Check again later:
// client.connection.once('ledgerClosed', ledgerClosedCallback)
// return
// }
// }
/**
* Generate a new Testnet account by requesting one from the faucet
*/
async function generateTestnetAccount(): Promise<{
account: {
xAddress: string,
classicAddress, string,
secret: string
},
balance: number
}> {
const options = {
hostname: 'faucet.altnet.rippletest.net',
port: 443,
path: '/accounts',
method: 'POST'
}
return new Promise((resolve, reject) => {
const request = https.request(options, response => {
const chunks = []
response.on('data', d => {
chunks.push(d)
})
response.on('end', () => {
const body = Buffer.concat(chunks).toString()
// for (let j = 0; j < finalResults.length; j++) {
// if (finalResults[j].id === signed.id) {
// finalResults[j].result = status.address
// ? {
// source: status.address,
// destination: status.specification.destination.address,
// deliveredAmount: status.outcome.deliveredAmount,
// result: status.outcome.result,
// timestamp: status.outcome.timestamp,
// ledgerVersion: status.outcome.ledgerVersion
// }
// : status
// process.stdout.write('.')
// return resolve()
// }
// }
// }
// client.connection.once('ledgerClosed', ledgerClosedCallback)
// })
// txFinalizedPromises.push(txFinalizedPromise)
// }
// await Promise.all(txFinalizedPromises)
// return finalResults
// }
// "application/json; charset=utf-8"
if (response.headers['content-type'].startsWith('application/json')) {
resolve(JSON.parse(body))
} else {
reject({
statusCode: response.statusCode,
contentType: response.headers['content-type'],
body
})
}
})
})
request.on('error', error => {
console.error(error)
reject(error)
})
request.end()
})
}
// /**
// * Generate a new Testnet account by requesting one from the faucet.
// */
// async function generateTestnetAccount(): Promise<{
// account: {
// xAddress: string
// classicAddress
// string
// secret: string
// }
// balance: number
// }> {
// const options = {
// hostname: 'faucet.altnet.rippletest.net',
// port: 443,
// path: '/accounts',
// method: 'POST'
// }
// return new Promise((resolve, reject) => {
// const request = https.request(options, (response) => {
// const chunks = []
// response.on('data', (d) => {
// chunks.push(d)
// })
// response.on('end', () => {
// const body = Buffer.concat(chunks).toString()
// // "application/json; charset=utf-8"
// if (response.headers['content-type'].startsWith('application/json')) {
// resolve(JSON.parse(body))
// } else {
// reject({
// statusCode: response.statusCode,
// contentType: response.headers['content-type'],
// body
// })
// }
// })
// })
// request.on('error', (error) => {
// console.error(error)
// reject(error)
// })
// request.end()
// })
// }

View File

@@ -1,12 +1,8 @@
{
"extends": "../tsconfig-base",
"extends": "../tsconfig.build.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"references": [
{ "path": "../src" }
],
"include": [
"./src/**/*.ts"
]

View File

@@ -1,12 +1,13 @@
import {fromSeed} from 'bip32'
import {mnemonicToSeedSync} from 'bip39'
import {decode, encodeForSigning} from 'ripple-binary-codec'
import {deriveKeypair, generateSeed, verify} from 'ripple-keypairs'
import ECDSA from './common/ecdsa'
import {SignedTransaction} from './common/types/objects'
import {signOffline} from './transaction/sign'
import {SignOptions} from './transaction/types'
import {ValidationError} from './common/errors'
import { fromSeed } from "bip32";
import { mnemonicToSeedSync } from "bip39";
import { decode, encodeForSigning } from "ripple-binary-codec";
import { deriveKeypair, generateSeed, verify } from "ripple-keypairs";
import ECDSA from "./common/ecdsa";
import { ValidationError } from "./common/errors";
import { SignedTransaction } from "./common/types/objects";
import { signOffline } from "./transaction/sign";
import { SignOptions } from "./transaction/types";
/**
* A utility for deriving a wallet composed of a keypair (publicKey/privateKey).
@@ -14,53 +15,61 @@ import {ValidationError} from './common/errors'
* It provides functionality to sign/verify transactions offline.
*/
class Wallet {
readonly publicKey: string
readonly privateKey: string
private static readonly defaultAlgorithm: ECDSA = ECDSA.ed25519
private static readonly defaultDerivationPath: string = "m/44'/144'/0'/0/0"
readonly publicKey: string;
readonly privateKey: string;
private static readonly defaultAlgorithm: ECDSA = ECDSA.ed25519;
private static readonly defaultDerivationPath: string = "m/44'/144'/0'/0/0";
constructor(publicKey: string, privateKey: string) {
this.publicKey = publicKey
this.privateKey = privateKey
this.publicKey = publicKey;
this.privateKey = privateKey;
}
/**
* Derives a wallet from a seed.
* @param {string} seed A string used to generate a keypair (publicKey/privateKey) to derive a wallet.
* @param {ECDSA} algorithm The digital signature algorithm to generate an address for.
* @returns {Wallet} A Wallet derived from a seed.
*
* @param seed - A string used to generate a keypair (publicKey/privateKey) to derive a wallet.
* @param algorithm - The digital signature algorithm to generate an address for.
* @returns A Wallet derived from a seed.
*/
static fromSeed(seed: string, algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet {
return Wallet.deriveWallet(seed, algorithm)
static fromSeed(
seed: string,
algorithm: ECDSA = Wallet.defaultAlgorithm
): Wallet {
return Wallet.deriveWallet(seed, algorithm);
}
/**
* Derives a wallet from a mnemonic.
* @param {string} mnemonic A string consisting of words (whitespace delimited) used to derive a wallet.
* @param {string} derivationPath The path to derive a keypair (publicKey/privateKey) from a seed (that was converted from a mnemonic).
* @returns {Wallet} A Wallet derived from a mnemonic.
*
* @param mnemonic - A string consisting of words (whitespace delimited) used to derive a wallet.
* @param derivationPath - The path to derive a keypair (publicKey/privateKey) from a seed (that was converted from a mnemonic).
* @returns A Wallet derived from a mnemonic.
*/
static fromMnemonic(
mnemonic: string,
derivationPath: string = Wallet.defaultDerivationPath
): Wallet {
const seed = mnemonicToSeedSync(mnemonic)
const masterNode = fromSeed(seed)
const node = masterNode.derivePath(derivationPath)
const seed = mnemonicToSeedSync(mnemonic);
const masterNode = fromSeed(seed);
const node = masterNode.derivePath(derivationPath);
if (node.privateKey === undefined) {
throw new ValidationError('Unable to derive privateKey from mnemonic input')
throw new ValidationError(
"Unable to derive privateKey from mnemonic input"
);
}
const publicKey = Wallet.hexFromBuffer(node.publicKey)
const privateKey = Wallet.hexFromBuffer(node.privateKey)
return new Wallet(publicKey, `00${privateKey}`)
const publicKey = Wallet.hexFromBuffer(node.publicKey);
const privateKey = Wallet.hexFromBuffer(node.privateKey);
return new Wallet(publicKey, `00${privateKey}`);
}
/**
* Derives a wallet from an entropy (array of random numbers).
* @param {Uint8Array | number[]} entropy An array of random numbers to generate a seed used to derive a wallet.
* @param {ECDSA} algorithm The digital signature algorithm to generate an address for.
* @returns {Wallet} A Wallet derived from an entropy.
*
* @param entropy - An array of random numbers to generate a seed used to derive a wallet.
* @param algorithm - The digital signature algorithm to generate an address for.
* @returns A Wallet derived from an entropy.
*/
static fromEntropy(
entropy: Uint8Array | number[],
@@ -68,45 +77,50 @@ class Wallet {
): Wallet {
const options = {
entropy: Uint8Array.from(entropy),
algorithm
}
const seed = generateSeed(options)
return Wallet.deriveWallet(seed, algorithm)
algorithm,
};
const seed = generateSeed(options);
return Wallet.deriveWallet(seed, algorithm);
}
private static hexFromBuffer(buffer: Buffer): string {
return buffer.toString('hex').toUpperCase()
return buffer.toString("hex").toUpperCase();
}
private static deriveWallet(seed: string, algorithm: ECDSA = Wallet.defaultAlgorithm): Wallet {
const {publicKey, privateKey} = deriveKeypair(seed, {algorithm})
return new Wallet(publicKey, privateKey)
private static deriveWallet(
seed: string,
algorithm: ECDSA = Wallet.defaultAlgorithm
): Wallet {
const { publicKey, privateKey } = deriveKeypair(seed, { algorithm });
return new Wallet(publicKey, privateKey);
}
/**
* Signs a transaction offline.
* @param {object} transaction A transaction to be signed offline.
* @param {SignOptions} options Options to include for signing.
* @returns {SignedTransaction} A signed transaction.
*
* @param transaction - A transaction to be signed offline.
* @param options - Options to include for signing.
* @returns A signed transaction.
*/
signTransaction(
transaction: any, // TODO: transaction should be typed with Transaction type.
options: SignOptions = {signAs: ''}
options: SignOptions = { signAs: "" }
): SignedTransaction {
return signOffline(this, JSON.stringify(transaction), options)
return signOffline(this, JSON.stringify(transaction), options);
}
/**
* Verifies a signed transaction offline.
* @param {string} signedTransaction A signed transaction (hex string of signTransaction result) to be verified offline.
* @returns {boolean} Returns true if a signedTransaction is valid.
*
* @param signedTransaction - A signed transaction (hex string of signTransaction result) to be verified offline.
* @returns Returns true if a signedTransaction is valid.
*/
verifyTransaction(signedTransaction: string): boolean {
const tx = decode(signedTransaction)
const messageHex: string = encodeForSigning(tx)
const signature = tx.TxnSignature
return verify(messageHex, signature, this.publicKey)
const tx = decode(signedTransaction);
const messageHex: string = encodeForSigning(tx);
const signature = tx.TxnSignature;
return verify(messageHex, signature, this.publicKey);
}
}
export default Wallet
export default Wallet;

View File

@@ -1,44 +1,42 @@
/*
* Original code based on "backo" - https://github.com/segmentio/backo
* MIT License - Copyright 2014 Segment.io
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// Original code based on "backo" - https://github.com/segmentio/backo
// MIT License - Copyright 2014 Segment.io
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* A Back off strategy that increases exponentially. Useful with repeated
* setTimeout calls over a network (where the destination may be down).
*/
export class ExponentialBackoff {
private readonly ms: number
private readonly max: number
private readonly factor: number = 2
private readonly jitter: number = 0
attempts: number = 0
private readonly ms: number;
private readonly max: number;
private readonly factor: number = 2;
private readonly jitter: number = 0;
attempts = 0;
constructor(opts: {min?: number; max?: number} = {}) {
this.ms = opts.min || 100
this.max = opts.max || 10000
constructor(opts: { min?: number; max?: number } = {}) {
this.ms = opts.min || 100;
this.max = opts.max || 10000;
}
/**
* Return the backoff duration.
*/
duration() {
var ms = this.ms * Math.pow(this.factor, this.attempts++)
let ms = this.ms * this.factor ** this.attempts++;
if (this.jitter) {
var rand = Math.random()
var deviation = Math.floor(rand * this.jitter * ms)
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation
const rand = Math.random();
const deviation = Math.floor(rand * this.jitter * ms);
ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;
}
return Math.min(ms, this.max) | 0
return Math.min(ms, this.max) | 0;
}
/**
* Reset the number of attempts.
*/
reset() {
this.attempts = 0
this.attempts = 0;
}
}

View File

@@ -1,62 +1,66 @@
import {Client, ClientOptions} from './'
import { Client, ClientOptions } from ".";
class BroadcastClient extends Client {
ledgerVersion: number | undefined = undefined
private _clients: Client[]
ledgerVersion: number | undefined = undefined;
private readonly _clients: Client[];
constructor(servers, options: ClientOptions = {}) {
super(servers[0], options)
super(servers[0], options);
const clients: Client[] = servers.map(
(server) => new Client(server, options)
)
);
// exposed for testing
this._clients = clients
this._clients = clients;
this.getMethodNames().forEach((name) => {
this[name] = function () {
// eslint-disable-line no-loop-func
return Promise.race(clients.map((client) => client[name](...arguments)))
}
})
return Promise.race(
clients.map((client) => client[name](...arguments))
);
};
});
// connection methods must be overridden to apply to all client instances
this.connect = async function () {
await Promise.all(clients.map((client) => client.connect()))
}
await Promise.all(clients.map((client) => client.connect()));
};
this.disconnect = async function () {
await Promise.all(clients.map((client) => client.disconnect()))
}
await Promise.all(clients.map((client) => client.disconnect()));
};
this.isConnected = function () {
return clients.map((client) => client.isConnected()).every(Boolean)
}
return clients.map((client) => client.isConnected()).every(Boolean);
};
// synchronous methods are all passed directly to the first client instance
const defaultClient = clients[0]
const syncMethods = ['sign']
const defaultClient = clients[0];
const syncMethods = ["sign"];
syncMethods.forEach((name) => {
this[name] = defaultClient[name].bind(defaultClient)
})
this[name] = defaultClient[name].bind(defaultClient);
});
clients.forEach((client) => {
client.on('error', (errorCode, errorMessage, data) =>
this.emit('error', errorCode, errorMessage, data)
)
})
client.on("error", (errorCode, errorMessage, data) =>
this.emit("error", errorCode, errorMessage, data)
);
});
}
getMethodNames() {
const methodNames: string[] = []
const firstClient = this._clients[0]
const methods = Object.getOwnPropertyNames(firstClient)
methods.push(...Object.getOwnPropertyNames(Object.getPrototypeOf(firstClient)))
const methodNames: string[] = [];
const firstClient = this._clients[0];
const methods = Object.getOwnPropertyNames(firstClient);
methods.push(
...Object.getOwnPropertyNames(Object.getPrototypeOf(firstClient))
);
for (const name of methods) {
if (typeof firstClient[name] === 'function' && name !== 'constructor') {
methodNames.push(name)
if (typeof firstClient[name] === "function" && name !== "constructor") {
methodNames.push(name);
}
}
return methodNames
return methodNames;
}
}
export {BroadcastClient}
export { BroadcastClient };

View File

@@ -1,7 +1,9 @@
import _ from 'lodash'
import {EventEmitter} from 'events'
import {parse as parseURL} from 'url'
import WebSocket from 'ws'
import { EventEmitter } from "events";
import { parse as parseURL } from "url";
import _ from "lodash";
import WebSocket from "ws";
import {
RippledError,
DisconnectedError,
@@ -9,25 +11,26 @@ import {
TimeoutError,
ResponseFormatError,
ConnectionError,
RippleError
} from '../common/errors'
import {ExponentialBackoff} from './backoff'
import { Response } from '../models/methods'
RippleError,
} from "../common/errors";
import { Response } from "../models/methods";
import { ExponentialBackoff } from "./backoff";
/**
* ConnectionOptions is the configuration for the Connection class.
*/
export interface ConnectionOptions {
trace?: boolean | ((id: string, message: string) => void)
proxy?: string
proxyAuthorization?: string
authorization?: string
trustedCertificates?: string[]
key?: string
passphrase?: string
certificate?: string
timeout: number // request timeout
connectionTimeout: number
trace?: boolean | ((id: string, message: string) => void);
proxy?: string;
proxyAuthorization?: string;
authorization?: string;
trustedCertificates?: string[];
key?: string;
passphrase?: string;
certificate?: string;
timeout: number; // request timeout
connectionTimeout: number;
}
/**
@@ -35,82 +38,88 @@ export interface ConnectionOptions {
* is optional, so any ConnectionOptions configuration that has a default value is
* still optional at the point that the user provides it.
*/
export type ConnectionUserOptions = Partial<ConnectionOptions>
export type ConnectionUserOptions = Partial<ConnectionOptions>;
/**
* Represents an intentionally triggered web-socket disconnect code.
* WebSocket spec allows 4xxx codes for app/library specific codes.
* See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
**/
const INTENTIONAL_DISCONNECT_CODE = 4000
//
// Represents an intentionally triggered web-socket disconnect code.
// WebSocket spec allows 4xxx codes for app/library specific codes.
// See: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
//
const INTENTIONAL_DISCONNECT_CODE = 4000;
/**
* Create a new websocket given your URL and optional proxy/certificate
* configuration.
*
* @param url
* @param config
*/
function createWebSocket(url: string, config: ConnectionOptions): WebSocket {
const options: WebSocket.ClientOptions = {}
const options: WebSocket.ClientOptions = {};
if (config.proxy != null) {
// TODO: replace deprecated method
const parsedURL = parseURL(url)
const parsedProxyURL = parseURL(config.proxy)
const parsedURL = parseURL(url);
const parsedProxyURL = parseURL(config.proxy);
const proxyOverrides = _.omitBy(
{
secureEndpoint: parsedURL.protocol === 'wss:',
secureProxy: parsedProxyURL.protocol === 'https:',
secureEndpoint: parsedURL.protocol === "wss:",
secureProxy: parsedProxyURL.protocol === "https:",
auth: config.proxyAuthorization,
ca: config.trustedCertificates,
key: config.key,
passphrase: config.passphrase,
cert: config.certificate
cert: config.certificate,
},
(value) => value == null
)
const proxyOptions = {...parsedProxyURL, ...proxyOverrides}
let HttpsProxyAgent
);
const proxyOptions = { ...parsedProxyURL, ...proxyOverrides };
let HttpsProxyAgent;
try {
HttpsProxyAgent = require('https-proxy-agent')
HttpsProxyAgent = require("https-proxy-agent");
} catch (error) {
throw new Error('"proxy" option is not supported in the browser')
throw new Error('"proxy" option is not supported in the browser');
}
options.agent = new HttpsProxyAgent(proxyOptions)
options.agent = new HttpsProxyAgent(proxyOptions);
}
if (config.authorization != null) {
const base64 = Buffer.from(config.authorization).toString('base64')
options.headers = {Authorization: `Basic ${base64}`}
const base64 = Buffer.from(config.authorization).toString("base64");
options.headers = { Authorization: `Basic ${base64}` };
}
const optionsOverrides = _.omitBy(
{
ca: config.trustedCertificates,
key: config.key,
passphrase: config.passphrase,
cert: config.certificate
cert: config.certificate,
},
(value) => value == null
)
const websocketOptions = {...options, ...optionsOverrides}
const websocket = new WebSocket(url, null, websocketOptions)
);
const websocketOptions = { ...options, ...optionsOverrides };
const websocket = new WebSocket(url, websocketOptions);
// we will have a listener for each outstanding request,
// so we have to raise the limit (the default is 10)
if (typeof websocket.setMaxListeners === 'function') {
websocket.setMaxListeners(Infinity)
if (typeof websocket.setMaxListeners === "function") {
websocket.setMaxListeners(Infinity);
}
return websocket
return websocket;
}
/**
* ws.send(), but promisified.
* Ws.send(), but promisified.
*
* @param ws
* @param message
*/
function websocketSendAsync(ws: WebSocket, message: string) {
return new Promise<void>((resolve, reject) => {
ws.send(message, undefined, (error) => {
ws.send(message, (error) => {
if (error) {
reject(new DisconnectedError(error.message, error))
reject(new DisconnectedError(error.message, error));
} else {
resolve()
resolve();
}
})
})
});
});
}
/**
@@ -119,25 +128,25 @@ function websocketSendAsync(ws: WebSocket, message: string) {
* after-the-fact.
*/
class ConnectionManager {
private promisesAwaitingConnection: {
resolve: Function
reject: Function
}[] = []
private promisesAwaitingConnection: Array<{
resolve: Function;
reject: Function;
}> = [];
resolveAllAwaiting() {
this.promisesAwaitingConnection.map(({resolve}) => resolve())
this.promisesAwaitingConnection = []
this.promisesAwaitingConnection.map(({ resolve }) => resolve());
this.promisesAwaitingConnection = [];
}
rejectAllAwaiting(error: Error) {
this.promisesAwaitingConnection.map(({reject}) => reject(error))
this.promisesAwaitingConnection = []
this.promisesAwaitingConnection.map(({ reject }) => reject(error));
this.promisesAwaitingConnection = [];
}
awaitConnection(): Promise<void> {
return new Promise((resolve, reject) => {
this.promisesAwaitingConnection.push({resolve, reject})
})
this.promisesAwaitingConnection.push({ resolve, reject });
});
}
}
@@ -148,231 +157,247 @@ class ConnectionManager {
* original request.
*/
class RequestManager {
private nextId = 0
private promisesAwaitingResponse: {
resolve: Function
reject: Function
timer: NodeJS.Timeout
}[] = []
private nextId = 0;
private promisesAwaitingResponse: Array<{
resolve: Function;
reject: Function;
timer: NodeJS.Timeout;
}> = [];
cancel(id: number) {
const {timer} = this.promisesAwaitingResponse[id]
clearTimeout(timer)
delete this.promisesAwaitingResponse[id]
const { timer } = this.promisesAwaitingResponse[id];
clearTimeout(timer);
delete this.promisesAwaitingResponse[id];
}
resolve(id: string | number, data: Response) {
const {timer, resolve} = this.promisesAwaitingResponse[id]
clearTimeout(timer)
resolve(data)
delete this.promisesAwaitingResponse[id]
const { timer, resolve } = this.promisesAwaitingResponse[id];
clearTimeout(timer);
resolve(data);
delete this.promisesAwaitingResponse[id];
}
reject(id: string | number, error: Error) {
const {timer, reject} = this.promisesAwaitingResponse[id]
clearTimeout(timer)
reject(error)
delete this.promisesAwaitingResponse[id]
const { timer, reject } = this.promisesAwaitingResponse[id];
clearTimeout(timer);
reject(error);
delete this.promisesAwaitingResponse[id];
}
rejectAll(error: Error) {
this.promisesAwaitingResponse.forEach((_, id) => {
this.reject(id, error)
})
this.reject(id, error);
});
}
/**
* Creates a new WebSocket request. This sets up a timeout timer to catch
* hung responses, and a promise that will resolve with the response once
* the response is seen & handled.
*
* @param data
* @param timeout
*/
createRequest(data: any, timeout: number): [string | number, string, Promise<any>] {
const newId = data.id ? data.id : this.nextId++
const newData = JSON.stringify({...data, id: newId})
createRequest(
data: any,
timeout: number
): [string | number, string, Promise<any>] {
const newId = data.id ? data.id : this.nextId++;
const newData = JSON.stringify({ ...data, id: newId });
const timer = setTimeout(
() => this.reject(newId, new TimeoutError()),
timeout
)
);
// Node.js won't exit if a timer is still running, so we tell Node to ignore.
// (Node will still wait for the request to complete).
if (timer.unref) {
timer.unref()
timer.unref();
}
const newPromise = new Promise((resolve: (data: Response) => void, reject) => {
this.promisesAwaitingResponse[newId] = {resolve, reject, timer}
})
return [newId, newData, newPromise]
const newPromise = new Promise(
(resolve: (data: Response) => void, reject) => {
this.promisesAwaitingResponse[newId] = { resolve, reject, timer };
}
);
return [newId, newData, newPromise];
}
/**
* Handle a "response". Responses match to the earlier request handlers,
* Handle a "response". Responses match to the earlier request handlers,
* and resolve/reject based on the data received.
*
* @param data
*/
handleResponse(data: Response) {
if (!Number.isInteger(data.id) || data.id < 0) {
throw new ResponseFormatError('valid id not found in response', data)
throw new ResponseFormatError("valid id not found in response", data);
}
if (!this.promisesAwaitingResponse[data.id]) {
return
return;
}
if (data.status === 'error') {
const error = new RippledError(data.error_message || data.error, data)
this.reject(data.id, error)
return
if (data.status === "error") {
const error = new RippledError(data.error_message || data.error, data);
this.reject(data.id, error);
return;
}
if (data.status !== 'success') {
if (data.status !== "success") {
const error = new ResponseFormatError(
`unrecognized status: ${data.status}`,
data
)
this.reject(data.id, error)
return
);
this.reject(data.id, error);
return;
}
this.resolve(data.id, data)
this.resolve(data.id, data);
}
}
/**
* The main Connection class. Responsible for connecting to & managing
* an active WebSocket connection to a XRPL node.
*
* @param errorOrCode
*/
export class Connection extends EventEmitter {
private _url: string
private _ws: null | WebSocket = null
private _reconnectTimeoutID: null | NodeJS.Timeout = null
private _heartbeatIntervalID: null | NodeJS.Timeout = null
private _retryConnectionBackoff = new ExponentialBackoff({
private readonly _url: string | undefined;
private _ws: null | WebSocket = null;
private _reconnectTimeoutID: null | NodeJS.Timeout = null;
private _heartbeatIntervalID: null | NodeJS.Timeout = null;
private readonly _retryConnectionBackoff = new ExponentialBackoff({
min: 100,
max: 60 * 1000
})
max: 60 * 1000,
});
private _trace: (id: string, message: string) => void = () => {}
private _config: ConnectionOptions
private _requestManager = new RequestManager()
private _connectionManager = new ConnectionManager()
private readonly _trace: (id: string, message: string) => void = () => {};
private readonly _config: ConnectionOptions;
private readonly _requestManager = new RequestManager();
private readonly _connectionManager = new ConnectionManager();
constructor(url?: string, options: ConnectionUserOptions = {}) {
super()
this.setMaxListeners(Infinity)
this._url = url
super();
this.setMaxListeners(Infinity);
this._url = url;
this._config = {
timeout: 20 * 1000,
connectionTimeout: 5 * 1000,
...options
}
if (typeof options.trace === 'function') {
this._trace = options.trace
} else if (options.trace === true) {
this._trace = console.log
...options,
};
if (typeof options.trace === "function") {
this._trace = options.trace;
} else if (options.trace) {
this._trace = console.log;
}
}
private _onMessage(message) {
this._trace('receive', message)
let data: any
this._trace("receive", message);
let data: any;
try {
data = JSON.parse(message)
data = JSON.parse(message);
} catch (error) {
this.emit('error', 'badMessage', error.message, message)
return
this.emit("error", "badMessage", error.message, message);
return;
}
if (data.type == null && data.error) {
this.emit('error', data.error, data.error_message, data) // e.g. slowDown
return
this.emit("error", data.error, data.error_message, data); // e.g. slowDown
return;
}
if (data.type) {
this.emit(data.type, data)
this.emit(data.type, data);
}
if (data.type === 'response') {
if (data.type === "response") {
try {
this._requestManager.handleResponse(data)
this._requestManager.handleResponse(data);
} catch (error) {
this.emit('error', 'badMessage', error.message, message)
this.emit("error", "badMessage", error.message, message);
}
}
}
private get _state() {
return this._ws ? this._ws.readyState : WebSocket.CLOSED
return this._ws ? this._ws.readyState : WebSocket.CLOSED;
}
private get _shouldBeConnected() {
return this._ws !== null
return this._ws !== null;
}
private _clearHeartbeatInterval = () => {
clearInterval(this._heartbeatIntervalID)
}
private readonly _clearHeartbeatInterval = () => {
if (this._heartbeatIntervalID) {
clearInterval(this._heartbeatIntervalID);
}
};
private _startHeartbeatInterval = () => {
this._clearHeartbeatInterval()
private readonly _startHeartbeatInterval = () => {
this._clearHeartbeatInterval();
this._heartbeatIntervalID = setInterval(
() => this._heartbeat(),
this._config.timeout
)
}
);
};
/**
* A heartbeat is just a "ping" command, sent on an interval.
* 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(() => {
private readonly _heartbeat = () => {
return this.request({ command: "ping" }).catch(() => {
return this.reconnect().catch((error) => {
this.emit('error', 'reconnect', error.message, error)
})
})
}
this.emit("error", "reconnect", error.message, error);
});
});
};
private _onConnectionFailed = (errorOrCode: Error | number | null) => {
private readonly _onConnectionFailed = (
errorOrCode: Error | number | null
) => {
if (this._ws) {
this._ws.removeAllListeners()
this._ws.on('error', () => {
this._ws.removeAllListeners();
this._ws.on("error", () => {
// Correctly listen for -- but ignore -- any future errors: If you
// don't have a listener on "error" node would log a warning on error.
})
this._ws.close()
this._ws = null
});
this._ws.close();
this._ws = null;
}
if (typeof errorOrCode === 'number') {
if (typeof errorOrCode === "number") {
this._connectionManager.rejectAllAwaiting(
new NotConnectedError(`Connection failed with code ${errorOrCode}.`, {
code: errorOrCode
code: errorOrCode,
})
)
);
} else if (errorOrCode && errorOrCode.message) {
this._connectionManager.rejectAllAwaiting(
new NotConnectedError(errorOrCode.message, errorOrCode)
)
);
} else {
this._connectionManager.rejectAllAwaiting(
new NotConnectedError('Connection failed.')
)
new NotConnectedError("Connection failed.")
);
}
}
};
isConnected() {
return this._state === WebSocket.OPEN
return this._state === WebSocket.OPEN;
}
connect(): Promise<void> {
if (this.isConnected()) {
return Promise.resolve()
return Promise.resolve();
}
if (this._state === WebSocket.CONNECTING) {
return this._connectionManager.awaitConnection()
return this._connectionManager.awaitConnection();
}
if (!this._url) {
return Promise.reject(
new ConnectionError('Cannot connect because no server was specified')
)
new ConnectionError("Cannot connect because no server was specified")
);
}
if (this._ws) {
return Promise.reject(
new RippleError('Websocket connection never cleaned up.', {
state: this._state
new RippleError("Websocket connection never cleaned up.", {
state: this._state,
})
)
);
}
// Create the connection timeout, in case the connection hangs longer than expected.
@@ -383,58 +408,71 @@ export class Connection extends EventEmitter {
`If your internet connection is working, the rippled server may be blocked or inaccessible. ` +
`You can also try setting the 'connectionTimeout' option in the Client constructor.`
)
)
}, this._config.connectionTimeout)
);
}, this._config.connectionTimeout);
// Connection listeners: these stay attached only until a connection is done/open.
this._ws = createWebSocket(this._url, this._config)
this._ws.on('error', this._onConnectionFailed)
this._ws.on('error', () => clearTimeout(connectionTimeoutID))
this._ws.on('close', this._onConnectionFailed)
this._ws.on('close', () => clearTimeout(connectionTimeoutID))
this._ws.once('open', async () => {
this._ws = createWebSocket(this._url, this._config);
if (this._ws == null) {
throw new Error("Connect: created null websocket");
}
this._ws.on("error", this._onConnectionFailed);
this._ws.on("error", () => clearTimeout(connectionTimeoutID));
this._ws.on("close", this._onConnectionFailed);
this._ws.on("close", () => clearTimeout(connectionTimeoutID));
this._ws.once("open", async () => {
if (this._ws == null) {
throw new Error("onceOpen: ws is null");
}
// Once the connection completes successfully, remove all old listeners
this._ws.removeAllListeners()
clearTimeout(connectionTimeoutID)
this._ws.removeAllListeners();
clearTimeout(connectionTimeoutID);
// Add new, long-term connected listeners for messages and errors
this._ws.on('message', (message: string) => this._onMessage(message))
this._ws.on('error', (error) =>
this.emit('error', 'websocket', error.message, error)
)
this._ws.on("message", (message: string) => this._onMessage(message));
this._ws.on("error", (error) =>
this.emit("error", "websocket", error.message, error)
);
// Handle a closed connection: reconnect if it was unexpected
this._ws.once('close', (code, reason) => {
this._clearHeartbeatInterval()
this._ws.once("close", (code, reason) => {
if (this._ws == null) {
throw new Error("onceClose: ws is null");
}
this._clearHeartbeatInterval();
this._requestManager.rejectAll(
new DisconnectedError(`websocket was closed, ${reason}`)
)
this._ws.removeAllListeners()
this._ws = null
this.emit('disconnected', code)
);
this._ws.removeAllListeners();
this._ws = null;
this.emit("disconnected", code);
// If this wasn't a manual disconnect, then lets reconnect ASAP.
if (code !== INTENTIONAL_DISCONNECT_CODE) {
const retryTimeout = this._retryConnectionBackoff.duration()
this._trace('reconnect', `Retrying connection in ${retryTimeout}ms.`)
this.emit('reconnecting', this._retryConnectionBackoff.attempts)
const retryTimeout = this._retryConnectionBackoff.duration();
this._trace("reconnect", `Retrying connection in ${retryTimeout}ms.`);
this.emit("reconnecting", this._retryConnectionBackoff.attempts);
// Start the reconnect timeout, but set it to `this._reconnectTimeoutID`
// so that we can cancel one in-progress on disconnect.
this._reconnectTimeoutID = setTimeout(() => {
this.reconnect().catch((error) => {
this.emit('error', 'reconnect', error.message, error)
})
}, retryTimeout)
this.emit("error", "reconnect", error.message, error);
});
}, retryTimeout);
}
})
});
// Finalize the connection and resolve all awaiting connect() requests
try {
this._retryConnectionBackoff.reset()
this._startHeartbeatInterval()
this._connectionManager.resolveAllAwaiting()
this.emit('connected')
this._retryConnectionBackoff.reset();
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.
this._connectionManager.rejectAllAwaiting(error);
await this.disconnect().catch(() => {}); // Ignore this error, propagate the root cause.
}
})
return this._connectionManager.awaitConnection()
});
return this._connectionManager.awaitConnection();
}
/**
@@ -445,20 +483,30 @@ 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
if (this._state === WebSocket.CLOSED || !this._ws) {
return Promise.resolve(undefined)
if (this._reconnectTimeoutID !== null) {
clearTimeout(this._reconnectTimeoutID);
this._reconnectTimeoutID = null;
}
if (this._state === WebSocket.CLOSED) {
return Promise.resolve(undefined);
}
if (this._ws === null) {
return Promise.resolve(undefined);
}
return new Promise((resolve) => {
this._ws.once('close', (code) => resolve(code))
if (this._ws === null) {
return Promise.resolve(undefined);
}
this._ws.once("close", (code) => resolve(code));
// Connection already has a disconnect handler for the disconnect logic.
// Just close the websocket manually (with our "intentional" code) to
// trigger that.
if (this._state !== WebSocket.CLOSING) {
this._ws.close(INTENTIONAL_DISCONNECT_CODE)
if (this._ws != null && this._state !== WebSocket.CLOSING) {
this._ws.close(INTENTIONAL_DISCONNECT_CODE);
}
})
});
}
/**
@@ -468,33 +516,36 @@ export class Connection extends EventEmitter {
// NOTE: We currently have a "reconnecting" event, but that only triggers
// through an unexpected connection retry logic.
// See: https://github.com/ripple/ripple-lib/pull/1101#issuecomment-565360423
this.emit('reconnect')
await this.disconnect()
await this.connect()
this.emit("reconnect");
await this.disconnect();
await this.connect();
}
async request<T extends {command: string}>(request: T, timeout?: number): Promise<any> {
if (!this._shouldBeConnected) {
throw new NotConnectedError()
async request<T extends { command: string }>(
request: T,
timeout?: number
): Promise<any> {
if (!this._shouldBeConnected || this._ws == null) {
throw new NotConnectedError();
}
const [id, message, responsePromise] = this._requestManager.createRequest(
request,
timeout || this._config.timeout
)
this._trace('send', message)
);
this._trace("send", message);
websocketSendAsync(this._ws, message).catch((error) => {
this._requestManager.reject(id, error)
})
this._requestManager.reject(id, error);
});
return responsePromise
return responsePromise;
}
/**
* Get the Websocket connection URL
* Get the Websocket connection URL.
*
* @returns The Websocket connection URL
* @returns The Websocket connection URL.
*/
getUrl(): string {
return this._url
return this._url ?? "";
}
}

View File

@@ -1,32 +1,31 @@
import {EventEmitter} from 'events'
import { EventEmitter } from "events";
import {
constants,
errors,
txFlags,
} from '../common'
import { Connection, ConnectionUserOptions } from './connection'
import getTrustlines from '../ledger/trustlines'
import getBalances from '../ledger/balances'
import getPaths from '../ledger/pathfind'
import {getOrderbook, formatBidsAndAsks} from '../ledger/orderbook'
import preparePayment from '../transaction/payment'
import prepareTrustline from '../transaction/trustline'
import prepareOrder from '../transaction/order'
import prepareOrderCancellation from '../transaction/ordercancellation'
import prepareEscrowCreation from '../transaction/escrow-creation'
import prepareEscrowExecution from '../transaction/escrow-execution'
import prepareEscrowCancellation from '../transaction/escrow-cancellation'
import preparePaymentChannelCreate from '../transaction/payment-channel-create'
import preparePaymentChannelFund from '../transaction/payment-channel-fund'
import preparePaymentChannelClaim from '../transaction/payment-channel-claim'
import prepareCheckCreate from '../transaction/check-create'
import prepareCheckCancel from '../transaction/check-cancel'
import prepareCheckCash from '../transaction/check-cash'
import prepareSettings from '../transaction/settings'
import prepareTicketCreate from '../transaction/ticket'
import {sign} from '../transaction/sign'
import combine from '../transaction/combine'
import {deriveAddress, deriveXAddress} from '../utils/derive'
classicAddressToXAddress,
xAddressToClassicAddress,
isValidXAddress,
isValidClassicAddress,
encodeSeed,
decodeSeed,
encodeAccountID,
decodeAccountID,
encodeNodePublic,
decodeNodePublic,
encodeAccountPublic,
decodeAccountPublic,
encodeXAddress,
decodeXAddress,
} from "ripple-address-codec";
import { constants, errors, txFlags, ensureClassicAddress } from "../common";
import { ValidationError } from "../common/errors";
import { getFee } from "../common/fee";
import * as schemaValidator from "../common/schema-validator";
import getBalances from "../ledger/balances";
import { getOrderbook, formatBidsAndAsks } from "../ledger/orderbook";
import getPaths from "../ledger/pathfind";
import getTrustlines from "../ledger/trustlines";
import { clamp } from "../ledger/utils";
import {
Request,
Response,
@@ -94,162 +93,172 @@ import {
PingRequest,
PingResponse,
RandomRequest,
RandomResponse
} from '../models/methods'
RandomResponse,
} from "../models/methods";
import prepareCheckCancel from "../transaction/check-cancel";
import prepareCheckCash from "../transaction/check-cash";
import prepareCheckCreate from "../transaction/check-create";
import combine from "../transaction/combine";
import prepareEscrowCancellation from "../transaction/escrow-cancellation";
import prepareEscrowCreation from "../transaction/escrow-creation";
import prepareEscrowExecution from "../transaction/escrow-execution";
import prepareOrder from "../transaction/order";
import prepareOrderCancellation from "../transaction/ordercancellation";
import preparePayment from "../transaction/payment";
import preparePaymentChannelClaim from "../transaction/payment-channel-claim";
import preparePaymentChannelCreate from "../transaction/payment-channel-create";
import preparePaymentChannelFund from "../transaction/payment-channel-fund";
import prepareSettings from "../transaction/settings";
import { sign } from "../transaction/sign";
import prepareTicketCreate from "../transaction/ticket";
import prepareTrustline from "../transaction/trustline";
import { TransactionJSON, Instructions, Prepare } from "../transaction/types";
import * as transactionUtils from "../transaction/utils";
import { deriveAddress, deriveXAddress } from "../utils/derive";
import generateFaucetWallet from "../wallet/wallet-generation";
import * as transactionUtils from '../transaction/utils'
import * as schemaValidator from '../common/schema-validator'
import {getFee} from '../common/fee'
import {ensureClassicAddress} from '../common'
import {clamp} from '../ledger/utils'
import {TransactionJSON, Instructions, Prepare} from '../transaction/types'
import {
classicAddressToXAddress,
xAddressToClassicAddress,
isValidXAddress,
isValidClassicAddress,
encodeSeed,
decodeSeed,
encodeAccountID,
decodeAccountID,
encodeNodePublic,
decodeNodePublic,
encodeAccountPublic,
decodeAccountPublic,
encodeXAddress,
decodeXAddress
} from 'ripple-address-codec'
import generateFaucetWallet from '../wallet/wallet-generation'
import { ValidationError } from '../common/errors'
import { Connection, ConnectionUserOptions } from "./connection";
export interface ClientOptions extends ConnectionUserOptions {
feeCushion?: number
maxFeeXRP?: string
proxy?: string
timeout?: number
feeCushion?: number;
maxFeeXRP?: string;
proxy?: string;
timeout?: number;
}
/**
* Get the response key / property name that contains the listed data for a
* command. This varies from command to command, but we need to know it to
* properly count across many requests.
*
* @param command
*/
function getCollectKeyFromCommand(command: string): string | null {
switch (command) {
case 'account_channels':
return 'channels'
case 'account_lines':
return 'lines'
case 'account_objects':
return 'account_objects'
case 'account_tx':
return 'transactions'
case 'account_offers':
case 'book_offers':
return 'offers'
case 'ledger_data':
return 'state'
case "account_channels":
return "channels";
case "account_lines":
return "lines";
case "account_objects":
return "account_objects";
case "account_tx":
return "transactions";
case "account_offers":
case "book_offers":
return "offers";
case "ledger_data":
return "state";
default:
return null
return null;
}
}
type MarkerRequest = AccountChannelsRequest
| AccountLinesRequest
| AccountObjectsRequest
| AccountOffersRequest
| AccountTxRequest
| LedgerDataRequest
type MarkerRequest =
| AccountChannelsRequest
| AccountLinesRequest
| AccountObjectsRequest
| AccountOffersRequest
| AccountTxRequest
| LedgerDataRequest;
type MarkerResponse = AccountChannelsResponse
| AccountLinesResponse
| AccountObjectsResponse
| AccountOffersResponse
| AccountTxResponse
| LedgerDataResponse
type MarkerResponse =
| AccountChannelsResponse
| AccountLinesResponse
| AccountObjectsResponse
| AccountOffersResponse
| AccountTxResponse
| LedgerDataResponse;
class Client extends EventEmitter {
// Factor to multiply estimated fee by to provide a cushion in case the
// required fee rises during submission of a transaction. Defaults to 1.2.
_feeCushion: number
_feeCushion: number;
// Maximum fee to use with transactions, in XRP. Must be a string-encoded
// number. Defaults to '2'.
_maxFeeXRP: string
_maxFeeXRP: string;
// New in > 0.21.0
// non-validated ledger versions are allowed, and passed to rippled as-is.
connection: Connection
connection: Connection;
constructor(server: string, options: ClientOptions = {}) {
super()
if (typeof server !== 'string' || !server.match("^(wss?|wss?\\+unix)://")) {
throw new ValidationError("server URI must start with `wss://`, `ws://`, `wss+unix://`, or `ws+unix://`.")
super();
if (typeof server !== "string" || !server.match("^(wss?|wss?\\+unix)://")) {
throw new ValidationError(
"server URI must start with `wss://`, `ws://`, `wss+unix://`, or `ws+unix://`."
);
}
this._feeCushion = options.feeCushion || 1.2
this._maxFeeXRP = options.maxFeeXRP || '2'
this._feeCushion = options.feeCushion || 1.2;
this._maxFeeXRP = options.maxFeeXRP || "2";
this.connection = new Connection(server, options)
this.connection = new Connection(server, options);
this.connection.on('error', (errorCode, errorMessage, data) => {
this.emit('error', errorCode, errorMessage, data)
})
this.connection.on("error", (errorCode, errorMessage, data) => {
this.emit("error", errorCode, errorMessage, data);
});
this.connection.on('connected', () => {
this.emit('connected')
})
this.connection.on('disconnected', (code) => {
let finalCode = code
this.connection.on("connected", () => {
this.emit("connected");
});
this.connection.on("disconnected", (code) => {
let finalCode = code;
// 4000: Connection uses a 4000 code internally to indicate a manual disconnect/close
// Since 4000 is a normal disconnect reason, we convert this to the standard exit code 1000
if (finalCode === 4000) {
finalCode = 1000
finalCode = 1000;
}
this.emit('disconnected', finalCode)
})
this.emit("disconnected", finalCode);
});
}
/**
* Makes a request to the client with the given command and
* additional request body parameters.
*/
public request(r: AccountChannelsRequest): Promise<AccountChannelsResponse>
public request(r: AccountCurrenciesRequest): Promise<AccountCurrenciesResponse>
public request(r: AccountInfoRequest): Promise<AccountInfoResponse>
public request(r: AccountLinesRequest): Promise<AccountLinesResponse>
public request(r: AccountObjectsRequest): Promise<AccountObjectsResponse>
public request(r: AccountOffersRequest): Promise<AccountOffersResponse>
public request(r: AccountTxRequest): Promise<AccountTxResponse>
public request(r: BookOffersRequest): Promise<BookOffersResponse>
public request(r: ChannelVerifyRequest): Promise<ChannelVerifyResponse>
public request(r: DepositAuthorizedRequest): Promise<DepositAuthorizedResponse>
public request(r: FeeRequest): Promise<FeeResponse>
public request(r: GatewayBalancesRequest): Promise<GatewayBalancesResponse>
public request(r: LedgerRequest): Promise<LedgerResponse>
public request(r: LedgerClosedRequest): Promise<LedgerClosedResponse>
public request(r: LedgerCurrentRequest): Promise<LedgerCurrentResponse>
public request(r: LedgerDataRequest): Promise<LedgerDataResponse>
public request(r: LedgerEntryRequest): Promise<LedgerEntryResponse>
public request(r: ManifestRequest): Promise<ManifestResponse>
public request(r: NoRippleCheckRequest): Promise<NoRippleCheckResponse>
public request(r: PathFindRequest): Promise<PathFindResponse>
public request(r: PingRequest): Promise<PingResponse>
public request(r: RandomRequest): Promise<RandomResponse>
public request(r: RipplePathFindRequest): Promise<RipplePathFindResponse>
public request(r: ServerInfoRequest): Promise<ServerInfoResponse>
public request(r: ServerStateRequest): Promise<ServerStateResponse>
public request(r: SubmitRequest): Promise<SubmitResponse>
public request(r: SubmitMultisignedRequest): Promise<SubmitMultisignedResponse>
public request(r: TransactionEntryRequest): Promise<TransactionEntryResponse>
public request(r: TxRequest): Promise<TxResponse>
public request(r: AccountChannelsRequest): Promise<AccountChannelsResponse>;
public request(
r: AccountCurrenciesRequest
): Promise<AccountCurrenciesResponse>;
public request(r: AccountInfoRequest): Promise<AccountInfoResponse>;
public request(r: AccountLinesRequest): Promise<AccountLinesResponse>;
public request(r: AccountObjectsRequest): Promise<AccountObjectsResponse>;
public request(r: AccountOffersRequest): Promise<AccountOffersResponse>;
public request(r: AccountTxRequest): Promise<AccountTxResponse>;
public request(r: BookOffersRequest): Promise<BookOffersResponse>;
public request(r: ChannelVerifyRequest): Promise<ChannelVerifyResponse>;
public request(
r: DepositAuthorizedRequest
): Promise<DepositAuthorizedResponse>;
public request(r: FeeRequest): Promise<FeeResponse>;
public request(r: GatewayBalancesRequest): Promise<GatewayBalancesResponse>;
public request(r: LedgerRequest): Promise<LedgerResponse>;
public request(r: LedgerClosedRequest): Promise<LedgerClosedResponse>;
public request(r: LedgerCurrentRequest): Promise<LedgerCurrentResponse>;
public request(r: LedgerDataRequest): Promise<LedgerDataResponse>;
public request(r: LedgerEntryRequest): Promise<LedgerEntryResponse>;
public request(r: ManifestRequest): Promise<ManifestResponse>;
public request(r: NoRippleCheckRequest): Promise<NoRippleCheckResponse>;
public request(r: PathFindRequest): Promise<PathFindResponse>;
public request(r: PingRequest): Promise<PingResponse>;
public request(r: RandomRequest): Promise<RandomResponse>;
public request(r: RipplePathFindRequest): Promise<RipplePathFindResponse>;
public request(r: ServerInfoRequest): Promise<ServerInfoResponse>;
public request(r: ServerStateRequest): Promise<ServerStateResponse>;
public request(r: SubmitRequest): Promise<SubmitResponse>;
public request(
r: SubmitMultisignedRequest
): Promise<SubmitMultisignedResponse>;
public request(r: TransactionEntryRequest): Promise<TransactionEntryResponse>;
public request(r: TxRequest): Promise<TxResponse>;
public request<R extends Request, T extends Response>(r: R): Promise<T> {
// TODO: should this be typed with `extends BaseRequest/BaseResponse`?
return this.connection.request({
...r,
// @ts-ignore
// @ts-expect-error
account: r.account ? ensureClassicAddress(r.account) : undefined,
})
});
}
/**
@@ -258,38 +267,64 @@ class Client extends EventEmitter {
* When there are more results than contained in the response, the response
* includes a `marker` field.
*
* See https://ripple.com/build/rippled-apis/#markers-and-pagination
* See https://ripple.com/build/rippled-apis/#markers-and-pagination.
*
* @param response
*/
hasNextPage(response: MarkerResponse): boolean {
return !!response.result.marker
return Boolean(response.result.marker);
}
async requestNextPage(req: AccountChannelsRequest, resp: AccountChannelsResponse): Promise<AccountChannelsResponse>
async requestNextPage(req: AccountLinesRequest, resp: AccountLinesResponse): Promise<AccountLinesResponse>
async requestNextPage(req: AccountObjectsRequest, resp: AccountObjectsResponse): Promise<AccountObjectsResponse>
async requestNextPage(req: AccountOffersRequest, resp: AccountOffersResponse): Promise<AccountOffersResponse>
async requestNextPage(req: AccountTxRequest, resp: AccountTxResponse): Promise<AccountTxResponse>
async requestNextPage(req: LedgerDataRequest, resp: LedgerDataResponse): Promise<LedgerDataResponse>
async requestNextPage<T extends MarkerRequest, U extends MarkerResponse>(req: T, resp: U): Promise<U> {
async requestNextPage(
req: AccountChannelsRequest,
resp: AccountChannelsResponse
): Promise<AccountChannelsResponse>;
async requestNextPage(
req: AccountLinesRequest,
resp: AccountLinesResponse
): Promise<AccountLinesResponse>;
async requestNextPage(
req: AccountObjectsRequest,
resp: AccountObjectsResponse
): Promise<AccountObjectsResponse>;
async requestNextPage(
req: AccountOffersRequest,
resp: AccountOffersResponse
): Promise<AccountOffersResponse>;
async requestNextPage(
req: AccountTxRequest,
resp: AccountTxResponse
): Promise<AccountTxResponse>;
async requestNextPage(
req: LedgerDataRequest,
resp: LedgerDataResponse
): Promise<LedgerDataResponse>;
async requestNextPage<T extends MarkerRequest, U extends MarkerResponse>(
req: T,
resp: U
): Promise<U> {
if (!resp.result.marker) {
return Promise.reject(
new errors.NotFoundError('response does not have a next page')
)
new errors.NotFoundError("response does not have a next page")
);
}
const nextPageRequest = {...req, marker: resp.result.marker}
return this.connection.request(nextPageRequest)
const nextPageRequest = { ...req, marker: resp.result.marker };
return this.connection.request(nextPageRequest);
}
/**
* Prepare a transaction.
*
* You can later submit the transaction with a `submit` request.
*
* @param txJSON
* @param instructions
*/
async prepareTransaction(
txJSON: TransactionJSON,
instructions: Instructions = {}
): Promise<Prepare> {
return transactionUtils.prepareTransaction(txJSON, this, instructions)
return transactionUtils.prepareTransaction(txJSON, this, instructions);
}
/**
@@ -297,15 +332,15 @@ class Client extends EventEmitter {
*
* This can be used to generate `MemoData`, `MemoType`, and `MemoFormat`.
*
* @param string string to convert to hex
* @param string - String to convert to hex.
*/
convertStringToHex(string: string): string {
return transactionUtils.convertStringToHex(string)
return transactionUtils.convertStringToHex(string);
}
/**
* Makes multiple paged requests to the client to return a given number of
* resources. requestAll() will make multiple requests until the `limit`
* resources. Multiple paged requests will be made until the `limit`
* number of resources is reached (if no `limit` is provided, a single request
* will be made).
*
@@ -316,129 +351,136 @@ class Client extends EventEmitter {
* general use. Instead, use rippled's built-in pagination and make multiple
* requests as needed.
*/
async requestAll(req: AccountChannelsRequest): Promise<AccountChannelsResponse[]>
async requestAll(req: AccountLinesRequest): Promise<AccountLinesResponse[]>
async requestAll(req: AccountObjectsRequest): Promise<AccountObjectsResponse[]>
async requestAll(req: AccountOffersRequest): Promise<AccountOffersResponse[]>
async requestAll(req: AccountTxRequest): Promise<AccountTxResponse[]>
async requestAll(req: BookOffersRequest): Promise<BookOffersResponse[]>
async requestAll(req: LedgerDataRequest): Promise<LedgerDataResponse[]>
async requestAll<T extends MarkerRequest, U extends MarkerResponse>(request: T, options: {collect?: string} = {}): Promise<U[]> {
async requestAll(
req: AccountChannelsRequest
): Promise<AccountChannelsResponse[]>;
async requestAll(req: AccountLinesRequest): Promise<AccountLinesResponse[]>;
async requestAll(
req: AccountObjectsRequest
): Promise<AccountObjectsResponse[]>;
async requestAll(req: AccountOffersRequest): Promise<AccountOffersResponse[]>;
async requestAll(req: AccountTxRequest): Promise<AccountTxResponse[]>;
async requestAll(req: BookOffersRequest): Promise<BookOffersResponse[]>;
async requestAll(req: LedgerDataRequest): Promise<LedgerDataResponse[]>;
async requestAll<T extends MarkerRequest, U extends MarkerResponse>(
request: T,
options: { collect?: string } = {}
): Promise<U[]> {
// The data under collection is keyed based on the command. Fail if command
// not recognized and collection key not provided.
const collectKey = options.collect || getCollectKeyFromCommand(request.command)
const collectKey =
options.collect || getCollectKeyFromCommand(request.command);
if (!collectKey) {
throw new errors.ValidationError(`no collect key for command ${request.command}`)
throw new errors.ValidationError(
`no collect key for command ${request.command}`
);
}
// If limit is not provided, fetches all data over multiple requests.
// NOTE: This may return much more than needed. Set limit when possible.
const countTo: number = request.limit != null ? request.limit : Infinity
let count: number = 0
let marker: string = request.marker
let lastBatchLength: number
const results = []
const countTo: number = request.limit != null ? request.limit : Infinity;
let count = 0;
let marker: string = request.marker;
let lastBatchLength: number;
const results: any[] = [];
do {
const countRemaining = clamp(countTo - count, 10, 400)
const countRemaining = clamp(countTo - count, 10, 400);
const repeatProps = {
...request,
limit: countRemaining,
marker
}
const singleResponse = await this.connection.request(repeatProps)
const singleResult = singleResponse.result
const collectedData = singleResult[collectKey]
marker = singleResult['marker']
results.push(singleResponse)
marker,
};
const singleResponse = await this.connection.request(repeatProps);
const singleResult = singleResponse.result;
const collectedData = singleResult[collectKey];
marker = singleResult.marker;
results.push(singleResponse);
// Make sure we handle when no data (not even an empty array) is returned.
const isExpectedFormat = Array.isArray(collectedData)
const isExpectedFormat = Array.isArray(collectedData);
if (isExpectedFormat) {
count += collectedData.length
lastBatchLength = collectedData.length
count += collectedData.length;
lastBatchLength = collectedData.length;
} else {
lastBatchLength = 0
lastBatchLength = 0;
}
} while (!!marker && count < countTo && lastBatchLength !== 0)
return results
} while (Boolean(marker) && count < countTo && lastBatchLength !== 0);
return results;
}
isConnected(): boolean {
return this.connection.isConnected()
return this.connection.isConnected();
}
async connect(): Promise<void> {
return this.connection.connect()
return this.connection.connect();
}
async disconnect(): Promise<void> {
// backwards compatibility: connection.disconnect() can return a number, but
// this method returns nothing. SO we await but don't return any result.
await this.connection.disconnect()
await this.connection.disconnect();
}
getFee = getFee
getFee = getFee;
getTrustlines = getTrustlines
getBalances = getBalances
getPaths = getPaths
getOrderbook = getOrderbook
getTrustlines = getTrustlines;
getBalances = getBalances;
getPaths = getPaths;
getOrderbook = getOrderbook;
preparePayment = preparePayment
prepareTrustline = prepareTrustline
prepareOrder = prepareOrder
prepareOrderCancellation = prepareOrderCancellation
prepareEscrowCreation = prepareEscrowCreation
prepareEscrowExecution = prepareEscrowExecution
prepareEscrowCancellation = prepareEscrowCancellation
preparePaymentChannelCreate = preparePaymentChannelCreate
preparePaymentChannelFund = preparePaymentChannelFund
preparePaymentChannelClaim = preparePaymentChannelClaim
prepareCheckCreate = prepareCheckCreate
prepareCheckCash = prepareCheckCash
prepareCheckCancel = prepareCheckCancel
prepareTicketCreate = prepareTicketCreate
prepareSettings = prepareSettings
sign = sign
combine = combine
preparePayment = preparePayment;
prepareTrustline = prepareTrustline;
prepareOrder = prepareOrder;
prepareOrderCancellation = prepareOrderCancellation;
prepareEscrowCreation = prepareEscrowCreation;
prepareEscrowExecution = prepareEscrowExecution;
prepareEscrowCancellation = prepareEscrowCancellation;
preparePaymentChannelCreate = preparePaymentChannelCreate;
preparePaymentChannelFund = preparePaymentChannelFund;
preparePaymentChannelClaim = preparePaymentChannelClaim;
prepareCheckCreate = prepareCheckCreate;
prepareCheckCash = prepareCheckCash;
prepareCheckCancel = prepareCheckCancel;
prepareTicketCreate = prepareTicketCreate;
prepareSettings = prepareSettings;
sign = sign;
combine = combine;
generateFaucetWallet = generateFaucetWallet
generateFaucetWallet = generateFaucetWallet;
errors = errors
errors = errors;
static deriveXAddress = deriveXAddress
static deriveXAddress = deriveXAddress;
// Client.deriveClassicAddress (static) is a new name for client.deriveAddress
static deriveClassicAddress = deriveAddress
static deriveClassicAddress = deriveAddress;
static formatBidsAndAsks = formatBidsAndAsks
static formatBidsAndAsks = formatBidsAndAsks;
/**
* Static methods to expose ripple-address-codec methods
* Static methods to expose ripple-address-codec methods.
*/
static classicAddressToXAddress = classicAddressToXAddress
static xAddressToClassicAddress = xAddressToClassicAddress
static isValidXAddress = isValidXAddress
static isValidClassicAddress = isValidClassicAddress
static encodeSeed = encodeSeed
static decodeSeed = decodeSeed
static encodeAccountID = encodeAccountID
static decodeAccountID = decodeAccountID
static encodeNodePublic = encodeNodePublic
static decodeNodePublic = decodeNodePublic
static encodeAccountPublic = encodeAccountPublic
static decodeAccountPublic = decodeAccountPublic
static encodeXAddress = encodeXAddress
static decodeXAddress = decodeXAddress
static classicAddressToXAddress = classicAddressToXAddress;
static xAddressToClassicAddress = xAddressToClassicAddress;
static isValidXAddress = isValidXAddress;
static isValidClassicAddress = isValidClassicAddress;
static encodeSeed = encodeSeed;
static decodeSeed = decodeSeed;
static encodeAccountID = encodeAccountID;
static decodeAccountID = decodeAccountID;
static encodeNodePublic = encodeNodePublic;
static decodeNodePublic = decodeNodePublic;
static encodeAccountPublic = encodeAccountPublic;
static decodeAccountPublic = decodeAccountPublic;
static encodeXAddress = encodeXAddress;
static decodeXAddress = decodeXAddress;
txFlags = txFlags
static txFlags = txFlags
accountSetFlags = constants.AccountSetFlags
static accountSetFlags = constants.AccountSetFlags
txFlags = txFlags;
static txFlags = txFlags;
accountSetFlags = constants.AccountSetFlags;
static accountSetFlags = constants.AccountSetFlags;
isValidAddress = schemaValidator.isValidAddress
isValidSecret = schemaValidator.isValidSecret
isValidAddress = schemaValidator.isValidAddress;
isValidSecret = schemaValidator.isValidSecret;
}
export {
Client,
Connection
}
export { Client, Connection };

View File

@@ -1,63 +1,64 @@
import * as _ from 'lodash'
import * as assert from 'assert'
import * as assert from "assert";
type Interval = [number, number]
import * as _ from "lodash";
type Interval = [number, number];
function mergeIntervals(intervals: Interval[]): Interval[] {
const stack: Interval[] = [[-Infinity, -Infinity]]
const stack: Interval[] = [[-Infinity, -Infinity]];
_.sortBy(intervals, (x) => x[0]).forEach((interval) => {
const lastInterval: Interval = stack.pop()!
const lastInterval: Interval = stack.pop()!;
if (interval[0] <= lastInterval[1] + 1) {
stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])])
stack.push([lastInterval[0], Math.max(interval[1], lastInterval[1])]);
} else {
stack.push(lastInterval)
stack.push(interval)
stack.push(lastInterval);
stack.push(interval);
}
})
return stack.slice(1)
});
return stack.slice(1);
}
class RangeSet {
ranges: Array<[number, number]>
ranges: Array<[number, number]> = [];
constructor() {
this.reset()
this.reset();
}
reset() {
this.ranges = []
this.ranges = [];
}
serialize() {
return this.ranges
.map((range) => range[0].toString() + '-' + range[1].toString())
.join(',')
.map((range) => `${range[0].toString()}-${range[1].toString()}`)
.join(",");
}
addRange(start: number, end: number) {
assert.ok(start <= end, `invalid range ${start} <= ${end}`)
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]))
assert.ok(start <= end, `invalid range ${start} <= ${end}`);
this.ranges = mergeIntervals(this.ranges.concat([[start, end]]));
}
addValue(value: number) {
this.addRange(value, value)
this.addRange(value, value);
}
parseAndAddRanges(rangesString: string) {
const rangeStrings = rangesString.split(',')
const rangeStrings = rangesString.split(",");
rangeStrings.forEach((rangeString) => {
const range = rangeString.split('-').map(Number)
this.addRange(range[0], range.length === 1 ? range[0] : range[1])
})
const range = rangeString.split("-").map(Number);
this.addRange(range[0], range.length === 1 ? range[0] : range[1]);
});
}
containsRange(start: number, end: number) {
return this.ranges.some((range) => range[0] <= start && range[1] >= end)
return this.ranges.some((range) => range[0] <= start && range[1] >= end);
}
containsValue(value: number) {
return this.containsRange(value, value)
return this.containsRange(value, value);
}
}
export default RangeSet
export default RangeSet;

View File

@@ -1,15 +1,15 @@
import {EventEmitter} from 'events'
import { EventEmitter } from "events";
// Define the global WebSocket class found on the native browser
declare class WebSocket {
onclose?: Function
onopen?: Function
onerror?: Function
onmessage?: Function
readyState: number
constructor(url: string)
close()
send(message: string)
onclose?: Function;
onopen?: Function;
onerror?: Function;
onmessage?: Function;
readyState: number;
constructor(url: string);
close();
send(message: string);
}
/**
@@ -17,46 +17,46 @@ declare class WebSocket {
* same, as `ws` package provides.
*/
export default class WSWrapper extends EventEmitter {
private _ws: WebSocket
static CONNECTING = 0
static OPEN = 1
static CLOSING = 2
static CLOSED = 3
private readonly _ws: WebSocket;
static CONNECTING = 0;
static OPEN = 1;
static CLOSING = 2;
static CLOSED = 3;
constructor(url, _protocols: any, _websocketOptions: any) {
super()
this.setMaxListeners(Infinity)
super();
this.setMaxListeners(Infinity);
this._ws = new WebSocket(url)
this._ws = new WebSocket(url);
this._ws.onclose = () => {
this.emit('close')
}
this.emit("close");
};
this._ws.onopen = () => {
this.emit('open')
}
this.emit("open");
};
this._ws.onerror = (error) => {
this.emit('error', error)
}
this.emit("error", error);
};
this._ws.onmessage = (message) => {
this.emit('message', message.data)
}
this.emit("message", message.data);
};
}
close() {
if (this.readyState === 1) {
this._ws.close()
this._ws.close();
}
}
send(message) {
this._ws.send(message)
this._ws.send(message);
}
get readyState() {
return this._ws.readyState
return this._ws.readyState;
}
}

View File

@@ -1,4 +1,4 @@
import {txFlagIndices} from './txflags'
import { txFlagIndices } from "./txflags";
// Ordering from https://developers.ripple.com/accountroot.html
const accountRootFlags = {
@@ -43,8 +43,8 @@ const accountRootFlags = {
// lsfRequireDestTag:
// Require a DestinationTag for incoming payments.
RequireDestTag: 0x00020000
}
RequireDestTag: 0x00020000,
};
const AccountFlags = {
passwordSpent: accountRootFlags.PasswordSpent,
@@ -55,19 +55,19 @@ const AccountFlags = {
disableMasterKey: accountRootFlags.DisableMaster,
noFreeze: accountRootFlags.NoFreeze,
globalFreeze: accountRootFlags.GlobalFreeze,
defaultRipple: accountRootFlags.DefaultRipple
}
defaultRipple: accountRootFlags.DefaultRipple,
};
export interface Settings {
passwordSpent?: boolean
requireDestinationTag?: boolean
requireAuthorization?: boolean
depositAuth?: boolean
disallowIncomingXRP?: boolean
disableMasterKey?: boolean
noFreeze?: boolean
globalFreeze?: boolean
defaultRipple?: boolean
passwordSpent?: boolean;
requireDestinationTag?: boolean;
requireAuthorization?: boolean;
depositAuth?: boolean;
disallowIncomingXRP?: boolean;
disableMasterKey?: boolean;
noFreeze?: boolean;
globalFreeze?: boolean;
defaultRipple?: boolean;
}
const AccountSetFlags = {
@@ -79,21 +79,21 @@ const AccountSetFlags = {
enableTransactionIDTracking: txFlagIndices.AccountSet.asfAccountTxnID,
noFreeze: txFlagIndices.AccountSet.asfNoFreeze,
globalFreeze: txFlagIndices.AccountSet.asfGlobalFreeze,
defaultRipple: txFlagIndices.AccountSet.asfDefaultRipple
}
defaultRipple: txFlagIndices.AccountSet.asfDefaultRipple,
};
const AccountFields = {
EmailHash: {
name: 'emailHash',
encoding: 'hex',
name: "emailHash",
encoding: "hex",
length: 32,
defaults: '00000000000000000000000000000000'
defaults: "00000000000000000000000000000000",
},
WalletLocator: {name: 'walletLocator'},
MessageKey: {name: 'messageKey'},
Domain: {name: 'domain', encoding: 'hex'},
TransferRate: {name: 'transferRate', defaults: 0, shift: 9},
TickSize: {name: 'tickSize', defaults: 0}
}
WalletLocator: { name: "walletLocator" },
MessageKey: { name: "messageKey" },
Domain: { name: "domain", encoding: "hex" },
TransferRate: { name: "transferRate", defaults: 0, shift: 9 },
TickSize: { name: "tickSize", defaults: 0 },
};
export {AccountFields, AccountSetFlags, AccountFlags}
export { AccountFields, AccountSetFlags, AccountFlags };

View File

@@ -1,6 +1,6 @@
enum ECDSA {
ed25519 = 'ed25519',
secp256k1 = 'ecdsa-secp256k1',
ed25519 = "ed25519",
secp256k1 = "ecdsa-secp256k1",
}
export default ECDSA
export default ECDSA;

View File

@@ -1,35 +1,35 @@
import {inspect} from 'util'
import { inspect } from "util";
class RippleError extends Error {
name: string
message: string
data?: any
name: string;
message: string;
data?: any;
constructor(message = '', data?: any) {
super(message)
constructor(message = "", data?: any) {
super(message);
this.name = this.constructor.name
this.message = message
this.data = data
this.name = this.constructor.name;
this.message = message;
this.data = data;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
Error.captureStackTrace(this, this.constructor);
}
}
toString() {
let result = '[' + this.name + '(' + this.message
let result = `[${this.name}(${this.message}`;
if (this.data) {
result += ', ' + inspect(this.data)
result += `, ${inspect(this.data)}`;
}
result += ')]'
return result
result += ")]";
return result;
}
/* console.log in node uses util.inspect on object, and util.inspect allows
us to customize its output:
https://nodejs.org/api/util.html#util_custom_inspect_function_on_objects */
// console.log in node uses util.inspect on object, and util.inspect allows
// us to customize its output:
// https://nodejs.org/api/util.html#util_custom_inspect_function_on_objects
inspect() {
return this.toString()
return this.toString();
}
}
@@ -56,14 +56,14 @@ class ValidationError extends RippleError {}
class XRPLFaucetError extends RippleError {}
class NotFoundError extends RippleError {
constructor(message = 'Not found') {
super(message)
constructor(message = "Not found") {
super(message);
}
}
class MissingLedgerHistoryError extends RippleError {
constructor(message?: string) {
super(message || 'Server is missing ledger history in the specified range')
super(message || "Server is missing ledger history in the specified range");
}
}
@@ -72,8 +72,8 @@ class PendingLedgerVersionError extends RippleError {
super(
message ||
"maxLedgerVersion is greater than server's most recent" +
' validated ledger'
)
" validated ledger"
);
}
}
@@ -92,5 +92,5 @@ export {
PendingLedgerVersionError,
MissingLedgerHistoryError,
LedgerVersionError,
XRPLFaucetError
}
XRPLFaucetError,
};

View File

@@ -1,29 +1,38 @@
import _ from 'lodash'
import BigNumber from 'bignumber.js'
import {Client} from '..'
import BigNumber from "bignumber.js";
import _ from "lodash";
import { Client } from "..";
// This is a public API that can be called directly.
// This is not used by the `prepare*` methods. See `src/transaction/utils.ts`
async function getFee(this: Client, cushion?: number): Promise<string> {
if (cushion == null) {
cushion = this._feeCushion
cushion = this._feeCushion;
}
if (cushion == null) {
cushion = 1.2
cushion = 1.2;
}
const serverInfo = (await this.request({command: "server_info"})).result.info
const baseFeeXrp = new BigNumber(serverInfo.validated_ledger.base_fee_xrp)
const serverInfo = (await this.request({ command: "server_info" })).result
.info;
const baseFee = serverInfo.validated_ledger?.base_fee_xrp;
if (baseFee == null) {
throw new Error("getFee: Could not get base_fee_xrp from server_info");
}
const baseFeeXrp = new BigNumber(baseFee);
if (serverInfo.load_factor == null) {
// https://github.com/ripple/rippled/issues/3812#issuecomment-816871100
serverInfo.load_factor = 1
serverInfo.load_factor = 1;
}
let fee = baseFeeXrp.times(serverInfo.load_factor).times(cushion)
let fee = baseFeeXrp.times(serverInfo.load_factor).times(cushion);
// Cap fee to `this._maxFeeXRP`
fee = BigNumber.min(fee, this._maxFeeXRP)
fee = BigNumber.min(fee, this._maxFeeXRP);
// Round fee to 6 decimal places
return new BigNumber(fee.toFixed(6)).toString(10)
return new BigNumber(fee.toFixed(6)).toString(10);
}
export {getFee}
export { getFee };

View File

@@ -1,27 +1,30 @@
import * as constants from './constants'
import * as errors from './errors'
import * as validate from './validate'
import {xAddressToClassicAddress, isValidXAddress} from 'ripple-address-codec'
import {
xAddressToClassicAddress,
isValidXAddress,
} from "ripple-address-codec";
import * as constants from "./constants";
import * as errors from "./errors";
import * as validate from "./validate";
export function ensureClassicAddress(account: string): string {
if (isValidXAddress(account)) {
const {classicAddress, tag} = xAddressToClassicAddress(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.'
)
"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
return classicAddress;
}
return account;
}
export {constants, errors, validate}
export {txFlags} from './txflags'
export { constants, errors, validate };
export { txFlags } from "./txflags";

View File

@@ -1,134 +1,140 @@
import _ from 'lodash'
import * as assert from 'assert'
const {Validator} = require('jsonschema')
import {ValidationError} from './errors'
import {isValidClassicAddress, isValidXAddress} from 'ripple-address-codec'
import {isValidSecret} from '../utils'
import * as assert from "assert";
import _ from "lodash";
import { isValidClassicAddress, isValidXAddress } from "ripple-address-codec";
import { isValidSecret } from "../utils";
import { ValidationError } from "./errors";
const { Validator } = require("jsonschema");
function loadSchemas() {
// listed explicitly for webpack (instead of scanning schemas directory)
const schemas = [
require('./schemas/objects/tx-json.json'),
require('./schemas/objects/transaction-type.json'),
require('./schemas/objects/hash128.json'),
require('./schemas/objects/hash256.json'),
require('./schemas/objects/sequence.json'),
require('./schemas/objects/ticket-sequence.json'),
require('./schemas/objects/signature.json'),
require('./schemas/objects/issue.json'),
require('./schemas/objects/ledger-version.json'),
require('./schemas/objects/max-adjustment.json'),
require('./schemas/objects/memo.json'),
require('./schemas/objects/memos.json'),
require('./schemas/objects/public-key.json'),
require('./schemas/objects/private-key.json'),
require('./schemas/objects/uint32.json'),
require('./schemas/objects/value.json'),
require('./schemas/objects/source-adjustment.json'),
require('./schemas/objects/destination-adjustment.json'),
require('./schemas/objects/tag.json'),
require('./schemas/objects/lax-amount.json'),
require('./schemas/objects/lax-lax-amount.json'),
require('./schemas/objects/min-adjustment.json'),
require('./schemas/objects/source-exact-adjustment.json'),
require('./schemas/objects/destination-exact-adjustment.json'),
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'),
require('./schemas/objects/amountbase.json'),
require('./schemas/objects/balance.json'),
require('./schemas/objects/blob.json'),
require('./schemas/objects/currency.json'),
require('./schemas/objects/signed-value.json'),
require('./schemas/objects/orderbook.json'),
require('./schemas/objects/instructions.json'),
require('./schemas/objects/settings-plus-memos.json'),
require('./schemas/specifications/settings.json'),
require('./schemas/specifications/payment.json'),
require('./schemas/specifications/get-payment.json'),
require('./schemas/specifications/escrow-cancellation.json'),
require('./schemas/specifications/order-cancellation.json'),
require('./schemas/specifications/order.json'),
require('./schemas/specifications/escrow-execution.json'),
require('./schemas/specifications/escrow-creation.json'),
require('./schemas/specifications/payment-channel-create.json'),
require('./schemas/specifications/payment-channel-fund.json'),
require('./schemas/specifications/payment-channel-claim.json'),
require('./schemas/specifications/check-create.json'),
require('./schemas/specifications/check-cash.json'),
require('./schemas/specifications/check-cancel.json'),
require('./schemas/specifications/trustline.json'),
require('./schemas/specifications/deposit-preauth.json'),
require('./schemas/specifications/account-delete.json'),
require('./schemas/output/sign.json'),
require('./schemas/output/submit.json'),
require('./schemas/output/get-account-info.json'),
require('./schemas/output/get-account-objects.json'),
require('./schemas/output/get-balances.json'),
require('./schemas/output/get-balance-sheet.json'),
require('./schemas/output/get-ledger.json'),
require('./schemas/output/get-orderbook.json'),
require('./schemas/output/get-orders.json'),
require('./schemas/output/order-change.json'),
require('./schemas/output/get-payment-channel.json'),
require('./schemas/output/prepare.json'),
require('./schemas/output/ledger-event.json'),
require('./schemas/output/get-paths.json'),
require('./schemas/output/get-server-info.json'),
require('./schemas/output/get-settings.json'),
require('./schemas/output/orderbook-orders.json'),
require('./schemas/output/outcome.json'),
require('./schemas/output/get-transaction.json'),
require('./schemas/output/get-transactions.json'),
require('./schemas/output/get-trustlines.json'),
require('./schemas/output/sign-payment-channel-claim.json'),
require('./schemas/output/verify-payment-channel-claim.json'),
require('./schemas/input/get-balances.json'),
require('./schemas/input/get-balance-sheet.json'),
require('./schemas/input/get-ledger.json'),
require('./schemas/input/get-orders.json'),
require('./schemas/input/get-orderbook.json'),
require('./schemas/input/get-paths.json'),
require('./schemas/input/get-payment-channel.json'),
require('./schemas/input/api-options.json'),
require('./schemas/input/get-settings.json'),
require('./schemas/input/get-account-info.json'),
require('./schemas/input/get-account-objects.json'),
require('./schemas/input/get-transaction.json'),
require('./schemas/input/get-transactions.json'),
require('./schemas/input/get-trustlines.json'),
require('./schemas/input/prepare-payment.json'),
require('./schemas/input/prepare-order.json'),
require('./schemas/input/prepare-trustline.json'),
require('./schemas/input/prepare-order-cancellation.json'),
require('./schemas/input/prepare-settings.json'),
require('./schemas/input/prepare-escrow-creation.json'),
require('./schemas/input/prepare-escrow-cancellation.json'),
require('./schemas/input/prepare-escrow-execution.json'),
require('./schemas/input/prepare-payment-channel-create.json'),
require('./schemas/input/prepare-payment-channel-fund.json'),
require('./schemas/input/prepare-payment-channel-claim.json'),
require('./schemas/input/prepare-check-create.json'),
require('./schemas/input/prepare-check-cash.json'),
require('./schemas/input/prepare-check-cancel.json'),
require('./schemas/input/prepare-ticket-create.json'),
require('./schemas/input/compute-ledger-hash.json'),
require('./schemas/input/sign.json'),
require('./schemas/input/submit.json'),
require('./schemas/input/generate-address.json'),
require('./schemas/input/sign-payment-channel-claim.json'),
require('./schemas/input/verify-payment-channel-claim.json'),
require('./schemas/input/combine.json')
]
const titles = schemas.map((schema) => schema.title)
const duplicates = Object.keys(_.pickBy(_.countBy(titles), (count) => count > 1))
assert.ok(duplicates.length === 0, 'Duplicate schemas for: ' + duplicates)
const validator = new Validator()
require("./schemas/objects/tx-json.json"),
require("./schemas/objects/transaction-type.json"),
require("./schemas/objects/hash128.json"),
require("./schemas/objects/hash256.json"),
require("./schemas/objects/sequence.json"),
require("./schemas/objects/ticket-sequence.json"),
require("./schemas/objects/signature.json"),
require("./schemas/objects/issue.json"),
require("./schemas/objects/ledger-version.json"),
require("./schemas/objects/max-adjustment.json"),
require("./schemas/objects/memo.json"),
require("./schemas/objects/memos.json"),
require("./schemas/objects/public-key.json"),
require("./schemas/objects/private-key.json"),
require("./schemas/objects/uint32.json"),
require("./schemas/objects/value.json"),
require("./schemas/objects/source-adjustment.json"),
require("./schemas/objects/destination-adjustment.json"),
require("./schemas/objects/tag.json"),
require("./schemas/objects/lax-amount.json"),
require("./schemas/objects/lax-lax-amount.json"),
require("./schemas/objects/min-adjustment.json"),
require("./schemas/objects/source-exact-adjustment.json"),
require("./schemas/objects/destination-exact-adjustment.json"),
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"),
require("./schemas/objects/amountbase.json"),
require("./schemas/objects/balance.json"),
require("./schemas/objects/blob.json"),
require("./schemas/objects/currency.json"),
require("./schemas/objects/signed-value.json"),
require("./schemas/objects/orderbook.json"),
require("./schemas/objects/instructions.json"),
require("./schemas/objects/settings-plus-memos.json"),
require("./schemas/specifications/settings.json"),
require("./schemas/specifications/payment.json"),
require("./schemas/specifications/get-payment.json"),
require("./schemas/specifications/escrow-cancellation.json"),
require("./schemas/specifications/order-cancellation.json"),
require("./schemas/specifications/order.json"),
require("./schemas/specifications/escrow-execution.json"),
require("./schemas/specifications/escrow-creation.json"),
require("./schemas/specifications/payment-channel-create.json"),
require("./schemas/specifications/payment-channel-fund.json"),
require("./schemas/specifications/payment-channel-claim.json"),
require("./schemas/specifications/check-create.json"),
require("./schemas/specifications/check-cash.json"),
require("./schemas/specifications/check-cancel.json"),
require("./schemas/specifications/trustline.json"),
require("./schemas/specifications/deposit-preauth.json"),
require("./schemas/specifications/account-delete.json"),
require("./schemas/output/sign.json"),
require("./schemas/output/submit.json"),
require("./schemas/output/get-account-info.json"),
require("./schemas/output/get-account-objects.json"),
require("./schemas/output/get-balances.json"),
require("./schemas/output/get-balance-sheet.json"),
require("./schemas/output/get-ledger.json"),
require("./schemas/output/get-orderbook.json"),
require("./schemas/output/get-orders.json"),
require("./schemas/output/order-change.json"),
require("./schemas/output/get-payment-channel.json"),
require("./schemas/output/prepare.json"),
require("./schemas/output/ledger-event.json"),
require("./schemas/output/get-paths.json"),
require("./schemas/output/get-server-info.json"),
require("./schemas/output/get-settings.json"),
require("./schemas/output/orderbook-orders.json"),
require("./schemas/output/outcome.json"),
require("./schemas/output/get-transaction.json"),
require("./schemas/output/get-transactions.json"),
require("./schemas/output/get-trustlines.json"),
require("./schemas/output/sign-payment-channel-claim.json"),
require("./schemas/output/verify-payment-channel-claim.json"),
require("./schemas/input/get-balances.json"),
require("./schemas/input/get-balance-sheet.json"),
require("./schemas/input/get-ledger.json"),
require("./schemas/input/get-orders.json"),
require("./schemas/input/get-orderbook.json"),
require("./schemas/input/get-paths.json"),
require("./schemas/input/get-payment-channel.json"),
require("./schemas/input/api-options.json"),
require("./schemas/input/get-settings.json"),
require("./schemas/input/get-account-info.json"),
require("./schemas/input/get-account-objects.json"),
require("./schemas/input/get-transaction.json"),
require("./schemas/input/get-transactions.json"),
require("./schemas/input/get-trustlines.json"),
require("./schemas/input/prepare-payment.json"),
require("./schemas/input/prepare-order.json"),
require("./schemas/input/prepare-trustline.json"),
require("./schemas/input/prepare-order-cancellation.json"),
require("./schemas/input/prepare-settings.json"),
require("./schemas/input/prepare-escrow-creation.json"),
require("./schemas/input/prepare-escrow-cancellation.json"),
require("./schemas/input/prepare-escrow-execution.json"),
require("./schemas/input/prepare-payment-channel-create.json"),
require("./schemas/input/prepare-payment-channel-fund.json"),
require("./schemas/input/prepare-payment-channel-claim.json"),
require("./schemas/input/prepare-check-create.json"),
require("./schemas/input/prepare-check-cash.json"),
require("./schemas/input/prepare-check-cancel.json"),
require("./schemas/input/prepare-ticket-create.json"),
require("./schemas/input/compute-ledger-hash.json"),
require("./schemas/input/sign.json"),
require("./schemas/input/submit.json"),
require("./schemas/input/generate-address.json"),
require("./schemas/input/sign-payment-channel-claim.json"),
require("./schemas/input/verify-payment-channel-claim.json"),
require("./schemas/input/combine.json"),
];
const titles = schemas.map((schema) => schema.title);
const duplicates = Object.keys(
_.pickBy(_.countBy(titles), (count) => count > 1)
);
assert.ok(duplicates.length === 0, `Duplicate schemas for: ${duplicates}`);
const validator = new Validator();
// Register custom format validators that ignore undefined instances
// since jsonschema will still call the format validator on a missing
// (optional) property
@@ -136,49 +142,47 @@ function loadSchemas() {
// This relies on "format": "xAddress" in `x-address.json`!
validator.customFormats.xAddress = function (instance) {
if (instance == null) {
return true
return true;
}
return isValidXAddress(instance)
}
return isValidXAddress(instance);
};
// This relies on "format": "classicAddress" in `classic-address.json`!
validator.customFormats.classicAddress = function (instance) {
if (instance == null) {
return true
return true;
}
return isValidAddress(instance)
}
return isValidAddress(instance);
};
validator.customFormats.secret = function (instance) {
if (instance == null) {
return true
return true;
}
return isValidSecret(instance)
}
return isValidSecret(instance);
};
// Register under the root URI '/'
schemas.forEach((schema) =>
validator.addSchema(schema, '/' + schema.title)
)
return validator
schemas.forEach((schema) => validator.addSchema(schema, `/${schema.title}`));
return validator;
}
const schemaValidator = loadSchemas()
const schemaValidator = loadSchemas();
function schemaValidate(schemaName: string, object: any): void {
// Lookup under the root URI '/'
const schema = schemaValidator.getSchema('/' + schemaName)
const schema = schemaValidator.getSchema(`/${schemaName}`);
if (schema == null) {
throw new ValidationError('no schema for ' + schemaName)
throw new ValidationError(`no schema for ${schemaName}`);
}
const result = schemaValidator.validate(object, schema)
const result = schemaValidator.validate(object, schema);
if (!result.valid) {
throw new ValidationError(result.errors.join())
throw new ValidationError(result.errors.join());
}
}
function isValidAddress(address: string): boolean {
return isValidXAddress(address) || isValidClassicAddress(address)
return isValidXAddress(address) || isValidClassicAddress(address);
}
export {schemaValidate, isValidSecret, isValidAddress}
export { schemaValidate, isValidSecret, isValidAddress };

View File

@@ -1,7 +1,7 @@
const txFlags = {
// Universal flags can apply to any transaction type
Universal: {
FullyCanonicalSig: 0x80000000
FullyCanonicalSig: 0x80000000,
},
AccountSet: {
@@ -10,7 +10,7 @@ const txFlags = {
RequireAuth: 0x00040000,
OptionalAuth: 0x00080000,
DisallowXRP: 0x00100000,
AllowXRP: 0x00200000
AllowXRP: 0x00200000,
},
TrustSet: {
@@ -19,27 +19,27 @@ const txFlags = {
SetNoRipple: 0x00020000,
ClearNoRipple: 0x00040000,
SetFreeze: 0x00100000,
ClearFreeze: 0x00200000
ClearFreeze: 0x00200000,
},
OfferCreate: {
Passive: 0x00010000,
ImmediateOrCancel: 0x00020000,
FillOrKill: 0x00040000,
Sell: 0x00080000
Sell: 0x00080000,
},
Payment: {
NoRippleDirect: 0x00010000,
PartialPayment: 0x00020000,
LimitQuality: 0x00040000
LimitQuality: 0x00040000,
},
PaymentChannelClaim: {
Renew: 0x00010000,
Close: 0x00020000
}
}
Close: 0x00020000,
},
};
// The following are integer (as opposed to bit) flags
// that can be set for particular transactions in the
@@ -54,8 +54,8 @@ const txFlagIndices = {
asfNoFreeze: 6,
asfGlobalFreeze: 7,
asfDefaultRipple: 8,
asfDepositAuth: 9
}
}
asfDepositAuth: 9,
},
};
export {txFlags, txFlagIndices}
export { txFlags, txFlagIndices };

View File

@@ -1,23 +1,23 @@
import {
AccountRootLedgerEntry,
SignerListLedgerEntry,
QueueData
} from '../objects'
QueueData,
} from "../objects";
export interface AccountInfoRequest {
account: string
strict?: boolean
queue?: boolean
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
signer_lists?: boolean
account: string;
strict?: boolean;
queue?: boolean;
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
signer_lists?: boolean;
}
export interface AccountInfoResponse {
account_data: AccountRootLedgerEntry
signer_lists?: SignerListLedgerEntry[]
ledger_current_index?: number
ledger_index?: number
queue_data?: QueueData
validated?: boolean
account_data: AccountRootLedgerEntry;
signer_lists?: SignerListLedgerEntry[];
ledger_current_index?: number;
ledger_index?: number;
queue_data?: QueueData;
validated?: boolean;
}

View File

@@ -1,19 +1,19 @@
import {Trustline} from '../objects'
import { Trustline } from "../objects";
export interface AccountLinesRequest {
account: string
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
peer?: string
limit?: number
marker?: any
account: string;
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
peer?: string;
limit?: number;
marker?: any;
}
export interface AccountLinesResponse {
account: string
lines: Trustline[]
ledger_current_index?: number
ledger_index?: number
ledger_hash?: string
marker?: any
account: string;
lines: Trustline[];
ledger_current_index?: number;
ledger_index?: number;
ledger_hash?: string;
marker?: any;
}

View File

@@ -1,4 +1,4 @@
import { AccountObjectType } from '../../../models/common';
import { AccountObjectType } from "../../../models/common";
import {
CheckLedgerEntry,
RippleStateLedgerEntry,
@@ -6,46 +6,46 @@ import {
SignerListLedgerEntry,
EscrowLedgerEntry,
PayChannelLedgerEntry,
DepositPreauthLedgerEntry
} from '../objects'
DepositPreauthLedgerEntry,
} from "../objects";
export interface GetAccountObjectsOptions {
type?: AccountObjectType
ledgerHash?: string
ledgerIndex?: number | ('validated' | 'closed' | 'current')
limit?: number
marker?: string
type?: AccountObjectType;
ledgerHash?: string;
ledgerIndex?: number | ("validated" | "closed" | "current");
limit?: number;
marker?: string;
}
export interface AccountObjectsRequest {
account: string
account: string;
// (Optional) Filter results to include only this type of ledger object.
type?:
| string
| (
| 'check'
| 'escrow'
| 'offer'
| 'payment_channel'
| 'signer_list'
| 'state'
)
| "check"
| "escrow"
| "offer"
| "payment_channel"
| "signer_list"
| "state"
);
// (Optional) A 20-byte hex string for the ledger version to use.
ledger_hash?: string
ledger_hash?: string;
// (Optional) The sequence number of the ledger to use,
// or a shortcut string to choose a ledger automatically.
ledger_index?: number | ('validated' | 'closed' | 'current')
ledger_index?: number | ("validated" | "closed" | "current");
limit?: number
limit?: number;
marker?: string
marker?: string;
}
export interface AccountObjectsResponse {
account: string
account: string;
// Array of objects owned by this account.
// from the getAccountObjects section of the dev center
@@ -57,29 +57,29 @@ export interface AccountObjectsResponse {
| EscrowLedgerEntry
| PayChannelLedgerEntry
| DepositPreauthLedgerEntry
>
>;
// (May be omitted) The identifying hash of the ledger
// that was used to generate this response.
ledger_hash?: string
ledger_hash?: string;
// (May be omitted) The sequence number of the ledger version
// that was used to generate this response.
ledger_index?: number
ledger_index?: number;
// (May be omitted) The sequence number of the current in-progress ledger
// version that was used to generate this response.
ledger_current_index?: number
ledger_current_index?: number;
// The limit that was used in this request, if any.
limit?: number
limit?: number;
// Server-defined value indicating the response is paginated. Pass this
// to the next call to resume where this call left off. Omitted when there
// are no additional pages after this one.
marker?: string
marker?: string;
// If true, this information comes from a ledger version
// that has been validated by consensus.
validated?: boolean
validated?: boolean;
}

View File

@@ -1,27 +1,27 @@
import {RippledAmount} from '../objects'
import { RippledAmount } from "../objects";
export interface AccountOffersRequest {
account: string
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
limit?: number
marker?: any
account: string;
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
limit?: number;
marker?: any;
}
export interface AccountOffersResponse {
account: string
ledger_hash?: string
ledger_current_index?: number
ledger_index?: number
marker?: any
offers?: AccountOffer[]
account: string;
ledger_hash?: string;
ledger_current_index?: number;
ledger_index?: number;
marker?: any;
offers?: AccountOffer[];
}
export interface AccountOffer {
seq: number
flags: number
taker_gets: RippledAmount
taker_pays: RippledAmount
quality: string
expiration?: number
seq: number;
flags: number;
taker_gets: RippledAmount;
taker_pays: RippledAmount;
quality: string;
expiration?: number;
}

View File

@@ -1,26 +1,30 @@
import {TakerRequestAmount, RippledAmount, OfferLedgerEntry} from '../objects'
import {
TakerRequestAmount,
RippledAmount,
OfferLedgerEntry,
} from "../objects";
export interface BookOffersRequest {
taker?: string
taker_gets: TakerRequestAmount
taker_pays: TakerRequestAmount
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
limit?: number
marker?: any
taker?: string;
taker_gets: TakerRequestAmount;
taker_pays: TakerRequestAmount;
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
limit?: number;
marker?: any;
}
export interface BookOffersResponse {
offers: BookOffer[]
ledger_hash?: string
ledger_current_index?: number
ledger_index?: number
marker?: any
offers: BookOffer[];
ledger_hash?: string;
ledger_current_index?: number;
ledger_index?: number;
marker?: any;
}
export interface BookOffer extends OfferLedgerEntry {
quality?: string
owner_funds?: string
taker_gets_funded?: RippledAmount
taker_pays_funded?: RippledAmount
quality?: string;
owner_funds?: string;
taker_gets_funded?: RippledAmount;
taker_pays_funded?: RippledAmount;
}

View File

@@ -1,19 +1,19 @@
import {Amount} from '../objects'
import { Amount } from "../objects";
export interface GatewayBalancesRequest {
account: string
strict?: boolean
hotwallet: string | Array<string>
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
account: string;
strict?: boolean;
hotwallet: string | string[];
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
}
export interface GatewayBalancesResponse {
account: string
obligations?: {[currency: string]: string}
balances?: {[address: string]: Amount[]}
assets?: {[address: string]: Amount[]}
ledger_hash?: string
ledger_current_index?: number
ledger_index?: number
account: string;
obligations?: { [currency: string]: string };
balances?: { [address: string]: Amount[] };
assets?: { [address: string]: Amount[] };
ledger_hash?: string;
ledger_current_index?: number;
ledger_index?: number;
}

View File

@@ -1,10 +1,10 @@
export * from './account_info'
export * from './account_lines'
export * from './account_objects'
export * from './account_offers'
export * from './book_offers'
export * from './gateway_balances'
export * from './ledger'
export * from './ledger_data'
export * from './ledger_entry'
export * from './server_info'
export * from "./account_info";
export * from "./account_lines";
export * from "./account_objects";
export * from "./account_offers";
export * from "./book_offers";
export * from "./gateway_balances";
export * from "./ledger";
export * from "./ledger_data";
export * from "./ledger_entry";
export * from "./server_info";

View File

@@ -1,20 +1,20 @@
import {Ledger, QueueData} from '../objects'
import { Ledger, QueueData } from "../objects";
export interface LedgerRequest {
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
full?: boolean
accounts?: boolean
transactions?: boolean
expand?: boolean
owner_funds?: boolean
binary?: boolean
queue?: boolean
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
full?: boolean;
accounts?: boolean;
transactions?: boolean;
expand?: boolean;
owner_funds?: boolean;
binary?: boolean;
queue?: boolean;
}
export interface LedgerResponse {
ledger_index: number
ledger_hash: string
ledger: Ledger
queue_data?: QueueData
ledger_index: number;
ledger_hash: string;
ledger: Ledger;
queue_data?: QueueData;
}

View File

@@ -1,12 +1,12 @@
import {LedgerData} from '../objects'
import { LedgerData } from "../objects";
export interface LedgerDataRequest {
id?: any
ledger_hash?: string
ledger_index?: string
binary?: boolean
limit?: number
marker?: string
id?: any;
ledger_hash?: string;
ledger_index?: string;
binary?: boolean;
limit?: number;
marker?: string;
}
export type LedgerDataResponse = LedgerData
export type LedgerDataResponse = LedgerData;

View File

@@ -1,36 +1,36 @@
import {LedgerEntry} from '../objects'
import { LedgerEntry } from "../objects";
export interface LedgerEntryRequest {
ledger_hash?: string
ledger_index?: number | ('validated' | 'closed' | 'current')
index?: string
account_root?: string
ledger_hash?: string;
ledger_index?: number | ("validated" | "closed" | "current");
index?: string;
account_root?: string;
directory?:
| string
| {
sub_index?: number
dir_root: string
sub_index?: number;
dir_root: string;
}
| {
sub_index?: number
owner: string
}
sub_index?: number;
owner: string;
};
offer?:
| string
| {
account: string
seq: number
}
account: string;
seq: number;
};
ripple_state?: {
accounts: [string, string]
currency: string
}
binary?: boolean
accounts: [string, string];
currency: string;
};
binary?: boolean;
}
export interface LedgerEntryResponse {
index: string
ledger_index: number
node_binary?: string
node?: LedgerEntry
index: string;
ledger_index: number;
node_binary?: string;
node?: LedgerEntry;
}

View File

@@ -1,51 +1,51 @@
export interface ServerInfoRequest {
id?: number
id?: number;
}
export interface ServerInfoResponse {
info: {
amendment_blocked?: boolean
build_version: string
closed_ledger?: LedgerInfo
complete_ledgers: string
hostid: string
io_latency_ms: number
amendment_blocked?: boolean;
build_version: string;
closed_ledger?: LedgerInfo;
complete_ledgers: string;
hostid: string;
io_latency_ms: number;
last_close: {
converge_time_s: number
proposers: number
}
converge_time_s: number;
proposers: number;
};
load?: {
job_types: {
job_type: string
per_second: number
in_progress: number
}[]
threads: number
}
load_factor: number
load_factor_local?: number
load_factor_net?: number
load_factor_cluster?: number
load_factor_fee_escalation?: number
load_factor_fee_queue?: number
load_factor_server?: number
peers: number
pubkey_node: string
pubkey_validator: string
server_state: string
state_accounting: any
uptime: number
validated_ledger?: LedgerInfo
validation_quorum: number
validator_list_expires: string
}
job_types: Array<{
job_type: string;
per_second: number;
in_progress: number;
}>;
threads: number;
};
load_factor: number;
load_factor_local?: number;
load_factor_net?: number;
load_factor_cluster?: number;
load_factor_fee_escalation?: number;
load_factor_fee_queue?: number;
load_factor_server?: number;
peers: number;
pubkey_node: string;
pubkey_validator: string;
server_state: string;
state_accounting: any;
uptime: number;
validated_ledger?: LedgerInfo;
validation_quorum: number;
validator_list_expires: string;
};
}
export interface LedgerInfo {
age: number
base_fee_xrp: number
hash: string
reserve_base_xrp: number
reserve_inc_xrp: number
seq: number
age: number;
base_fee_xrp: number;
hash: string;
reserve_base_xrp: number;
reserve_inc_xrp: number;
seq: number;
}

View File

@@ -1,19 +1,19 @@
import {Amount} from './amounts'
import { Amount } from "./amounts";
export type Adjustment = {
address: string
amount: Amount
tag?: number
export interface Adjustment {
address: string;
amount: Amount;
tag?: number;
}
export type MaxAdjustment = {
address: string
maxAmount: Amount
tag?: number
export interface MaxAdjustment {
address: string;
maxAmount: Amount;
tag?: number;
}
export type MinAdjustment = {
address: string
minAmount: Amount
tag?: number
export interface MinAdjustment {
address: string;
minAmount: Amount;
tag?: number;
}

View File

@@ -1,8 +1,8 @@
export interface Amount extends Issue {
value: string
value: string;
}
export type RippledAmount = string | Amount
export type RippledAmount = string | Amount;
/**
* Specification of which currency the account taking the offer would pay/
@@ -10,15 +10,15 @@ export type RippledAmount = string | Amount
* Similar to currency amounts.
*/
export interface TakerRequestAmount {
currency: string
issuer?: string
currency: string;
issuer?: string;
}
/**
* A currency-counterparty pair, or just currency if it's XRP.
*/
export interface Issue {
currency: string
issuer?: string
counterparty?: string
currency: string;
issuer?: string;
counterparty?: string;
}

View File

@@ -1,12 +1,12 @@
export * from './adjustments'
export * from './amounts'
export * from './ledger'
export * from './ledger_data'
export * from './ledger_entries'
export * from './memos'
export * from './orders'
export * from './queue_data'
export * from './settings'
export * from './signers'
export * from './transactions'
export * from './trustlines'
export * from "./adjustments";
export * from "./amounts";
export * from "./ledger";
export * from "./ledger_data";
export * from "./ledger_entries";
export * from "./memos";
export * from "./orders";
export * from "./queue_data";
export * from "./settings";
export * from "./signers";
export * from "./transactions";
export * from "./trustlines";

View File

@@ -1,31 +1,31 @@
export interface Ledger {
account_hash: string
close_time: number
close_time_human: string
close_time_resolution: number
closed: boolean
ledger_hash: string
ledger_index: string
parent_hash: string
total_coins: string
transaction_hash: string
transactions: string[] | object[]
close_flags?: number
parent_close_time?: number
accountState?: any[]
validated?: boolean
account_hash: string;
close_time: number;
close_time_human: string;
close_time_resolution: number;
closed: boolean;
ledger_hash: string;
ledger_index: string;
parent_hash: string;
total_coins: string;
transaction_hash: string;
transactions: string[] | object[];
close_flags?: number;
parent_close_time?: number;
accountState?: any[];
validated?: boolean;
}
// https://xrpl.org/subscribe.html#ledger-stream
export type LedgerClosedEvent = {
type: 'ledgerClosed'
fee_base: number
fee_ref: number
ledger_hash: string
ledger_index: number
ledger_time: number
reserve_base: number
reserve_inc: number
txn_count: number
validated_ledgers: string
export interface LedgerClosedEvent {
type: "ledgerClosed";
fee_base: number;
fee_ref: number;
ledger_hash: string;
ledger_index: number;
ledger_time: number;
reserve_base: number;
reserve_inc: number;
txn_count: number;
validated_ledgers: string;
}

View File

@@ -1,6 +1,8 @@
export interface LedgerData {
ledger_index: string
ledger_hash: string
marker: string
state: ({data?: string; LedgerEntryType?: string; index: string} & any)[]
ledger_index: string;
ledger_hash: string;
marker: string;
state: Array<
{ data?: string; LedgerEntryType?: string; index: string } & any
>;
}

View File

@@ -1,175 +1,176 @@
import {SignerEntry} from './index'
import {Amount, RippledAmount} from './amounts'
import { Amount, RippledAmount } from "./amounts";
import { SignerEntry } from "./index";
export interface AccountRootLedgerEntry {
LedgerEntryType: 'AccountRoot'
Account: string
Balance: string
Flags: number
OwnerCount: number
PreviousTxnID: string
PreviousTxnLgrSeq: number
Sequence: number
AccountTxnID?: string
Domain?: string
EmailHash?: string
MessageKey?: string
RegularKey?: string
TickSize?: number
TransferRate?: number
WalletLocator?: string
WalletSize?: number // DEPRECATED
LedgerEntryType: "AccountRoot";
Account: string;
Balance: string;
Flags: number;
OwnerCount: number;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
Sequence: number;
AccountTxnID?: string;
Domain?: string;
EmailHash?: string;
MessageKey?: string;
RegularKey?: string;
TickSize?: number;
TransferRate?: number;
WalletLocator?: string;
WalletSize?: number; // DEPRECATED
}
export interface AmendmentsLedgerEntry {
LedgerEntryType: 'Amendments'
Amendments?: string[]
Majorities?: any[]
Flags: 0
LedgerEntryType: "Amendments";
Amendments?: string[];
Majorities?: any[];
Flags: 0;
}
export interface CheckLedgerEntry {
LedgerEntryType: 'Check'
Account: string
Destination
string
Flags: 0
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
SendMax: string | object
Sequence: number
DestinationNode: string
DestinationTag: number
Expiration: number
InvoiceID: string
SourceTag: number
LedgerEntryType: "Check";
Account: string;
Destination;
string;
Flags: 0;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
SendMax: string | object;
Sequence: number;
DestinationNode: string;
DestinationTag: number;
Expiration: number;
InvoiceID: string;
SourceTag: number;
}
export interface DepositPreauthLedgerEntry {
LedgerEntryType: 'DepositPreauth'
Account: string
Authorize: string
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
LedgerEntryType: "DepositPreauth";
Account: string;
Authorize: string;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
}
export interface DirectoryNodeLedgerEntry {
LedgerEntryType: 'DirectoryNode'
Flags: number
RootIndex: string
Indexes: string[]
IndexNext?: number
IndexPrevious?: number
LedgerEntryType: "DirectoryNode";
Flags: number;
RootIndex: string;
Indexes: string[];
IndexNext?: number;
IndexPrevious?: number;
}
export interface OfferDirectoryNodeLedgerEntry
extends DirectoryNodeLedgerEntry {
TakerPaysCurrency: string
TakerPaysIssuer: string
TakerGetsCurrency: string
TakerGetsIssuer: string
ExchangeRate?: number // DEPRECATED
TakerPaysCurrency: string;
TakerPaysIssuer: string;
TakerGetsCurrency: string;
TakerGetsIssuer: string;
ExchangeRate?: number; // DEPRECATED
}
export interface OwnerDirectoryNodeLedgerEntry
extends DirectoryNodeLedgerEntry {
Owner: string
Owner: string;
}
export interface EscrowLedgerEntry {
LedgerEntryType: 'Escrow'
Account: string
Destination: string
Amount: string
Condition?: string
CancelAfter?: number
FinishAfter?: number
Flags: number
SourceTag?: number
DestinationTag?: number
OwnerNode: string
DestinationNode?: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
LedgerEntryType: "Escrow";
Account: string;
Destination: string;
Amount: string;
Condition?: string;
CancelAfter?: number;
FinishAfter?: number;
Flags: number;
SourceTag?: number;
DestinationTag?: number;
OwnerNode: string;
DestinationNode?: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
}
export interface FeeSettingsLedgerEntry {
LedgerEntryType: 'FeeSettings'
BaseFee: string
ReferenceFeeUnits: number
ReserveBase: number
ReserveIncrement: number
Flags: number
LedgerEntryType: "FeeSettings";
BaseFee: string;
ReferenceFeeUnits: number;
ReserveBase: number;
ReserveIncrement: number;
Flags: number;
}
export interface LedgerHashesLedgerEntry {
LedgerEntryType: 'LedgerHashes'
Hashes: string[]
Flags: number
FirstLedgerSequence?: number // DEPRECATED
LastLedgerSequence?: number
LedgerEntryType: "LedgerHashes";
Hashes: string[];
Flags: number;
FirstLedgerSequence?: number; // DEPRECATED
LastLedgerSequence?: number;
}
export interface OfferLedgerEntry {
LedgerEntryType: 'Offer'
Flags: number
Account: string
Sequence: number
TakerPays: RippledAmount
TakerGets: RippledAmount
BookDirectory: string
BookNode: string
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
Expiration?: number
LedgerEntryType: "Offer";
Flags: number;
Account: string;
Sequence: number;
TakerPays: RippledAmount;
TakerGets: RippledAmount;
BookDirectory: string;
BookNode: string;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
Expiration?: number;
}
export interface PayChannelLedgerEntry {
LedgerEntryType: 'PayChannel'
Sequence: number
Account: string
Amount: string
Balance: string
PublicKey: string
Destination: string
SettleDelay: number
Expiration?: number
CancelAfter?: number
SourceTag?: number
DestinationTag?: number
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
index: string
LedgerEntryType: "PayChannel";
Sequence: number;
Account: string;
Amount: string;
Balance: string;
PublicKey: string;
Destination: string;
SettleDelay: number;
Expiration?: number;
CancelAfter?: number;
SourceTag?: number;
DestinationTag?: number;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
index: string;
}
export interface RippleStateLedgerEntry {
LedgerEntryType: 'RippleState'
Flags: number
Balance: Amount
LowLimit: Amount
HighLimit: Amount
PreviousTxnID: string
PreviousTxnLgrSeq: number
LowNode?: string
HighNode?: string
LowQualityIn?: number
LowQualityOut?: number
HighQualityIn?: number
HighQualityOut?: number
LedgerEntryType: "RippleState";
Flags: number;
Balance: Amount;
LowLimit: Amount;
HighLimit: Amount;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
LowNode?: string;
HighNode?: string;
LowQualityIn?: number;
LowQualityOut?: number;
HighQualityIn?: number;
HighQualityOut?: number;
}
export interface SignerListLedgerEntry {
LedgerEntryType: 'SignerList'
OwnerNode: string
SignerQuorum: number
SignerEntries: SignerEntry[]
SignerListID: number
PreviousTxnID: string
PreviousTxnLgrSeq: number
LedgerEntryType: "SignerList";
OwnerNode: string;
SignerQuorum: number;
SignerEntries: SignerEntry[];
SignerListID: number;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
}
// see https://ripple.com/build/ledger-format/#ledger-object-types
@@ -187,4 +188,4 @@ export type LedgerEntry =
| OfferLedgerEntry
| PayChannelLedgerEntry
| RippleStateLedgerEntry
| SignerListLedgerEntry
| SignerListLedgerEntry;

View File

@@ -1,5 +1,5 @@
export type Memo = {
type?: string
format?: string
data?: string
export interface Memo {
type?: string;
format?: string;
data?: string;
}

View File

@@ -1,17 +1,17 @@
import {Amount} from './amounts'
import {Memo} from './memos'
import { Amount } from "./amounts";
import { Memo } from "./memos";
export type FormattedOrderSpecification = {
direction: string
quantity: Amount
totalPrice: Amount
immediateOrCancel?: boolean
fillOrKill?: boolean
expirationTime?: string
orderToReplace?: number
memos?: Memo[]
export interface FormattedOrderSpecification {
direction: string;
quantity: Amount;
totalPrice: Amount;
immediateOrCancel?: boolean;
fillOrKill?: boolean;
expirationTime?: string;
orderToReplace?: number;
memos?: Memo[];
// If enabled, the offer will not consume offers that exactly match it, and
// instead becomes an Offer node in the ledger. It will still consume offers
// that cross it.
passive?: boolean
passive?: boolean;
}

View File

@@ -1,16 +1,16 @@
export interface QueueTransaction {
auth_change: boolean
fee: string
fee_level: string
max_spend_drops: string
seq: number
auth_change: boolean;
fee: string;
fee_level: string;
max_spend_drops: string;
seq: number;
}
export interface QueueData {
txn_count: number
auth_change_queued?: boolean
lowest_sequence?: number
highest_sequence?: number
max_spend_drops_total?: string
transactions?: QueueTransaction[]
txn_count: number;
auth_change_queued?: boolean;
lowest_sequence?: number;
highest_sequence?: number;
max_spend_drops_total?: string;
transactions?: QueueTransaction[];
}

View File

@@ -1,33 +1,33 @@
import {Memo} from './memos'
import { Memo } from "./memos";
export type WeightedSigner = {
address: string
weight: number
export interface WeightedSigner {
address: string;
weight: number;
}
export type Signers = {
threshold?: number
weights: WeightedSigner[]
export interface Signers {
threshold?: number;
weights: WeightedSigner[];
}
export type FormattedSettings = {
defaultRipple?: boolean
depositAuth?: boolean
disableMasterKey?: boolean
disallowIncomingXRP?: boolean
domain?: string
emailHash?: string | null
walletLocator?: string | null
enableTransactionIDTracking?: boolean
globalFreeze?: boolean
memos?: Memo[]
messageKey?: string
noFreeze?: boolean
passwordSpent?: boolean
regularKey?: string
requireAuthorization?: boolean
requireDestinationTag?: boolean
signers?: Signers
transferRate?: number | null
tickSize?: number
export interface FormattedSettings {
defaultRipple?: boolean;
depositAuth?: boolean;
disableMasterKey?: boolean;
disallowIncomingXRP?: boolean;
domain?: string;
emailHash?: string | null;
walletLocator?: string | null;
enableTransactionIDTracking?: boolean;
globalFreeze?: boolean;
memos?: Memo[];
messageKey?: string;
noFreeze?: boolean;
passwordSpent?: boolean;
regularKey?: string;
requireAuthorization?: boolean;
requireDestinationTag?: boolean;
signers?: Signers;
transferRate?: number | null;
tickSize?: number;
}

View File

@@ -1,6 +1,6 @@
export interface SignerEntry {
SignerEntry: {
Account: string
SignerWeight: number
}
Account: string;
SignerWeight: number;
};
}

View File

@@ -1,27 +1,27 @@
import {RippledAmount} from './amounts'
import {Memo} from './memos'
import { RippledAmount } from "./amounts";
import { Memo } from "./memos";
export interface OfferCreateTransaction {
TransactionType: 'OfferCreate'
Account: string
AccountTxnID?: string
Fee: string
Field: any
Flags: number
LastLedgerSequence?: number
Sequence: number
Signers: any[]
SigningPubKey: string
SourceTag?: number
TakerGets: RippledAmount
TakerPays: RippledAmount
TxnSignature: string
Expiration?: number
Memos?: Memo[]
OfferSequence?: number
TransactionType: "OfferCreate";
Account: string;
AccountTxnID?: string;
Fee: string;
Field: any;
Flags: number;
LastLedgerSequence?: number;
Sequence: number;
Signers: any[];
SigningPubKey: string;
SourceTag?: number;
TakerGets: RippledAmount;
TakerPays: RippledAmount;
TxnSignature: string;
Expiration?: number;
Memos?: Memo[];
OfferSequence?: number;
}
export interface SignedTransaction {
signedTransaction: string
id: string
signedTransaction: string;
id: string;
}

View File

@@ -1,42 +1,42 @@
import {Memo} from './memos'
import { Memo } from "./memos";
export interface Trustline {
account: string
balance: string
currency: string
limit: string
limit_peer: string
quality_in: number
quality_out: number
no_ripple?: boolean
no_ripple_peer?: boolean
freeze?: boolean
freeze_peer?: boolean
authorized?: boolean
peer_authorized?: boolean
account: string;
balance: string;
currency: string;
limit: string;
limit_peer: string;
quality_in: number;
quality_out: number;
no_ripple?: boolean;
no_ripple_peer?: boolean;
freeze?: boolean;
freeze_peer?: boolean;
authorized?: boolean;
peer_authorized?: boolean;
}
export type FormattedTrustlineSpecification = {
currency: string
counterparty: string
limit: string
qualityIn?: number
qualityOut?: number
ripplingDisabled?: boolean
authorized?: boolean
frozen?: boolean
memos?: Memo[]
export interface FormattedTrustlineSpecification {
currency: string;
counterparty: string;
limit: string;
qualityIn?: number;
qualityOut?: number;
ripplingDisabled?: boolean;
authorized?: boolean;
frozen?: boolean;
memos?: Memo[];
}
export type FormattedTrustline = {
specification: FormattedTrustlineSpecification
export interface FormattedTrustline {
specification: FormattedTrustlineSpecification;
counterparty: {
limit: string
ripplingDisabled?: boolean
frozen?: boolean
authorized?: boolean
}
limit: string;
ripplingDisabled?: boolean;
frozen?: boolean;
authorized?: boolean;
};
state: {
balance: string
}
balance: string;
};
}

View File

@@ -1,9 +1,10 @@
import _ from 'lodash'
import {ValidationError} from './errors'
import {schemaValidate} from './schema-validator'
import _ from "lodash";
import { ValidationError } from "./errors";
import { schemaValidate } from "./schema-validator";
function error(text) {
return new ValidationError(text)
return new ValidationError(text);
}
function validateLedgerRange(options) {
@@ -13,158 +14,161 @@ function validateLedgerRange(options) {
options.maxLedgerVersion != null
) {
if (Number(options.minLedgerVersion) > Number(options.maxLedgerVersion)) {
throw error('minLedgerVersion must not be greater than maxLedgerVersion')
throw error("minLedgerVersion must not be greater than maxLedgerVersion");
}
}
}
function validateOptions(schema, instance) {
schemaValidate(schema, instance)
validateLedgerRange(instance.options)
schemaValidate(schema, instance);
validateLedgerRange(instance.options);
}
export const getPaths = _.partial(schemaValidate, 'getPathsParameters')
export const getPaths = _.partial(schemaValidate, "getPathsParameters");
export const getTransactions = _.partial(
validateOptions,
'getTransactionsParameters'
)
"getTransactionsParameters"
);
export const getSettings = _.partial(validateOptions, 'getSettingsParameters')
export const getSettings = _.partial(validateOptions, "getSettingsParameters");
export const getAccountInfo = _.partial(
validateOptions,
'getAccountInfoParameters'
)
"getAccountInfoParameters"
);
export const getTrustlines = _.partial(
validateOptions,
'getTrustlinesParameters'
)
"getTrustlinesParameters"
);
export const getBalances = _.partial(validateOptions, 'getBalancesParameters')
export const getBalances = _.partial(validateOptions, "getBalancesParameters");
export const getBalanceSheet = _.partial(
validateOptions,
'getBalanceSheetParameters'
)
"getBalanceSheetParameters"
);
export const getOrders = _.partial(validateOptions, 'getOrdersParameters')
export const getOrders = _.partial(validateOptions, "getOrdersParameters");
export const getOrderbook = _.partial(validateOptions, 'getOrderbookParameters')
export const getOrderbook = _.partial(
validateOptions,
"getOrderbookParameters"
);
export const getTransaction = _.partial(
validateOptions,
'getTransactionParameters'
)
"getTransactionParameters"
);
export const getPaymentChannel = _.partial(
validateOptions,
'getPaymentChannelParameters'
)
"getPaymentChannelParameters"
);
export const getLedger = _.partial(validateOptions, 'getLedgerParameters')
export const getLedger = _.partial(validateOptions, "getLedgerParameters");
export const preparePayment = _.partial(
schemaValidate,
'preparePaymentParameters'
)
"preparePaymentParameters"
);
export const prepareOrder = _.partial(schemaValidate, 'prepareOrderParameters')
export const prepareOrder = _.partial(schemaValidate, "prepareOrderParameters");
export const prepareOrderCancellation = _.partial(
schemaValidate,
'prepareOrderCancellationParameters'
)
"prepareOrderCancellationParameters"
);
export const prepareTrustline = _.partial(
schemaValidate,
'prepareTrustlineParameters'
)
"prepareTrustlineParameters"
);
export const prepareSettings = _.partial(
schemaValidate,
'prepareSettingsParameters'
)
"prepareSettingsParameters"
);
export const prepareEscrowCreation = _.partial(
schemaValidate,
'prepareEscrowCreationParameters'
)
"prepareEscrowCreationParameters"
);
export const prepareEscrowCancellation = _.partial(
schemaValidate,
'prepareEscrowCancellationParameters'
)
"prepareEscrowCancellationParameters"
);
export const prepareEscrowExecution = _.partial(
schemaValidate,
'prepareEscrowExecutionParameters'
)
"prepareEscrowExecutionParameters"
);
export const preparePaymentChannelCreate = _.partial(
schemaValidate,
'preparePaymentChannelCreateParameters'
)
"preparePaymentChannelCreateParameters"
);
export const preparePaymentChannelFund = _.partial(
schemaValidate,
'preparePaymentChannelFundParameters'
)
"preparePaymentChannelFundParameters"
);
export const preparePaymentChannelClaim = _.partial(
schemaValidate,
'preparePaymentChannelClaimParameters'
)
"preparePaymentChannelClaimParameters"
);
export const prepareCheckCreate = _.partial(
schemaValidate,
'prepareCheckCreateParameters'
)
"prepareCheckCreateParameters"
);
export const prepareCheckCash = _.partial(
schemaValidate,
'prepareCheckCashParameters'
)
"prepareCheckCashParameters"
);
export const prepareCheckCancel = _.partial(
schemaValidate,
'prepareCheckCancelParameters'
)
"prepareCheckCancelParameters"
);
export const prepareTicketCreate = _.partial(
schemaValidate,
'prepareTicketParameters'
)
"prepareTicketParameters"
);
export const sign = _.partial(schemaValidate, 'signParameters')
export const sign = _.partial(schemaValidate, "signParameters");
export const combine = _.partial(schemaValidate, 'combineParameters')
export const combine = _.partial(schemaValidate, "combineParameters");
export const submit = _.partial(schemaValidate, 'submitParameters')
export const submit = _.partial(schemaValidate, "submitParameters");
export const computeLedgerHash = _.partial(
schemaValidate,
'computeLedgerHashParameters'
)
"computeLedgerHashParameters"
);
export const generateAddress = _.partial(
schemaValidate,
'generateAddressParameters'
)
"generateAddressParameters"
);
export const signPaymentChannelClaim = _.partial(
schemaValidate,
'signPaymentChannelClaimParameters'
)
"signPaymentChannelClaimParameters"
);
export const verifyPaymentChannelClaim = _.partial(
schemaValidate,
'verifyPaymentChannelClaimParameters'
)
"verifyPaymentChannelClaimParameters"
);
export const apiOptions = _.partial(schemaValidate, 'api-options')
export const apiOptions = _.partial(schemaValidate, "api-options");
export const instructions = _.partial(schemaValidate, 'instructions')
export const instructions = _.partial(schemaValidate, "instructions");
export const tx_json = _.partial(schemaValidate, 'tx-json')
export const tx_json = _.partial(schemaValidate, "tx-json");

View File

@@ -1,14 +1,14 @@
export {Client} from './client'
export { Client } from "./client";
export * from './transaction/types'
export * from "./transaction/types";
export * from './common/types/objects/ledger'
export * from "./common/types/objects/ledger";
export * from './models/methods'
export * from "./models/methods";
export * from './utils'
export * from "./utils";
// Broadcast client is experimental
export {BroadcastClient} from './client/broadcastClient'
export { BroadcastClient } from "./client/broadcastClient";
export * from './Wallet'
export * from "./Wallet";

View File

@@ -1,42 +1,46 @@
import * as utils from './utils'
import {validate, ensureClassicAddress} from '../common'
import {Connection} from '../client'
import {GetTrustlinesOptions} from './trustlines'
import {FormattedTrustline} from '../common/types/objects/trustlines'
import {Client} from '..'
import { Client } from "..";
import { Connection } from "../client";
import { validate, ensureClassicAddress } from "../common";
import { FormattedTrustline } from "../common/types/objects/trustlines";
export type Balance = {
value: string
currency: string
counterparty?: string
import { GetTrustlinesOptions } from "./trustlines";
import * as utils from "./utils";
export interface Balance {
value: string;
currency: string;
counterparty?: string;
}
export type GetBalances = Array<Balance>
export type GetBalances = Balance[];
function getTrustlineBalanceAmount(trustline: FormattedTrustline): Balance {
return {
currency: trustline.specification.currency,
counterparty: trustline.specification.counterparty,
value: trustline.state.balance
}
value: trustline.state.balance,
};
}
function formatBalances(options: GetTrustlinesOptions, balances: {xrp: string, trustlines: FormattedTrustline[]}) {
const result = balances.trustlines.map(getTrustlineBalanceAmount)
function formatBalances(
options: GetTrustlinesOptions,
balances: { xrp: string; trustlines: FormattedTrustline[] }
) {
const result = balances.trustlines.map(getTrustlineBalanceAmount);
if (
!(options.counterparty || (options.currency && options.currency !== 'XRP'))
!(options.counterparty || (options.currency && options.currency !== "XRP"))
) {
const xrpBalance = {
currency: 'XRP',
value: balances.xrp
}
result.unshift(xrpBalance)
currency: "XRP",
value: balances.xrp,
};
result.unshift(xrpBalance);
}
if (options.limit && result.length > options.limit) {
const toRemove = result.length - options.limit
result.splice(-toRemove, toRemove)
const toRemove = result.length - options.limit;
result.splice(-toRemove, toRemove);
}
return result
return result;
}
function getLedgerVersionHelper(
@@ -44,12 +48,14 @@ function getLedgerVersionHelper(
optionValue?: number
): Promise<number> {
if (optionValue != null && optionValue !== null) {
return Promise.resolve(optionValue)
return Promise.resolve(optionValue);
}
return connection.request({
command: 'ledger',
ledger_index: 'validated'
}).then(response => response.result.ledger_index);
return connection
.request({
command: "ledger",
ledger_index: "validated",
})
.then((response) => response.result.ledger_index);
}
function getBalances(
@@ -57,26 +63,23 @@ function getBalances(
address: string,
options: GetTrustlinesOptions = {}
): Promise<GetBalances> {
validate.getTrustlines({address, options})
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)
address = ensureClassicAddress(address);
return Promise.all([
getLedgerVersionHelper(
this.connection,
options.ledgerVersion
).then((ledgerVersion) =>
utils.getXRPBalance(this, address, ledgerVersion)
getLedgerVersionHelper(this.connection, options.ledgerVersion).then(
(ledgerVersion) => utils.getXRPBalance(this, address, ledgerVersion)
),
this.getTrustlines(address, options)
this.getTrustlines(address, options),
]).then((results) =>
formatBalances(options, {xrp: results[0], trustlines: results[1]})
)
formatBalances(options, { xrp: results[0], trustlines: results[1] })
);
}
export default getBalances
export default getBalances;

View File

@@ -1,45 +1,46 @@
import _ from 'lodash'
import * as utils from './utils'
import BigNumber from "bignumber.js";
import _ from "lodash";
import type { Client } from "../client";
import type { BookOffer } from "../common/types/commands";
import type { Issue } from "../common/types/objects";
import {
parseOrderbookOrder,
FormattedOrderbookOrder
} from './parse/orderbook-order'
import {validate} from '../common'
import {Issue} from '../common/types/objects'
import {BookOffer} from '../common/types/commands'
import {Client} from '..'
import BigNumber from 'bignumber.js'
FormattedOrderbookOrder,
} from "./parse/orderbook-order";
import * as utils from "./utils";
export type FormattedOrderbook = {
bids: FormattedOrderbookOrder[]
asks: FormattedOrderbookOrder[]
export interface FormattedOrderbook {
bids: FormattedOrderbookOrder[];
asks: FormattedOrderbookOrder[];
}
function isSameIssue(a: Issue, b: Issue) {
return a.currency === b.currency && a.counterparty === b.counterparty
return a.currency === b.currency && a.counterparty === b.counterparty;
}
function directionFilter(direction: string, order: FormattedOrderbookOrder) {
return order.specification.direction === direction
return order.specification.direction === direction;
}
function flipOrder(order: FormattedOrderbookOrder) {
const specification = order.specification
const specification = order.specification;
const flippedSpecification = {
quantity: specification.totalPrice,
totalPrice: specification.quantity,
direction: specification.direction === 'buy' ? 'sell' : 'buy'
}
const newSpecification = _.merge({}, specification, flippedSpecification)
return _.merge({}, order, {specification: newSpecification})
direction: specification.direction === "buy" ? "sell" : "buy",
};
const newSpecification = _.merge({}, specification, flippedSpecification);
return _.merge({}, order, { specification: newSpecification });
}
function alignOrder(
base: Issue,
order: FormattedOrderbookOrder
): FormattedOrderbookOrder {
const quantity = order.specification.quantity
return isSameIssue(quantity, base) ? order : flipOrder(order)
const quantity = order.specification.quantity;
return isSameIssue(quantity, base) ? order : flipOrder(order);
}
export function formatBidsAndAsks(
@@ -58,14 +59,17 @@ export function formatBidsAndAsks(
// we sort the orders so that earlier orders are closer to mid-market
const orders = offers
.sort((a, b) => {
return new BigNumber(a.quality).comparedTo(b.quality)
})
.map(parseOrderbookOrder)
const qualityA = a.quality ?? 0;
const qualityB = b.quality ?? 0;
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base))
const bids = alignedOrders.filter(_.partial(directionFilter, 'buy'))
const asks = alignedOrders.filter(_.partial(directionFilter, 'sell'))
return {bids, asks}
return new BigNumber(qualityA).comparedTo(qualityB);
})
.map(parseOrderbookOrder);
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base));
const bids = alignedOrders.filter(_.partial(directionFilter, "buy"));
const asks = alignedOrders.filter(_.partial(directionFilter, "sell"));
return { bids, asks };
}
// account is to specify a "perspective", which affects which unfunded offers
@@ -79,25 +83,26 @@ async function makeRequest(
) {
const orderData = utils.renameCounterpartyToIssuerInOrder({
taker_gets: takerGets,
taker_pays: takerPays
})
return client.requestAll({command: 'book_offers',
taker_pays: takerPays,
});
return client.requestAll({
command: "book_offers",
taker_gets: orderData.taker_gets,
taker_pays: orderData.taker_pays,
ledger_index: options.ledgerVersion || 'validated',
ledger_index: options.ledgerVersion || "validated",
limit: options.limit,
taker
})
taker,
});
}
export type GetOrderbookOptions = {
limit?: number
ledgerVersion?: number
export interface GetOrderbookOptions {
limit?: number;
ledgerVersion?: number;
}
export type OrderbookInfo = {
base: Issue
counter: Issue
export interface OrderbookInfo {
base: Issue;
counter: Issue;
}
export async function getOrderbook(
@@ -106,21 +111,19 @@ export async function getOrderbook(
orderbook: OrderbookInfo,
options: GetOrderbookOptions = {}
): Promise<FormattedOrderbook> {
// 1. Validate
validate.getOrderbook({address, orderbook, options})
// 2. Make Request
const [directOfferResults, reverseOfferResults] = await Promise.all([
makeRequest(this, address, options, orderbook.base, orderbook.counter),
makeRequest(this, address, options, orderbook.counter, orderbook.base)
])
makeRequest(this, address, options, orderbook.counter, orderbook.base),
]);
// 3. Return Formatted Response
const directOffers = _.flatMap(
directOfferResults,
(directOfferResult) => directOfferResult.result.offers
)
);
const reverseOffers = _.flatMap(
reverseOfferResults,
(reverseOfferResult) => reverseOfferResult.result.offers
)
return formatBidsAndAsks(orderbook, [...directOffers, ...reverseOffers])
);
return formatBidsAndAsks(orderbook, [...directOffers, ...reverseOffers]);
}

View File

@@ -1,25 +1,28 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import {classicAddressToXAddress} from 'ripple-address-codec'
import {parseMemos} from './utils'
import * as assert from "assert";
export type FormattedAccountDelete = {
import { classicAddressToXAddress } from "ripple-address-codec";
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
export interface FormattedAccountDelete {
// account (address) of an account to receive any leftover XRP after deleting the sending account.
// Must be a funded account in the ledger, and must not be the sending account.
destination: string
destination: string;
// (Optional) Arbitrary destination tag that identifies a hosted recipient or other information
// for the recipient of the deleted account's leftover XRP. NB: Ensure that the hosted recipient is
// able to account for AccountDelete transactions; if not, your balance may not be properly credited.
destinationTag?: number
destinationTag?: number;
// X-address of an account to receive any leftover XRP after deleting the sending account.
// Must be a funded account in the ledger, and must not be the sending account.
destinationXAddress: string
destinationXAddress: string;
}
function parseAccountDelete(tx: any): FormattedAccountDelete {
assert.ok(tx.TransactionType === 'AccountDelete')
assert.ok(tx.TransactionType === "AccountDelete");
return removeUndefined({
memos: parseMemos(tx),
@@ -29,8 +32,8 @@ function parseAccountDelete(tx: any): FormattedAccountDelete {
tx.Destination,
tx.DestinationTag == null ? false : tx.DestinationTag,
false
)
})
),
});
}
export default parseAccountDelete
export default parseAccountDelete;

View File

@@ -1,23 +1,25 @@
import BigNumber from 'bignumber.js'
import parseAmount from './amount'
import {parseTimestamp, adjustQualityForXRP} from './utils'
import {removeUndefined} from '../../utils'
import {orderFlags} from './flags'
import {FormattedOrderSpecification} from '../../common/types/objects'
import BigNumber from "bignumber.js";
export type FormattedAccountOrder = {
specification: FormattedOrderSpecification
import { FormattedOrderSpecification } from "../../common/types/objects";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { orderFlags } from "./flags";
import { parseTimestamp, adjustQualityForXRP } from "./utils";
export interface FormattedAccountOrder {
specification: FormattedOrderSpecification;
properties: {
maker: string
sequence: number
makerExchangeRate: string
}
maker: string;
sequence: number;
makerExchangeRate: string;
};
}
// TODO: remove this function once rippled provides quality directly
function computeQuality(takerGets, takerPays) {
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value)
return quotient.precision(16, BigNumber.ROUND_HALF_UP).toString()
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value);
return quotient.precision(16, BigNumber.ROUND_HALF_UP).toString();
}
// rippled 'account_offers' returns a different format for orders than 'tx'
@@ -26,22 +28,22 @@ export function parseAccountOrder(
address: string,
order: any
): FormattedAccountOrder {
const direction = (order.flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(order.taker_gets)
const takerPaysAmount = parseAmount(order.taker_pays)
const quantity = direction === 'buy' ? takerPaysAmount : takerGetsAmount
const totalPrice = direction === 'buy' ? takerGetsAmount : takerPaysAmount
const direction = (order.flags & orderFlags.Sell) === 0 ? "buy" : "sell";
const takerGetsAmount = parseAmount(order.taker_gets);
const takerPaysAmount = parseAmount(order.taker_pays);
const quantity = direction === "buy" ? takerPaysAmount : takerGetsAmount;
const totalPrice = direction === "buy" ? takerGetsAmount : takerPaysAmount;
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
const specification = removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
direction,
quantity,
totalPrice,
passive: (order.flags & orderFlags.Passive) !== 0 || undefined,
// rippled currently does not provide "expiration" in account_offers
expirationTime: parseTimestamp(order.expiration)
})
expirationTime: parseTimestamp(order.expiration),
});
const makerExchangeRate = order.quality
? adjustQualityForXRP(
@@ -49,12 +51,12 @@ export function parseAccountOrder(
takerGetsAmount.currency,
takerPaysAmount.currency
)
: computeQuality(takerGetsAmount, takerPaysAmount)
: computeQuality(takerGetsAmount, takerPaysAmount);
const properties = {
maker: address,
sequence: order.seq,
makerExchangeRate: makerExchangeRate
}
makerExchangeRate,
};
return {specification, properties}
return { specification, properties };
}

View File

@@ -1,9 +1,10 @@
import {parseQuality} from './utils'
import {removeUndefined} from '../../utils'
import {
Trustline,
FormattedTrustline
} from '../../common/types/objects/trustlines'
FormattedTrustline,
} from "../../common/types/objects/trustlines";
import { removeUndefined } from "../../utils";
import { parseQuality } from "./utils";
// rippled 'account_lines' returns a different format for
// trustlines than 'tx'
@@ -16,19 +17,19 @@ function parseAccountTrustline(trustline: Trustline): FormattedTrustline {
qualityOut: parseQuality(trustline.quality_out) || undefined,
ripplingDisabled: trustline.no_ripple,
frozen: trustline.freeze,
authorized: trustline.authorized
})
authorized: trustline.authorized,
});
// rippled doesn't provide the counterparty's qualities
const counterparty = removeUndefined({
limit: trustline.limit_peer,
ripplingDisabled: trustline.no_ripple_peer,
frozen: trustline.freeze_peer,
authorized: trustline.peer_authorized
})
authorized: trustline.peer_authorized,
});
const state = {
balance: trustline.balance
}
return {specification, counterparty, state}
balance: trustline.balance,
};
return { specification, counterparty, state };
}
export default parseAccountTrustline
export default parseAccountTrustline;

View File

@@ -1,7 +1,7 @@
function parseAmendment(tx: any) {
return {
amendment: tx.Amendment
}
amendment: tx.Amendment,
};
}
export default parseAmendment
export default parseAmendment;

View File

@@ -1,18 +1,18 @@
import {Amount, RippledAmount} from '../../common/types/objects'
import {dropsToXrp} from '../../utils'
import { Amount, RippledAmount } from "../../common/types/objects";
import { dropsToXrp } from "../../utils";
function parseAmount(amount: RippledAmount): Amount {
if (typeof amount === 'string') {
if (typeof amount === "string") {
return {
currency: 'XRP',
value: dropsToXrp(amount)
}
currency: "XRP",
value: dropsToXrp(amount),
};
}
return {
currency: amount.currency,
value: amount.value,
counterparty: amount.issuer
}
counterparty: amount.issuer,
};
}
export default parseAmount
export default parseAmount;

View File

@@ -1,12 +1,13 @@
import * as assert from 'assert'
import {parseMemos} from './utils'
import * as assert from "assert";
import { parseMemos } from "./utils";
function parseOrderCancellation(tx: any): object {
assert.ok(tx.TransactionType === 'OfferCancel')
assert.ok(tx.TransactionType === "OfferCancel");
return {
memos: parseMemos(tx),
orderSequence: tx.OfferSequence
}
orderSequence: tx.OfferSequence,
};
}
export default parseOrderCancellation
export default parseOrderCancellation;

View File

@@ -1,19 +1,21 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import {parseMemos} from './utils'
import * as assert from "assert";
export type FormattedCheckCancel = {
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
export interface FormattedCheckCancel {
// ID of the Check ledger object to cancel.
checkID: string
checkID: string;
}
function parseCheckCancel(tx: any): FormattedCheckCancel {
assert.ok(tx.TransactionType === 'CheckCancel')
assert.ok(tx.TransactionType === "CheckCancel");
return removeUndefined({
memos: parseMemos(tx),
checkID: tx.CheckID
})
checkID: tx.CheckID,
});
}
export default parseCheckCancel
export default parseCheckCancel;

View File

@@ -1,36 +1,38 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import parseAmount from './amount'
import {Amount} from '../../common/types/objects'
import {parseMemos} from './utils'
import * as assert from "assert";
export type FormattedCheckCash = {
import { Amount } from "../../common/types/objects";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseMemos } from "./utils";
export interface FormattedCheckCash {
// ID of the Check ledger object to cash.
checkID: string
checkID: string;
// (Optional) redeem the Check for exactly this amount, if possible.
// The currency must match that of the `SendMax` of the corresponding
// `CheckCreate` transaction.
amount: Amount
amount: Amount;
// (Optional) redeem the Check for at least this amount and
// for as much as possible.
// The currency must match that of the `SendMax` of the corresponding
// `CheckCreate` transaction.
deliverMin: Amount
deliverMin: Amount;
// *must* include either Amount or DeliverMin, but not both.
}
function parseCheckCash(tx: any): FormattedCheckCash {
assert.ok(tx.TransactionType === 'CheckCash')
assert.ok(tx.TransactionType === "CheckCash");
return removeUndefined({
memos: parseMemos(tx),
checkID: tx.CheckID,
amount: tx.Amount && parseAmount(tx.Amount),
deliverMin: tx.DeliverMin && parseAmount(tx.DeliverMin)
})
deliverMin: tx.DeliverMin && parseAmount(tx.DeliverMin),
});
}
export default parseCheckCash
export default parseCheckCash;

View File

@@ -1,30 +1,31 @@
import * as assert from 'assert'
import {parseTimestamp} from './utils'
import {removeUndefined} from '../../utils'
import parseAmount from './amount'
import {Amount} from '../../common/types/objects'
import {parseMemos} from './utils'
import * as assert from "assert";
export type FormattedCheckCreate = {
import { Amount } from "../../common/types/objects";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseTimestamp, parseMemos } from "./utils";
export interface FormattedCheckCreate {
// account that can cash the check.
destination: string
destination: string;
// amount the check is allowed to debit the sender,
// including transfer fees on non-XRP currencies.
sendMax: Amount
sendMax: Amount;
// (Optional) identifies the reason for the check, or a hosted recipient.
destinationTag?: string
destinationTag?: string;
// (Optional) time in seconds since the Ripple Epoch.
expiration?: string
expiration?: string;
// (Optional) 256-bit hash representing a specific reason or identifier.
invoiceID?: string
invoiceID?: string;
}
function parseCheckCreate(tx: any): FormattedCheckCreate {
assert.ok(tx.TransactionType === 'CheckCreate')
assert.ok(tx.TransactionType === "CheckCreate");
return removeUndefined({
memos: parseMemos(tx),
@@ -32,8 +33,8 @@ function parseCheckCreate(tx: any): FormattedCheckCreate {
sendMax: parseAmount(tx.SendMax),
destinationTag: tx.DestinationTag,
expiration: tx.Expiration && parseTimestamp(tx.Expiration),
invoiceID: tx.InvoiceID
})
invoiceID: tx.InvoiceID,
});
}
export default parseCheckCreate
export default parseCheckCreate;

View File

@@ -1,23 +1,25 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import {parseMemos} from './utils'
import * as assert from "assert";
export type FormattedDepositPreauth = {
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
export interface FormattedDepositPreauth {
// account (address) of the sender to preauthorize
authorize: string
authorize: string;
// account (address) of the sender whose preauthorization should be revoked
unauthorize: string
unauthorize: string;
}
function parseDepositPreauth(tx: any): FormattedDepositPreauth {
assert.ok(tx.TransactionType === 'DepositPreauth')
assert.ok(tx.TransactionType === "DepositPreauth");
return removeUndefined({
memos: parseMemos(tx),
authorize: tx.Authorize,
unauthorize: tx.Unauthorize
})
unauthorize: tx.Unauthorize,
});
}
export default parseDepositPreauth
export default parseDepositPreauth;

View File

@@ -1,15 +1,17 @@
import * as assert from 'assert'
import {parseMemos} from './utils'
import {removeUndefined} from '../../utils'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
function parseEscrowCancellation(tx: any): object {
assert.ok(tx.TransactionType === 'EscrowCancel')
assert.ok(tx.TransactionType === "EscrowCancel");
return removeUndefined({
memos: parseMemos(tx),
owner: tx.Owner,
escrowSequence: tx.OfferSequence
})
escrowSequence: tx.OfferSequence,
});
}
export default parseEscrowCancellation
export default parseEscrowCancellation;

View File

@@ -1,10 +1,12 @@
import * as assert from 'assert'
import parseAmount from './amount'
import {parseTimestamp, parseMemos} from './utils'
import {removeUndefined} from '../../utils'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseTimestamp, parseMemos } from "./utils";
function parseEscrowCreation(tx: any): object {
assert.ok(tx.TransactionType === 'EscrowCreate')
assert.ok(tx.TransactionType === "EscrowCreate");
return removeUndefined({
amount: parseAmount(tx.Amount).value,
@@ -14,8 +16,8 @@ function parseEscrowCreation(tx: any): object {
allowCancelAfter: parseTimestamp(tx.CancelAfter),
allowExecuteAfter: parseTimestamp(tx.FinishAfter),
sourceTag: tx.SourceTag,
destinationTag: tx.DestinationTag
})
destinationTag: tx.DestinationTag,
});
}
export default parseEscrowCreation
export default parseEscrowCreation;

View File

@@ -1,17 +1,19 @@
import * as assert from 'assert'
import {parseMemos} from './utils'
import {removeUndefined} from '../../utils'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
function parseEscrowExecution(tx: any): object {
assert.ok(tx.TransactionType === 'EscrowFinish')
assert.ok(tx.TransactionType === "EscrowFinish");
return removeUndefined({
memos: parseMemos(tx),
owner: tx.Owner,
escrowSequence: tx.OfferSequence,
condition: tx.Condition,
fulfillment: tx.Fulfillment
})
fulfillment: tx.Fulfillment,
});
}
export default parseEscrowExecution
export default parseEscrowExecution;

View File

@@ -1,16 +1,18 @@
import BigNumber from 'bignumber.js'
import {dropsToXrp} from '../../utils'
import {parseMemos} from './utils'
import BigNumber from "bignumber.js";
import { dropsToXrp } from "../../utils";
import { parseMemos } from "./utils";
function parseFeeUpdate(tx: any) {
const baseFeeDrops = new BigNumber(tx.BaseFee, 16).toString()
const baseFeeDrops = new BigNumber(tx.BaseFee, 16).toString();
return {
memos: parseMemos(tx),
baseFeeXRP: dropsToXrp(baseFeeDrops),
referenceFeeUnits: tx.ReferenceFeeUnits,
reserveBaseXRP: dropsToXrp(tx.ReserveBase),
reserveIncrementXRP: dropsToXrp(tx.ReserveIncrement)
}
reserveIncrementXRP: dropsToXrp(tx.ReserveIncrement),
};
}
export default parseFeeUpdate
export default parseFeeUpdate;

View File

@@ -1,52 +1,54 @@
import _ from 'lodash'
import BigNumber from 'bignumber.js'
import {constants} from '../../common'
const AccountFields = constants.AccountFields
import BigNumber from "bignumber.js";
import _ from "lodash";
import { constants } from "../../common";
const AccountFields = constants.AccountFields;
function parseField(info, value) {
if (info.encoding === 'hex' && !info.length) {
if (info.encoding === "hex" && !info.length) {
// e.g. "domain"
return Buffer.from(value, 'hex').toString('ascii')
return Buffer.from(value, "hex").toString("ascii");
}
if (info.shift) {
return new BigNumber(value).shiftedBy(-info.shift).toNumber()
return new BigNumber(value).shiftedBy(-info.shift).toNumber();
}
return value
return value;
}
function parseFields(data: any): object {
const settings: any = {}
const settings: any = {};
for (const fieldName in AccountFields) {
const fieldValue = data[fieldName]
const fieldValue = data[fieldName];
if (fieldValue != null) {
const info = AccountFields[fieldName]
settings[info.name] = parseField(info, fieldValue)
const info = AccountFields[fieldName];
settings[info.name] = parseField(info, fieldValue);
}
}
if (data.RegularKey) {
settings.regularKey = data.RegularKey
settings.regularKey = data.RegularKey;
}
// Since an account can own at most one SignerList,
// this array must have exactly one member if it is present.
if (data.signer_lists && data.signer_lists.length === 1) {
settings.signers = {}
settings.signers = {};
if (data.signer_lists[0].SignerQuorum) {
settings.signers.threshold = data.signer_lists[0].SignerQuorum
settings.signers.threshold = data.signer_lists[0].SignerQuorum;
}
if (data.signer_lists[0].SignerEntries) {
settings.signers.weights = data.signer_lists[0].SignerEntries.map(
(entry: any) => {
return {
address: entry.SignerEntry.Account,
weight: entry.SignerEntry.SignerWeight
}
weight: entry.SignerEntry.SignerWeight,
};
}
)
);
}
}
return settings
return settings;
}
export default parseFields
export default parseFields;

View File

@@ -1,7 +1,7 @@
const orderFlags = {
Passive: 0x00010000,
Sell: 0x00020000 // offer was placed as a sell
}
Sell: 0x00020000, // offer was placed as a sell
};
const trustlineFlags = {
LowReserve: 0x00010000, // entry counts toward reserve
@@ -11,7 +11,7 @@ const trustlineFlags = {
LowNoRipple: 0x00100000,
HighNoRipple: 0x00200000,
LowFreeze: 0x00400000,
HighFreeze: 0x00800000
}
HighFreeze: 0x00800000,
};
export {orderFlags, trustlineFlags}
export { orderFlags, trustlineFlags };

View File

@@ -1,87 +1,92 @@
import _ from 'lodash'
import {removeUndefined, rippleTimeToISOTime} from '../../utils'
import parseTransaction from './transaction'
import { TransactionAndMetadata } from '../../models/transactions'
import _ from "lodash";
export type FormattedLedger = {
import { TransactionAndMetadata } from "../../models/transactions";
import { removeUndefined, rippleTimeToISOTime } from "../../utils";
import parseTransaction from "./transaction";
export interface FormattedLedger {
// TODO: properties in type don't match response object. Fix!
// closed: boolean,
stateHash: string
closeTime: string
closeTimeResolution: number
closeFlags: number
ledgerHash: string
ledgerVersion: number
parentLedgerHash: string
parentCloseTime: string
totalDrops: string
transactionHash: string
transactions?: Array<object>
transactionHashes?: Array<string>
rawState?: string
stateHashes?: Array<string>
stateHash: string;
closeTime: string;
closeTimeResolution: number;
closeFlags: number;
ledgerHash: string;
ledgerVersion: number;
parentLedgerHash: string;
parentCloseTime: string;
totalDrops: string;
transactionHash: string;
transactions?: object[];
transactionHashes?: string[];
rawState?: string;
stateHashes?: string[];
}
function parseTransactionWrapper(ledgerVersion: number, tx: TransactionAndMetadata) {
function parseTransactionWrapper(
ledgerVersion: number,
tx: TransactionAndMetadata
) {
// renames metaData to meta and adds ledger_index
const transaction = Object.assign({}, _.omit(tx, 'metadata'), {
const transaction = {
..._.omit(tx, "metadata"),
meta: tx.metadata,
ledger_index: ledgerVersion
})
const result = parseTransaction(transaction, true)
ledger_index: ledgerVersion,
};
const result = parseTransaction(transaction, true);
if (!result.outcome.ledgerVersion) {
result.outcome.ledgerVersion = ledgerVersion
result.outcome.ledgerVersion = ledgerVersion;
}
return result
return result;
}
function parseTransactions(transactions: string[] | TransactionAndMetadata[], ledgerVersion: number) {
function parseTransactions(
transactions: string[] | TransactionAndMetadata[],
ledgerVersion: number
) {
if (_.isEmpty(transactions)) {
return {}
return {};
}
if (typeof transactions[0] === 'string') {
return {transactionHashes: transactions as unknown as string[]}
if (typeof transactions[0] === "string") {
return { transactionHashes: transactions as unknown as string[] };
}
return {
transactions: (transactions as unknown as TransactionAndMetadata[]).map(
_.partial(parseTransactionWrapper, ledgerVersion)
)
}
),
};
}
function parseState(state) {
if (_.isEmpty(state)) {
return {}
return {};
}
if (typeof state[0] === 'string') {
return {stateHashes: state}
if (typeof state[0] === "string") {
return { stateHashes: state };
}
return {rawState: JSON.stringify(state)}
return { rawState: JSON.stringify(state) };
}
/**
* @param {Ledger} ledger must be a *closed* ledger with valid `close_time` and `parent_close_time`
* @returns {FormattedLedger} formatted ledger
* @throws RangeError: Invalid time value (rippleTimeToISOTime)
* @param ledger - Must be a *closed* ledger with valid `close_time` and `parent_close_time`.
* @returns Formatted ledger.
* @throws RangeError: Invalid time value (rippleTimeToISOTime).
*/
export function parseLedger(ledger): FormattedLedger {
const ledgerVersion = parseInt(ledger.ledger_index, 10)
return removeUndefined(
Object.assign(
{
stateHash: ledger.account_hash,
closeTime: rippleTimeToISOTime(ledger.close_time),
closeTimeResolution: ledger.close_time_resolution,
closeFlags: ledger.close_flags,
ledgerHash: ledger.ledger_hash,
ledgerVersion: ledgerVersion,
parentLedgerHash: ledger.parent_hash,
parentCloseTime: rippleTimeToISOTime(ledger.parent_close_time),
totalDrops: ledger.total_coins,
transactionHash: ledger.transaction_hash
},
parseTransactions(ledger.transactions, ledgerVersion),
parseState(ledger.accountState)
)
)
const ledgerVersion = parseInt(ledger.ledger_index, 10);
return removeUndefined({
stateHash: ledger.account_hash,
closeTime: rippleTimeToISOTime(ledger.close_time),
closeTimeResolution: ledger.close_time_resolution,
closeFlags: ledger.close_flags,
ledgerHash: ledger.ledger_hash,
ledgerVersion,
parentLedgerHash: ledger.parent_hash,
parentCloseTime: rippleTimeToISOTime(ledger.parent_close_time),
totalDrops: ledger.total_coins,
transactionHash: ledger.transaction_hash,
...parseTransactions(ledger.transactions, ledgerVersion),
...parseState(ledger.accountState),
});
}

View File

@@ -1,35 +1,36 @@
import * as assert from 'assert'
import {parseTimestamp} from './utils'
import {parseMemos} from './utils'
import parseAmount from './amount'
import {removeUndefined} from '../../utils'
import {txFlags} from '../../common'
import * as assert from "assert";
import { txFlags } from "../../common";
import {
FormattedOrderSpecification,
OfferCreateTransaction
} from '../../common/types/objects/index'
OfferCreateTransaction,
} from "../../common/types/objects/index";
import { removeUndefined } from "../../utils";
const flags = txFlags.OfferCreate
import parseAmount from "./amount";
import { parseTimestamp, parseMemos } from "./utils";
const flags = txFlags.OfferCreate;
function parseOrder(tx: OfferCreateTransaction): FormattedOrderSpecification {
assert.ok(tx.TransactionType === 'OfferCreate')
assert.ok(tx.TransactionType === "OfferCreate");
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(tx.TakerGets)
const takerPaysAmount = parseAmount(tx.TakerPays)
const quantity = direction === 'buy' ? takerPaysAmount : takerGetsAmount
const totalPrice = direction === 'buy' ? takerGetsAmount : takerPaysAmount
const direction = (tx.Flags & flags.Sell) === 0 ? "buy" : "sell";
const takerGetsAmount = parseAmount(tx.TakerGets);
const takerPaysAmount = parseAmount(tx.TakerPays);
const quantity = direction === "buy" ? takerPaysAmount : takerGetsAmount;
const totalPrice = direction === "buy" ? takerGetsAmount : takerPaysAmount;
return removeUndefined({
memos: parseMemos(tx),
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
direction,
quantity,
totalPrice,
passive: (tx.Flags & flags.Passive) !== 0 || undefined,
immediateOrCancel: (tx.Flags & flags.ImmediateOrCancel) !== 0 || undefined,
fillOrKill: (tx.Flags & flags.FillOrKill) !== 0 || undefined,
expirationTime: parseTimestamp(tx.Expiration)
})
expirationTime: parseTimestamp(tx.Expiration),
});
}
export default parseOrder
export default parseOrder;

View File

@@ -1,42 +1,50 @@
import _ from 'lodash'
import {parseTimestamp, adjustQualityForXRP} from './utils'
import {removeUndefined} from '../../utils'
import _ from "lodash";
import {orderFlags} from './flags'
import parseAmount from './amount'
import {BookOffer} from '../../common/types/commands'
import {Amount, FormattedOrderSpecification} from '../../common/types/objects'
import { BookOffer } from "../../common/types/commands";
import {
Amount,
FormattedOrderSpecification,
} from "../../common/types/objects";
import { removeUndefined } from "../../utils";
export type FormattedOrderbookOrder = {
specification: FormattedOrderSpecification
import parseAmount from "./amount";
import { orderFlags } from "./flags";
import { parseTimestamp, adjustQualityForXRP } from "./utils";
export interface FormattedOrderbookOrder {
specification: FormattedOrderSpecification;
properties: {
maker: string
sequence: number
makerExchangeRate: string
}
maker: string;
sequence: number;
makerExchangeRate: string;
};
state?: {
fundedAmount: Amount
priceOfFundedAmount: Amount
}
data: BookOffer
fundedAmount: Amount;
priceOfFundedAmount: Amount;
};
data: BookOffer;
}
export function parseOrderbookOrder(data: BookOffer): FormattedOrderbookOrder {
const direction = (data.Flags & orderFlags.Sell) === 0 ? 'buy' : 'sell'
const takerGetsAmount = parseAmount(data.TakerGets)
const takerPaysAmount = parseAmount(data.TakerPays)
const quantity = direction === 'buy' ? takerPaysAmount : takerGetsAmount
const totalPrice = direction === 'buy' ? takerGetsAmount : takerPaysAmount
const direction = (data.Flags & orderFlags.Sell) === 0 ? "buy" : "sell";
const takerGetsAmount = parseAmount(data.TakerGets);
const takerPaysAmount = parseAmount(data.TakerPays);
const quantity = direction === "buy" ? takerPaysAmount : takerGetsAmount;
const totalPrice = direction === "buy" ? takerGetsAmount : takerPaysAmount;
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
// so we can omit those flags here
const specification: FormattedOrderSpecification = removeUndefined({
direction: direction,
quantity: quantity,
totalPrice: totalPrice,
direction,
quantity,
totalPrice,
passive: (data.Flags & orderFlags.Passive) !== 0 || undefined,
expirationTime: parseTimestamp(data.Expiration)
})
expirationTime: parseTimestamp(data.Expiration),
});
if (data.quality == null) {
throw new Error("parseOrderBookOrder: Could not find quality");
}
const properties = {
maker: data.Account,
@@ -45,19 +53,25 @@ export function parseOrderbookOrder(data: BookOffer): FormattedOrderbookOrder {
data.quality,
takerGetsAmount.currency,
takerPaysAmount.currency
)
}
),
};
const takerGetsFunded = data.taker_gets_funded
? parseAmount(data.taker_gets_funded)
: undefined
: undefined;
const takerPaysFunded = data.taker_pays_funded
? parseAmount(data.taker_pays_funded)
: undefined
: undefined;
const available = removeUndefined({
fundedAmount: takerGetsFunded,
priceOfFundedAmount: takerPaysFunded
})
const state = _.isEmpty(available) ? undefined : available
return removeUndefined({specification, properties, state, data})
priceOfFundedAmount: takerPaysFunded,
});
const state = _.isEmpty(available) ? undefined : available;
return removeUndefined({
specification,
properties,
state,
data,
}) as FormattedOrderbookOrder;
}

View File

@@ -1,31 +1,33 @@
import _ from 'lodash'
import parseAmount from './amount'
import {Amount, RippledAmount} from '../../common/types/objects'
import {Path, GetPaths, RippledPathsResponse} from '../pathfind-types'
import _ from "lodash";
import { Amount, RippledAmount } from "../../common/types/objects";
import { Path, GetPaths, RippledPathsResponse } from "../pathfind-types";
import parseAmount from "./amount";
function parsePaths(paths) {
return paths.map((steps) =>
steps.map((step) => _.omit(step, ['type', 'type_hex']))
)
steps.map((step) => _.omit(step, ["type", "type_hex"]))
);
}
function removeAnyCounterpartyEncoding(address: string, amount: Amount) {
return amount.counterparty === address
? _.omit(amount, 'counterparty')
: amount
? _.omit(amount, "counterparty")
: amount;
}
function createAdjustment(
address: string,
adjustmentWithoutAddress: object
): any {
const amountKey = Object.keys(adjustmentWithoutAddress)[0]
const amount = adjustmentWithoutAddress[amountKey]
const amountKey = Object.keys(adjustmentWithoutAddress)[0];
const amount = adjustmentWithoutAddress[amountKey];
return _.set(
{address: address},
{ address },
amountKey,
removeAnyCounterpartyEncoding(address, amount)
)
);
}
function parseAlternative(
@@ -39,28 +41,30 @@ function parseAlternative(
const amounts =
alternative.destination_amount != null
? {
source: {amount: parseAmount(alternative.source_amount)},
destination: {minAmount: parseAmount(alternative.destination_amount)}
source: { amount: parseAmount(alternative.source_amount) },
destination: {
minAmount: parseAmount(alternative.destination_amount),
},
}
: {
source: {maxAmount: parseAmount(alternative.source_amount)},
destination: {amount: parseAmount(destinationAmount)}
}
source: { maxAmount: parseAmount(alternative.source_amount) },
destination: { amount: parseAmount(destinationAmount) },
};
return {
source: createAdjustment(sourceAddress, amounts.source),
destination: createAdjustment(destinationAddress, amounts.destination),
paths: JSON.stringify(parsePaths(alternative.paths_computed))
}
paths: JSON.stringify(parsePaths(alternative.paths_computed)),
};
}
function parsePathfind(pathfindResult: RippledPathsResponse): GetPaths {
const sourceAddress = pathfindResult.source_account
const destinationAddress = pathfindResult.destination_account
const destinationAmount = pathfindResult.destination_amount
const sourceAddress = pathfindResult.source_account;
const destinationAddress = pathfindResult.destination_account;
const destinationAmount = pathfindResult.destination_amount;
return pathfindResult.alternatives.map((alt) =>
parseAlternative(sourceAddress, destinationAddress, destinationAmount, alt)
)
);
}
export default parsePathfind
export default parsePathfind;

View File

@@ -1,12 +1,15 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import {txFlags} from '../../common'
import parseAmount from './amount'
import {parseMemos} from './utils'
const claimFlags = txFlags.PaymentChannelClaim
import * as assert from "assert";
import { txFlags } from "../../common";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseMemos } from "./utils";
const claimFlags = txFlags.PaymentChannelClaim;
function parsePaymentChannelClaim(tx: any): object {
assert.ok(tx.TransactionType === 'PaymentChannelClaim')
assert.ok(tx.TransactionType === "PaymentChannelClaim");
return removeUndefined({
memos: parseMemos(tx),
@@ -16,8 +19,8 @@ function parsePaymentChannelClaim(tx: any): object {
signature: tx.Signature,
publicKey: tx.PublicKey,
renew: Boolean(tx.Flags & claimFlags.Renew) || undefined,
close: Boolean(tx.Flags & claimFlags.Close) || undefined
})
close: Boolean(tx.Flags & claimFlags.Close) || undefined,
});
}
export default parsePaymentChannelClaim
export default parsePaymentChannelClaim;

View File

@@ -1,10 +1,12 @@
import * as assert from 'assert'
import {parseTimestamp,parseMemos} from './utils'
import {removeUndefined} from '../../utils'
import parseAmount from './amount'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseTimestamp, parseMemos } from "./utils";
function parsePaymentChannelCreate(tx: any): object {
assert.ok(tx.TransactionType === 'PaymentChannelCreate')
assert.ok(tx.TransactionType === "PaymentChannelCreate");
return removeUndefined({
memos: parseMemos(tx),
@@ -14,8 +16,8 @@ function parsePaymentChannelCreate(tx: any): object {
publicKey: tx.PublicKey,
cancelAfter: tx.CancelAfter && parseTimestamp(tx.CancelAfter),
sourceTag: tx.SourceTag,
destinationTag: tx.DestinationTag
})
destinationTag: tx.DestinationTag,
});
}
export default parsePaymentChannelCreate
export default parsePaymentChannelCreate;

View File

@@ -1,17 +1,19 @@
import * as assert from 'assert'
import {parseTimestamp,parseMemos} from './utils'
import {removeUndefined} from '../../utils'
import parseAmount from './amount'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import { parseTimestamp, parseMemos } from "./utils";
function parsePaymentChannelFund(tx: any): object {
assert.ok(tx.TransactionType === 'PaymentChannelFund')
assert.ok(tx.TransactionType === "PaymentChannelFund");
return removeUndefined({
memos: parseMemos(tx),
channel: tx.Channel,
amount: parseAmount(tx.Amount).value,
expiration: tx.Expiration && parseTimestamp(tx.Expiration)
})
expiration: tx.Expiration && parseTimestamp(tx.Expiration),
});
}
export default parsePaymentChannelFund
export default parsePaymentChannelFund;

View File

@@ -1,25 +1,24 @@
import {parseTimestamp, parseMemos} from './utils'
import {removeUndefined, dropsToXrp} from '../../utils'
import { PayChannel } from '../../models/ledger'
import { PayChannel } from "../../models/ledger";
import { removeUndefined, dropsToXrp } from "../../utils";
export type FormattedPaymentChannel = {
account: string
amount: string
balance: string
publicKey: string
destination: string
settleDelay: number
expiration?: string
cancelAfter?: string
sourceTag?: number
destinationTag?: number
previousAffectingTransactionID: string
previousAffectingTransactionLedgerVersion: number
import { parseTimestamp, parseMemos } from "./utils";
export interface FormattedPaymentChannel {
account: string;
amount: string;
balance: string;
publicKey: string;
destination: string;
settleDelay: number;
expiration?: string;
cancelAfter?: string;
sourceTag?: number;
destinationTag?: number;
previousAffectingTransactionID: string;
previousAffectingTransactionLedgerVersion: number;
}
export function parsePaymentChannel(
data: PayChannel
): FormattedPaymentChannel {
export function parsePaymentChannel(data: PayChannel): FormattedPaymentChannel {
return removeUndefined({
memos: parseMemos(data),
account: data.Account,
@@ -33,6 +32,6 @@ export function parsePaymentChannel(
sourceTag: data.SourceTag,
destinationTag: data.DestinationTag,
previousAffectingTransactionID: data.PreviousTxnID,
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
})
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq,
});
}

View File

@@ -1,27 +1,30 @@
import _ from 'lodash'
import * as assert from 'assert'
import * as utils from './utils'
import {txFlags} from '../../common'
import {removeUndefined} from '../../utils'
import parseAmount from './amount'
import * as assert from "assert";
import _ from "lodash";
import { txFlags } from "../../common";
import { removeUndefined } from "../../utils";
import parseAmount from "./amount";
import * as utils from "./utils";
function isNoDirectRipple(tx) {
return (tx.Flags & txFlags.Payment.NoRippleDirect) !== 0
return (tx.Flags & txFlags.Payment.NoRippleDirect) !== 0;
}
function isQualityLimited(tx) {
return (tx.Flags & txFlags.Payment.LimitQuality) !== 0
return (tx.Flags & txFlags.Payment.LimitQuality) !== 0;
}
function removeGenericCounterparty(amount, address) {
return amount.counterparty === address
? _.omit(amount, 'counterparty')
: amount
? _.omit(amount, "counterparty")
: amount;
}
// Payment specification
function parsePayment(tx: any): object {
assert.ok(tx.TransactionType === 'Payment')
assert.ok(tx.TransactionType === "Payment");
const source = {
address: tx.Account,
@@ -29,17 +32,17 @@ function parsePayment(tx: any): object {
parseAmount(tx.SendMax || tx.Amount),
tx.Account
),
tag: tx.SourceTag
}
tag: tx.SourceTag,
};
const destination: {
address: string
tag: number | undefined
address: string;
tag: number | undefined;
} = {
address: tx.Destination,
tag: tx.DestinationTag
tag: tx.DestinationTag,
// Notice that `amount` is omitted to prevent misinterpretation
}
};
return removeUndefined({
source: removeUndefined(source),
@@ -49,8 +52,8 @@ function parsePayment(tx: any): object {
paths: tx.Paths ? JSON.stringify(tx.Paths) : undefined,
allowPartialPayment: utils.isPartialPayment(tx) || undefined,
noDirectRipple: isNoDirectRipple(tx) || undefined,
limitQuality: isQualityLimited(tx) || undefined
})
limitQuality: isQualityLimited(tx) || undefined,
});
}
export default parsePayment
export default parsePayment;

View File

@@ -1,65 +1,69 @@
import _ from 'lodash'
import * as assert from 'assert'
import {constants} from '../../common'
const AccountFlags = constants.AccountFlags
import parseFields from './fields'
import * as assert from "assert";
import _ from "lodash";
import { constants } from "../../common";
import parseFields from "./fields";
const AccountFlags = constants.AccountFlags;
function getAccountRootModifiedNode(tx: any) {
const modifiedNodes = tx.meta.AffectedNodes.filter(
(node) => node.ModifiedNode.LedgerEntryType === 'AccountRoot'
)
assert.ok(modifiedNodes.length === 1)
return modifiedNodes[0].ModifiedNode
(node) => node.ModifiedNode.LedgerEntryType === "AccountRoot"
);
assert.ok(modifiedNodes.length === 1);
return modifiedNodes[0].ModifiedNode;
}
function parseFlags(tx: any): any {
const settings: any = {}
if (tx.TransactionType !== 'AccountSet') {
return settings
const settings: any = {};
if (tx.TransactionType !== "AccountSet") {
return settings;
}
const node = getAccountRootModifiedNode(tx)
const oldFlags = _.get(node.PreviousFields, 'Flags')
const newFlags = _.get(node.FinalFields, 'Flags')
const node = getAccountRootModifiedNode(tx);
const oldFlags = _.get(node.PreviousFields, "Flags");
const newFlags = _.get(node.FinalFields, "Flags");
if (oldFlags != null && newFlags != null) {
const changedFlags = oldFlags ^ newFlags
const setFlags = newFlags & changedFlags
const clearedFlags = oldFlags & changedFlags
Object.entries(AccountFlags).forEach(entry => {
const changedFlags = oldFlags ^ newFlags;
const setFlags = newFlags & changedFlags;
const clearedFlags = oldFlags & changedFlags;
Object.entries(AccountFlags).forEach((entry) => {
const [flagName, flagValue] = entry;
if (setFlags & flagValue) {
settings[flagName] = true
settings[flagName] = true;
} else if (clearedFlags & flagValue) {
settings[flagName] = false
settings[flagName] = false;
}
})
});
}
// enableTransactionIDTracking requires a special case because it
// does not affect the Flags field; instead it adds/removes a field called
// "AccountTxnID" to/from the account root.
const oldField = _.get(node.PreviousFields, 'AccountTxnID')
const newField = _.get(node.FinalFields, 'AccountTxnID')
const oldField = _.get(node.PreviousFields, "AccountTxnID");
const newField = _.get(node.FinalFields, "AccountTxnID");
if (newField && !oldField) {
settings.enableTransactionIDTracking = true
settings.enableTransactionIDTracking = true;
} else if (oldField && !newField) {
settings.enableTransactionIDTracking = false
settings.enableTransactionIDTracking = false;
}
return settings
return settings;
}
function parseSettings(tx: any) {
const txType = tx.TransactionType
const txType = tx.TransactionType;
assert.ok(
txType === 'AccountSet' ||
txType === 'SetRegularKey' ||
txType === 'SignerListSet'
)
txType === "AccountSet" ||
txType === "SetRegularKey" ||
txType === "SignerListSet"
);
return Object.assign({}, parseFlags(tx), parseFields(tx))
return { ...parseFlags(tx), ...parseFields(tx) };
}
export default parseSettings
export default parseSettings;

View File

@@ -1,13 +1,15 @@
import * as assert from 'assert'
import {removeUndefined} from '../../utils'
import {parseMemos} from './utils'
import * as assert from "assert";
import { removeUndefined } from "../../utils";
import { parseMemos } from "./utils";
function parseTicketCreate(tx: any): object {
assert.ok(tx.TransactionType === 'TicketCreate')
assert.ok(tx.TransactionType === "TicketCreate");
return removeUndefined({
memos: parseMemos(tx),
ticketCount: tx.TicketCount
})
ticketCount: tx.TicketCount,
});
}
export default parseTicketCreate
export default parseTicketCreate;

View File

@@ -1,59 +1,58 @@
import {parseOutcome} from './utils'
import {removeUndefined} from '../../utils'
import { removeUndefined } from "../../utils";
import parseSettings from './settings'
import parseAccountDelete from './account-delete'
import parseCheckCancel from './check-cancel'
import parseCheckCash from './check-cash'
import parseCheckCreate from './check-create'
import parseDepositPreauth from './deposit-preauth'
import parseEscrowCancellation from './escrow-cancellation'
import parseEscrowCreation from './escrow-creation'
import parseEscrowExecution from './escrow-execution'
import parseOrderCancellation from './cancellation'
import parseOrder from './order'
import parsePayment from './payment'
import parsePaymentChannelClaim from './payment-channel-claim'
import parsePaymentChannelCreate from './payment-channel-create'
import parsePaymentChannelFund from './payment-channel-fund'
import parseTicketCreate from './ticket-create'
import parseTrustline from './trustline'
import parseAmendment from './amendment' // pseudo-transaction
import parseFeeUpdate from './fee-update' // pseudo-transaction
import parseAccountDelete from "./account-delete";
import parseAmendment from "./amendment"; // pseudo-transaction
import parseOrderCancellation from "./cancellation";
import parseCheckCancel from "./check-cancel";
import parseCheckCash from "./check-cash";
import parseCheckCreate from "./check-create";
import parseDepositPreauth from "./deposit-preauth";
import parseEscrowCancellation from "./escrow-cancellation";
import parseEscrowCreation from "./escrow-creation";
import parseEscrowExecution from "./escrow-execution";
import parseFeeUpdate from "./fee-update"; // pseudo-transaction
import parseOrder from "./order";
import parsePayment from "./payment";
import parsePaymentChannelClaim from "./payment-channel-claim";
import parsePaymentChannelCreate from "./payment-channel-create";
import parsePaymentChannelFund from "./payment-channel-fund";
import parseSettings from "./settings";
import parseTicketCreate from "./ticket-create";
import parseTrustline from "./trustline";
import { parseOutcome } from "./utils";
function parseTransactionType(type) {
// Ordering matches https://developers.ripple.com/transaction-types.html
const mapping = {
AccountSet: 'settings',
AccountDelete: 'accountDelete',
CheckCancel: 'checkCancel',
CheckCash: 'checkCash',
CheckCreate: 'checkCreate',
DepositPreauth: 'depositPreauth',
EscrowCancel: 'escrowCancellation',
EscrowCreate: 'escrowCreation',
EscrowFinish: 'escrowExecution',
OfferCancel: 'orderCancellation',
OfferCreate: 'order',
Payment: 'payment',
PaymentChannelClaim: 'paymentChannelClaim',
PaymentChannelCreate: 'paymentChannelCreate',
PaymentChannelFund: 'paymentChannelFund',
SetRegularKey: 'settings',
SignerListSet: 'settings',
TicketCreate: 'ticketCreate',
TrustSet: 'trustline',
AccountSet: "settings",
AccountDelete: "accountDelete",
CheckCancel: "checkCancel",
CheckCash: "checkCash",
CheckCreate: "checkCreate",
DepositPreauth: "depositPreauth",
EscrowCancel: "escrowCancellation",
EscrowCreate: "escrowCreation",
EscrowFinish: "escrowExecution",
OfferCancel: "orderCancellation",
OfferCreate: "order",
Payment: "payment",
PaymentChannelClaim: "paymentChannelClaim",
PaymentChannelCreate: "paymentChannelCreate",
PaymentChannelFund: "paymentChannelFund",
SetRegularKey: "settings",
SignerListSet: "settings",
TicketCreate: "ticketCreate",
TrustSet: "trustline",
EnableAmendment: 'amendment', // pseudo-transaction
SetFee: 'feeUpdate' // pseudo-transaction
}
return mapping[type] || null
EnableAmendment: "amendment", // pseudo-transaction
SetFee: "feeUpdate", // pseudo-transaction
};
return mapping[type] || null;
}
// includeRawTransaction: undefined by default (getTransaction)
function parseTransaction(tx: any, includeRawTransaction: boolean): any {
const type = parseTransactionType(tx.TransactionType)
const type = parseTransactionType(tx.TransactionType);
const mapping = {
settings: parseSettings,
accountDelete: parseAccountDelete,
@@ -74,31 +73,31 @@ function parseTransaction(tx: any, includeRawTransaction: boolean): any {
trustline: parseTrustline,
amendment: parseAmendment, // pseudo-transaction
feeUpdate: parseFeeUpdate // pseudo-transaction
}
const parser: Function = mapping[type]
feeUpdate: parseFeeUpdate, // pseudo-transaction
};
const parser: Function = mapping[type];
const specification = parser
? parser(tx)
: {
UNAVAILABLE: 'Unrecognized transaction type.',
UNAVAILABLE: "Unrecognized transaction type.",
SEE_RAW_TRANSACTION:
'Since this type is unrecognized, `rawTransaction` is included in this response.'
}
"Since this type is unrecognized, `rawTransaction` is included in this response.",
};
if (!parser) {
includeRawTransaction = true
includeRawTransaction = true;
}
const outcome = parseOutcome(tx)
const outcome = parseOutcome(tx);
return removeUndefined({
type: type,
type,
address: tx.Account,
sequence: tx.Sequence,
id: tx.hash,
specification: removeUndefined(specification),
outcome: outcome ? removeUndefined(outcome) : undefined,
rawTransaction: includeRawTransaction ? JSON.stringify(tx) : undefined
})
rawTransaction: includeRawTransaction ? JSON.stringify(tx) : undefined,
});
}
export default parseTransaction
export default parseTransaction;

View File

@@ -1,21 +1,24 @@
import * as assert from 'assert'
import {parseQuality, parseMemos} from './utils'
import {txFlags} from '../../common'
import {removeUndefined} from '../../utils'
const flags = txFlags.TrustSet
import * as assert from "assert";
import { txFlags } from "../../common";
import { removeUndefined } from "../../utils";
import { parseQuality, parseMemos } from "./utils";
const flags = txFlags.TrustSet;
function parseFlag(flagsValue, trueValue, falseValue) {
if (flagsValue & trueValue) {
return true
return true;
}
if (flagsValue & falseValue) {
return false
return false;
}
return undefined
return undefined;
}
function parseTrustline(tx: any): object {
assert.ok(tx.TransactionType === 'TrustSet')
assert.ok(tx.TransactionType === "TrustSet");
return removeUndefined({
limit: tx.LimitAmount.value,
@@ -30,8 +33,8 @@ function parseTrustline(tx: any): object {
flags.ClearNoRipple
),
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),
authorized: parseFlag(tx.Flags, flags.SetAuth, 0)
})
authorized: parseFlag(tx.Flags, flags.SetAuth, 0),
});
}
export default parseTrustline
export default parseTrustline;

View File

@@ -1,32 +1,33 @@
import transactionParser from 'ripple-lib-transactionparser'
import BigNumber from 'bignumber.js'
import parseAmount from './amount'
import BigNumber from "bignumber.js";
import transactionParser from "ripple-lib-transactionparser";
import {Amount, Memo} from '../../common/types/objects'
import {txFlags} from '../../common'
import {removeUndefined, dropsToXrp, rippleTimeToISOTime} from '../../utils'
import { txFlags } from "../../common";
import { Amount, Memo } from "../../common/types/objects";
import { removeUndefined, dropsToXrp, rippleTimeToISOTime } from "../../utils";
type OfferDescription = {
direction: string,
quantity: any,
totalPrice: any,
sequence: number,
status: string,
makerExchangeRate: string
import parseAmount from "./amount";
interface OfferDescription {
direction: string;
quantity: any;
totalPrice: any;
sequence: number;
status: string;
makerExchangeRate: string;
}
type Orderbook = {
[key: string]: OfferDescription[]
interface Orderbook {
[key: string]: OfferDescription[];
}
type BalanceSheetItem = {
counterparty: string,
currency: string,
value: string
interface BalanceSheetItem {
counterparty: string;
currency: string;
value: string;
}
type BalanceSheet = {
[key: string]: BalanceSheetItem[]
interface BalanceSheet {
[key: string]: BalanceSheetItem[];
}
function adjustQualityForXRP(
@@ -36,77 +37,79 @@ function adjustQualityForXRP(
) {
// quality = takerPays.value/takerGets.value
// using drops (1e-6 XRP) for XRP values
const numeratorShift = takerPaysCurrency === 'XRP' ? -6 : 0
const denominatorShift = takerGetsCurrency === 'XRP' ? -6 : 0
const shift = numeratorShift - denominatorShift
const numeratorShift = takerPaysCurrency === "XRP" ? -6 : 0;
const denominatorShift = takerGetsCurrency === "XRP" ? -6 : 0;
const shift = numeratorShift - denominatorShift;
return shift === 0
? quality
: new BigNumber(quality).shiftedBy(shift).toString()
: new BigNumber(quality).shiftedBy(shift).toString();
}
function parseQuality(quality?: number | null): number | undefined {
if (typeof quality !== 'number') {
return undefined
if (typeof quality !== "number") {
return undefined;
}
return new BigNumber(quality).shiftedBy(-9).toNumber()
return new BigNumber(quality).shiftedBy(-9).toNumber();
}
function parseTimestamp(rippleTime?: number | null): string | undefined {
if (typeof rippleTime !== 'number') {
return undefined
if (typeof rippleTime !== "number") {
return undefined;
}
return rippleTimeToISOTime(rippleTime)
return rippleTimeToISOTime(rippleTime);
}
function removeEmptyCounterparty(amount) {
if (amount.counterparty === '') {
delete amount.counterparty
if (amount.counterparty === "") {
delete amount.counterparty;
}
}
function removeEmptyCounterpartyInBalanceChanges(balanceChanges: BalanceSheet) {
Object.entries(balanceChanges).forEach(([_, changes]) => {
changes.forEach(removeEmptyCounterparty)
})
changes.forEach(removeEmptyCounterparty);
});
}
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges: Orderbook) {
function removeEmptyCounterpartyInOrderbookChanges(
orderbookChanges: Orderbook
) {
Object.entries(orderbookChanges).forEach(([_, changes]) => {
changes.forEach((change) => {
Object.entries(change).forEach(removeEmptyCounterparty)
})
})
Object.entries(change).forEach(removeEmptyCounterparty);
});
});
}
function isPartialPayment(tx: any) {
return (tx.Flags & txFlags.Payment.PartialPayment) !== 0
return (tx.Flags & txFlags.Payment.PartialPayment) !== 0;
}
function parseDeliveredAmount(tx: any): Amount | void {
if (
tx.TransactionType !== 'Payment' ||
tx.meta.TransactionResult !== 'tesSUCCESS'
tx.TransactionType !== "Payment" ||
tx.meta.TransactionResult !== "tesSUCCESS"
) {
return undefined
return undefined;
}
if (tx.meta.delivered_amount && tx.meta.delivered_amount === 'unavailable') {
return undefined
if (tx.meta.delivered_amount && tx.meta.delivered_amount === "unavailable") {
return undefined;
}
// parsable delivered_amount
if (tx.meta.delivered_amount) {
return parseAmount(tx.meta.delivered_amount)
return parseAmount(tx.meta.delivered_amount);
}
// DeliveredAmount only present on partial payments
if (tx.meta.DeliveredAmount) {
return parseAmount(tx.meta.DeliveredAmount)
return parseAmount(tx.meta.DeliveredAmount);
}
// no partial payment flag, use tx.Amount
if (tx.Amount && !isPartialPayment(tx)) {
return parseAmount(tx.Amount)
return parseAmount(tx.Amount);
}
// DeliveredAmount field was introduced at
@@ -116,52 +119,52 @@ function parseDeliveredAmount(tx: any): Amount | void {
// transferred with a partial payment before
// that date must be derived from metadata.
if (tx.Amount && tx.ledger_index > 4594094) {
return parseAmount(tx.Amount)
return parseAmount(tx.Amount);
}
return undefined
return undefined;
}
function parseOutcome(tx: any): any | undefined {
const metadata = tx.meta || tx.metaData
const metadata = tx.meta || tx.metaData;
if (!metadata) {
return undefined
return undefined;
}
const balanceChanges = transactionParser.parseBalanceChanges(metadata)
const orderbookChanges = transactionParser.parseOrderbookChanges(metadata)
const channelChanges = transactionParser.parseChannelChanges(metadata)
const balanceChanges = transactionParser.parseBalanceChanges(metadata);
const orderbookChanges = transactionParser.parseOrderbookChanges(metadata);
const channelChanges = transactionParser.parseChannelChanges(metadata);
removeEmptyCounterpartyInBalanceChanges(balanceChanges)
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges)
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);
return removeUndefined({
result: tx.meta.TransactionResult,
timestamp: parseTimestamp(tx.date),
fee: dropsToXrp(tx.Fee),
balanceChanges: balanceChanges,
orderbookChanges: orderbookChanges,
channelChanges: channelChanges,
balanceChanges,
orderbookChanges,
channelChanges,
ledgerVersion: tx.ledger_index,
indexInLedger: tx.meta.TransactionIndex,
deliveredAmount: parseDeliveredAmount(tx)
})
deliveredAmount: parseDeliveredAmount(tx),
});
}
function hexToString(hex: string): string | undefined {
return hex ? Buffer.from(hex, 'hex').toString('utf-8') : undefined
return hex ? Buffer.from(hex, "hex").toString("utf-8") : undefined;
}
function parseMemos(tx: any): Array<Memo> | undefined {
function parseMemos(tx: any): Memo[] | undefined {
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
return undefined
return undefined;
}
return tx.Memos.map((m) => {
return removeUndefined({
type: m.Memo.parsed_memo_type || hexToString(m.Memo.MemoType),
format: m.Memo.parsed_memo_format || hexToString(m.Memo.MemoFormat),
data: m.Memo.parsed_memo_data || hexToString(m.Memo.MemoData)
})
})
data: m.Memo.parsed_memo_data || hexToString(m.Memo.MemoData),
});
});
}
export {
@@ -171,5 +174,5 @@ export {
hexToString,
parseTimestamp,
adjustQualityForXRP,
isPartialPayment
}
isPartialPayment,
};

View File

@@ -3,64 +3,64 @@ import {
RippledAmount,
Adjustment,
MaxAdjustment,
MinAdjustment
} from '../common/types/objects'
MinAdjustment,
} from "../common/types/objects";
// Amount where counterparty and value are optional
export type LaxLaxAmount = {
currency: string
value?: string
issuer?: string
counterparty?: string
export interface LaxLaxAmount {
currency: string;
value?: string;
issuer?: string;
counterparty?: string;
}
export type Path = {
source: Adjustment | MaxAdjustment
destination: Adjustment | MinAdjustment
paths: string
export interface Path {
source: Adjustment | MaxAdjustment;
destination: Adjustment | MinAdjustment;
paths: string;
}
export type GetPaths = Array<Path>
export type GetPaths = Path[];
export type PathFind = {
export interface PathFind {
source: {
address: string
amount?: Amount
currencies?: Array<{currency: string; counterparty?: string}>
}
address: string;
amount?: Amount;
currencies?: Array<{ currency: string; counterparty?: string }>;
};
destination: {
address: string
amount: LaxLaxAmount
}
address: string;
amount: LaxLaxAmount;
};
}
export type PathFindRequest = {
command: string
source_account: string
destination_amount: RippledAmount
destination_account: string
source_currencies?: {currency: string; issuer?: string}[]
send_max?: RippledAmount
export interface PathFindRequest {
command: string;
source_account: string;
destination_amount: RippledAmount;
destination_account: string;
source_currencies?: Array<{ currency: string; issuer?: string }>;
send_max?: RippledAmount;
}
export type RippledPathsResponse = {
export interface RippledPathsResponse {
alternatives: Array<{
paths_computed: Array<
Array<{
type: number
type_hex: string
account?: string
issuer?: string
currency?: string
type: number;
type_hex: string;
account?: string;
issuer?: string;
currency?: string;
}>
>
source_amount: RippledAmount
}>
type: string
destination_account: string
destination_amount: RippledAmount
destination_currencies?: Array<string>
source_account: string
source_currencies?: Array<{currency: string}>
full_reply?: boolean
>;
source_amount: RippledAmount;
}>;
type: string;
destination_account: string;
destination_amount: RippledAmount;
destination_currencies?: string[];
source_account: string;
source_currencies?: Array<{ currency: string }>;
full_reply?: boolean;
}

View File

@@ -1,87 +1,90 @@
import _ from 'lodash'
import BigNumber from 'bignumber.js'
import {getXRPBalance, renameCounterpartyToIssuer} from './utils'
import {
validate,
errors
} from '../common'
import {toRippledAmount, xrpToDrops, dropsToXrp} from '../utils'
import {Connection} from '../client'
import parsePathfind from './parse/pathfind'
import {RippledAmount, Amount} from '../common/types/objects'
import BigNumber from "bignumber.js";
import _ from "lodash";
import type { Client } from "..";
import { Connection } from "../client";
import { validate, errors } from "../common";
import { RippledAmount, Amount } from "../common/types/objects";
import { RipplePathFindRequest } from "../models/methods";
import { toRippledAmount, xrpToDrops, dropsToXrp } from "../utils";
import parsePathfind from "./parse/pathfind";
import {
GetPaths,
PathFind,
RippledPathsResponse,
PathFindRequest
} from './pathfind-types'
import {Client} from '..'
import { RipplePathFindRequest } from '../models/methods'
const NotFoundError = errors.NotFoundError
const ValidationError = errors.ValidationError
PathFindRequest,
} from "./pathfind-types";
import { getXRPBalance, renameCounterpartyToIssuer } from "./utils";
const NotFoundError = errors.NotFoundError;
const ValidationError = errors.ValidationError;
function addParams(
request: PathFindRequest,
result: RippledPathsResponse
): RippledPathsResponse {
return _.defaults(
Object.assign({}, result, {
{
...result,
source_account: request.source_account,
source_currencies: request.source_currencies
}),
{destination_amount: request.destination_amount}
)
source_currencies: request.source_currencies,
},
{ destination_amount: request.destination_amount }
);
}
function requestPathFind(
connection: Connection,
pathfind: PathFind
): Promise<RippledPathsResponse> {
const destinationAmount: Amount = Object.assign(
{
// This is converted back to drops by toRippledAmount()
value:
pathfind.destination.amount.currency === 'XRP' ? dropsToXrp('-1') : '-1'
},
pathfind.destination.amount
)
const destinationAmount: Amount = {
// This is converted back to drops by toRippledAmount()
value:
pathfind.destination.amount.currency === "XRP" ? dropsToXrp("-1") : "-1",
...pathfind.destination.amount,
};
const request: RipplePathFindRequest = {
command: 'ripple_path_find',
command: "ripple_path_find",
source_account: pathfind.source.address,
destination_account: pathfind.destination.address,
// @ts-ignore
destination_amount: destinationAmount
}
// @ts-expect-error
destination_amount: destinationAmount,
};
if (
typeof request.destination_amount === 'object' &&
typeof request.destination_amount === "object" &&
!request.destination_amount.issuer
) {
// Convert blank issuer to sender's address
// (Ripple convention for 'any issuer')
// https://developers.ripple.com/payment.html#special-issuer-values-for-sendmax-and-amount
request.destination_amount.issuer = request.destination_account
request.destination_amount.issuer = request.destination_account;
}
if (pathfind.source.currencies && pathfind.source.currencies.length > 0) {
// @ts-ignore
// @ts-expect-error
request.source_currencies = pathfind.source.currencies.map((amount) =>
renameCounterpartyToIssuer(amount)
)
);
}
if (pathfind.source.amount) {
if (pathfind.destination.amount.value != null) {
throw new ValidationError(
'Cannot specify both source.amount' +
' and destination.amount.value in getPaths'
)
"Cannot specify both source.amount" +
" and destination.amount.value in getPaths"
);
}
// @ts-ignore
request.send_max = toRippledAmount(pathfind.source.amount)
if (typeof request.send_max !== 'string' && !request.send_max.issuer) {
request.send_max.issuer = pathfind.source.address
// @ts-expect-error
request.send_max = toRippledAmount(pathfind.source.amount);
if (
request.send_max != null &&
typeof request.send_max !== "string" &&
!request.send_max.issuer
) {
request.send_max.issuer = pathfind.source.address;
}
}
// @ts-ignore
return connection.request(request).then((paths) => addParams(request, paths))
// @ts-expect-error
return connection.request(request).then((paths) => addParams(request, paths));
}
function addDirectXrpPath(
@@ -89,22 +92,22 @@ function addDirectXrpPath(
xrpBalance: string
): RippledPathsResponse {
// Add XRP "path" only if the source acct has enough XRP to make the payment
const destinationAmount = paths.destination_amount
// @ts-ignore: destinationAmount can be a currency amount object! Fix!
const destinationAmount = paths.destination_amount;
// @ts-expect-error: destinationAmount can be a currency amount object! Fix!
if (new BigNumber(xrpBalance).isGreaterThanOrEqualTo(destinationAmount)) {
paths.alternatives.unshift({
paths_computed: [],
source_amount: paths.destination_amount
})
source_amount: paths.destination_amount,
});
}
return paths
return paths;
}
function isRippledIOUAmount(amount: RippledAmount) {
// rippled XRP amounts are specified as decimal strings
return (
typeof amount === 'object' && amount.currency && amount.currency !== 'XRP'
)
typeof amount === "object" && amount.currency && amount.currency !== "XRP"
);
}
function conditionallyAddDirectXRPPath(
@@ -114,13 +117,14 @@ function conditionallyAddDirectXRPPath(
): Promise<RippledPathsResponse> {
if (
isRippledIOUAmount(paths.destination_amount) ||
!paths.destination_currencies.includes('XRP')
(paths.destination_currencies &&
!paths.destination_currencies.includes("XRP"))
) {
return Promise.resolve(paths)
return Promise.resolve(paths);
}
return getXRPBalance(client, address, undefined).then((xrpBalance) =>
addDirectXrpPath(paths, xrpBalance)
)
);
}
function filterSourceFundsLowPaths(
@@ -129,74 +133,72 @@ function filterSourceFundsLowPaths(
): RippledPathsResponse {
if (
pathfind.source.amount &&
pathfind.destination.amount.value == null &&
paths.alternatives
paths.alternatives &&
pathfind.destination.amount.value == null
) {
paths.alternatives = paths.alternatives.filter((alt) => {
if (!alt.source_amount) {
return false
return false;
}
if (pathfind.source.amount === undefined) {
return false;
}
const pathfindSourceAmountValue = new BigNumber(
pathfind.source.amount.currency === 'XRP'
pathfind.source.amount.currency === "XRP"
? xrpToDrops(pathfind.source.amount.value)
: pathfind.source.amount.value
)
);
const altSourceAmountValue = new BigNumber(
typeof alt.source_amount === 'string'
typeof alt.source_amount === "string"
? alt.source_amount
: alt.source_amount.value
)
return altSourceAmountValue.eq(pathfindSourceAmountValue)
})
);
return altSourceAmountValue.eq(pathfindSourceAmountValue);
});
}
return paths
return paths;
}
function formatResponse(pathfind: PathFind, paths: RippledPathsResponse) {
if (paths.alternatives && paths.alternatives.length > 0) {
return parsePathfind(paths)
return parsePathfind(paths);
}
if (
paths.destination_currencies != null &&
!paths.destination_currencies.includes(
pathfind.destination.amount.currency
)
!paths.destination_currencies.includes(pathfind.destination.amount.currency)
) {
throw new NotFoundError(
'No paths found. ' +
'The destination_account does not accept ' +
pathfind.destination.amount.currency +
', they only accept: ' +
paths.destination_currencies.join(', ')
)
`${"No paths found. " + "The destination_account does not accept "}${
pathfind.destination.amount.currency
}, they only accept: ${paths.destination_currencies.join(", ")}`
);
} else if (paths.source_currencies && paths.source_currencies.length > 0) {
throw new NotFoundError(
'No paths found. Please ensure' +
' that the source_account has sufficient funds to execute' +
' the payment in one of the specified source_currencies. If it does' +
' there may be insufficient liquidity in the network to execute' +
' this payment right now'
)
"No paths found. Please ensure" +
" that the source_account has sufficient funds to execute" +
" the payment in one of the specified source_currencies. If it does" +
" there may be insufficient liquidity in the network to execute" +
" this payment right now"
);
} else {
throw new NotFoundError(
'No paths found.' +
' Please ensure that the source_account has sufficient funds to' +
' execute the payment. If it does there may be insufficient liquidity' +
' in the network to execute this payment right now'
)
"No paths found." +
" Please ensure that the source_account has sufficient funds to" +
" execute the payment. If it does there may be insufficient liquidity" +
" in the network to execute this payment right now"
);
}
}
function getPaths(this: Client, pathfind: PathFind): Promise<GetPaths> {
validate.getPaths({pathfind})
validate.getPaths({ pathfind });
const address = pathfind.source.address
const address = pathfind.source.address;
return requestPathFind(this.connection, pathfind)
.then((paths) =>
conditionallyAddDirectXRPPath(this, address, paths)
)
.then((paths) => conditionallyAddDirectXRPPath(this, address, paths))
.then((paths) => filterSourceFundsLowPaths(pathfind, paths))
.then((paths) => formatResponse(pathfind, paths))
.then((paths) => formatResponse(pathfind, paths));
}
export default getPaths
export default getPaths;

View File

@@ -1,18 +1,23 @@
import _ from 'lodash'
import {validate, ensureClassicAddress} from '../common'
import parseAccountTrustline from './parse/account-trustline'
import {Client} from '..'
import {FormattedTrustline} from '../common/types/objects/trustlines'
import _ from "lodash";
export type GetTrustlinesOptions = {
counterparty?: string
currency?: string
limit?: number
ledgerVersion?: number
import type { Client } from "..";
import { validate, ensureClassicAddress } from "../common";
import { FormattedTrustline } from "../common/types/objects";
import parseAccountTrustline from "./parse/account-trustline";
export interface GetTrustlinesOptions {
counterparty?: string;
currency?: string;
limit?: number;
ledgerVersion?: number;
}
function currencyFilter(currency: string, trustline: FormattedTrustline) {
return currency === null || trustline.specification.currency === currency
function currencyFilter(
currency: string | null,
trustline: FormattedTrustline
) {
return currency === null || trustline.specification.currency === currency;
}
async function getTrustlines(
@@ -21,25 +26,26 @@ async function getTrustlines(
options: GetTrustlinesOptions = {}
): Promise<FormattedTrustline[]> {
// 1. Validate
validate.getTrustlines({address, options})
validate.getTrustlines({ address, options });
// Only support retrieving trustlines without a tag,
// since it does not make sense to filter trustlines
// by tag.
address = ensureClassicAddress(address)
address = ensureClassicAddress(address);
// 2. Make Request
const responses = await this.requestAll({command: 'account_lines',
const responses = await this.requestAll({
command: "account_lines",
account: address,
ledger_index: options.ledgerVersion ?? 'validated',
ledger_index: options.ledgerVersion ?? "validated",
limit: options.limit,
peer: options.counterparty
})
peer: options.counterparty,
});
// 3. Return Formatted Response
const trustlines = _.flatMap(responses, (response) => response.result.lines)
const trustlines = _.flatMap(responses, (response) => response.result.lines);
return trustlines.map(parseAccountTrustline).filter((trustline) => {
return currencyFilter(options.currency || null, trustline)
})
return currencyFilter(options.currency ?? null, trustline);
});
}
export default getTrustlines
export default getTrustlines;

View File

@@ -1,23 +1,27 @@
import _ from 'lodash'
import * as assert from 'assert'
import * as common from '../common'
import {Connection} from '../client'
import {FormattedTransactionType} from '../transaction/types'
import {Issue} from '../common/types/objects'
import {Client} from '..'
import {AccountInfoRequest} from '../models/methods'
import {dropsToXrp} from '..'
import * as assert from "assert";
export type RecursiveData = {
marker: string
results: Array<any>
import _ from "lodash";
import { Client, dropsToXrp } from "..";
import { Connection } from "../client";
import * as common from "../common";
import { Issue } from "../common/types/objects";
import { AccountInfoRequest } from "../models/methods";
import { FormattedTransactionType } from "../transaction/types";
export interface RecursiveData {
marker: string;
results: any[];
}
export type Getter = (marker?: string, limit?: number) => Promise<RecursiveData>
export type Getter = (
marker?: string,
limit?: number
) => Promise<RecursiveData>;
function clamp(value: number, min: number, max: number): number {
assert.ok(min <= max, 'Illegal clamp bounds')
return Math.min(Math.max(value, min), max)
assert.ok(min <= max, "Illegal clamp bounds");
return Math.min(Math.max(value, min), max);
}
async function getXRPBalance(
@@ -26,13 +30,12 @@ async function getXRPBalance(
ledgerVersion?: number
): Promise<string> {
const request: AccountInfoRequest = {
command: 'account_info',
command: "account_info",
account: address,
ledger_index: ledgerVersion
}
const data = await client
.request(request)
return dropsToXrp(data.result.account_data.Balance)
ledger_index: ledgerVersion,
};
const data = await client.request(request);
return dropsToXrp(data.result.account_data.Balance);
}
// If the marker is omitted from a response, you have reached the end
@@ -40,64 +43,71 @@ async function getRecursiveRecur(
getter: Getter,
marker: string | undefined,
limit: number
): Promise<Array<any>> {
const data = await getter(marker, limit)
const remaining = limit - data.results.length
): Promise<any[]> {
const data = await getter(marker, limit);
const remaining = limit - data.results.length;
if (remaining > 0 && data.marker != null) {
return getRecursiveRecur(getter, data.marker, remaining).then((results) => data.results.concat(results)
)
return getRecursiveRecur(getter, data.marker, remaining).then((results) =>
data.results.concat(results)
);
}
return data.results.slice(0, limit)
return data.results.slice(0, limit);
}
function getRecursive(getter: Getter, limit?: number): Promise<Array<any>> {
return getRecursiveRecur(getter, undefined, limit || Infinity)
function getRecursive(getter: Getter, limit?: number): Promise<any[]> {
return getRecursiveRecur(getter, undefined, limit || Infinity);
}
function renameCounterpartyToIssuer<T>(
obj: T & {counterparty?: string; issuer?: string}
): T & {issuer?: string} {
obj: T & { counterparty?: string; issuer?: string }
): T & { issuer?: string } {
const issuer =
obj.counterparty != null
? obj.counterparty
: obj.issuer != null
? obj.issuer
: undefined
const withIssuer = Object.assign({}, obj, {issuer})
delete withIssuer.counterparty
return withIssuer
: undefined;
const withIssuer = { ...obj, issuer };
delete withIssuer.counterparty;
return withIssuer;
}
export type RequestBookOffersArgs = {taker_gets: Issue; taker_pays: Issue}
export interface RequestBookOffersArgs {
taker_gets: Issue;
taker_pays: Issue;
}
function renameCounterpartyToIssuerInOrder(order: RequestBookOffersArgs) {
const taker_gets = renameCounterpartyToIssuer(order.taker_gets)
const taker_pays = renameCounterpartyToIssuer(order.taker_pays)
const changes = {taker_gets, taker_pays}
return Object.assign({}, order, _.omitBy(changes, value => value == null))
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
const changes = { taker_gets, taker_pays };
return { ...order, ..._.omitBy(changes, (value) => value == null) };
}
function signum(num) {
return num === 0 ? 0 : num > 0 ? 1 : -1
return num === 0 ? 0 : num > 0 ? 1 : -1;
}
/**
* Order two rippled transactions based on their ledger_index.
* If two transactions took place in the same ledger, sort
* them based on TransactionIndex
* See: https://developers.ripple.com/transaction-metadata.html
* Order two rippled transactions based on their ledger_index.
* If two transactions took place in the same ledger, sort
* them based on TransactionIndex
* See: https://developers.ripple.com/transaction-metadata.html.
*
* @param first
* @param second
*/
function compareTransactions(
first: FormattedTransactionType,
second: FormattedTransactionType
): number {
if (!first.outcome || !second.outcome) {
return 0
return 0;
}
if (first.outcome.ledgerVersion === second.outcome.ledgerVersion) {
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger)
return signum(first.outcome.indexInLedger - second.outcome.indexInLedger);
}
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1;
}
async function isPendingLedgerVersion(
@@ -105,27 +115,30 @@ async function isPendingLedgerVersion(
maxLedgerVersion?: number
): Promise<boolean> {
const response = await client.request({
command: 'ledger',
ledger_index: 'validated'
})
const ledgerVersion = response.result.ledger_index
return ledgerVersion < (maxLedgerVersion || 0)
command: "ledger",
ledger_index: "validated",
});
const ledgerVersion = response.result.ledger_index;
return ledgerVersion < (maxLedgerVersion || 0);
}
async function ensureLedgerVersion(this: Client, options: any): Promise<object> {
async function ensureLedgerVersion(
this: Client,
options: any
): Promise<object> {
if (
Boolean(options) &&
options.ledgerVersion != null &&
options.ledgerVersion !== null
) {
return Promise.resolve(options)
return Promise.resolve(options);
}
const response = await this.request({
command: 'ledger',
ledger_index: 'validated'
})
const ledgerVersion = response.result.ledger_index
return Object.assign({}, options, { ledgerVersion })
command: "ledger",
ledger_index: "validated",
});
const ledgerVersion = response.result.ledger_index;
return { ...options, ledgerVersion };
}
export {
@@ -138,5 +151,5 @@ export {
isPendingLedgerVersion,
clamp,
common,
Connection
}
Connection,
};

View File

@@ -1,23 +1,29 @@
export type LedgerIndex = number | ('validated' | 'closed' | 'current')
export type LedgerIndex = number | ("validated" | "closed" | "current");
export type AccountObjectType = 'check' | 'escrow' | 'offer' | 'payment_channel' | 'signer_list' | 'state'
export type AccountObjectType =
| "check"
| "escrow"
| "offer"
| "payment_channel"
| "signer_list"
| "state";
export interface XRP {
currency: "XRP"
currency: "XRP";
}
export interface IssuedCurrency {
currency: string
issuer: string
currency: string;
issuer: string;
}
export type Currency = IssuedCurrency | XRP
export type Currency = IssuedCurrency | XRP;
export interface IssuedCurrencyAmount extends IssuedCurrency {
value: string
value: string;
}
export type Amount = IssuedCurrencyAmount | string
export type Amount = IssuedCurrencyAmount | string;
export interface Signer {
Account: string;
@@ -31,17 +37,25 @@ export interface Memo {
MemoFormat?: string;
}
export type StreamType = "consensus" | "ledger" | "manifests" | "peer_status" | "transactions" | "transactions_proposed" | "server" | "validations"
export type StreamType =
| "consensus"
| "ledger"
| "manifests"
| "peer_status"
| "transactions"
| "transactions_proposed"
| "server"
| "validations";
interface PathStep {
account?: string
currency?: string
issuer?: string
account?: string;
currency?: string;
issuer?: string;
}
export type Path = PathStep[]
export type Path = PathStep[];
export interface SignerEntry {
Account: string;
SignerWeight: number;
}
}

View File

@@ -1,38 +1,38 @@
import { Amount } from ".";
interface CreatedNode {
CreatedNode: {
LedgerEntryType: string
LedgerIndex: string
NewFields: {[field: string]: any}
}
CreatedNode: {
LedgerEntryType: string;
LedgerIndex: string;
NewFields: { [field: string]: any };
};
}
interface ModifiedNode {
ModifiedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
PreviousFields: {[field: string]: any}
PreviousTxnID?: string
PreviouTxnLgrSeq?: number
}
ModifiedNode: {
LedgerEntryType: string;
LedgerIndex: string;
FinalFields: { [field: string]: any };
PreviousFields: { [field: string]: any };
PreviousTxnID?: string;
PreviouTxnLgrSeq?: number;
};
}
interface DeletedNode {
DeletedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
}
DeletedNode: {
LedgerEntryType: string;
LedgerIndex: string;
FinalFields: { [field: string]: any };
};
}
type Node = CreatedNode | ModifiedNode | DeletedNode
type Node = CreatedNode | ModifiedNode | DeletedNode;
export default interface Metadata {
AffectedNodes: Node[]
DeliveredAmount?: Amount
delivered_amount?: Amount
TransactionIndex: number
TransactionResult: string
}
AffectedNodes: Node[];
DeliveredAmount?: Amount;
delivered_amount?: Amount;
TransactionIndex: number;
TransactionResult: string;
}

View File

@@ -1,38 +1,38 @@
import { Amount } from ".";
interface CreatedNode {
CreatedNode: {
LedgerEntryType: string
LedgerIndex: string
NewFields: {[field: string]: any}
}
CreatedNode: {
LedgerEntryType: string;
LedgerIndex: string;
NewFields: { [field: string]: any };
};
}
interface ModifiedNode {
ModifiedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
PreviousFields: {[field: string]: any}
PreviousTxnID?: string
PreviouTxnLgrSeq?: number
}
ModifiedNode: {
LedgerEntryType: string;
LedgerIndex: string;
FinalFields: { [field: string]: any };
PreviousFields: { [field: string]: any };
PreviousTxnID?: string;
PreviouTxnLgrSeq?: number;
};
}
interface DeletedNode {
DeletedNode: {
LedgerEntryType: string
LedgerIndex: string
FinalFields: {[field: string]: any}
}
DeletedNode: {
LedgerEntryType: string;
LedgerIndex: string;
FinalFields: { [field: string]: any };
};
}
type Node = CreatedNode | ModifiedNode | DeletedNode
type Node = CreatedNode | ModifiedNode | DeletedNode;
export interface TransactionMetadata {
AffectedNodes: Node[]
DeliveredAmount?: Amount
delivered_amount?: Amount
TransactionIndex: number
TransactionResult: string
}
AffectedNodes: Node[];
DeliveredAmount?: Amount;
delivered_amount?: Amount;
TransactionIndex: number;
TransactionResult: string;
}

View File

@@ -1,20 +1,20 @@
import { BaseLedgerEntry } from "./baseLedgerEntry";
export interface AccountRoot extends BaseLedgerEntry{
LedgerEntryType: 'AccountRoot'
Account: string
Balance: string
Flags: number
OwnerCount: number
PreviousTxnID: string
PreviousTxnLgrSeq: number
Sequence: number
AccountTxnID?: string
Domain?: string
EmailHash?: string
MessageKey?: string
RegularKey?: string
TicketCount?: number
TickSize?: number
TransferRate?: number
}
export interface AccountRoot extends BaseLedgerEntry {
LedgerEntryType: "AccountRoot";
Account: string;
Balance: string;
Flags: number;
OwnerCount: number;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
Sequence: number;
AccountTxnID?: string;
Domain?: string;
EmailHash?: string;
MessageKey?: string;
RegularKey?: string;
TicketCount?: number;
TickSize?: number;
TransferRate?: number;
}

View File

@@ -2,14 +2,14 @@ import { BaseLedgerEntry } from "./baseLedgerEntry";
interface Majority {
Majority: {
Amendment: string
CloseTime: number
}
Amendment: string;
CloseTime: number;
};
}
export interface Amendments extends BaseLedgerEntry {
LedgerEntryType: 'Amendments'
Amendments?: string[]
Majorities?: Majority[]
Flags: 0
}
LedgerEntryType: "Amendments";
Amendments?: string[];
Majorities?: Majority[];
Flags: 0;
}

View File

@@ -1,3 +1,3 @@
export interface BaseLedgerEntry {
index: string
}
index: string;
}

View File

@@ -1,19 +1,20 @@
import { Amount } from "../common";
import { BaseLedgerEntry } from "./baseLedgerEntry";
export interface Check extends BaseLedgerEntry {
LedgerEntryType: 'Check'
Account: string
Destination: string
Flags: 0
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
SendMax: Amount
Sequence: number
DestinationNode?: string
DestinationTag?: number
Expiration?: number
InvoiceID?: string
SourceTag?: number
}
LedgerEntryType: "Check";
Account: string;
Destination: string;
Flags: 0;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
SendMax: Amount;
Sequence: number;
DestinationNode?: string;
DestinationTag?: number;
Expiration?: number;
InvoiceID?: string;
SourceTag?: number;
}

View File

@@ -1,11 +1,11 @@
import { BaseLedgerEntry } from "./baseLedgerEntry";
export interface DepositPreauth extends BaseLedgerEntry {
LedgerEntryType: 'DepositPreauth'
Account: string
Authorize: string
Flags: 0
OwnerNode: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
}
LedgerEntryType: "DepositPreauth";
Account: string;
Authorize: string;
Flags: 0;
OwnerNode: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
}

View File

@@ -1,15 +1,15 @@
import { BaseLedgerEntry } from "./baseLedgerEntry";
export interface DirectoryNode extends BaseLedgerEntry {
LedgerEntryType: 'DirectoryNode'
Flags: number
RootIndex: string
Indexes: string[]
IndexNext?: number
IndexPrevious?: number
Owner?: string
TakerPaysCurrency?: string
TakerPaysIssuer?: string
TakerGetsCurrency?: string
TakerGetsIssuer?: string
}
LedgerEntryType: "DirectoryNode";
Flags: number;
RootIndex: string;
Indexes: string[];
IndexNext?: number;
IndexPrevious?: number;
Owner?: string;
TakerPaysCurrency?: string;
TakerPaysIssuer?: string;
TakerGetsCurrency?: string;
TakerGetsIssuer?: string;
}

View File

@@ -1,18 +1,18 @@
import { BaseLedgerEntry } from "./baseLedgerEntry";
export interface Escrow extends BaseLedgerEntry {
LedgerEntryType: 'Escrow'
Account: string
Destination: string
Amount: string
Condition?: string
CancelAfter?: number
FinishAfter?: number
Flags: number
SourceTag?: number
DestinationTag?: number
OwnerNode: string
DestinationNode?: string
PreviousTxnID: string
PreviousTxnLgrSeq: number
}
LedgerEntryType: "Escrow";
Account: string;
Destination: string;
Amount: string;
Condition?: string;
CancelAfter?: number;
FinishAfter?: number;
Flags: number;
SourceTag?: number;
DestinationTag?: number;
OwnerNode: string;
DestinationNode?: string;
PreviousTxnID: string;
PreviousTxnLgrSeq: number;
}

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