feat: Jest Test Runner (#2170)

This commit is contained in:
justinr1234
2023-02-03 17:03:07 -06:00
committed by GitHub
parent 5a63f18faf
commit 5fe480ece4
229 changed files with 13497 additions and 17033 deletions

View File

@@ -57,7 +57,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x, 16.x, 18.x]
node-version: [14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@v3
@@ -98,7 +98,7 @@ jobs:
strategy:
matrix:
node-version: [12.x, 14.x, 16.x, 18.x]
node-version: [14.x, 16.x, 18.x]
services:
rippled:

4
.ncurc.json Normal file
View File

@@ -0,0 +1,4 @@
{
"reject": [
]
}

2
.nvmrc
View File

@@ -1 +1 @@
v12
v14

View File

@@ -39,4 +39,12 @@
"**/.git/subtree-cache/**": true,
"**/.hg/store/**": true
},
"search.exclude": {
"**/.git": true,
"**/node_modules": true,
"**/tmp": true,
"**/docs/**/*.html": true,
"**/fixtures/**/*.json": true,
"**/docs/assets": true
},
}

View File

@@ -122,8 +122,6 @@ If your code connects to the ledger (ex. Adding a new transaction type) it's han
All integration tests should be written in the `test/integration` folder, with new `Requests` and `Transactions` tests being in their respective folders.
One last note for integration tests is that all imports from `xrpl.js` should be from `xrpl-local` instead of `../../src`. This is required for the integraiton tests to run in the browser.
For an example of how to write an integration test for `xrpl.js`, you can look at the [Payment integration test](./packages/xrpl/test/integration/transactions/payment.ts).
## Generate reference docs

View File

@@ -25,7 +25,7 @@ All of which works in Node.js (tested for v14+) & web browsers (tested for Chrom
### Requirements
- **[Node.js v14](https://nodejs.org/)** is recommended. We also support v12 and v16. Other versions may work but are not frequently tested.
+ **[Node.js v14](https://nodejs.org/)** is recommended. We also support v16 and v18. Other versions may work but are not frequently tested.
### Installing xrpl.js

20
jest.config.base.js Normal file
View File

@@ -0,0 +1,20 @@
const { TextDecoder, TextEncoder } = require("util");
module.exports = {
roots: ["<rootDir>/src"],
transform: {
"^.+\\.ts$": "ts-jest",
},
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
collectCoverage: true,
verbose: true,
testEnvironment: "node",
globals: {
TextDecoder: TextDecoder,
TextEncoder: TextEncoder,
error: console.error,
warn: console.warn,
info: console.info,
debug: console.debug,
},
};

8
jest.config.js Normal file
View File

@@ -0,0 +1,8 @@
const path = require("path");
const base = require("./jest.config.base.js");
module.exports = {
...base,
projects: ["<rootDir>/packages/**/jest.config.js"],
coverageDirectory: "<rootDir>/coverage/",
};

17067
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,9 @@
"lint": "lerna run lint --stream",
"clean": "lerna run clean --stream",
"build": "lerna run build --stream",
"docgen": "lerna run docgen --stream"
"docgen": "lerna run docgen --stream",
"update:check": "npx npm-check-updates --configFileName .ncurc.json",
"update:confirm": "npx npm-check-updates --configFileName .ncurc.json -u"
},
"dependencies": {
"ripple-address-codec": "file:packages/ripple-address-codec",
@@ -17,11 +19,12 @@
"xrpl": "file:packages/xrpl"
},
"devDependencies": {
"@types/brorand": "^1.0.30",
"@types/chai": "^4.2.21",
"@types/create-hash": "^1.2.2",
"@types/jest": "^29.2.2",
"@types/lodash": "^4.14.136",
"@types/mocha": "^9.0.0",
"@types/node": "^17.0.14",
"@types/node": "^14.18.35",
"@types/puppeteer": "5.4.6",
"@types/ws": "^8.2.0",
"@typescript-eslint/eslint-plugin": "^4.30.0",
@@ -40,15 +43,15 @@
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.24.1",
"eslint-plugin-jsdoc": "^37.1.0",
"eslint-plugin-mocha": "^10.0.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-tsdoc": "^0.2.14",
"eventemitter2": "^6.0.0",
"expect": "^29.3.1",
"https-browserify": "^1.0.0",
"jest": "^26.0.1",
"jest": "^29.3.1",
"jest-mock": "^29.3.1",
"lerna": "^4.0.0",
"mocha": "^10",
"npm-run-all": "^4.1.5",
"nyc": "^15",
"path-browserify": "1.0.1",
@@ -59,7 +62,7 @@
"source-map-support": "^0.5.16",
"stream-browserify": "^3.0.0",
"stream-http": "3.2.0",
"ts-jest": "^26.4.4",
"ts-jest": "^29.0.3",
"ts-loader": "^9.2.5",
"ts-node": "^10.2.1",
"typescript": "^4.4.2",
@@ -69,7 +72,7 @@
"webpack-cli": "^4.2.0"
},
"workspaces": [
"packages/*"
"./packages/*"
],
"engines": {
"node": ">=12.0.0",

View File

@@ -40,5 +40,6 @@ module.exports = {
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param': 'off',
'tsdoc/syntax': 'off',
'@typescript-eslint/no-require-imports': 'off',
},
}

View File

@@ -1,6 +1,8 @@
# ripple-address-codec
## Unreleased
### Changed
- All tests now use the Jest test runner and have been refactored for consistency across all packages
## 4.2.4 (2022-04-21)
### Fixed

View File

@@ -1,8 +1,7 @@
// Jest configuration for api
const base = require('../../jest.config.base.js')
module.exports = {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
...base,
displayName: 'ripple-address-codec',
}

View File

@@ -0,0 +1,245 @@
{
"name": "ripple-address-codec",
"version": "4.2.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ripple-address-codec",
"version": "4.2.4",
"license": "ISC",
"dependencies": {
"base-x": "^3.0.9",
"create-hash": "^1.1.2"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dependencies": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"md5.js": "^1.3.4",
"ripemd160": "^2.0.1",
"sha.js": "^2.4.0"
}
},
"node_modules/hash-base": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"dependencies": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"dependencies": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
},
"bin": {
"sha.js": "bin.js"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}
},
"dependencies": {
"base-x": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"cipher-base": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"create-hash": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"requires": {
"cipher-base": "^1.0.1",
"inherits": "^2.0.1",
"md5.js": "^1.3.4",
"ripemd160": "^2.0.1",
"sha.js": "^2.4.0"
}
},
"hash-base": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"requires": {
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"md5.js": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1",
"safe-buffer": "^5.1.2"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"ripemd160": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}
}
}

View File

@@ -10,7 +10,7 @@
"types": "dist/index.d.ts",
"license": "ISC",
"dependencies": {
"base-x": "3.0.9",
"base-x": "^3.0.9",
"create-hash": "^1.1.2"
},
"repository": {
@@ -21,7 +21,7 @@
"prepublishOnly": "tslint -b ./ && jest",
"scripts": {
"build": "tsc -b",
"test": "jest",
"test": "jest --verbose false --silent=false ./src/*.test.js",
"lint": "eslint . --ext .ts",
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo"
},

View File

@@ -2,7 +2,7 @@ const {
classicAddressToXAddress,
xAddressToClassicAddress,
isValidXAddress,
encodeXAddress
encodeXAddress,
} = require('./index')
const testCases = [
@@ -10,137 +10,137 @@ const testCases = [
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
false,
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
'T719a5UwUCnEs54UsxG9CJYYDhwmFCqkr7wxCcNcfZ6p5GZ'
'T719a5UwUCnEs54UsxG9CJYYDhwmFCqkr7wxCcNcfZ6p5GZ',
],
[
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
1,
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaGZMhc9YTE92ehJ2Fu',
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvbJNZbi37gBGkRkbE'
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvbJNZbi37gBGkRkbE',
],
[
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
14,
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaGo2K5VpXpmCqbV2gS',
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvqXVCALUGJGSbNV3x'
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvqXVCALUGJGSbNV3x',
],
[
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
11747,
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaLFuhLRuNXPrDeJd9A',
'T719a5UwUCnEs54UsxG9CJYYDhwmFCziiNHtUukubF2Mg6t'
'T719a5UwUCnEs54UsxG9CJYYDhwmFCziiNHtUukubF2Mg6t',
],
[
'rLczgQHxPhWtjkaQqn3Q6UM8AbRbbRvs5K',
false,
'XVZVpQj8YSVpNyiwXYSqvQoQqgBttTxAZwMcuJd4xteQHyt',
'TVVrSWtmQQssgVcmoMBcFQZKKf56QscyWLKnUyiuZW8ALU4'
'TVVrSWtmQQssgVcmoMBcFQZKKf56QscyWLKnUyiuZW8ALU4',
],
[
'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
false,
'X7YenJqxv3L66CwhBSfd3N8RzGXxYqPopMGMsCcpho79rex',
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhRsFyXDoMLqYC4BzQtV'
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhRsFyXDoMLqYC4BzQtV',
],
[
'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
58,
'X7YenJqxv3L66CwhBSfd3N8RzGXxYqV56ZkTCa9UCzgaao1',
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhR9kej6uxm4jGcQD7rZ'
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhR9kej6uxm4jGcQD7rZ',
],
[
'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
23480,
'X7d3eHCXzwBeWrZec1yT24iZerQjYL8m8zCJ16ACxu1BrBY',
'T7YChPFWifjCAXLEtg5N74c7fSAYsvSokwcmBPBUZWhxH5P'
'T7YChPFWifjCAXLEtg5N74c7fSAYsvSokwcmBPBUZWhxH5P',
],
[
'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
11747,
'X7d3eHCXzwBeWrZec1yT24iZerQjYLo2CJf8oVC5CMWey5m',
'T7YChPFWifjCAXLEtg5N74c7fSAYsvTcc7nEfwuEEvn5Q4w'
'T7YChPFWifjCAXLEtg5N74c7fSAYsvTcc7nEfwuEEvn5Q4w',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
false,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV5fdx1mHp98tDMoQXb',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn49b3qD26PK7FcGSKE'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn49b3qD26PK7FcGSKE',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
0,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8AqEL4xcZj5whKbmc',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSy8RHqGHoGJ59spi2'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSy8RHqGHoGJ59spi2',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
1,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8xvjGQTYPiAx6gwDC',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSz1uDimDdPYXzSpyw'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSz1uDimDdPYXzSpyw',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
2,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8zpDURx7DzBCkrQE7',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTryP9tG9TW8GeMBmd'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTryP9tG9TW8GeMBmd',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
32,
'XVLhHMPHU98es4dbozjVtdWzVrDjtVoYiC9UvKfjKar4LJe',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnT2oqaCDzMEuCDAj1j'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnT2oqaCDzMEuCDAj1j',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
276,
'XVLhHMPHU98es4dbozjVtdWzVrDjtVoKj3MnFGMXEFMnvJV',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTMgJJYfAbsiPsc6Zg'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTMgJJYfAbsiPsc6Zg',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
65591,
'XVLhHMPHU98es4dbozjVtdWzVrDjtVozpjdhPQVdt3ghaWw',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn7ryu2W6njw7mT1jmS'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn7ryu2W6njw7mT1jmS',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
16781933,
'XVLhHMPHU98es4dbozjVtdWzVrDjtVqrDUk2vDpkTjPsY73',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnVsw45sDtGHhLi27Qa'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnVsw45sDtGHhLi27Qa',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
4294967294,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnX8tDFQ53itLNqs6vU'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnX8tDFQ53itLNqs6vU',
],
[
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
4294967295,
'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnXoy6kSDh6rZzApc69'
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnXoy6kSDh6rZzApc69',
],
[
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
false,
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2gYsjNFQLKYW33DzBm',
'TVd2rqMkYL2AyS97NdELcpeiprNBjwLZzuUG5rZnaewsahi'
'TVd2rqMkYL2AyS97NdELcpeiprNBjwLZzuUG5rZnaewsahi',
],
[
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
0,
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2m4Er6SnvjVLpMWPjR',
'TVd2rqMkYL2AyS97NdELcpeiprNBjwRQUBetPbyrvXSTuxU'
'TVd2rqMkYL2AyS97NdELcpeiprNBjwRQUBetPbyrvXSTuxU',
],
[
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
13371337,
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2qwGkhgc48zzcx6Gkr',
'TVd2rqMkYL2AyS97NdELcpeiprNBjwVUDvp3vhpXbNhLwJi'
]
'TVd2rqMkYL2AyS97NdELcpeiprNBjwVUDvp3vhpXbNhLwJi',
],
]
;[false, true].forEach(isTestAddress => {
;[false, true].forEach((isTestAddress) => {
const MAX_32_BIT_UNSIGNED_INT = 4294967295
const network = isTestAddress ? ' (test)' : ' (main)'
@@ -149,13 +149,17 @@ const testCases = [
const classicAddress = testCase[0]
const tag = testCase[1] !== false ? testCase[1] : false
const xAddress = isTestAddress ? testCase[3] : testCase[2]
test(`Converts ${classicAddress}${tag ? ':' + tag : ''} to ${xAddress}${network}`, () => {
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(xAddress)
test(`Converts ${classicAddress}${
tag ? `:${tag}` : ''
} to ${xAddress}${network}`, () => {
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(
xAddress,
)
const myClassicAddress = xAddressToClassicAddress(xAddress)
expect(myClassicAddress).toEqual({
classicAddress,
tag,
test: isTestAddress
test: isTestAddress,
})
expect(isValidXAddress(xAddress)).toBe(true)
})
@@ -177,23 +181,28 @@ const testCases = [
test(`Invalid classic address: Converting ${classicAddress}${network} throws`, () => {
expect(() => {
classicAddressToXAddress(classicAddress, false, isTestAddress)
}).toThrowError(new Error('invalid_input_size: decoded data must have length >= 5'))
}).toThrowError(
new Error('invalid_input_size: decoded data must have length >= 5'),
)
})
}
{
const highAndLowAccounts = [
Buffer.from('00'.repeat(20), 'hex'),
Buffer.from('00'.repeat(19) + '01', 'hex'),
Buffer.from(`${'00'.repeat(19)}01`, 'hex'),
Buffer.from('01'.repeat(20), 'hex'),
Buffer.from('FF'.repeat(20), 'hex')
Buffer.from('FF'.repeat(20), 'hex'),
]
highAndLowAccounts.forEach(accountId => {
[false, 0, 1, MAX_32_BIT_UNSIGNED_INT].forEach(t => {
const tag = (t | false)
highAndLowAccounts.forEach((accountId) => {
const testCases = [false, 0, 1, MAX_32_BIT_UNSIGNED_INT]
testCases.forEach((t) => {
const tag = t | false
const xAddress = encodeXAddress(accountId, tag, isTestAddress)
test(`Encoding ${accountId.toString('hex')}${tag ? ':' + tag : ''} to ${xAddress} has expected length`, () => {
test(`Encoding ${accountId.toString('hex')}${
tag ? `:${tag}` : ''
} to ${xAddress} has expected length`, () => {
expect(xAddress.length).toBe(47)
})
})
@@ -237,7 +246,9 @@ test(`Invalid Account ID throws`, () => {
})
test(`isValidXAddress returns false for invalid X-address`, () => {
expect(isValidXAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8zeUygYrCgrPh')).toBe(false)
expect(
isValidXAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8zeUygYrCgrPh'),
).toBe(false)
})
test(`Converts X7AcgcsBL6XDcUb... to r9cZA1mLK5R5A... and tag: false`, () => {
@@ -245,12 +256,14 @@ test(`Converts X7AcgcsBL6XDcUb... to r9cZA1mLK5R5A... and tag: false`, () => {
const tag = false
const xAddress = 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ'
const isTestAddress = false
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(xAddress)
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(
xAddress,
)
const myClassicAddress = xAddressToClassicAddress(xAddress)
expect(myClassicAddress).toEqual({
classicAddress,
tag,
test: isTestAddress
test: isTestAddress,
})
expect(isValidXAddress(xAddress)).toBe(true)

View File

@@ -119,6 +119,7 @@ function isBufferForTestAddress(buf: Buffer): boolean {
if (PREFIX_BYTES.test.equals(decodedPrefix)) {
return true
}
throw new Error('Invalid X-address: bad prefix')
}

View File

@@ -17,28 +17,38 @@ function toBytes(hex) {
* @param hex Hexadecimal representation of expected decoded data
*/
function makeEncodeDecodeTest(encoder, decoder, base58, hex) {
test(`can translate between ${hex} and ${base58}`, function() {
test(`can translate between ${hex} and ${base58}`, function () {
const actual = encoder(toBytes(hex))
expect(actual).toBe(base58)
})
test(`can translate between ${base58} and ${hex})`, function() {
test(`can translate between ${base58} and ${hex})`, function () {
const buf = decoder(base58)
expect(toHex(buf)).toBe(hex)
})
}
makeEncodeDecodeTest(api.encodeAccountID, api.decodeAccountID, 'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN',
'BA8E78626EE42C41B46D46C3048DF3A1C3C87072')
makeEncodeDecodeTest(
api.encodeAccountID,
api.decodeAccountID,
'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN',
'BA8E78626EE42C41B46D46C3048DF3A1C3C87072',
)
makeEncodeDecodeTest(api.encodeNodePublic, api.decodeNodePublic,
makeEncodeDecodeTest(
api.encodeNodePublic,
api.decodeNodePublic,
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
)
makeEncodeDecodeTest(api.encodeAccountPublic, api.decodeAccountPublic,
makeEncodeDecodeTest(
api.encodeAccountPublic,
api.decodeAccountPublic,
'aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3',
'023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6')
'023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6',
)
test('can decode arbitrary seeds', function() {
test('can decode arbitrary seeds', function () {
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519')
@@ -48,7 +58,7 @@ test('can decode arbitrary seeds', function() {
expect(decoded2.type).toBe('secp256k1')
})
test('can pass a type as second arg to encodeSeed', function() {
test('can pass a type as second arg to encodeSeed', function () {
const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2'
const decoded = api.decodeSeed(edSeed)
const type = 'ed25519'
@@ -57,125 +67,161 @@ test('can pass a type as second arg to encodeSeed', function() {
expect(api.encodeSeed(decoded.bytes, type)).toBe(edSeed)
})
test('isValidClassicAddress - secp256k1 address valid', function() {
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1')).toBe(true)
test('isValidClassicAddress - secp256k1 address valid', function () {
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1')).toBe(
true,
)
})
test('isValidClassicAddress - ed25519 address valid', function() {
expect(api.isValidClassicAddress('rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD')).toBe(true)
test('isValidClassicAddress - ed25519 address valid', function () {
expect(api.isValidClassicAddress('rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD')).toBe(
true,
)
})
test('isValidClassicAddress - invalid', function() {
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw2')).toBe(false)
test('isValidClassicAddress - invalid', function () {
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw2')).toBe(
false,
)
})
test('isValidClassicAddress - empty', function() {
test('isValidClassicAddress - empty', function () {
expect(api.isValidClassicAddress('')).toBe(false)
})
describe('encodeSeed', function() {
it('encodes a secp256k1 seed', function() {
const result = api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFF', 'hex'), 'secp256k1')
describe('encodeSeed', function () {
it('encodes a secp256k1 seed', function () {
const result = api.encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFF', 'hex'),
'secp256k1',
)
expect(result).toBe('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
})
it('encodes low secp256k1 seed', function() {
const result = api.encodeSeed(Buffer.from('00000000000000000000000000000000', 'hex'), 'secp256k1')
it('encodes low secp256k1 seed', function () {
const result = api.encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'),
'secp256k1',
)
expect(result).toBe('sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
})
it('encodes high secp256k1 seed', function() {
const result = api.encodeSeed(Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), 'secp256k1')
it('encodes high secp256k1 seed', function () {
const result = api.encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'),
'secp256k1',
)
expect(result).toBe('saGwBRReqUNKuWNLpUAq8i8NkXEPN')
})
it('encodes an ed25519 seed', function() {
const result = api.encodeSeed(Buffer.from('4C3A1D213FBDFB14C7C28D609469B341', 'hex'), 'ed25519')
it('encodes an ed25519 seed', function () {
const result = api.encodeSeed(
Buffer.from('4C3A1D213FBDFB14C7C28D609469B341', 'hex'),
'ed25519',
)
expect(result).toBe('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
})
it('encodes low ed25519 seed', function() {
const result = api.encodeSeed(Buffer.from('00000000000000000000000000000000', 'hex'), 'ed25519')
it('encodes low ed25519 seed', function () {
const result = api.encodeSeed(
Buffer.from('00000000000000000000000000000000', 'hex'),
'ed25519',
)
expect(result).toBe('sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
})
it('encodes high ed25519 seed', function() {
const result = api.encodeSeed(Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), 'ed25519')
it('encodes high ed25519 seed', function () {
const result = api.encodeSeed(
Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'),
'ed25519',
)
expect(result).toBe('sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG')
})
test('attempting to encode a seed with less than 16 bytes of entropy throws', function() {
test('attempting to encode a seed with less than 16 bytes of entropy throws', function () {
expect(() => {
api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7B', 'hex'), 'secp256k1')
api.encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7B', 'hex'),
'secp256k1',
)
}).toThrow('entropy must have length 16')
})
test('attempting to encode a seed with more than 16 bytes of entropy throws', function() {
test('attempting to encode a seed with more than 16 bytes of entropy throws', function () {
expect(() => {
api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF', 'hex'), 'secp256k1')
api.encodeSeed(
Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF', 'hex'),
'secp256k1',
)
}).toThrow('entropy must have length 16')
})
})
describe('decodeSeed', function() {
it('can decode an Ed25519 seed', function() {
describe('decodeSeed', function () {
it('can decode an Ed25519 seed', function () {
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
expect(decoded.type).toBe('ed25519')
})
it('can decode a secp256k1 seed', function() {
it('can decode a secp256k1 seed', function () {
const decoded = api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
expect(toHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
expect(decoded.type).toBe('secp256k1')
})
})
describe('encodeAccountID', function() {
it('can encode an AccountID', function() {
const encoded = api.encodeAccountID(Buffer.from('BA8E78626EE42C41B46D46C3048DF3A1C3C87072', 'hex'))
describe('encodeAccountID', function () {
it('can encode an AccountID', function () {
const encoded = api.encodeAccountID(
Buffer.from('BA8E78626EE42C41B46D46C3048DF3A1C3C87072', 'hex'),
)
expect(encoded).toBe('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
})
test('unexpected length should throw', function() {
test('unexpected length should throw', function () {
expect(() => {
api.encodeAccountID(Buffer.from('ABCDEF', 'hex'))
}).toThrow(
'unexpected_payload_length: bytes.length does not match expectedLength'
'unexpected_payload_length: bytes.length does not match expectedLength',
)
})
})
describe('decodeNodePublic', function() {
it('can decode a NodePublic', function() {
const decoded = api.decodeNodePublic('n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH')
expect(toHex(decoded)).toBe('0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
describe('decodeNodePublic', function () {
it('can decode a NodePublic', function () {
const decoded = api.decodeNodePublic(
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
)
expect(toHex(decoded)).toBe(
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
)
})
})
test('encodes 123456789 with version byte of 0', () => {
expect(api.codec.encode(Buffer.from('123456789'), {
expect(
api.codec.encode(Buffer.from('123456789'), {
versions: [0],
expectedLength: 9
})).toBe('rnaC7gW34M77Kneb78s')
expectedLength: 9,
}),
).toBe('rnaC7gW34M77Kneb78s')
})
test('multiple versions with no expected length should throw', () => {
expect(() => {
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0, 1]
versions: [0, 1],
})
}).toThrow('expectedLength is required because there are >= 2 possible versions')
}).toThrow(
'expectedLength is required because there are >= 2 possible versions',
)
})
test('attempting to decode data with length < 5 should throw', () => {
expect(() => {
api.codec.decode('1234', {
versions: [0]
versions: [0],
})
}).toThrow('invalid_input_size: decoded data must have length >= 5')
})
@@ -183,15 +229,17 @@ test('attempting to decode data with length < 5 should throw', () => {
test('attempting to decode data with unexpected version should throw', () => {
expect(() => {
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [2]
versions: [2],
})
}).toThrow('version_invalid: version bytes do not match any of the provided version(s)')
}).toThrow(
'version_invalid: version bytes do not match any of the provided version(s)',
)
})
test('invalid checksum should throw', () => {
expect(() => {
api.codec.decode('123456789', {
versions: [0, 1]
versions: [0, 1],
})
}).toThrow('checksum_invalid')
})
@@ -199,48 +247,51 @@ test('invalid checksum should throw', () => {
test('empty payload should throw', () => {
expect(() => {
api.codec.decode('', {
versions: [0, 1]
versions: [0, 1],
})
}).toThrow('invalid_input_size: decoded data must have length >= 5')
})
test('decode data', () => {
expect(api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0]
})).toStrictEqual({
version: [0],
bytes: Buffer.from('123456789'),
type: null
})
})
test('decode data with expected length', function() {
expect(api.codec.decode('rnaC7gW34M77Kneb78s', {
expect(
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0],
expectedLength: 9
})
}),
).toStrictEqual({
version: [0],
bytes: Buffer.from('123456789'),
type: null
type: null,
})
})
test('decode data with wrong expected length should throw', function() {
test('decode data with expected length', function () {
expect(
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0],
expectedLength: 9,
}),
).toStrictEqual({
version: [0],
bytes: Buffer.from('123456789'),
type: null,
})
})
test('decode data with wrong expected length should throw', function () {
expect(() => {
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0],
expectedLength: 8
expectedLength: 8,
})
}).toThrow(
'version_invalid: version bytes do not match any of the provided version(s)'
'version_invalid: version bytes do not match any of the provided version(s)',
)
expect(() => {
api.codec.decode('rnaC7gW34M77Kneb78s', {
versions: [0],
expectedLength: 10
expectedLength: 10,
})
}).toThrow(
'version_invalid: version bytes do not match any of the provided version(s)'
'version_invalid: version bytes do not match any of the provided version(s)',
)
})

View File

@@ -2,15 +2,16 @@
* Codec class
*/
import * as baseCodec from 'base-x'
import * as createHash from 'create-hash'
import baseCodec = require('base-x')
import type { BaseConverter } from 'base-x'
import createHash = require('create-hash')
import { seqEqual, concatArgs } from './utils'
class Codec {
private readonly _sha256: (bytes: Uint8Array) => Buffer
private readonly _alphabet: string
private readonly _codec: baseCodec.BaseConverter
private readonly _codec: BaseConverter
public constructor(options: {
sha256: (bytes: Uint8Array) => Buffer
@@ -56,7 +57,7 @@ class Codec {
): {
version: number[]
bytes: Buffer
type: string | null
type: 'ed25519' | 'secp256k1' | null
} {
const versions = opts.versions
const types = opts.versionTypes
@@ -204,7 +205,7 @@ export function decodeSeed(
): {
version: number[]
bytes: Buffer
type: string | null
type: 'ed25519' | 'secp256k1' | null
} {
return codecWithXrpAlphabet.decode(seed, opts)
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts", "src/*.test.ts"]
}

View File

@@ -13,7 +13,8 @@
"preserveConstEnums": false,
"suppressImplicitAnyIndexErrors": false,
"skipLibCheck": true,
"declaration": true
"declaration": true,
"strictNullChecks": true
},
"include": ["src/**/*.ts"]
}

View File

@@ -14,6 +14,8 @@ module.exports = {
},
},
ignorePatterns: ['jest.config.js', '.eslintrc.js'],
// Specify global variables that are predefined
env: {
browser: true, // Enable browser global variables
@@ -103,19 +105,19 @@ module.exports = {
'max-lines-per-function': 'off',
'require-unicode-regexp': 'off',
'no-undef-init': 'off',
'curly': 'off',
'eqeqeq': 'off',
curly: 'off',
eqeqeq: 'off',
'no-console': 'off',
'max-classes-per-file': 'off',
'operator-assignment': 'off',
'class-methods-use-this': 'off',
'no-else-return': 'off',
'yoda': 'off',
yoda: 'off',
'max-depth': 'off',
'multiline-comment-style': 'off',
'one-var': 'off',
'no-negated-condition': 'off',
'radix': 'off',
radix: 'off',
'no-nested-ternary': 'off',
'no-useless-concat': 'off',
'object-shorthand': 'off',

View File

@@ -1 +0,0 @@
10.22.0

View File

@@ -1,6 +1,8 @@
# ripple-binary-codec Release History
## Unreleased
### Changed
- All tests now use the Jest test runner and have been refactored for consistency across all packages
## 1.4.2 (2022-06-27)
- Fixed standard currency codes with lowercase and allowed symbols not decoding into standard codes.

View File

@@ -0,0 +1,8 @@
// Jest configuration for api
const base = require('../../jest.config.base.js')
module.exports = {
...base,
roots: [...base.roots, '<rootDir>/test'],
displayName: 'ripple-binary-codec',
}

View File

@@ -23,7 +23,7 @@
"build": "tsc -b && copyfiles ./src/enums/definitions.json ./dist/enums/",
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo",
"prepare": "npm run build && npm test",
"test": "jest",
"test": "jest --verbose false --silent=false ./test/*.test.js",
"lint": "eslint . --ext .ts --ext .test.js"
},
"repository": {
@@ -38,6 +38,6 @@
"readmeFilename": "README.md",
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">=10.22.0"
"node": ">= 10"
}
}

View File

@@ -10,7 +10,7 @@ import { FieldInstance } from './enums'
import { STObject } from './types/st-object'
import { JsonObject } from './types/serialized-type'
import { Buffer } from 'buffer/'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
/**
* Construct a BinaryParser

View File

@@ -1,5 +1,5 @@
import { HashPrefix } from './hash-prefixes'
import * as createHash from 'create-hash'
import createHash = require('create-hash')
import { Hash256 } from './types/hash-256'
import { BytesList } from './serdes/binary-serializer'
import { Buffer } from 'buffer/'

View File

@@ -102,7 +102,7 @@ function decodeQuality(value: string): string {
return quality.decode(value).toString()
}
export = {
export {
decode,
encode,
encodeForSigning,

View File

@@ -10,7 +10,7 @@ import { UInt32 } from './types/uint-32'
import { UInt8 } from './types/uint-8'
import { BinaryParser } from './serdes/binary-parser'
import { JsonObject } from './types/serialized-type'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
/**
* Computes the hash of a list of objects

View File

@@ -1,6 +1,6 @@
import { coreTypes } from './types'
import { Decimal } from 'decimal.js'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
import { Buffer } from 'buffer/'
/**

View File

@@ -5,7 +5,7 @@ import { BinaryParser } from '../serdes/binary-parser'
import { AccountID } from './account-id'
import { Currency } from './currency'
import { JsonObject, SerializedType } from './serialized-type'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
import { Buffer } from 'buffer/'
/**

View File

@@ -1,6 +1,6 @@
import { BytesList } from '../serdes/binary-serializer'
import { BinaryParser } from '../serdes/binary-parser'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
import { Buffer } from 'buffer/'
type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject

View File

@@ -1,6 +1,6 @@
import { UInt } from './uint'
import { BinaryParser } from '../serdes/binary-parser'
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
import { isInstance } from 'big-integer'
import { Buffer } from 'buffer/'

View File

@@ -1,4 +1,4 @@
import * as bigInt from 'big-integer'
import bigInt = require('big-integer')
import { Comparable } from './serialized-type'
import { Buffer } from 'buffer/'

View File

@@ -1,5 +1,5 @@
const { loadFixture } = require('./utils')
const { coreTypes } = require('../dist/types')
const { coreTypes } = require('../src/types')
const { Amount } = coreTypes
const fixtures = loadFixture('data-driven-tests.json')

View File

@@ -1,5 +1,5 @@
const fixtures = require('./fixtures/codec-fixtures.json')
const { decode, encode, decodeLedgerData } = require('../dist')
const { decode, encode, decodeLedgerData } = require('../src')
function json(object) {
return JSON.stringify(object)

View File

@@ -1,14 +1,14 @@
const { coreTypes } = require('../dist/types')
const { coreTypes } = require('../src/types')
const Decimal = require('decimal.js')
const { encodeAccountID } = require('ripple-address-codec')
const { binary } = require('../dist/coretypes')
const { binary } = require('../src/coretypes')
const { Amount, Hash160 } = coreTypes
const { makeParser, readJSON } = binary
const { Field, TransactionType } = require('./../dist/enums')
const { Field, TransactionType } = require('./../src/enums')
const { parseHexOnly, hexOnly, loadFixture } = require('./utils')
const fixtures = loadFixture('data-driven-tests.json')
const { BytesList } = require('../dist/serdes/binary-serializer')
const { BytesList } = require('../src/serdes/binary-serializer')
const { Buffer } = require('buffer/')
const __ = hexOnly

View File

@@ -1,7 +1,7 @@
const { binary } = require('../dist/coretypes')
const { encode, decode } = require('../dist')
const { binary } = require('../src/coretypes')
const { encode, decode } = require('../src')
const { makeParser, BytesList, BinarySerializer } = binary
const { coreTypes } = require('../dist/types')
const { coreTypes } = require('../src/types')
const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes
const bigInt = require('big-integer')
const { Buffer } = require('buffer/')

View File

@@ -1,4 +1,4 @@
const { coreTypes } = require('../dist/types')
const { coreTypes } = require('../src/types')
const { Hash128, Hash160, Hash256, AccountID, Currency } = coreTypes
const { Buffer } = require('buffer/')

View File

@@ -3,7 +3,7 @@ const {
transactionTreeHash,
ledgerHash,
accountStateHash,
} = require('../dist/ledger-hashes')
} = require('../src/ledger-hashes')
describe('Ledger Hashes', function () {
function testFactory(ledgerFixture) {

View File

@@ -1,4 +1,4 @@
const { encode, decode } = require('../dist')
const { encode, decode } = require('../src')
let str =
'1100612200000000240000000125000068652D0000000055B6632D6376A2D9319F20A1C6DCCB486432D1E4A79951229D4C3DE2946F51D56662400009184E72A00081140DD319918CD5AE792BF7EC80D63B0F01B4573BBC'

View File

@@ -1,4 +1,4 @@
const { encode, decode } = require('../dist')
const { encode, decode } = require('../src')
let json = {
Account: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',

View File

@@ -1,4 +1,4 @@
const { quality } = require('../dist/coretypes')
const { quality } = require('../src/coretypes')
describe('Quality encode/decode', function () {
const bookDirectory =

View File

@@ -1,6 +1,6 @@
const { ShaMap } = require('../dist/shamap.js')
const { binary, HashPrefix } = require('../dist/coretypes')
const { coreTypes } = require('../dist/types')
const { ShaMap } = require('../src/shamap')
const { binary, HashPrefix } = require('../src/coretypes')
const { coreTypes } = require('../src/types')
const { loadFixture } = require('./utils')
const { Buffer } = require('buffer/')

View File

@@ -3,7 +3,7 @@ const {
encodeForSigning,
encodeForSigningClaim,
encodeForMultisigning,
} = require('../dist')
} = require('../src')
const tx_json = {
Account: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',

View File

@@ -1,4 +1,4 @@
const { encode, decode } = require('../dist')
const { encode, decode } = require('../src')
// Notice: no Amount or Fee
const tx_json = {

View File

@@ -1,5 +1,5 @@
const { coreTypes } = require('../dist/types')
const { SerializedType } = require('../dist/types/serialized-type')
const { coreTypes } = require('../src/types')
const { SerializedType } = require('../src/types/serialized-type')
describe('SerializedType interfaces', () => {
Object.entries(coreTypes).forEach(([name, Value]) => {

View File

@@ -1,7 +1,7 @@
const { coreTypes } = require('../dist/types')
const { coreTypes } = require('../src/types')
const { UInt8, UInt64 } = coreTypes
const { encode } = require('../dist')
const { encode } = require('../src')
const binary =
'11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F'

View File

@@ -1,4 +1,4 @@
const { encode, decode } = require('./../dist/index')
const { encode, decode } = require('./../src/index')
const fixtures = require('./fixtures/x-codec-fixtures.json')
let json_x1 = {

View File

@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "es5",
"target": "es6",
"lib": [
"es2017"
],

View File

@@ -5,7 +5,7 @@ module.exports = {
parserOptions: {
// Enable linting rules with type information from our tsconfig
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
project: ['./tsconfig.json', './tsconfig.eslint.json'],
sourceType: 'module', // Allow the use of imports / ES modules
@@ -19,11 +19,11 @@ module.exports = {
browser: true, // Enable browser global variables
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
mocha: true, // Add Mocha testing global variables
jest: true, // Add Jest testing global variables
},
plugins: [],
extends: ['@xrplf/eslint-config/base', 'plugin:mocha/recommended'],
extends: ['@xrplf/eslint-config/base'],
rules: {
// ** TODO **
@@ -56,5 +56,6 @@ module.exports = {
'eslint-comments/require-description': 'off',
'no-shadow': 'off',
'multiline-comment-style': 'off',
'@typescript-eslint/no-require-imports': 'off',
},
}

View File

@@ -1,6 +1,8 @@
# ripple-keypairs Release History
## Unreleased
### Changed
- All tests now use the Jest test runner and have been refactored for consistency across all packages
## 1.1.4 (2022-05-02)
- `hexToBytes` now produces empty output for empty input, rather than `[0]`.

View File

@@ -0,0 +1,8 @@
// Jest configuration for api
const base = require('../../jest.config.base.js')
module.exports = {
...base,
roots: [...base.roots, '<rootDir>/test'],
displayName: 'ripple-keypairs',
}

View File

@@ -4,7 +4,7 @@
"description": "Cryptographic key pairs for the XRP Ledger",
"scripts": {
"build": "tsc -b",
"test": "tsc -b && nyc mocha",
"test": "jest --verbose false --silent=false ./test/*.test.ts",
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo",
"lint": "eslint . --ext .ts",
"prepublish": "npm run lint && npm test"

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-bitwise --
* lots of bitwise operators necessary for this */
import * as hashjs from 'hash.js'
import * as BigNum from 'bn.js'
import BigNum = require('bn.js')
export default class Sha512 {
// TODO: type of `hash`?

View File

@@ -1,5 +1,5 @@
import * as assert from 'assert'
import * as brorand from 'brorand'
import brorand = require('brorand')
import * as hashjs from 'hash.js'
import * as elliptic from 'elliptic'
@@ -157,7 +157,7 @@ function deriveNodeAddress(publicKey): string {
const { decodeSeed } = addressCodec
export = {
export {
generateSeed,
deriveKeypair,
sign,

View File

@@ -1,6 +1,6 @@
import * as assert from 'assert'
import * as hashjs from 'hash.js'
import * as BN from 'bn.js'
import BN = require('bn.js')
function bytesToHex(a: Iterable<number> | ArrayLike<number>): string {
return Array.from(a, (byteValue) => {

View File

@@ -1,33 +1,36 @@
'use strict' // eslint-disable-line strict
import assert from 'assert'
import fixtures from './fixtures/api.json'
import * as api from '../src'
const assert = require('assert')
const fixtures = require('./fixtures/api.json')
const api = require('../dist')
const decodeSeed = api.decodeSeed
const entropy = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
const entropy = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
])
describe('api', () => {
it('generateSeed - secp256k1', () => {
assert.strictEqual(api.generateSeed({entropy}), fixtures.secp256k1.seed)
assert.strictEqual(api.generateSeed({ entropy }), fixtures.secp256k1.seed)
})
it('generateSeed - secp256k1, random', () => {
const seed = api.generateSeed()
assert(seed.charAt(0) === 's')
const {type, bytes} = decodeSeed(seed)
assert(seed.startsWith('s'))
const { type, bytes } = decodeSeed(seed)
assert(type === 'secp256k1')
assert(bytes.length === 16)
})
it('generateSeed - ed25519', () => {
assert.strictEqual(api.generateSeed({entropy, algorithm: 'ed25519'}),
fixtures.ed25519.seed)
assert.strictEqual(
api.generateSeed({ entropy, algorithm: 'ed25519' }),
fixtures.ed25519.seed,
)
})
it('generateSeed - ed25519, random', () => {
const seed = api.generateSeed({algorithm: 'ed25519'})
assert(seed.slice(0, 3) === 'sEd')
const {type, bytes} = decodeSeed(seed)
const seed = api.generateSeed({ algorithm: 'ed25519' })
assert(seed.startsWith('sEd'))
const { type, bytes } = decodeSeed(seed)
assert(type === 'ed25519')
assert(bytes.length === 16)
})
@@ -43,12 +46,16 @@ describe('api', () => {
})
it('deriveKeypair - secp256k1 - validator', () => {
const keypair = api.deriveKeypair(fixtures.secp256k1.seed, {validator: true})
const keypair = api.deriveKeypair(fixtures.secp256k1.seed, {
validator: true,
})
assert.deepEqual(keypair, fixtures.secp256k1.validatorKeypair)
})
it('deriveKeypair - ed25519 - validator', () => {
const keypair = api.deriveKeypair(fixtures.ed25519.seed, {validator: true})
const keypair = api.deriveKeypair(fixtures.ed25519.seed, {
validator: true,
})
assert.deepEqual(keypair, fixtures.ed25519.validatorKeypair)
})
@@ -65,7 +72,7 @@ describe('api', () => {
it('sign - secp256k1', () => {
const privateKey = fixtures.secp256k1.keypair.privateKey
const message = fixtures.secp256k1.message
const messageHex = (Buffer.from(message, 'utf8')).toString('hex')
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const signature = api.sign(messageHex, privateKey)
assert.strictEqual(signature, fixtures.secp256k1.signature)
})
@@ -74,14 +81,14 @@ describe('api', () => {
const signature = fixtures.secp256k1.signature
const publicKey = fixtures.secp256k1.keypair.publicKey
const message = fixtures.secp256k1.message
const messageHex = (Buffer.from(message, 'utf8')).toString('hex')
const messageHex = Buffer.from(message, 'utf8').toString('hex')
assert(api.verify(messageHex, signature, publicKey))
})
it('sign - ed25519', () => {
const privateKey = fixtures.ed25519.keypair.privateKey
const message = fixtures.ed25519.message
const messageHex = (Buffer.from(message, 'utf8')).toString('hex')
const messageHex = Buffer.from(message, 'utf8').toString('hex')
const signature = api.sign(messageHex, privateKey)
assert.strictEqual(signature, fixtures.ed25519.signature)
})
@@ -90,20 +97,20 @@ describe('api', () => {
const signature = fixtures.ed25519.signature
const publicKey = fixtures.ed25519.keypair.publicKey
const message = fixtures.ed25519.message
const messageHex = (Buffer.from(message, 'utf8')).toString('hex')
const messageHex = Buffer.from(message, 'utf8').toString('hex')
assert(api.verify(messageHex, signature, publicKey))
})
it('deriveNodeAddress', () => {
const x = 'n9KHn8NfbBsZV5q8bLfS72XyGqwFt5mgoPbcTV4c6qKiuPTAtXYk'
const y = 'rU7bM9ENDkybaxNrefAVjdLTyNLuue1KaJ'
assert.strictEqual(api.deriveNodeAddress(x), y)
const addrX = 'n9KHn8NfbBsZV5q8bLfS72XyGqwFt5mgoPbcTV4c6qKiuPTAtXYk'
const addrY = 'rU7bM9ENDkybaxNrefAVjdLTyNLuue1KaJ'
assert.strictEqual(api.deriveNodeAddress(addrX), addrY)
})
it('Random Address', () => {
const seed = api.generateSeed()
const keypair = api.deriveKeypair(seed)
const address = api.deriveAddress(keypair.publicKey)
assert(address[0] === 'r')
assert(address.startsWith('r'))
})
})

View File

@@ -1,9 +1,5 @@
/* eslint-disable no-unused-expressions/no-unused-expressions */
'use strict'
const assert = require('assert')
const api = require('ripple-address-codec')
import assert from 'assert'
import * as api from 'ripple-address-codec'
function toHex(bytes) {
return Buffer.from(bytes).toString('hex').toUpperCase()
@@ -13,27 +9,31 @@ function toBytes(hex) {
return Buffer.from(hex, 'hex').toJSON().data
}
describe('ripple-address-codec', function() {
describe('ripple-address-codec', function () {
function makeTest(type, base58, hex) {
it('can translate between ' + hex + ' and ' + base58 + ' (encode ' + type + ')', function() {
const actual = api['encode' + type](toBytes(hex))
it(`can translate between ${hex} and ${base58} (encode ${type})`, () => {
const actual = api[`encode${type}`](toBytes(hex))
assert.equal(actual, base58)
})
it('can translate between ' + base58 + ' and ' + hex + ' (decode ' + type + ')', function() {
const buf = api['decode' + type](base58)
it(`can translate between ${base58} and ${hex} (decode ${type})`, () => {
const buf = api[`decode${type}`](base58)
assert.equal(toHex(buf), hex)
})
}
makeTest('AccountID', 'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN',
'BA8E78626EE42C41B46D46C3048DF3A1C3C87072')
makeTest(
'AccountID',
'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN',
'BA8E78626EE42C41B46D46C3048DF3A1C3C87072',
)
makeTest(
'NodePublic',
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
)
it('can decode arbitrary seeds', function() {
it('can decode arbitrary seeds', () => {
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
assert.equal(toHex(decoded.bytes), '4C3A1D213FBDFB14C7C28D609469B341')
assert.equal(decoded.type, 'ed25519')
@@ -43,11 +43,16 @@ describe('ripple-address-codec', function() {
assert.equal(decoded2.type, 'secp256k1')
})
it('can pass a type as second arg to encodeSeed', function() {
it('can pass a type as second arg to encodeSeed', () => {
const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2'
const decoded = api.decodeSeed(edSeed)
assert.equal(toHex(decoded.bytes), '4C3A1D213FBDFB14C7C28D609469B341')
assert.equal(decoded.type, 'ed25519')
if (decoded.type === null) {
assert.fail('decoded.type should not be null')
}
assert.equal(api.encodeSeed(decoded.bytes, decoded.type), edSeed)
})
})
export {}

View File

@@ -1,9 +1,7 @@
'use strict' // eslint-disable-line strict
import assert from 'assert'
import * as utils from '../src/utils'
const assert = require('assert')
const utils = require('../dist/utils')
describe('utils', () => {
describe('utils', function () {
it('hexToBytes - empty', () => {
assert.deepEqual(utils.hexToBytes(''), [])
})
@@ -18,9 +16,14 @@ describe('utils', () => {
it('bytesToHex - DEADBEEF', () => {
assert.deepEqual(utils.bytesToHex([222, 173, 190, 239]), 'DEADBEEF')
});
})
it('bytesToHex - DEADBEEF (Uint8Array)', () => {
assert.deepEqual(utils.bytesToHex(new Uint8Array([222, 173, 190, 239])), 'DEADBEEF')
});
assert.deepEqual(
utils.bytesToHex(new Uint8Array([222, 173, 190, 239])),
'DEADBEEF',
)
})
})
export {}

View File

@@ -1,81 +0,0 @@
/* eslint-disable no-unused-expressions/no-unused-expressions */
'use strict'
const assert = require('assert')
const api = require('ripple-address-codec')
function toHex(bytes) {
return Buffer.from(bytes).toString('hex').toUpperCase()
}
function toBytes(hex) {
return Buffer.from(hex, 'hex').toJSON().data
}
describe('ripple-address-codec', function() {
describe('encodeSeed', function() {
it('encodes a secp256k1 seed', function() {
const result = api.encodeSeed(toBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFF'), 'secp256k1')
assert.equal(result, 'sn259rEFXrQrWyx3Q7XneWcwV6dfL')
})
it('encodes low secp256k1 seed', function() {
const result = api.encodeSeed(toBytes('00000000000000000000000000000000'), 'secp256k1')
assert.equal(result, 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
})
it('encodes high secp256k1 seed', function() {
const result = api.encodeSeed(toBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'), 'secp256k1')
assert.equal(result, 'saGwBRReqUNKuWNLpUAq8i8NkXEPN')
})
it('encodes an ed25519 seed', function() {
const result = api.encodeSeed(toBytes('4C3A1D213FBDFB14C7C28D609469B341'), 'ed25519')
assert.equal(result, 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
})
it('encodes low ed25519 seed', function() {
const result = api.encodeSeed(toBytes('00000000000000000000000000000000'), 'ed25519')
assert.equal(result, 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
})
it('encodes high ed25519 seed', function() {
const result = api.encodeSeed(toBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'), 'ed25519')
assert.equal(result, 'sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG')
})
})
describe('decodeSeed', function() {
it('can decode an Ed25519 seed', function() {
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
assert.equal(toHex(decoded.bytes), '4C3A1D213FBDFB14C7C28D609469B341')
assert.equal(decoded.type, 'ed25519')
})
it('can decode a secp256k1 seed', function() {
const decoded = api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
assert.equal(toHex(decoded.bytes), 'CF2DE378FBDD7E2EE87D486DFB5A7BFF')
assert.equal(decoded.type, 'secp256k1')
})
})
describe('encodeAccountID', function() {
it('can encode an AccountID', function() {
const encoded = api.encodeAccountID(toBytes('BA8E78626EE42C41B46D46C3048DF3A1C3C87072'))
assert.equal(encoded, 'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
})
})
describe('decodeNodePublic', function() {
it('can decode a NodePublic', function() {
const decoded = api.decodeNodePublic('n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH')
assert.equal(toHex(decoded), '0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
})
})
})

View File

@@ -0,0 +1,99 @@
import assert from 'assert'
import * as api from 'ripple-address-codec'
function toHex(bytes: Buffer) {
return Buffer.from(bytes).toString('hex').toUpperCase()
}
function toBytes(hex: string) {
return Buffer.from(hex, 'hex').toJSON().data
}
describe('ripple-address-codec', function () {
describe('encodeSeed', function () {
it('encodes a secp256k1 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('CF2DE378FBDD7E2EE87D486DFB5A7BFF')),
'secp256k1',
)
assert.equal(result, 'sn259rEFXrQrWyx3Q7XneWcwV6dfL')
})
it('encodes low secp256k1 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('00000000000000000000000000000000')),
'secp256k1',
)
assert.equal(result, 'sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
})
it('encodes high secp256k1 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')),
'secp256k1',
)
assert.equal(result, 'saGwBRReqUNKuWNLpUAq8i8NkXEPN')
})
it('encodes an ed25519 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('4C3A1D213FBDFB14C7C28D609469B341')),
'ed25519',
)
assert.equal(result, 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
})
it('encodes low ed25519 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('00000000000000000000000000000000')),
'ed25519',
)
assert.equal(result, 'sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
})
it('encodes high ed25519 seed', () => {
const result = api.encodeSeed(
Buffer.from(toBytes('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')),
'ed25519',
)
assert.equal(result, 'sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG')
})
})
describe('decodeSeed', function () {
it('can decode an Ed25519 seed', () => {
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
assert.equal(toHex(decoded.bytes), '4C3A1D213FBDFB14C7C28D609469B341')
assert.equal(decoded.type, 'ed25519')
})
it('can decode a secp256k1 seed', () => {
const decoded = api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
assert.equal(toHex(decoded.bytes), 'CF2DE378FBDD7E2EE87D486DFB5A7BFF')
assert.equal(decoded.type, 'secp256k1')
})
})
describe('encodeAccountID', function () {
it('can encode an AccountID', () => {
const encoded = api.encodeAccountID(
Buffer.from(toBytes('BA8E78626EE42C41B46D46C3048DF3A1C3C87072')),
)
assert.equal(encoded, 'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
})
})
describe('decodeNodePublic', function () {
it('can decode a NodePublic', () => {
const decoded = api.decodeNodePublic(
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
)
assert.equal(
toHex(decoded),
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828',
)
})
})
})
export {}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts", "test/**/*.ts"]
}

View File

@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2017",
"target": "es6",
"declaration": true,
"declarationMap": true,
"outDir": "./dist",
@@ -11,7 +11,9 @@
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
"strictNullChecks": true,
"resolveJsonModule": true
},
"references": [{
"path": "../ripple-address-codec/tsconfig.json"

View File

@@ -6,7 +6,12 @@ module.exports = {
parserOptions: {
// Enable linting rules with type information from our tsconfig
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json'],
project: [
'./tsconfig.eslint.json',
'../ripple-binary-codec/tsconfig.eslint.json',
'../ripple-address-codec/tsconfig.eslint.json',
'../ripple-keypairs/tsconfig.eslint.json',
],
// Allow the use of imports / ES modules
sourceType: 'module',
@@ -23,11 +28,13 @@ module.exports = {
node: true,
// Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020
es2020: true,
jest: true,
},
plugins: [],
extends: ['@xrplf/eslint-config/base', 'plugin:mocha/recommended'],
extends: ['@xrplf/eslint-config/base'],
rules: {
'multiline-comment-style': 'off',
// Disabled until https://github.com/import-js/eslint-plugin-import/pull/2305 is resolved to
// accomodate this change https://github.com/XRPLF/xrpl.js/pull/2133
'import/no-unused-modules': 'off',
@@ -51,7 +58,6 @@ module.exports = {
// no-shadow has false-positives for enum, @typescript-eslint version fixes that
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'multiline-comment-style': ['error', 'starred-block'],
'jsdoc/check-examples': 'off',
'tsdoc/syntax': 'off',
@@ -74,13 +80,16 @@ module.exports = {
'max-statements': 'off',
// Snippets have logs on console to better understand the working.
'no-console': 'off',
'import/no-extraneous-dependencies': 'off',
},
},
{
files: ['test/**/*.ts'],
rules: {
// Because this project is managed by lerna, dev dependencies are
// hoisted and do not appear in the package.json.
/*
* Because this project is managed by lerna, dev dependencies are
* hoisted and do not appear in the package.json.
*/
'import/no-extraneous-dependencies': 'off',
'node/no-extraneous-import': 'off',
@@ -102,19 +111,12 @@ module.exports = {
// Tests are already in 2 callbacks, so max 3 is pretty restrictive
'max-nested-callbacks': 'off',
// setup/teardown client is easier to do in before/after, even if there is only one testcase
'mocha/no-hooks-for-single-case': 'off',
// messes with fixtures
'consistent-default-export-name/default-import-match-filename': 'off',
},
},
{
files: ['test/client/*.ts'],
rules: {
// Rule does not work with dynamically generated tests.
'mocha/no-setup-in-describe': 'off',
},
},
{
files: ['test/models/*.ts'],

View File

@@ -4,11 +4,16 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased
### Fixed
* Code splitting improvements for lodash
* Fixed missing reason code in websocket implemntation on websocket disconnect
* Fix timeout error in request manager
* Improved typescript typing
### Added
### Changed
* All tests now use the Jest test runner and have been refactored for consistency across all packages
### Deprecated
Wallet.fromMmnemonic()

View File

@@ -0,0 +1,8 @@
// Jest configuration for api
const base = require('../../jest.config.base.js')
module.exports = {
...base,
roots: [...base.roots, '<rootDir>/test'],
displayName: 'xrpl.js',
}

View File

@@ -0,0 +1,14 @@
// the jest.fn() API
import * as jest from 'jest-mock'
// The matchers API
import expect from 'expect'
// Add missing Jest functions
window.test = window.it
window.test.each = (inputs) => (testName, test) =>
inputs.forEach((args) => window.it(testName, () => test(...args)))
window.test.todo = function () {
return undefined
}
window.jest = jest
window.expect = expect

View File

@@ -0,0 +1,34 @@
const webpackConfig = require('./test/webpack.config')[0]()
delete webpackConfig.entry
module.exports = function (config) {
config.set({
plugins: ['karma-webpack', 'karma-jasmine', 'karma-chrome-launcher'],
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
webpack: webpackConfig,
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'build/xrpl-latest.js',
'test/integration/index.ts',
'karma-setup.js',
],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'karma-setup.js': ['webpack'],
// Use webpack to bundle our test files
'test/integration/index.ts': ['webpack'],
},
browsers: ['ChromeHeadless'],
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -33,9 +33,19 @@
"ws": "^8.2.2"
},
"devDependencies": {
"@geut/browser-node-core": "^2.0.13",
"@types/node": "^14.18.36",
"assert-browserify": "^2.0.0",
"browserify-fs": "^1.0.0",
"constants-browserify": "^1.0.0",
"https-browserify": "^1.0.0",
"karma": "^6.4.1",
"karma-chrome-launcher": "^3.1.1",
"karma-jasmine": "^5.1.0",
"karma-webpack": "^5.0.0",
"node-polyfill-webpack-plugin": "^2.0.1",
"react": "^18.2.0",
"typedoc": "^0.23.24",
"xrpl-local": "file:./src"
"typedoc": "^0.23.24"
},
"resolutions": {
"elliptic": "^6.5.4"
@@ -48,13 +58,13 @@
"build:browserTests": "webpack --config ./test/webpack.config.js",
"analyze": "run-s build:web --analyze",
"watch": "run-s build:lib --watch",
"clean": "rm -rf dist",
"clean": "rm -rf dist build coverage",
"docgen": "tsc --build tsconfig.docs.json && typedoc && echo js.xrpl.org >> ../../docs/CNAME",
"prepublish": "run-s clean build",
"test": "nyc mocha --config=test/.mocharc.json --exit",
"test:integration": "TS_NODE_PROJECT=tsconfig.build.json nyc mocha ./test/integration/**/*.test.ts ./test/integration/*.test.ts",
"test:browser": "npm run build:browserTests && 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",
"test": "jest --verbose false --silent=false ./test/**/*.test.ts --testPathIgnorePatterns=./test/integration --testPathIgnorePatterns=./test/fixtures",
"test:integration": "TS_NODE_PROJECT=tsconfig.build.json jest --verbose false --silent=false --runInBand ./test/integration/**/*.test.ts",
"test:browser": "npm run build && npm run build:browserTests && karma start ./karma.config.js --single-run",
"test:watch": "jest --watch --verbose false --silent=false --runInBand ./test/**/*.test.ts --testPathIgnorePatterns=./test/integration --testPathIgnorePatterns=./test/fixtures",
"format": "prettier --write '{src,test}/**/*.ts'",
"lint": "eslint . --ext .ts --max-warnings 0",
"perf": "./scripts/perf_test.sh",

View File

@@ -4,7 +4,7 @@ import {
PaymentChannelCreate,
PaymentChannelClaim,
hashes,
} from '../../dist/npm'
} from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,4 +1,4 @@
import { Client, LedgerResponse, TxResponse } from '../../dist/npm'
import { Client, LedgerResponse, TxResponse } from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -4,7 +4,7 @@ import {
AccountSet,
convertStringToHex,
SignerListSet,
} from '../../dist/npm'
} from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,4 +1,4 @@
import { Client, Payment, PaymentFlags, TrustSet } from '../../dist/npm'
import { Client, Payment, PaymentFlags, TrustSet } from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,4 +1,4 @@
import { Client, Payment, RipplePathFindResponse } from '../../dist/npm'
import { Client, Payment, RipplePathFindResponse } from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,4 +1,4 @@
import { Client, Payment } from '../../dist/npm'
import { Client, Payment } from '../../src'
/**
* When implementing Reliable Transaction Submission, there are many potential solutions, each with different trade-offs.

View File

@@ -4,7 +4,7 @@ import {
EscrowCreate,
EscrowFinish,
isoTimeToRippleTime,
} from '../../dist/npm'
} from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,4 +1,4 @@
import { Client, Payment, SetRegularKey } from '../../dist/npm'
import { Client, Payment, SetRegularKey } from '../../src'
const client = new Client('wss://s.altnet.rippletest.net:51233')

View File

@@ -1,7 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
"rootDir": "../../xrpl"
},
"include": ["./src/**/*.ts"]
"include": ["./src/**/*.ts", "../src/**/*.ts", "../src/**/*.json"]
}

View File

@@ -1,4 +1,4 @@
import type { Client } from '..'
import type { Client } from '../client'
import { XRPLFaucetError } from '../errors'
export interface FaucetWallet {

View File

@@ -3,7 +3,7 @@ import { request as httpsRequest, RequestOptions } from 'https'
import { isValidClassicAddress } from 'ripple-address-codec'
import type { Client } from '..'
import type { Client } from '../client'
import { RippledError, XRPLFaucetError } from '../errors'
import {

View File

@@ -2,7 +2,7 @@
import BigNumber from 'bignumber.js'
import { fromSeed } from 'bip32'
import { mnemonicToSeedSync, validateMnemonic } from 'bip39'
import _ from 'lodash'
import isEqual from 'lodash/isEqual'
import {
classicAddressToXAddress,
isValidXAddress,
@@ -498,7 +498,7 @@ class Wallet {
})
/* eslint-enable @typescript-eslint/consistent-type-assertions -- Done with dynamic checking */
if (!_.isEqual(decoded, txCopy)) {
if (!isEqual(decoded, txCopy)) {
const data = {
decoded,
tx,

View File

@@ -20,7 +20,7 @@ export default class RequestManager {
{
resolve: (value: Response | PromiseLike<Response>) => void
reject: (value: Error) => void
timer: NodeJS.Timeout
timer: ReturnType<typeof setTimeout>
}
>()
@@ -34,7 +34,10 @@ export default class RequestManager {
public resolve(id: string | number, response: Response): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new XrplError(`No existing promise with id ${id}`)
throw new XrplError(`No existing promise with id ${id}`, {
type: 'resolve',
response,
})
}
clearTimeout(promise.timer)
promise.resolve(response)
@@ -51,7 +54,10 @@ export default class RequestManager {
public reject(id: string | number, error: Error): void {
const promise = this.promisesAwaitingResponse.get(id)
if (promise == null) {
throw new XrplError(`No existing promise with id ${id}`)
throw new XrplError(`No existing promise with id ${id}`, {
type: 'reject',
error,
})
}
clearTimeout(promise.timer)
// TODO: figure out how to have a better stack trace for an error
@@ -93,20 +99,35 @@ export default class RequestManager {
newId = request.id
}
const newRequest = JSON.stringify({ ...request, id: newId })
const timer = setTimeout(
() => this.reject(newId, new TimeoutError()),
timeout,
// Typing required for Jest running in browser
const timer: ReturnType<typeof setTimeout> = setTimeout(() => {
this.reject(
newId,
new TimeoutError(
`Timeout for request: ${JSON.stringify(request)} with id ${newId}`,
request,
),
)
}, 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).
*/
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Reason above.
if (timer.unref) {
timer.unref()
// The following type assertions are required to get this code to pass in browser environments
// where setTimeout has a different type
// eslint-disable-next-line max-len -- Necessary to disable both rules.
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- Reason above.
if ((timer as unknown as any).unref) {
// eslint-disable-next-line max-len -- Necessary to disable both rules.
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call -- Reason above.
;(timer as unknown as any).unref()
}
if (this.promisesAwaitingResponse.has(newId)) {
throw new XrplError(`Response with id '${newId}' is already pending`)
clearTimeout(timer)
throw new XrplError(
`Response with id '${newId}' is already pending`,
request,
)
}
const newPromise = new Promise<Response>(
(resolve: (value: Response | PromiseLike<Response>) => void, reject) => {

View File

@@ -3,13 +3,13 @@ import { EventEmitter } from 'events'
// Define the global WebSocket class found on the native browser
declare class WebSocket {
public onclose?: () => void
public onopen?: () => void
public onclose?: (closeEvent: CloseEvent) => void
public onopen?: (openEvent: Event) => void
public onerror?: (error: Error) => void
public onmessage?: (message: MessageEvent) => void
public readyState: number
public constructor(url: string)
public close(code?: number): void
public close(code?: number, reason?: Buffer): void
public send(message: string): void
}
@@ -52,8 +52,13 @@ export default class WSWrapper extends EventEmitter {
this.ws = new WebSocket(url)
this.ws.onclose = (): void => {
this.emit('close')
this.ws.onclose = (closeEvent: CloseEvent): void => {
let reason: Uint8Array | undefined
if (closeEvent.reason) {
const enc = new TextEncoder()
reason = enc.encode(closeEvent.reason)
}
this.emit('close', closeEvent.code, reason)
}
this.ws.onopen = (): void => {
@@ -71,10 +76,13 @@ export default class WSWrapper extends EventEmitter {
/**
* Closes the websocket.
*
* @param code - Close code.
* @param reason - Close reason.
*/
public close(): void {
public close(code?: number, reason?: Buffer): void {
if (this.readyState === 1) {
this.ws.close()
this.ws.close(code, reason)
}
}

View File

@@ -1,8 +1,9 @@
/* eslint-disable max-lines -- Connection is a large file w/ lots of imports/exports */
import { EventEmitter } from 'events'
import { Agent } from 'http'
import _ from 'lodash'
import omitBy from 'lodash/omitBy'
import WebSocket from 'ws'
import {
@@ -63,7 +64,7 @@ function getAgent(url: string, config: ConnectionOptions): Agent | undefined {
const parsedURL = new URL(url)
const parsedProxyURL = new URL(config.proxy)
const proxyOptions = _.omitBy(
const proxyOptions = omitBy(
{
secureEndpoint: parsedURL.protocol === 'wss:',
secureProxy: parsedProxyURL.protocol === 'https:',
@@ -125,7 +126,7 @@ function createWebSocket(
Authorization: `Basic ${base64}`,
}
}
const optionsOverrides = _.omitBy(
const optionsOverrides = omitBy(
{
ca: config.trustedCertificates,
key: config.key,
@@ -175,8 +176,10 @@ async function websocketSendAsync(
export class Connection extends EventEmitter {
private readonly url: string | undefined
private ws: WebSocket | null = null
private reconnectTimeoutID: null | NodeJS.Timeout = null
private heartbeatIntervalID: null | NodeJS.Timeout = null
// Typing necessary for Jest tests running in browser
private reconnectTimeoutID: null | ReturnType<typeof setTimeout> = null
// Typing necessary for Jest tetsts running in browser
private heartbeatIntervalID: null | ReturnType<typeof setTimeout> = null
private readonly retryConnectionBackoff = new ExponentialBackoff({
min: 100,
max: SECONDS_PER_MINUTE * 1000,
@@ -224,6 +227,7 @@ export class Connection extends EventEmitter {
* @returns When the websocket is connected.
* @throws ConnectionError if there is a connection error, RippleError if there is already a WebSocket in existence.
*/
// eslint-disable-next-line max-lines-per-function -- Necessary
public async connect(): Promise<void> {
if (this.isConnected()) {
return Promise.resolve()
@@ -245,14 +249,17 @@ export class Connection extends EventEmitter {
}
// Create the connection timeout, in case the connection hangs longer than expected.
const connectionTimeoutID = setTimeout(() => {
const connectionTimeoutID: ReturnType<typeof setTimeout> = setTimeout(
() => {
this.onConnectionFailed(
new ConnectionError(
`Error: connect() timed out after ${this.config.connectionTimeout} ms. 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)
@@ -337,7 +344,7 @@ export class Connection extends EventEmitter {
timeout?: number,
): Promise<unknown> {
if (!this.shouldBeConnected || this.ws == null) {
throw new NotConnectedError()
throw new NotConnectedError(JSON.stringify(request), request)
}
const [id, message, responsePromise] = this.requestManager.createRequest(
request,
@@ -429,7 +436,9 @@ export class Connection extends EventEmitter {
* @throws Error if the websocket initialized is somehow null.
*/
// eslint-disable-next-line max-lines-per-function -- Many error code conditionals to check.
private async onceOpen(connectionTimeoutID: NodeJS.Timeout): Promise<void> {
private async onceOpen(
connectionTimeoutID: ReturnType<typeof setTimeout>,
): Promise<void> {
if (this.ws == null) {
throw new XrplError('onceOpen: ws is null')
}
@@ -458,13 +467,14 @@ export class Connection extends EventEmitter {
this.ws = null
if (code === undefined) {
const reasonText = reason ? reason.toString() : 'undefined'
// eslint-disable-next-line no-console -- The error is helpful for debugging.
console.error(
`Disconnected but the disconnect code was undefined (The given reason was ${reasonText}).` +
`This could be caused by an exception being thrown during a 'connect' callback. ` +
`Disconnecting with code 1011 to indicate an internal error has occurred.`,
)
// Useful to keep this code for debugging purposes.
// const reasonText = reason ? reason.toString() : 'undefined'
// // eslint-disable-next-line no-console -- The error is helpful for debugging.
// console.error(
// `Disconnected but the disconnect code was undefined (The given reason was ${reasonText}).` +
// `This could be caused by an exception being thrown during a 'connect' callback. ` +
// `Disconnecting with code 1011 to indicate an internal error has occurred.`,
// )
/*
* Error code 1011 represents an Internal Error according to

View File

@@ -454,6 +454,10 @@ class Client extends EventEmitter {
event: 'consensusPhase',
listener: (phase: ConsensusStream) => void,
): this
public on(
event: 'manifestReceived',
listener: (manifest: ManifestResponse) => void,
): this
public on(event: 'path_find', listener: (path: PathFindStream) => void): this
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- needs to be any for overload
public on(event: 'error', listener: (...err: any[]) => void): this

View File

@@ -108,6 +108,10 @@ export interface ResponseOnlyTxInfo {
* The sequence number of the ledger that included this transaction.
*/
ledger_index?: number
/**
* @deprecated Alias for ledger_index.
*/
inLedger?: number
}
/**

View File

@@ -49,7 +49,7 @@ export interface LedgerDataRequest extends BaseRequest {
type LabeledLedgerEntry = { ledgerEntryType: string } & LedgerEntry
interface BinaryLedgerEntry {
export interface BinaryLedgerEntry {
data: string
}

View File

@@ -33,6 +33,14 @@ export interface JobType {
in_progress?: number
}
// The states for validating and proposing do not exist in the field state_accounting
// See https://github.com/XRPLF/rippled/blob/develop/src/ripple/app/misc/NetworkOPs.cpp#L4545
// https://github.com/XRPLF/rippled/blob/develop/src/ripple/app/misc/NetworkOPs.h#L66
export type StateAccountingFinal = Record<
Exclude<ServerState, 'validating' | 'proposing'>,
StateAccounting
>
/**
* Response expected from a {@link ServerInfoRequest}.
*
@@ -158,6 +166,14 @@ export interface ServerInfoResponse extends BaseResponse {
* cost.
*/
load_factor_server?: number
/**
* The number of peer connections which were severed.
*/
peer_disconnects?: string
/**
* The number of peer connections which were severed due to excess resource consumption.
*/
peer_disconnects_resources?: string
network_ledger?: 'waiting'
/** How many other rippled servers this one is currently connected to. */
peers: number
@@ -179,13 +195,13 @@ export interface ServerInfoResponse extends BaseResponse {
* The number of consecutive microseconds the server has been in the
* current state.
*/
server_state_duration_us: number
server_state_duration_us: string
/**
* A map of various server states with information about the time the
* server spends in each. This can be useful for tracking the long-term
* health of your server's connectivity to the network.
*/
state_accounting: Record<ServerState, StateAccounting>
state_accounting: StateAccountingFinal
/** The current time in UTC, according to the server's clock. */
time: string
/** Number of consecutive seconds that the server has been operational. */
@@ -227,6 +243,11 @@ export interface ServerInfoResponse extends BaseResponse {
* static validator list.
*/
validator_list_expires?: string
validator_list?: {
count: number
expiration: 'never' | 'unknown' | string
status: 'active' | 'expired' | 'unknown'
}
}
}
}

View File

@@ -1,5 +1,5 @@
import { BaseRequest, BaseResponse } from './baseMethod'
import { JobType, ServerState, StateAccounting } from './serverInfo'
import { JobType, ServerState, StateAccountingFinal } from './serverInfo'
/**
* The `server_state` command asks the server for various machine-readable
@@ -35,7 +35,10 @@ export interface ServerStateResponse extends BaseResponse {
io_latency_ms: number
jq_trans_overflow: string
last_close: {
converge_time_s: number
// coverage_time_s only exists for `server_info` requests. `server_state` is a "non human" api request,
// therefore the type is coverage_time
// See https://github.com/XRPLF/rippled/blob/83faf43140e27e5d6d6779eaa0ffb75c33d98029/src/ripple/app/misc/NetworkOPs.cpp#L2458
converge_time: number
proposers: number
}
load?: {
@@ -48,24 +51,27 @@ export interface ServerStateResponse extends BaseResponse {
load_factor_fee_queue?: number
load_factor_fee_reference?: number
load_factor_server?: number
peer_disconnects?: string
peer_disconnects_resources?: string
peers: number
pubkey_node: string
pubkey_validator?: string
server_state: ServerState
server_state_duration_us: number
state_accounting: Record<ServerState, StateAccounting>
server_state_duration_us: string
state_accounting: StateAccountingFinal
time: string
uptime: number
validated_ledger?: {
age: number
age?: number
base_fee: number
close_time: number
hash: string
reserve_base: number
reserve_inc: number
seq: number
}
validation_quorum: number
validator_list_expires?: string
validator_list_expires?: number
}
}
}

View File

@@ -53,12 +53,14 @@ export function validateCheckCash(tx: Record<string, unknown>): void {
)
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Necessary check
if (tx.Amount != null && tx.Amount !== undefined && !isAmount(tx.Amount)) {
throw new ValidationError('CheckCash: invalid Amount')
}
if (
tx.DeliverMin != null &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Necessary check
tx.DeliverMin !== undefined &&
!isAmount(tx.DeliverMin)
) {

View File

@@ -1,7 +1,8 @@
/* eslint-disable complexity -- verifies 19 tx types hence a lot of checks needed */
/* eslint-disable max-lines-per-function -- need to work with a lot of Tx verifications */
import _ from 'lodash'
import isEqual from 'lodash/isEqual'
import omitBy from 'lodash/omitBy'
import { encode, decode } from 'ripple-binary-codec'
import { ValidationError } from '../../errors'
@@ -210,9 +211,9 @@ export function validate(transaction: Record<string, unknown>): void {
}
if (
!_.isEqual(
!isEqual(
decode(encode(tx)),
_.omitBy(tx, (value) => value == null),
omitBy(tx, (value) => value == null),
)
) {
throw new ValidationError(`Invalid Transaction: ${tx.TransactionType}`)

View File

@@ -1,4 +1,4 @@
import _ from 'lodash'
import flatMap from 'lodash/flatMap'
import type { Client } from '..'
import { LedgerIndex } from '../models/common'
@@ -111,7 +111,7 @@ async function getBalances(
// combine results
await Promise.all([xrpPromise, linesPromise]).then(
([xrpBalance, linesResponses]) => {
const accountLinesBalance = _.flatMap(linesResponses, (response) =>
const accountLinesBalance = flatMap(linesResponses, (response) =>
formatBalances(response.result.lines),
)
if (xrpBalance !== '') {

View File

@@ -1,8 +1,9 @@
/* eslint-disable max-lines-per-function -- Needs to process orderbooks. */
import BigNumber from 'bignumber.js'
import _ from 'lodash'
import flatMap from 'lodash/flatMap'
import type { Client } from '../client'
import { ValidationError } from '../errors'
import { LedgerIndex } from '../models/common'
import { OfferFlags } from '../models/ledger/Offer'
import {
@@ -22,6 +23,13 @@ function sortOffers(offers: BookOffer[]): BookOffer[] {
})
}
const getOrderbookOptionsSet = new Set([
'limit',
'ledger_index',
'ledger_hash',
'taker',
])
/**
* Fetch orderbook (buy/sell orders) between two accounts.
*
@@ -40,7 +48,7 @@ function sortOffers(offers: BookOffer[]): BookOffer[] {
* the order book. Defaults to 20.
* @returns An object containing buy and sell objects.
*/
// eslint-disable-next-line max-params -- Once bound to Client, getOrderbook only has 3 parameters.
// eslint-disable-next-line max-params, complexity -- Once bound to Client, getOrderbook only has 3 parameters.
async function getOrderbook(
this: Client,
takerPays: TakerAmount,
@@ -48,21 +56,60 @@ async function getOrderbook(
options: {
limit?: number
ledger_index?: LedgerIndex
ledger_hash?: string
taker?: string
ledger_hash?: string | null
taker?: string | null
} = {},
): Promise<{
buy: BookOffer[]
sell: BookOffer[]
}> {
Object.keys(options).forEach((key) => {
if (!getOrderbookOptionsSet.has(key)) {
throw new ValidationError(`Unexpected option: ${key}`, options)
}
})
if (options.limit && typeof options.limit !== 'number') {
throw new ValidationError('limit must be a number', options.limit)
}
if (
options.ledger_index &&
!(
typeof options.ledger_index === 'number' ||
(typeof options.ledger_index === 'string' &&
['validated', 'closed', 'current'].includes(options.ledger_index))
)
) {
throw new ValidationError(
'ledger_index must be a number or a string of "validated", "closed", or "current"',
options.ledger_index,
)
}
if (
options.ledger_hash !== undefined &&
options.ledger_hash !== null &&
typeof options.ledger_hash !== 'string'
) {
throw new ValidationError(
'ledger_hash must be a string',
options.ledger_hash,
)
}
if (options.taker !== undefined && typeof options.taker !== 'string') {
throw new ValidationError('taker must be a string', options.taker)
}
const request: BookOffersRequest = {
command: 'book_offers',
taker_pays: takerPays,
taker_gets: takerGets,
ledger_index: options.ledger_index ?? 'validated',
ledger_hash: options.ledger_hash,
ledger_hash: options.ledger_hash === null ? undefined : options.ledger_hash,
limit: options.limit ?? DEFAULT_LIMIT,
taker: options.taker,
taker: options.taker ? options.taker : undefined,
}
// 2. Make Request
const directOfferResults = await this.requestAll(request)
@@ -70,11 +117,11 @@ async function getOrderbook(
request.taker_pays = takerGets
const reverseOfferResults = await this.requestAll(request)
// 3. Return Formatted Response
const directOffers = _.flatMap(
const directOffers = flatMap(
directOfferResults,
(directOfferResult) => directOfferResult.result.offers,
)
const reverseOffers = _.flatMap(
const reverseOffers = flatMap(
reverseOfferResults,
(reverseOfferResult) => reverseOfferResult.result.offers,
)

View File

@@ -1,5 +1,6 @@
import BigNumber from 'bignumber.js'
import _ from 'lodash'
import flatten from 'lodash/flatten'
import groupBy from 'lodash/groupBy'
import { Amount, IssuedCurrencyAmount } from '../models/common'
import { TransactionMetadata, Node } from '../models/transactions/metadata'
@@ -63,7 +64,7 @@ function groupByAccount(balanceChanges: BalanceChange[]): Array<{
account: string
balances: Balance[]
}> {
const grouped = _.groupBy(balanceChanges, (node) => node.account)
const grouped = groupBy(balanceChanges, (node) => node.account)
return Object.entries(grouped).map(([account, items]) => {
return { account, balances: items.map((item) => item.balance) }
})
@@ -186,5 +187,5 @@ export default function getBalanceChanges(
}
return []
})
return groupByAccount(_.flatten(quantities))
return groupByAccount(flatten(quantities))
}

View File

@@ -1,5 +1,5 @@
import binary from 'ripple-binary-codec'
import keypairs from 'ripple-keypairs'
import { encodeForSigningClaim } from 'ripple-binary-codec'
import { sign } from 'ripple-keypairs'
import { xrpToDrops } from './xrpConversion'
@@ -17,11 +17,11 @@ function signPaymentChannelClaim(
amount: string,
privateKey: string,
): string {
const signingData = binary.encodeForSigningClaim({
const signingData = encodeForSigningClaim({
channel,
amount: xrpToDrops(amount),
})
return keypairs.sign(signingData, privateKey)
return sign(signingData, privateKey)
}
export default signPaymentChannelClaim

View File

@@ -1,5 +1,5 @@
import binary from 'ripple-binary-codec'
import keypairs from 'ripple-keypairs'
import { encodeForSigningClaim } from 'ripple-binary-codec'
import { verify } from 'ripple-keypairs'
import { xrpToDrops } from './xrpConversion'
@@ -20,11 +20,11 @@ function verifyPaymentChannelClaim(
signature: string,
publicKey: string,
): boolean {
const signingData = binary.encodeForSigningClaim({
const signingData = encodeForSigningClaim({
channel,
amount: xrpToDrops(amount),
})
return keypairs.verify(signingData, signature, publicKey)
return verify(signingData, signature, publicKey)
}
export default verifyPaymentChannelClaim

View File

@@ -1,5 +1,6 @@
import { assert } from 'chai'
import ExponentialBackoff from 'xrpl-local/client/ExponentialBackoff'
import ExponentialBackoff from '../src/client/ExponentialBackoff'
describe('ExponentialBackoff', function () {
it('duration() return value starts with the min value', function () {

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