├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .nvmrc ├── HISTORY.md ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── README.md ├── binary.ts ├── coretypes.ts ├── enums │ ├── README.md │ ├── definitions.json │ ├── index.ts │ └── utils-renumber.ts ├── hash-prefixes.ts ├── hashes.ts ├── index.ts ├── ledger-hashes.ts ├── quality.ts ├── serdes │ ├── binary-parser.ts │ └── binary-serializer.ts ├── shamap.ts └── types │ ├── account-id.ts │ ├── amount.ts │ ├── blob.ts │ ├── currency.ts │ ├── hash-128.ts │ ├── hash-160.ts │ ├── hash-256.ts │ ├── hash.ts │ ├── index.ts │ ├── path-set.ts │ ├── serialized-type.ts │ ├── st-array.ts │ ├── st-object.ts │ ├── uint-16.ts │ ├── uint-32.ts │ ├── uint-64.ts │ ├── uint-8.ts │ ├── uint.ts │ └── vector-256.ts ├── test ├── amount.test.js ├── binary-json.test.js ├── binary-parser.test.js ├── binary-serializer.test.js ├── fixtures │ ├── account-tx-transactions.db │ ├── codec-fixtures.json │ ├── data-driven-tests.json │ ├── delivermin-tx-binary.json │ ├── delivermin-tx.json │ ├── deposit-preauth-tx-binary.json │ ├── deposit-preauth-tx-meta-binary.json │ ├── deposit-preauth-tx.json │ ├── escrow-cancel-binary.json │ ├── escrow-cancel-tx.json │ ├── escrow-create-binary.json │ ├── escrow-create-tx.json │ ├── escrow-finish-binary.json │ ├── escrow-finish-meta-binary.json │ ├── escrow-finish-tx.json │ ├── ledger-full-38129.json │ ├── ledger-full-40000.json │ ├── negative-unl.json │ ├── payment-channel-claim-binary.json │ ├── payment-channel-claim-tx.json │ ├── payment-channel-create-binary.json │ ├── payment-channel-create-tx.json │ ├── payment-channel-fund-binary.json │ ├── payment-channel-fund-tx.json │ ├── signerlistset-tx-binary.json │ ├── signerlistset-tx-meta-binary.json │ ├── signerlistset-tx.json │ ├── ticket-create-binary.json │ ├── ticket-create-tx.json │ └── x-codec-fixtures.json ├── hash.test.js ├── ledger.test.js ├── lower-case-hex.test.js ├── pseudo-transaction.test.js ├── quality.test.js ├── shamap.test.js ├── signing-data-encoding.test.js ├── tx-encode-decode.test.js ├── types.test.js ├── uint.test.js ├── utils.js └── x-address.test.js ├── tsconfig.eslint.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .github 4 | coverage -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | parser: '@typescript-eslint/parser', // Make ESLint compatible with TypeScript 5 | parserOptions: { 6 | // Enable linting rules with type information from our tsconfig 7 | tsconfigRootDir: __dirname, 8 | project: ['./tsconfig.eslint.json'], 9 | 10 | sourceType: 'module', // Allow the use of imports / ES modules 11 | 12 | ecmaFeatures: { 13 | impliedStrict: true, // Enable global strict mode 14 | }, 15 | }, 16 | 17 | // Specify global variables that are predefined 18 | env: { 19 | browser: true, // Enable browser global variables 20 | node: true, // Enable node global variables & Node.js scoping 21 | es2020: true, // Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020 22 | jest: true, // Add Mocha testing global variables 23 | }, 24 | 25 | plugins: [ 26 | '@typescript-eslint', // Add some TypeScript specific rules, and disable rules covered by the typechecker 27 | 'import', // Add rules that help validate proper imports 28 | 'prettier', // Allows running prettier as an ESLint rule, and reporting differences as individual linting issues 29 | 'jest' 30 | ], 31 | 32 | extends: [ 33 | // ESLint recommended rules 34 | 'eslint:recommended', 35 | 36 | // Add TypeScript-specific rules, and disable rules covered by typechecker 37 | 'plugin:@typescript-eslint/eslint-recommended', 38 | 'plugin:@typescript-eslint/recommended', 39 | 40 | // Add rules for import/export syntax 41 | 'plugin:import/errors', 42 | 'plugin:import/warnings', 43 | 'plugin:import/typescript', 44 | 45 | // Add rules that specifically require type information using our tsconfig 46 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 47 | 48 | // Enable Prettier for ESLint --fix, and disable rules that conflict with Prettier 49 | 'prettier/@typescript-eslint', 50 | 'plugin:prettier/recommended', 51 | ], 52 | 53 | // rules: { 54 | // // This rule is about explicitly using `return undefined` when a function returns any non-undefined object. 55 | // // However, since we're using TypeScript, it will yell at us if a function is not allowed to return `undefined` in its signature, so we don't need this rule. 56 | // "consistent-return": "off", 57 | // }, 58 | 59 | overrides: [ 60 | // Overrides for all test files 61 | { 62 | files: 'test/**/*.test.js', 63 | extends: ["plugin:jest/recommended"], 64 | rules: { 65 | // For our Mocha test files, the pattern has been to have unnamed functions 66 | 'func-names': 'off', 67 | // For some test files, we shadow testing constants with function parameter names 68 | 'no-shadow': 'off', 69 | // Some of our test files declare helper classes with errors 70 | 'max-classes-per-file': 'off', 71 | // Test files are in javascript, turn off TypeScript linting. 72 | '@typescript-eslint/no-var-requires': 'off', 73 | '@typescript-eslint/no-unsafe-call': 'off', 74 | '@typescript-eslint/no-unsafe-member-access': 'off', 75 | '@typescript-eslint/restrict-template-expressions': 'off', 76 | '@typescript-eslint/no-unsafe-assignment': 'off', 77 | '@typescript-eslint/restrict-template-expressions': 'off', 78 | '@typescript-eslint/no-unsafe-return': 'off', 79 | '@typescript-eslint/unbound-method': 'off' 80 | }, 81 | }, 82 | { 83 | files: '**/*.ts', 84 | rules: { 85 | // Allow unused variables in our files when explicitly prepended with `_`. 86 | '@typescript-eslint/no-unused-vars': [ 87 | 'error', 88 | { argsIgnorePattern: '^_' }, 89 | ], 90 | 91 | '@typescript-eslint/ban-types': 'off', 92 | 93 | // These rules are deprecated, but we have an old config that enables it 94 | '@typescript-eslint/camelcase': 'off', 95 | '@typescript-eslint/ban-ts-ignore': 'off', 96 | 97 | // These rules are actually disabled in @xpring-eng/eslint-config-base/loose at the moment 98 | '@typescript-eslint/no-unsafe-call': 'off', 99 | '@typescript-eslint/no-unsafe-member-access': 'off', 100 | '@typescript-eslint/no-unsafe-assignment': 'off', 101 | "spaced-comment": ["error", "always"], 102 | }, 103 | }, 104 | { 105 | files: ['src/XRP/default-xrp-client.ts'], 106 | rules: { 107 | // This is actually a good rule to have enabled, but for the XRPClient, we define a helper error message class in the same file 108 | 'max-classes-per-file': 'off', 109 | }, 110 | }, 111 | ], 112 | } -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build_with_nvm: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Read .nvmrc # From https://github.com/actions/setup-node/issues/32#issuecomment-525791142 20 | run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" 21 | id: nvm 22 | - name: Use Node.js (.nvmrc) 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: "${{ steps.nvm.outputs.NVMRC }}" 26 | - run: npm install 27 | - run: npm test 28 | - run: npm run lint 29 | - run: npm run compile 30 | 31 | build: 32 | 33 | runs-on: ubuntu-latest 34 | 35 | strategy: 36 | matrix: 37 | node-version: [10.x, 12.x, 13.x, 14.x] 38 | 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Use Node.js ${{ matrix.node-version }} 42 | uses: actions/setup-node@v1 43 | with: 44 | node-version: ${{ matrix.node-version }} 45 | - run: npm install 46 | - run: npm test 47 | - run: npm run lint 48 | - run: npm run compile 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | 3 | # Ignore vim swap files. 4 | *.swp 5 | 6 | # Ignore SCons support files. 7 | .sconsign.dblite 8 | 9 | # Ignore python compiled files. 10 | *.pyc 11 | 12 | # Ignore Macintosh Desktop Services Store files. 13 | .DS_Store 14 | 15 | # Ignore backup/temps 16 | *~ 17 | 18 | # Ignore object files. 19 | *.o 20 | build/ 21 | distrib/ 22 | tags 23 | bin/rippled 24 | Debug/*.* 25 | Release/*.* 26 | 27 | # Ignore locally installed node_modules 28 | node_modules 29 | !test/node_modules 30 | 31 | # Ignore tmp directory. 32 | tmp 33 | 34 | # Ignore database directory. 35 | db/*.db 36 | db/*.db-* 37 | 38 | # Ignore customized configs 39 | rippled.cfg 40 | validators.txt 41 | test/config.js 42 | 43 | # Ignore coverage files 44 | /lib-cov 45 | /src-cov 46 | /coverage.html 47 | /coverage 48 | 49 | # Ignore IntelliJ files 50 | .idea 51 | 52 | # Ignore npm-debug 53 | npm-debug.log 54 | 55 | # Ignore dist folder, build for bower 56 | dist/ 57 | 58 | # Ignore flow output directory 59 | out/ 60 | 61 | # Ignore perf test cache 62 | scripts/cache 63 | 64 | eslintrc 65 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.22.0 2 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # ripple-binary-codec Release History 2 | 3 | ## 1.1.3 (2021-06-11) 4 | - Fix for case UInt64.from string allowing lowercase hex (#135) 5 | - Fix for `ValidatorToReEnable` field code (#130) 6 | 7 | ## 1.1.2 (2021-03-10) 8 | - Fix for case UInt64.from string '0' due to changes in rippled 1.7.0 9 | 10 | ## 1.1.1 (2021-02-12) 11 | - PathSet.toJSON() does not return undefined values 12 | - Add support for X-Addresses in Issued Currency Amounts 13 | - Fix STArray error message 14 | 15 | ## 1.1.0 (2020-12-03) 16 | - Add support for Tickets (TicketBatch amendment) 17 | - Fix web browser compatibility 18 | 19 | ## 1.0.2 (2020-09-11) 20 | - Allow currencies to be encoded from any 3 character ASCII code 21 | 22 | ## 1.0.1 (2020-09-08) 23 | - Filter out fields with undefined values 24 | 25 | ## 1.0.0 (2020-08-17) 26 | 27 | - Migrate to TypeScript 28 | - Javascript classes used 29 | - Generics for constructing core types 30 | - Reduced dependencies 31 | - Dependent on create-hash, decimal.js, ripple-address-codec 32 | - Migrate testing to Jest and added tests 33 | - Tests for pseudo-transactions 34 | - Added support for NegativeUNL pseudo-transactions 35 | 36 | ## 0.2.6 (2019-12-31) 37 | 38 | - Update dependencies 39 | - decimal.js, fs-extra, mocha, handlebars, bn.js, babel-eslint, ripple-address-codec 40 | 41 | ## 0.2.5 (2019-12-14) 42 | 43 | - Add support for AccountDelete (#37) 44 | 45 | ## 0.2.4 (2019-09-04) 46 | 47 | - Update ripple-address-codec to 3.0.4 48 | 49 | ## 0.2.3 (2019-08-29) 50 | 51 | - Expand node version compatibility (#32, #33) 52 | 53 | ## 0.2.2 (2019-07-26) 54 | 55 | - Input validation - Amount and Fee should not allow fractional XRP drops ([#31](https://github.com/ripple/ripple-binary-codec/issues/31)) 56 | - Fix lint errors 57 | - Update dependencies (including lodash and mocha) 58 | - Require node 10 (.nvmrc) 59 | - Remove assert-diff 60 | - Remove codecov.io as it did not appear to work. The `package.json` script was: 61 | - `"codecov": "cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js"` 62 | 63 | ## 0.2.1 64 | 65 | - Add tecKILLED from amendment fix1578 (PR #27 fixes #25) 66 | 67 | ## 0.2.0 68 | 69 | - Add DepositPreauth fields 70 | - https://developers.ripple.com/depositauth.html 71 | 72 | ## 0.1.14 73 | 74 | - Skip amount validation when deserializing f72c115 75 | 76 | ## 0.1.13 77 | 78 | - Add Check, CheckCreate, CheckCash, CheckCancel 79 | 80 | ## 0.1.11 81 | 82 | - Add ledger header decode function 83 | 84 | ## 0.1.8 85 | 86 | ## 0.1.7 87 | 88 | ## 0.1.6 89 | 90 | ## 0.1.3 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ripple Labs Inc. 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Development Migration 2 | Development of this package has been moved to a mono-repo [xrpl.js](https://github.com/XRPLF/xrpl.js/tree/develop/packages/ripple-binary-codec) 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ripple-binary-codec", 3 | "version": "1.1.3", 4 | "description": "XRP Ledger binary codec", 5 | "files": [ 6 | "dist/*", 7 | "bin/*", 8 | "test/*" 9 | ], 10 | "main": "dist/", 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "dependencies": { 15 | "assert": "^2.0.0", 16 | "buffer": "5.6.0", 17 | "big-integer": "^1.6.48", 18 | "create-hash": "^1.2.0", 19 | "decimal.js": "^10.2.0", 20 | "ripple-address-codec": "^4.1.1" 21 | }, 22 | "devDependencies": { 23 | "@types/jest": "^26.0.7", 24 | "@types/node": "^14.0.10", 25 | "@typescript-eslint/eslint-plugin": "^3.2.0", 26 | "@typescript-eslint/parser": "^3.2.0", 27 | "eslint": "^7.7.0", 28 | "eslint-config-prettier": "^6.11.0", 29 | "eslint-plugin-import": "^2.21.1", 30 | "eslint-plugin-jest": "^23.20.0", 31 | "eslint-plugin-mocha": "^7.0.1", 32 | "eslint-plugin-prettier": "^3.1.3", 33 | "jest": "^26.0.1", 34 | "prettier": "^2.0.4", 35 | "typescript": "^3.9.5" 36 | }, 37 | "scripts": { 38 | "compile": "tsc && cp ./src/enums/definitions.json ./dist/enums", 39 | "prepare": "npm run compile && npm test", 40 | "test": "jest", 41 | "lint": "eslint . --ext .ts --ext .test.js" 42 | }, 43 | "repository": { 44 | "type": "git", 45 | "url": "git://github.com/ripple/ripple-binary-codec.git" 46 | }, 47 | "bugs": { 48 | "url": "https://github.com/ripple/ripple-binary-codec/issues" 49 | }, 50 | "homepage": "https://github.com/ripple/ripple-binary-codec#readme", 51 | "license": "ISC", 52 | "readmeFilename": "README.md", 53 | "prettier": { 54 | "semi": true 55 | }, 56 | "engines": { 57 | "node": ">=10.22.0", 58 | "npm": ">=7.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # ripple-binary-codec 2 | 3 | Serialize and deserialize transactions according to the XRP Ledger protocol. -------------------------------------------------------------------------------- /src/binary.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-style */ 2 | 3 | import { coreTypes } from "./types"; 4 | import { BinaryParser } from "./serdes/binary-parser"; 5 | import { AccountID } from "./types/account-id"; 6 | import { HashPrefix } from "./hash-prefixes"; 7 | import { BinarySerializer, BytesList } from "./serdes/binary-serializer"; 8 | import { sha512Half, transactionID } from "./hashes"; 9 | import { FieldInstance } from "./enums"; 10 | import { STObject } from "./types/st-object"; 11 | import { JsonObject } from "./types/serialized-type"; 12 | import { Buffer } from "buffer/"; 13 | import * as bigInt from "big-integer"; 14 | 15 | /** 16 | * Construct a BinaryParser 17 | * 18 | * @param bytes hex-string to construct BinaryParser from 19 | * @returns A BinaryParser 20 | */ 21 | const makeParser = (bytes: string): BinaryParser => new BinaryParser(bytes); 22 | 23 | /** 24 | * Parse BinaryParser into JSON 25 | * 26 | * @param parser BinaryParser object 27 | * @returns JSON for the bytes in the BinaryParser 28 | */ 29 | const readJSON = (parser: BinaryParser): JsonObject => 30 | (parser.readType(coreTypes.STObject) as STObject).toJSON(); 31 | 32 | /** 33 | * Parse a hex-string into its JSON interpretation 34 | * 35 | * @param bytes hex-string to parse into JSON 36 | * @returns JSON 37 | */ 38 | const binaryToJSON = (bytes: string): JsonObject => readJSON(makeParser(bytes)); 39 | 40 | /** 41 | * Interface for passing parameters to SerializeObject 42 | * 43 | * @field set signingFieldOnly to true if you want to serialize only signing fields 44 | */ 45 | interface OptionObject { 46 | prefix?: Buffer; 47 | suffix?: Buffer; 48 | signingFieldsOnly?: boolean; 49 | } 50 | 51 | /** 52 | * Function to serialize JSON object representing a transaction 53 | * 54 | * @param object JSON object to serialize 55 | * @param opts options for serializing, including optional prefix, suffix, and signingFieldOnly 56 | * @returns A Buffer containing the serialized object 57 | */ 58 | function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer { 59 | const { prefix, suffix, signingFieldsOnly = false } = opts; 60 | const bytesList = new BytesList(); 61 | 62 | if (prefix) { 63 | bytesList.put(prefix); 64 | } 65 | 66 | const filter = signingFieldsOnly 67 | ? (f: FieldInstance): boolean => f.isSigningField 68 | : undefined; 69 | coreTypes.STObject.from(object, filter).toBytesSink(bytesList); 70 | 71 | if (suffix) { 72 | bytesList.put(suffix); 73 | } 74 | 75 | return bytesList.toBytes(); 76 | } 77 | 78 | /** 79 | * Serialize an object for signing 80 | * 81 | * @param transaction Transaction to serialize 82 | * @param prefix Prefix bytes to put before the serialized object 83 | * @returns A Buffer with the serialized object 84 | */ 85 | function signingData( 86 | transaction: JsonObject, 87 | prefix: Buffer = HashPrefix.transactionSig 88 | ): Buffer { 89 | return serializeObject(transaction, { prefix, signingFieldsOnly: true }); 90 | } 91 | 92 | /** 93 | * Interface describing fields required for a Claim 94 | */ 95 | interface ClaimObject extends JsonObject { 96 | channel: string; 97 | amount: string | number; 98 | } 99 | 100 | /** 101 | * Serialize a signingClaim 102 | * 103 | * @param claim A claim object to serialize 104 | * @returns the serialized object with appropriate prefix 105 | */ 106 | function signingClaimData(claim: ClaimObject): Buffer { 107 | const num = bigInt(String(claim.amount)); 108 | const prefix = HashPrefix.paymentChannelClaim; 109 | const channel = coreTypes.Hash256.from(claim.channel).toBytes(); 110 | const amount = coreTypes.UInt64.from(num).toBytes(); 111 | 112 | const bytesList = new BytesList(); 113 | 114 | bytesList.put(prefix); 115 | bytesList.put(channel); 116 | bytesList.put(amount); 117 | return bytesList.toBytes(); 118 | } 119 | 120 | /** 121 | * Serialize a transaction object for multiSigning 122 | * 123 | * @param transaction transaction to serialize 124 | * @param signingAccount Account to sign the transaction with 125 | * @returns serialized transaction with appropriate prefix and suffix 126 | */ 127 | function multiSigningData( 128 | transaction: JsonObject, 129 | signingAccount: string | AccountID 130 | ): Buffer { 131 | const prefix = HashPrefix.transactionMultiSig; 132 | const suffix = coreTypes.AccountID.from(signingAccount).toBytes(); 133 | return serializeObject(transaction, { 134 | prefix, 135 | suffix, 136 | signingFieldsOnly: true, 137 | }); 138 | } 139 | 140 | export { 141 | BinaryParser, 142 | BinarySerializer, 143 | BytesList, 144 | ClaimObject, 145 | makeParser, 146 | serializeObject, 147 | readJSON, 148 | multiSigningData, 149 | signingData, 150 | signingClaimData, 151 | binaryToJSON, 152 | sha512Half, 153 | transactionID, 154 | }; 155 | -------------------------------------------------------------------------------- /src/coretypes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Field, 3 | TransactionType, 4 | LedgerEntryType, 5 | Type, 6 | TransactionResult, 7 | } from "./enums"; 8 | import * as types from "./types"; 9 | import * as binary from "./binary"; 10 | import { ShaMap } from "./shamap"; 11 | import * as ledgerHashes from "./ledger-hashes"; 12 | import * as hashes from "./hashes"; 13 | import { quality } from "./quality"; 14 | import { HashPrefix } from "./hash-prefixes"; 15 | 16 | export { 17 | hashes, 18 | binary, 19 | ledgerHashes, 20 | Field, 21 | TransactionType, 22 | LedgerEntryType, 23 | Type, 24 | TransactionResult, 25 | quality, 26 | HashPrefix, 27 | ShaMap, 28 | types, 29 | }; 30 | -------------------------------------------------------------------------------- /src/enums/README.md: -------------------------------------------------------------------------------- 1 | # Definitions 2 | 3 | ## Types 4 | 5 | These are the [types](https://xrpl.org/serialization.html#type-list) associated with a given Serialization Field. Each type has an arbitrary [type_code](https://xrpl.org/serialization.html#type-codes), with lower codes sorting first. 6 | 7 | ## Ledger Entry Types 8 | 9 | Each ledger's state tree contain [ledger objects](https://xrpl.org/ledger-object-types.html), which represent all settings, balances, and relationships in the shared ledger. 10 | 11 | ## Fields 12 | 13 | These are Serialization Fields (`sf`) [defined in rippled's SField.cpp](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/SField.cpp). Fields with undefined values are omitted before encoding. 14 | 15 | ### Key 16 | 17 | The key is the string defined in the rippled source code, such as "LedgerEntry", "Transaction", etc. 18 | 19 | ### nth 20 | 21 | `nth` is the sort code, meaning "nth of type." It is is combined with the type code in order to construct the Field ID of this field. The Field ID is only used for sorting the fields. Since there are multiple fields with the same data type, the `nth` is used to deterministically order each field among other fields of the same data type. 22 | 23 | Each field has a Field ID, which is used to sort fields that have the same type as one another with lower codes sorting first. 24 | 25 | - [Field definitions](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266) 26 | - [Constructing the `SField` field codes](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/SField.h#L95-L98) 27 | 28 | For example, the `Account` field has sort code (nth) `1`, so it comes before the `Destination` field which has sort code `3`. 29 | 30 | Sort code numbers are reused for fields of different types, but different fields of the same type never have the same sort code. When you combine the type code with the sort code, you get the field's unique _Field ID_. 31 | 32 | The unique [Field ID](https://xrpl.org/serialization.html#field-ids) is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and the field codes it combines. 33 | 34 | ### isVLEncoded 35 | 36 | If true, the field is Variable Length encoded and [length-prefixed](https://xrpl.org/serialization.html#length-prefixing). The variable-length encoded fields are `STI_VL`/`Blob`, `STI_ACCOUNT`/`AccountID`, and `STI_VECTOR256`/`Vector256`. 37 | 38 | ### isSerialized 39 | 40 | Fields are serialized if they are not [one of these](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/impl/SField.cpp#L71-L78) or if they are not an SField. 41 | 42 | - https://github.com/ripple/ripple-binary-codec/blob/14e76e68ead7e4bcd83c942dbdc9064d5a66869b/src/enums/definitions.json#L832 43 | - https://github.com/ripple/rippled/search?utf8=%E2%9C%93&q=taker_gets_funded&type= 44 | 45 | ### isSigningField 46 | 47 | True unless the field is [specified with `SField::notSigning`](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/impl/SField.cpp#L198). 48 | 49 | ## Transaction Results 50 | 51 | See: 52 | 53 | - https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/TER.h 54 | - https://xrpl.org/transaction-results.html 55 | 56 | TODO: Write a script to read rippled's source file and generate the necessary mapping. 57 | 58 | ## Transaction Types 59 | 60 | See https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/TxFormats.h 61 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | import * as enums from "./definitions.json"; 2 | import { SerializedType } from "../types/serialized-type"; 3 | import { Buffer } from "buffer/"; 4 | 5 | const TYPE_WIDTH = 2; 6 | const LEDGER_ENTRY_WIDTH = 2; 7 | const TRANSACTION_TYPE_WIDTH = 2; 8 | const TRANSACTION_RESULT_WIDTH = 1; 9 | 10 | /* 11 | * @brief: Serialize a field based on type_code and Field.nth 12 | */ 13 | function fieldHeader(type: number, nth: number): Buffer { 14 | const header: Array = []; 15 | if (type < 16) { 16 | if (nth < 16) { 17 | header.push((type << 4) | nth); 18 | } else { 19 | header.push(type << 4, nth); 20 | } 21 | } else if (nth < 16) { 22 | header.push(nth, type); 23 | } else { 24 | header.push(0, type, nth); 25 | } 26 | return Buffer.from(header); 27 | } 28 | 29 | /* 30 | * @brief: Bytes, name, and ordinal representing one type, ledger_type, transaction type, or result 31 | */ 32 | class Bytes { 33 | readonly bytes: Uint8Array; 34 | 35 | constructor( 36 | readonly name: string, 37 | readonly ordinal: number, 38 | readonly ordinalWidth: number 39 | ) { 40 | this.bytes = Buffer.alloc(ordinalWidth); 41 | for (let i = 0; i < ordinalWidth; i++) { 42 | this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff; 43 | } 44 | } 45 | 46 | toJSON(): string { 47 | return this.name; 48 | } 49 | 50 | toBytesSink(sink): void { 51 | sink.put(this.bytes); 52 | } 53 | 54 | toBytes(): Uint8Array { 55 | return this.bytes; 56 | } 57 | } 58 | 59 | /* 60 | * @brief: Collection of Bytes objects, mapping bidirectionally 61 | */ 62 | class BytesLookup { 63 | constructor(types: Record, readonly ordinalWidth: number) { 64 | Object.entries(types).forEach(([k, v]) => { 65 | this[k] = new Bytes(k, v, ordinalWidth); 66 | this[v.toString()] = this[k]; 67 | }); 68 | } 69 | 70 | from(value: Bytes | string): Bytes { 71 | return value instanceof Bytes ? value : (this[value] as Bytes); 72 | } 73 | 74 | fromParser(parser): Bytes { 75 | return this.from(parser.readUIntN(this.ordinalWidth).toString()); 76 | } 77 | } 78 | 79 | /* 80 | * type FieldInfo is the type of the objects containing information about each field in definitions.json 81 | */ 82 | interface FieldInfo { 83 | nth: number; 84 | isVLEncoded: boolean; 85 | isSerialized: boolean; 86 | isSigningField: boolean; 87 | type: string; 88 | } 89 | 90 | interface FieldInstance { 91 | readonly nth: number; 92 | readonly isVariableLengthEncoded: boolean; 93 | readonly isSerialized: boolean; 94 | readonly isSigningField: boolean; 95 | readonly type: Bytes; 96 | readonly ordinal: number; 97 | readonly name: string; 98 | readonly header: Buffer; 99 | readonly associatedType: typeof SerializedType; 100 | } 101 | 102 | function buildField([name, info]: [string, FieldInfo]): FieldInstance { 103 | const typeOrdinal = enums.TYPES[info.type]; 104 | const field = fieldHeader(typeOrdinal, info.nth); 105 | return { 106 | name: name, 107 | nth: info.nth, 108 | isVariableLengthEncoded: info.isVLEncoded, 109 | isSerialized: info.isSerialized, 110 | isSigningField: info.isSigningField, 111 | ordinal: (typeOrdinal << 16) | info.nth, 112 | type: new Bytes(info.type, typeOrdinal, TYPE_WIDTH), 113 | header: field, 114 | associatedType: SerializedType, // For later assignment in ./types/index.js 115 | }; 116 | } 117 | 118 | /* 119 | * @brief: The collection of all fields as defined in definitions.json 120 | */ 121 | class FieldLookup { 122 | constructor(fields: Array<[string, FieldInfo]>) { 123 | fields.forEach(([k, v]) => { 124 | this[k] = buildField([k, v]); 125 | this[this[k].ordinal.toString()] = this[k]; 126 | }); 127 | } 128 | 129 | fromString(value: string): FieldInstance { 130 | return this[value] as FieldInstance; 131 | } 132 | } 133 | 134 | const Type = new BytesLookup(enums.TYPES, TYPE_WIDTH); 135 | const LedgerEntryType = new BytesLookup( 136 | enums.LEDGER_ENTRY_TYPES, 137 | LEDGER_ENTRY_WIDTH 138 | ); 139 | const TransactionType = new BytesLookup( 140 | enums.TRANSACTION_TYPES, 141 | TRANSACTION_TYPE_WIDTH 142 | ); 143 | const TransactionResult = new BytesLookup( 144 | enums.TRANSACTION_RESULTS, 145 | TRANSACTION_RESULT_WIDTH 146 | ); 147 | const Field = new FieldLookup(enums.FIELDS as Array<[string, FieldInfo]>); 148 | 149 | export { 150 | Field, 151 | FieldInstance, 152 | Type, 153 | LedgerEntryType, 154 | TransactionResult, 155 | TransactionType, 156 | }; 157 | -------------------------------------------------------------------------------- /src/enums/utils-renumber.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Quick script to re-number values 3 | */ 4 | 5 | const input = { 6 | temBAD_SEND_XRP_PATHS: -283, 7 | temBAD_SEQUENCE: -282, 8 | temBAD_SIGNATURE: -281, 9 | temBAD_SRC_ACCOUNT: -280, 10 | temBAD_TRANSFER_RATE: -279, 11 | temDST_IS_SRC: -278, 12 | temDST_NEEDED: -277, 13 | temINVALID: -276, 14 | temINVALID_FLAG: -275, 15 | temREDUNDANT: -274, 16 | temRIPPLE_EMPTY: -273, 17 | temDISABLED: -272, 18 | temBAD_SIGNER: -271, 19 | temBAD_QUORUM: -270, 20 | temBAD_WEIGHT: -269, 21 | temBAD_TICK_SIZE: -268, 22 | temINVALID_ACCOUNT_ID: -267, 23 | temCANNOT_PREAUTH_SELF: -266, 24 | 25 | temUNCERTAIN: -265, 26 | temUNKNOWN: -264, 27 | 28 | tefFAILURE: -199, 29 | tefALREADY: -198, 30 | tefBAD_ADD_AUTH: -197, 31 | tefBAD_AUTH: -196, 32 | tefBAD_LEDGER: -195, 33 | tefCREATED: -194, 34 | tefEXCEPTION: -193, 35 | tefINTERNAL: -192, 36 | tefNO_AUTH_REQUIRED: -191, 37 | tefPAST_SEQ: -190, 38 | tefWRONG_PRIOR: -189, 39 | tefMASTER_DISABLED: -188, 40 | tefMAX_LEDGER: -187, 41 | tefBAD_SIGNATURE: -186, 42 | tefBAD_QUORUM: -185, 43 | tefNOT_MULTI_SIGNING: -184, 44 | tefBAD_AUTH_MASTER: -183, 45 | tefINVARIANT_FAILED: -182, 46 | tefTOO_BIG: -181, 47 | 48 | terRETRY: -99, 49 | terFUNDS_SPENT: -98, 50 | terINSUF_FEE_B: -97, 51 | terNO_ACCOUNT: -96, 52 | terNO_AUTH: -95, 53 | terNO_LINE: -94, 54 | terOWNERS: -93, 55 | terPRE_SEQ: -92, 56 | terLAST: -91, 57 | terNO_RIPPLE: -90, 58 | terQUEUED: -89, 59 | 60 | tesSUCCESS: 0, 61 | 62 | tecCLAIM: 100, 63 | tecPATH_PARTIAL: 101, 64 | tecUNFUNDED_ADD: 102, 65 | tecUNFUNDED_OFFER: 103, 66 | tecUNFUNDED_PAYMENT: 104, 67 | tecFAILED_PROCESSING: 105, 68 | tecDIR_FULL: 121, 69 | tecINSUF_RESERVE_LINE: 122, 70 | tecINSUF_RESERVE_OFFER: 123, 71 | tecNO_DST: 124, 72 | tecNO_DST_INSUF_XRP: 125, 73 | tecNO_LINE_INSUF_RESERVE: 126, 74 | tecNO_LINE_REDUNDANT: 127, 75 | tecPATH_DRY: 128, 76 | tecUNFUNDED: 129, 77 | tecNO_ALTERNATIVE_KEY: 130, 78 | tecNO_REGULAR_KEY: 131, 79 | tecOWNERS: 132, 80 | tecNO_ISSUER: 133, 81 | tecNO_AUTH: 134, 82 | tecNO_LINE: 135, 83 | tecINSUFF_FEE: 136, 84 | tecFROZEN: 137, 85 | tecNO_TARGET: 138, 86 | tecNO_PERMISSION: 139, 87 | tecNO_ENTRY: 140, 88 | tecINSUFFICIENT_RESERVE: 141, 89 | tecNEED_MASTER_KEY: 142, 90 | tecDST_TAG_NEEDED: 143, 91 | tecINTERNAL: 144, 92 | tecOVERSIZE: 145, 93 | tecCRYPTOCONDITION_ERROR: 146, 94 | tecINVARIANT_FAILED: 147, 95 | tecEXPIRED: 148, 96 | tecDUPLICATE: 149, 97 | tecKILLED: 150, 98 | tecHAS_OBLIGATIONS: 151, 99 | tecTOO_SOON: 152, 100 | }; 101 | 102 | let startingFromTemBADSENDXRPPATHS = -284; 103 | 104 | let startingFromTefFAILURE = -199; 105 | 106 | let startingFromTerRETRY = -99; 107 | 108 | const tesSUCCESS = 0; 109 | 110 | let startingFromTecCLAIM = 100; 111 | 112 | const startingFromTecDIRFULL = 121; 113 | 114 | let previousKey = "tem"; 115 | Object.keys(input).forEach((key) => { 116 | if (key.substring(0, 3) !== previousKey.substring(0, 3)) { 117 | console.log(); 118 | previousKey = key; 119 | } 120 | if (key.substring(0, 3) === "tem") { 121 | console.log(` "${key}": ${startingFromTemBADSENDXRPPATHS++},`); 122 | } else if (key.substring(0, 3) === "tef") { 123 | console.log(` "${key}": ${startingFromTefFAILURE++},`); 124 | } else if (key.substring(0, 3) === "ter") { 125 | console.log(` "${key}": ${startingFromTerRETRY++},`); 126 | } else if (key.substring(0, 3) === "tes") { 127 | console.log(` "${key}": ${tesSUCCESS},`); 128 | } else if (key.substring(0, 3) === "tec") { 129 | if (key === "tecDIR_FULL") { 130 | startingFromTecCLAIM = startingFromTecDIRFULL; 131 | } 132 | console.log(` "${key}": ${startingFromTecCLAIM++},`); 133 | } 134 | }); 135 | -------------------------------------------------------------------------------- /src/hash-prefixes.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer/"; 2 | 3 | /** 4 | * Write a 32 bit integer to a Buffer 5 | * 6 | * @param uint32 32 bit integer to write to buffer 7 | * @returns a buffer with the bytes representation of uint32 8 | */ 9 | function bytes(uint32: number): Buffer { 10 | const result = Buffer.alloc(4); 11 | result.writeUInt32BE(uint32, 0); 12 | return result; 13 | } 14 | 15 | /** 16 | * Maps HashPrefix names to their byte representation 17 | */ 18 | const HashPrefix: Record = { 19 | transactionID: bytes(0x54584e00), 20 | // transaction plus metadata 21 | transaction: bytes(0x534e4400), 22 | // account state 23 | accountStateEntry: bytes(0x4d4c4e00), 24 | // inner node in tree 25 | innerNode: bytes(0x4d494e00), 26 | // ledger master data for signing 27 | ledgerHeader: bytes(0x4c575200), 28 | // inner transaction to sign 29 | transactionSig: bytes(0x53545800), 30 | // inner transaction to sign 31 | transactionMultiSig: bytes(0x534d5400), 32 | // validation for signing 33 | validation: bytes(0x56414c00), 34 | // proposal for signing 35 | proposal: bytes(0x50525000), 36 | // payment channel claim 37 | paymentChannelClaim: bytes(0x434c4d00), 38 | }; 39 | 40 | export { HashPrefix }; 41 | -------------------------------------------------------------------------------- /src/hashes.ts: -------------------------------------------------------------------------------- 1 | import { HashPrefix } from "./hash-prefixes"; 2 | import * as createHash from "create-hash"; 3 | import { Hash256 } from "./types/hash-256"; 4 | import { BytesList } from "./serdes/binary-serializer"; 5 | import { Buffer } from "buffer/"; 6 | 7 | /** 8 | * Class for hashing with SHA512 9 | * @extends BytesList So SerializedTypes can write bytes to a Sha512Half 10 | */ 11 | class Sha512Half extends BytesList { 12 | private hash: createHash = createHash("sha512"); 13 | 14 | /** 15 | * Construct a new Sha512Hash and write bytes this.hash 16 | * 17 | * @param bytes bytes to write to this.hash 18 | * @returns the new Sha512Hash object 19 | */ 20 | static put(bytes: Buffer): Sha512Half { 21 | return new Sha512Half().put(bytes); 22 | } 23 | 24 | /** 25 | * Write bytes to an existing Sha512Hash 26 | * 27 | * @param bytes bytes to write to object 28 | * @returns the Sha512 object 29 | */ 30 | put(bytes: Buffer): Sha512Half { 31 | this.hash.update(bytes); 32 | return this; 33 | } 34 | 35 | /** 36 | * Compute SHA512 hash and slice in half 37 | * 38 | * @returns half of a SHA512 hash 39 | */ 40 | finish256(): Buffer { 41 | const bytes: Buffer = this.hash.digest(); 42 | return bytes.slice(0, 32); 43 | } 44 | 45 | /** 46 | * Constructs a Hash256 from the Sha512Half object 47 | * 48 | * @returns a Hash256 object 49 | */ 50 | finish(): Hash256 { 51 | return new Hash256(this.finish256()); 52 | } 53 | } 54 | 55 | /** 56 | * compute SHA512 hash of a list of bytes 57 | * 58 | * @param args zero or more arguments to hash 59 | * @returns the sha512half hash of the arguments. 60 | */ 61 | function sha512Half(...args: Buffer[]): Buffer { 62 | const hash = new Sha512Half(); 63 | args.forEach((a) => hash.put(a)); 64 | return hash.finish256(); 65 | } 66 | 67 | /** 68 | * Construct a transactionID from a Serialized Transaction 69 | * 70 | * @param serialized bytes to hash 71 | * @returns a Hash256 object 72 | */ 73 | function transactionID(serialized: Buffer): Hash256 { 74 | return new Hash256(sha512Half(HashPrefix.transactionID, serialized)); 75 | } 76 | 77 | export { Sha512Half, sha512Half, transactionID }; 78 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { quality, binary } from "./coretypes"; 3 | import { decodeLedgerData } from "./ledger-hashes"; 4 | import { ClaimObject } from "./binary"; 5 | import { JsonObject } from "./types/serialized-type"; 6 | const { 7 | signingData, 8 | signingClaimData, 9 | multiSigningData, 10 | binaryToJSON, 11 | serializeObject, 12 | } = binary; 13 | 14 | /** 15 | * Decode a transaction 16 | * 17 | * @param binary hex-string of the encoded transaction 18 | * @returns the JSON representation of the transaction 19 | */ 20 | function decode(binary: string): JsonObject { 21 | assert(typeof binary === "string", "binary must be a hex string"); 22 | return binaryToJSON(binary); 23 | } 24 | 25 | /** 26 | * Encode a transaction 27 | * 28 | * @param json The JSON representation of a transaction 29 | * @returns A hex-string of the encoded transaction 30 | */ 31 | function encode(json: object): string { 32 | assert(typeof json === "object"); 33 | return serializeObject(json as JsonObject) 34 | .toString("hex") 35 | .toUpperCase(); 36 | } 37 | 38 | /** 39 | * Encode a transaction and prepare for signing 40 | * 41 | * @param json JSON object representing the transaction 42 | * @param signer string representing the account to sign the transaction with 43 | * @returns a hex string of the encoded transaction 44 | */ 45 | function encodeForSigning(json: object): string { 46 | assert(typeof json === "object"); 47 | return signingData(json as JsonObject) 48 | .toString("hex") 49 | .toUpperCase(); 50 | } 51 | 52 | /** 53 | * Encode a transaction and prepare for signing with a claim 54 | * 55 | * @param json JSON object representing the transaction 56 | * @param signer string representing the account to sign the transaction with 57 | * @returns a hex string of the encoded transaction 58 | */ 59 | function encodeForSigningClaim(json: object): string { 60 | assert(typeof json === "object"); 61 | return signingClaimData(json as ClaimObject) 62 | .toString("hex") 63 | .toUpperCase(); 64 | } 65 | 66 | /** 67 | * Encode a transaction and prepare for multi-signing 68 | * 69 | * @param json JSON object representing the transaction 70 | * @param signer string representing the account to sign the transaction with 71 | * @returns a hex string of the encoded transaction 72 | */ 73 | function encodeForMultisigning(json: object, signer: string): string { 74 | assert(typeof json === "object"); 75 | assert.equal(json["SigningPubKey"], ""); 76 | return multiSigningData(json as JsonObject, signer) 77 | .toString("hex") 78 | .toUpperCase(); 79 | } 80 | 81 | /** 82 | * Encode a quality value 83 | * 84 | * @param value string representation of a number 85 | * @returns a hex-string representing the quality 86 | */ 87 | function encodeQuality(value: string): string { 88 | assert(typeof value === "string"); 89 | return quality.encode(value).toString("hex").toUpperCase(); 90 | } 91 | 92 | /** 93 | * Decode a quality value 94 | * 95 | * @param value hex-string of a quality 96 | * @returns a string representing the quality 97 | */ 98 | function decodeQuality(value: string): string { 99 | assert(typeof value === "string"); 100 | return quality.decode(value).toString(); 101 | } 102 | 103 | export = { 104 | decode, 105 | encode, 106 | encodeForSigning, 107 | encodeForSigningClaim, 108 | encodeForMultisigning, 109 | encodeQuality, 110 | decodeQuality, 111 | decodeLedgerData, 112 | }; 113 | -------------------------------------------------------------------------------- /src/ledger-hashes.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { ShaMap, ShaMapNode, ShaMapLeaf } from "./shamap"; 3 | import { HashPrefix } from "./hash-prefixes"; 4 | import { Sha512Half } from "./hashes"; 5 | import { BinarySerializer, serializeObject } from "./binary"; 6 | import { Hash256 } from "./types/hash-256"; 7 | import { STObject } from "./types/st-object"; 8 | import { UInt64 } from "./types/uint-64"; 9 | import { UInt32 } from "./types/uint-32"; 10 | import { UInt8 } from "./types/uint-8"; 11 | import { BinaryParser } from "./serdes/binary-parser"; 12 | import { JsonObject } from "./types/serialized-type"; 13 | import * as bigInt from "big-integer"; 14 | 15 | /** 16 | * Computes the hash of a list of objects 17 | * 18 | * @param itemizer Converts an item into a format that can be added to SHAMap 19 | * @param itemsJson Array of items to add to a SHAMap 20 | * @returns the hash of the SHAMap 21 | */ 22 | function computeHash( 23 | itemizer: (item: JsonObject) => [Hash256?, ShaMapNode?, ShaMapLeaf?], 24 | itemsJson: Array 25 | ): Hash256 { 26 | const map = new ShaMap(); 27 | itemsJson.forEach((item) => map.addItem(...itemizer(item))); 28 | return map.hash(); 29 | } 30 | 31 | /** 32 | * Interface describing a transaction item 33 | */ 34 | interface transactionItemObject extends JsonObject { 35 | hash: string; 36 | metaData: JsonObject; 37 | } 38 | 39 | /** 40 | * Convert a transaction into an index and an item 41 | * 42 | * @param json transaction with metadata 43 | * @returns a tuple of index and item to be added to SHAMap 44 | */ 45 | function transactionItemizer( 46 | json: transactionItemObject 47 | ): [Hash256, ShaMapNode, undefined] { 48 | assert(json.hash); 49 | const index = Hash256.from(json.hash); 50 | const item = { 51 | hashPrefix() { 52 | return HashPrefix.transaction; 53 | }, 54 | toBytesSink(sink) { 55 | const serializer = new BinarySerializer(sink); 56 | serializer.writeLengthEncoded(STObject.from(json)); 57 | serializer.writeLengthEncoded(STObject.from(json.metaData)); 58 | }, 59 | } as ShaMapNode; 60 | return [index, item, undefined]; 61 | } 62 | 63 | /** 64 | * Interface describing an entry item 65 | */ 66 | interface entryItemObject extends JsonObject { 67 | index: string; 68 | } 69 | 70 | /** 71 | * Convert an entry to a pair Hash256 and ShaMapNode 72 | * 73 | * @param json JSON describing a ledger entry item 74 | * @returns a tuple of index and item to be added to SHAMap 75 | */ 76 | function entryItemizer( 77 | json: entryItemObject 78 | ): [Hash256, ShaMapNode, undefined] { 79 | const index = Hash256.from(json.index); 80 | const bytes = serializeObject(json); 81 | const item = { 82 | hashPrefix() { 83 | return HashPrefix.accountStateEntry; 84 | }, 85 | toBytesSink(sink) { 86 | sink.put(bytes); 87 | }, 88 | } as ShaMapNode; 89 | return [index, item, undefined]; 90 | } 91 | 92 | /** 93 | * Function computing the hash of a transaction tree 94 | * 95 | * @param param An array of transaction objects to hash 96 | * @returns A Hash256 object 97 | */ 98 | function transactionTreeHash(param: Array): Hash256 { 99 | const itemizer = transactionItemizer as ( 100 | json: JsonObject 101 | ) => [Hash256, ShaMapNode, undefined]; 102 | return computeHash(itemizer, param); 103 | } 104 | 105 | /** 106 | * Function computing the hash of accountState 107 | * 108 | * @param param A list of accountStates hash 109 | * @returns A Hash256 object 110 | */ 111 | function accountStateHash(param: Array): Hash256 { 112 | const itemizer = entryItemizer as ( 113 | json: JsonObject 114 | ) => [Hash256, ShaMapNode, undefined]; 115 | return computeHash(itemizer, param); 116 | } 117 | 118 | /** 119 | * Interface describing a ledger header 120 | */ 121 | interface ledgerObject { 122 | ledger_index: number; 123 | total_coins: string | number | bigInt.BigInteger; 124 | parent_hash: string; 125 | transaction_hash: string; 126 | account_hash: string; 127 | parent_close_time: number; 128 | close_time: number; 129 | close_time_resolution: number; 130 | close_flags: number; 131 | } 132 | 133 | /** 134 | * Serialize and hash a ledger header 135 | * 136 | * @param header a ledger header 137 | * @returns the hash of header 138 | */ 139 | function ledgerHash(header: ledgerObject): Hash256 { 140 | const hash = new Sha512Half(); 141 | hash.put(HashPrefix.ledgerHeader); 142 | assert(header.parent_close_time !== undefined); 143 | assert(header.close_flags !== undefined); 144 | 145 | UInt32.from(header.ledger_index).toBytesSink(hash); 146 | UInt64.from( 147 | bigInt(String(header.total_coins)) 148 | ).toBytesSink(hash); 149 | Hash256.from(header.parent_hash).toBytesSink(hash); 150 | Hash256.from(header.transaction_hash).toBytesSink(hash); 151 | Hash256.from(header.account_hash).toBytesSink(hash); 152 | UInt32.from(header.parent_close_time).toBytesSink(hash); 153 | UInt32.from(header.close_time).toBytesSink(hash); 154 | UInt8.from(header.close_time_resolution).toBytesSink(hash); 155 | UInt8.from(header.close_flags).toBytesSink(hash); 156 | return hash.finish(); 157 | } 158 | 159 | /** 160 | * Decodes a serialized ledger header 161 | * 162 | * @param binary A serialized ledger header 163 | * @returns A JSON object describing a ledger header 164 | */ 165 | function decodeLedgerData(binary: string): object { 166 | assert(typeof binary === "string", "binary must be a hex string"); 167 | const parser = new BinaryParser(binary); 168 | return { 169 | ledger_index: parser.readUInt32(), 170 | total_coins: parser.readType(UInt64).valueOf().toString(), 171 | parent_hash: parser.readType(Hash256).toHex(), 172 | transaction_hash: parser.readType(Hash256).toHex(), 173 | account_hash: parser.readType(Hash256).toHex(), 174 | parent_close_time: parser.readUInt32(), 175 | close_time: parser.readUInt32(), 176 | close_time_resolution: parser.readUInt8(), 177 | close_flags: parser.readUInt8(), 178 | }; 179 | } 180 | 181 | export { accountStateHash, transactionTreeHash, ledgerHash, decodeLedgerData }; 182 | -------------------------------------------------------------------------------- /src/quality.ts: -------------------------------------------------------------------------------- 1 | import { coreTypes } from "./types"; 2 | import { Decimal } from "decimal.js"; 3 | import * as bigInt from "big-integer"; 4 | import { Buffer } from "buffer/"; 5 | 6 | /** 7 | * class for encoding and decoding quality 8 | */ 9 | class quality { 10 | /** 11 | * Encode quality amount 12 | * 13 | * @param arg string representation of an amount 14 | * @returns Serialized quality 15 | */ 16 | static encode(quality: string): Buffer { 17 | const decimal = new Decimal(quality); 18 | const exponent = decimal.e - 15; 19 | const qualityString = decimal.times(`1e${-exponent}`).abs().toString(); 20 | const bytes = coreTypes.UInt64.from(bigInt(qualityString)).toBytes(); 21 | bytes[0] = exponent + 100; 22 | return bytes; 23 | } 24 | 25 | /** 26 | * Decode quality amount 27 | * 28 | * @param arg hex-string denoting serialized quality 29 | * @returns deserialized quality 30 | */ 31 | static decode(quality: string): Decimal { 32 | const bytes = Buffer.from(quality, "hex").slice(-8); 33 | const exponent = bytes[0] - 100; 34 | const mantissa = new Decimal(`0x${bytes.slice(1).toString("hex")}`); 35 | return mantissa.times(`1e${exponent}`); 36 | } 37 | } 38 | 39 | export { quality }; 40 | -------------------------------------------------------------------------------- /src/serdes/binary-parser.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { Field, FieldInstance } from "../enums"; 3 | import { SerializedType } from "../types/serialized-type"; 4 | import { Buffer } from "buffer/"; 5 | 6 | /** 7 | * BinaryParser is used to compute fields and values from a HexString 8 | */ 9 | class BinaryParser { 10 | private bytes: Buffer; 11 | 12 | /** 13 | * Initialize bytes to a hex string 14 | * 15 | * @param hexBytes a hex string 16 | */ 17 | constructor(hexBytes: string) { 18 | this.bytes = Buffer.from(hexBytes, "hex"); 19 | } 20 | 21 | /** 22 | * Peek the first byte of the BinaryParser 23 | * 24 | * @returns The first byte of the BinaryParser 25 | */ 26 | peek(): number { 27 | assert(this.bytes.byteLength !== 0); 28 | return this.bytes[0]; 29 | } 30 | 31 | /** 32 | * Consume the first n bytes of the BinaryParser 33 | * 34 | * @param n the number of bytes to skip 35 | */ 36 | skip(n: number): void { 37 | assert(n <= this.bytes.byteLength); 38 | this.bytes = this.bytes.slice(n); 39 | } 40 | 41 | /** 42 | * read the first n bytes from the BinaryParser 43 | * 44 | * @param n The number of bytes to read 45 | * @return The bytes 46 | */ 47 | read(n: number): Buffer { 48 | assert(n <= this.bytes.byteLength); 49 | 50 | const slice = this.bytes.slice(0, n); 51 | this.skip(n); 52 | return slice; 53 | } 54 | 55 | /** 56 | * Read an integer of given size 57 | * 58 | * @param n The number of bytes to read 59 | * @return The number represented by those bytes 60 | */ 61 | readUIntN(n: number): number { 62 | assert(0 < n && n <= 4, "invalid n"); 63 | return this.read(n).reduce((a, b) => (a << 8) | b) >>> 0; 64 | } 65 | 66 | readUInt8(): number { 67 | return this.readUIntN(1); 68 | } 69 | 70 | readUInt16(): number { 71 | return this.readUIntN(2); 72 | } 73 | 74 | readUInt32(): number { 75 | return this.readUIntN(4); 76 | } 77 | 78 | size(): number { 79 | return this.bytes.byteLength; 80 | } 81 | 82 | end(customEnd?: number): boolean { 83 | const length = this.bytes.byteLength; 84 | return length === 0 || (customEnd !== undefined && length <= customEnd); 85 | } 86 | 87 | /** 88 | * Reads variable length encoded bytes 89 | * 90 | * @return The variable length bytes 91 | */ 92 | readVariableLength(): Buffer { 93 | return this.read(this.readVariableLengthLength()); 94 | } 95 | 96 | /** 97 | * Reads the length of the variable length encoded bytes 98 | * 99 | * @return The length of the variable length encoded bytes 100 | */ 101 | readVariableLengthLength(): number { 102 | const b1 = this.readUInt8(); 103 | if (b1 <= 192) { 104 | return b1; 105 | } else if (b1 <= 240) { 106 | const b2 = this.readUInt8(); 107 | return 193 + (b1 - 193) * 256 + b2; 108 | } else if (b1 <= 254) { 109 | const b2 = this.readUInt8(); 110 | const b3 = this.readUInt8(); 111 | return 12481 + (b1 - 241) * 65536 + b2 * 256 + b3; 112 | } 113 | throw new Error("Invalid variable length indicator"); 114 | } 115 | 116 | /** 117 | * Reads the field ordinal from the BinaryParser 118 | * 119 | * @return Field ordinal 120 | */ 121 | readFieldOrdinal(): number { 122 | let type = this.readUInt8(); 123 | let nth = type & 15; 124 | type >>= 4; 125 | 126 | if (type === 0) { 127 | type = this.readUInt8(); 128 | if (type === 0 || type < 16) { 129 | throw new Error("Cannot read FieldOrdinal, type_code out of range"); 130 | } 131 | } 132 | 133 | if (nth === 0) { 134 | nth = this.readUInt8(); 135 | if (nth === 0 || nth < 16) { 136 | throw new Error("Cannot read FieldOrdinal, field_code out of range"); 137 | } 138 | } 139 | 140 | return (type << 16) | nth; 141 | } 142 | 143 | /** 144 | * Read the field from the BinaryParser 145 | * 146 | * @return The field represented by the bytes at the head of the BinaryParser 147 | */ 148 | readField(): FieldInstance { 149 | return Field.fromString(this.readFieldOrdinal().toString()); 150 | } 151 | 152 | /** 153 | * Read a given type from the BinaryParser 154 | * 155 | * @param type The type that you want to read from the BinaryParser 156 | * @return The instance of that type read from the BinaryParser 157 | */ 158 | readType(type: typeof SerializedType): SerializedType { 159 | return type.fromParser(this); 160 | } 161 | 162 | /** 163 | * Get the type associated with a given field 164 | * 165 | * @param field The field that you wan to get the type of 166 | * @return The type associated with the given field 167 | */ 168 | typeForField(field: FieldInstance): typeof SerializedType { 169 | return field.associatedType; 170 | } 171 | 172 | /** 173 | * Read value of the type specified by field from the BinaryParser 174 | * 175 | * @param field The field that you want to get the associated value for 176 | * @return The value associated with the given field 177 | */ 178 | readFieldValue(field: FieldInstance): SerializedType { 179 | const type = this.typeForField(field); 180 | if (!type) { 181 | throw new Error(`unsupported: (${field.name}, ${field.type.name})`); 182 | } 183 | const sizeHint = field.isVariableLengthEncoded 184 | ? this.readVariableLengthLength() 185 | : undefined; 186 | const value = type.fromParser(this, sizeHint); 187 | if (value === undefined) { 188 | throw new Error( 189 | `fromParser for (${field.name}, ${field.type.name}) -> undefined ` 190 | ); 191 | } 192 | return value; 193 | } 194 | 195 | /** 196 | * Get the next field and value from the BinaryParser 197 | * 198 | * @return The field and value 199 | */ 200 | readFieldAndValue(): [FieldInstance, SerializedType] { 201 | const field = this.readField(); 202 | return [field, this.readFieldValue(field)]; 203 | } 204 | } 205 | 206 | export { BinaryParser }; 207 | -------------------------------------------------------------------------------- /src/serdes/binary-serializer.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { FieldInstance } from "../enums"; 3 | import { SerializedType } from "../types/serialized-type"; 4 | import { Buffer } from "buffer/"; 5 | 6 | /** 7 | * Bytes list is a collection of buffer objects 8 | */ 9 | class BytesList { 10 | private bytesArray: Array = []; 11 | 12 | /** 13 | * Get the total number of bytes in the BytesList 14 | * 15 | * @return the number of bytes 16 | */ 17 | public getLength(): number { 18 | return Buffer.concat(this.bytesArray).byteLength; 19 | } 20 | 21 | /** 22 | * Put bytes in the BytesList 23 | * 24 | * @param bytesArg A Buffer 25 | * @return this BytesList 26 | */ 27 | public put(bytesArg: Buffer): BytesList { 28 | const bytes = Buffer.from(bytesArg); // Temporary, to catch instances of Uint8Array being passed in 29 | this.bytesArray.push(bytes); 30 | return this; 31 | } 32 | 33 | /** 34 | * Write this BytesList to the back of another bytes list 35 | * 36 | * @param list The BytesList to write to 37 | */ 38 | public toBytesSink(list: BytesList): void { 39 | list.put(this.toBytes()); 40 | } 41 | 42 | public toBytes(): Buffer { 43 | return Buffer.concat(this.bytesArray); 44 | } 45 | 46 | toHex(): string { 47 | return this.toBytes().toString("hex").toUpperCase(); 48 | } 49 | } 50 | 51 | /** 52 | * BinarySerializer is used to write fields and values to buffers 53 | */ 54 | class BinarySerializer { 55 | private sink: BytesList = new BytesList(); 56 | 57 | constructor(sink: BytesList) { 58 | this.sink = sink; 59 | } 60 | 61 | /** 62 | * Write a value to this BinarySerializer 63 | * 64 | * @param value a SerializedType value 65 | */ 66 | write(value: SerializedType): void { 67 | value.toBytesSink(this.sink); 68 | } 69 | 70 | /** 71 | * Write bytes to this BinarySerializer 72 | * 73 | * @param bytes the bytes to write 74 | */ 75 | put(bytes: Buffer): void { 76 | this.sink.put(bytes); 77 | } 78 | 79 | /** 80 | * Write a value of a given type to this BinarySerializer 81 | * 82 | * @param type the type to write 83 | * @param value a value of that type 84 | */ 85 | writeType(type: typeof SerializedType, value: SerializedType): void { 86 | this.write(type.from(value)); 87 | } 88 | 89 | /** 90 | * Write BytesList to this BinarySerializer 91 | * 92 | * @param bl BytesList to write to BinarySerializer 93 | */ 94 | writeBytesList(bl: BytesList): void { 95 | bl.toBytesSink(this.sink); 96 | } 97 | 98 | /** 99 | * Calculate the header of Variable Length encoded bytes 100 | * 101 | * @param length the length of the bytes 102 | */ 103 | private encodeVariableLength(length: number): Buffer { 104 | const lenBytes = Buffer.alloc(3); 105 | if (length <= 192) { 106 | lenBytes[0] = length; 107 | return lenBytes.slice(0, 1); 108 | } else if (length <= 12480) { 109 | length -= 193; 110 | lenBytes[0] = 193 + (length >>> 8); 111 | lenBytes[1] = length & 0xff; 112 | return lenBytes.slice(0, 2); 113 | } else if (length <= 918744) { 114 | length -= 12481; 115 | lenBytes[0] = 241 + (length >>> 16); 116 | lenBytes[1] = (length >> 8) & 0xff; 117 | lenBytes[2] = length & 0xff; 118 | return lenBytes.slice(0, 3); 119 | } 120 | throw new Error("Overflow error"); 121 | } 122 | 123 | /** 124 | * Write field and value to BinarySerializer 125 | * 126 | * @param field field to write to BinarySerializer 127 | * @param value value to write to BinarySerializer 128 | */ 129 | writeFieldAndValue(field: FieldInstance, value: SerializedType): void { 130 | const associatedValue = field.associatedType.from(value); 131 | assert(associatedValue.toBytesSink !== undefined); 132 | assert(field.name !== undefined); 133 | 134 | this.sink.put(field.header); 135 | 136 | if (field.isVariableLengthEncoded) { 137 | this.writeLengthEncoded(associatedValue); 138 | } else { 139 | associatedValue.toBytesSink(this.sink); 140 | } 141 | } 142 | 143 | /** 144 | * Write a variable length encoded value to the BinarySerializer 145 | * 146 | * @param value length encoded value to write to BytesList 147 | */ 148 | public writeLengthEncoded(value: SerializedType): void { 149 | const bytes = new BytesList(); 150 | value.toBytesSink(bytes); 151 | this.put(this.encodeVariableLength(bytes.getLength())); 152 | this.writeBytesList(bytes); 153 | } 154 | } 155 | 156 | export { BytesList, BinarySerializer }; 157 | -------------------------------------------------------------------------------- /src/shamap.ts: -------------------------------------------------------------------------------- 1 | import { strict as assert } from "assert"; 2 | import { coreTypes } from "./types"; 3 | import { HashPrefix } from "./hash-prefixes"; 4 | import { Sha512Half } from "./hashes"; 5 | import { Hash256 } from "./types/hash-256"; 6 | import { BytesList } from "./serdes/binary-serializer"; 7 | import { Buffer } from "buffer/"; 8 | 9 | /** 10 | * Abstract class describing a SHAMapNode 11 | */ 12 | abstract class ShaMapNode { 13 | abstract hashPrefix(): Buffer; 14 | abstract isLeaf(): boolean; 15 | abstract isInner(): boolean; 16 | abstract toBytesSink(list: BytesList): void; 17 | abstract hash(): Hash256; 18 | } 19 | 20 | /** 21 | * Class describing a Leaf of SHAMap 22 | */ 23 | class ShaMapLeaf extends ShaMapNode { 24 | constructor(public index: Hash256, public item?: ShaMapNode) { 25 | super(); 26 | } 27 | 28 | /** 29 | * @returns true as ShaMapLeaf is a leaf node 30 | */ 31 | isLeaf(): boolean { 32 | return true; 33 | } 34 | 35 | /** 36 | * @returns false as ShaMapLeaf is not an inner node 37 | */ 38 | isInner(): boolean { 39 | return false; 40 | } 41 | 42 | /** 43 | * Get the prefix of the this.item 44 | * 45 | * @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer 46 | */ 47 | hashPrefix(): Buffer { 48 | return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix(); 49 | } 50 | 51 | /** 52 | * Hash the bytes representation of this 53 | * 54 | * @returns hash of this.item concatenated with this.index 55 | */ 56 | hash(): Hash256 { 57 | const hash = Sha512Half.put(this.hashPrefix()); 58 | this.toBytesSink(hash); 59 | return hash.finish(); 60 | } 61 | 62 | /** 63 | * Write the bytes representation of this to a BytesList 64 | * @param list BytesList to write bytes to 65 | */ 66 | toBytesSink(list: BytesList): void { 67 | if (this.item !== undefined) { 68 | this.item.toBytesSink(list); 69 | } 70 | this.index.toBytesSink(list); 71 | } 72 | } 73 | 74 | /** 75 | * Class defining an Inner Node of a SHAMap 76 | */ 77 | class ShaMapInner extends ShaMapNode { 78 | private slotBits = 0; 79 | private branches: Array = Array(16); 80 | 81 | constructor(private depth: number = 0) { 82 | super(); 83 | } 84 | 85 | /** 86 | * @returns true as ShaMapInner is an inner node 87 | */ 88 | isInner(): boolean { 89 | return true; 90 | } 91 | 92 | /** 93 | * @returns false as ShaMapInner is not a leaf node 94 | */ 95 | isLeaf(): boolean { 96 | return false; 97 | } 98 | 99 | /** 100 | * Get the hash prefix for this node 101 | * 102 | * @returns hash prefix describing an inner node 103 | */ 104 | hashPrefix(): Buffer { 105 | return HashPrefix.innerNode; 106 | } 107 | 108 | /** 109 | * Set a branch of this node to be another node 110 | * 111 | * @param slot Slot to add branch to this.branches 112 | * @param branch Branch to add 113 | */ 114 | setBranch(slot: number, branch: ShaMapNode): void { 115 | this.slotBits = this.slotBits | (1 << slot); 116 | this.branches[slot] = branch; 117 | } 118 | 119 | /** 120 | * @returns true if node is empty 121 | */ 122 | empty(): boolean { 123 | return this.slotBits === 0; 124 | } 125 | 126 | /** 127 | * Compute the hash of this node 128 | * 129 | * @returns The hash of this node 130 | */ 131 | hash(): Hash256 { 132 | if (this.empty()) { 133 | return coreTypes.Hash256.ZERO_256; 134 | } 135 | const hash = Sha512Half.put(this.hashPrefix()); 136 | this.toBytesSink(hash); 137 | return hash.finish(); 138 | } 139 | 140 | /** 141 | * Writes the bytes representation of this node to a BytesList 142 | * 143 | * @param list BytesList to write bytes to 144 | */ 145 | toBytesSink(list: BytesList): void { 146 | for (let i = 0; i < this.branches.length; i++) { 147 | const branch = this.branches[i]; 148 | const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256; 149 | hash.toBytesSink(list); 150 | } 151 | } 152 | 153 | /** 154 | * Add item to the SHAMap 155 | * 156 | * @param index Hash of the index of the item being inserted 157 | * @param item Item to insert in the map 158 | * @param leaf Leaf node to insert when branch doesn't exist 159 | */ 160 | addItem(index?: Hash256, item?: ShaMapNode, leaf?: ShaMapLeaf): void { 161 | assert(index !== undefined); 162 | const nibble = index.nibblet(this.depth); 163 | const existing = this.branches[nibble]; 164 | 165 | if (existing === undefined) { 166 | this.setBranch(nibble, leaf || new ShaMapLeaf(index, item)); 167 | } else if (existing instanceof ShaMapLeaf) { 168 | const newInner = new ShaMapInner(this.depth + 1); 169 | newInner.addItem(existing.index, undefined, existing); 170 | newInner.addItem(index, item, leaf); 171 | this.setBranch(nibble, newInner); 172 | } else if (existing instanceof ShaMapInner) { 173 | existing.addItem(index, item, leaf); 174 | } else { 175 | throw new Error("invalid ShaMap.addItem call"); 176 | } 177 | } 178 | } 179 | 180 | class ShaMap extends ShaMapInner {} 181 | 182 | export { ShaMap, ShaMapNode, ShaMapLeaf }; 183 | -------------------------------------------------------------------------------- /src/types/account-id.ts: -------------------------------------------------------------------------------- 1 | import { 2 | decodeAccountID, 3 | encodeAccountID, 4 | isValidXAddress, 5 | xAddressToClassicAddress, 6 | } from "ripple-address-codec"; 7 | import { Hash160 } from "./hash-160"; 8 | import { Buffer } from "buffer/"; 9 | 10 | const HEX_REGEX = /^[A-F0-9]{40}$/; 11 | 12 | /** 13 | * Class defining how to encode and decode an AccountID 14 | */ 15 | class AccountID extends Hash160 { 16 | static readonly defaultAccountID: AccountID = new AccountID(Buffer.alloc(20)); 17 | 18 | constructor(bytes?: Buffer) { 19 | super(bytes ?? AccountID.defaultAccountID.bytes); 20 | } 21 | 22 | /** 23 | * Defines how to construct an AccountID 24 | * 25 | * @param value either an existing AccountID, a hex-string, or a base58 r-Address 26 | * @returns an AccountID object 27 | */ 28 | static from(value: T): AccountID { 29 | if (value instanceof AccountID) { 30 | return value; 31 | } 32 | 33 | if (typeof value === "string") { 34 | if (value === "") { 35 | return new AccountID(); 36 | } 37 | 38 | return HEX_REGEX.test(value) 39 | ? new AccountID(Buffer.from(value, "hex")) 40 | : this.fromBase58(value); 41 | } 42 | 43 | throw new Error("Cannot construct AccountID from value given"); 44 | } 45 | 46 | /** 47 | * Defines how to build an AccountID from a base58 r-Address 48 | * 49 | * @param value a base58 r-Address 50 | * @returns an AccountID object 51 | */ 52 | static fromBase58(value: string): AccountID { 53 | if (isValidXAddress(value)) { 54 | const classic = xAddressToClassicAddress(value); 55 | 56 | if (classic.tag !== false) 57 | throw new Error("Only allowed to have tag on Account or Destination"); 58 | 59 | value = classic.classicAddress; 60 | } 61 | 62 | return new AccountID(Buffer.from(decodeAccountID(value))); 63 | } 64 | 65 | /** 66 | * Overload of toJSON 67 | * 68 | * @returns the base58 string for this AccountID 69 | */ 70 | toJSON(): string { 71 | return this.toBase58(); 72 | } 73 | 74 | /** 75 | * Defines how to encode AccountID into a base58 address 76 | * 77 | * @returns the base58 string defined by this.bytes 78 | */ 79 | toBase58(): string { 80 | /* eslint-disable @typescript-eslint/no-explicit-any */ 81 | return encodeAccountID(this.bytes as any); 82 | /* eslint-enable @typescript-eslint/no-explicit-any */ 83 | } 84 | } 85 | 86 | export { AccountID }; 87 | -------------------------------------------------------------------------------- /src/types/amount.ts: -------------------------------------------------------------------------------- 1 | import { Decimal } from "decimal.js"; 2 | 3 | import { BinaryParser } from "../serdes/binary-parser"; 4 | 5 | import { AccountID } from "./account-id"; 6 | import { Currency } from "./currency"; 7 | import { JsonObject, SerializedType } from "./serialized-type"; 8 | import * as bigInt from "big-integer"; 9 | import { Buffer } from "buffer/"; 10 | 11 | /** 12 | * Constants for validating amounts 13 | */ 14 | const MIN_IOU_EXPONENT = -96; 15 | const MAX_IOU_EXPONENT = 80; 16 | const MAX_IOU_PRECISION = 16; 17 | const MAX_DROPS = new Decimal("1e17"); 18 | const MIN_XRP = new Decimal("1e-6"); 19 | const mask = bigInt(0x00000000ffffffff); 20 | 21 | /** 22 | * decimal.js configuration for Amount IOUs 23 | */ 24 | Decimal.config({ 25 | toExpPos: MAX_IOU_EXPONENT + MAX_IOU_PRECISION, 26 | toExpNeg: MIN_IOU_EXPONENT - MAX_IOU_PRECISION, 27 | }); 28 | 29 | /** 30 | * Interface for JSON objects that represent amounts 31 | */ 32 | interface AmountObject extends JsonObject { 33 | value: string; 34 | currency: string; 35 | issuer: string; 36 | } 37 | 38 | /** 39 | * Type guard for AmountObject 40 | */ 41 | function isAmountObject(arg): arg is AmountObject { 42 | const keys = Object.keys(arg).sort(); 43 | return ( 44 | keys.length === 3 && 45 | keys[0] === "currency" && 46 | keys[1] === "issuer" && 47 | keys[2] === "value" 48 | ); 49 | } 50 | 51 | /** 52 | * Class for serializing/Deserializing Amounts 53 | */ 54 | class Amount extends SerializedType { 55 | static defaultAmount: Amount = new Amount( 56 | Buffer.from("4000000000000000", "hex") 57 | ); 58 | 59 | constructor(bytes: Buffer) { 60 | super(bytes ?? Amount.defaultAmount.bytes); 61 | } 62 | 63 | /** 64 | * Construct an amount from an IOU or string amount 65 | * 66 | * @param value An Amount, object representing an IOU, or a string 67 | * representing an integer amount 68 | * @returns An Amount object 69 | */ 70 | static from(value: T): Amount { 71 | if (value instanceof Amount) { 72 | return value; 73 | } 74 | 75 | let amount = Buffer.alloc(8); 76 | if (typeof value === "string") { 77 | Amount.assertXrpIsValid(value); 78 | 79 | const number = bigInt(value); 80 | 81 | const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]; 82 | intBuf[0].writeUInt32BE(Number(number.shiftRight(32)), 0); 83 | intBuf[1].writeUInt32BE(Number(number.and(mask)), 0); 84 | 85 | amount = Buffer.concat(intBuf); 86 | 87 | amount[0] |= 0x40; 88 | 89 | return new Amount(amount); 90 | } 91 | 92 | if (isAmountObject(value)) { 93 | const number = new Decimal(value.value); 94 | Amount.assertIouIsValid(number); 95 | 96 | if (number.isZero()) { 97 | amount[0] |= 0x80; 98 | } else { 99 | const integerNumberString = number 100 | .times(`1e${-(number.e - 15)}`) 101 | .abs() 102 | .toString(); 103 | 104 | const num = bigInt(integerNumberString); 105 | const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]; 106 | intBuf[0].writeUInt32BE(Number(num.shiftRight(32)), 0); 107 | intBuf[1].writeUInt32BE(Number(num.and(mask)), 0); 108 | 109 | amount = Buffer.concat(intBuf); 110 | 111 | amount[0] |= 0x80; 112 | 113 | if (number.gt(new Decimal(0))) { 114 | amount[0] |= 0x40; 115 | } 116 | 117 | const exponent = number.e - 15; 118 | const exponentByte = 97 + exponent; 119 | amount[0] |= exponentByte >>> 2; 120 | amount[1] |= (exponentByte & 0x03) << 6; 121 | } 122 | 123 | const currency = Currency.from(value.currency).toBytes(); 124 | const issuer = AccountID.from(value.issuer).toBytes(); 125 | return new Amount(Buffer.concat([amount, currency, issuer])); 126 | } 127 | 128 | throw new Error("Invalid type to construct an Amount"); 129 | } 130 | 131 | /** 132 | * Read an amount from a BinaryParser 133 | * 134 | * @param parser BinaryParser to read the Amount from 135 | * @returns An Amount object 136 | */ 137 | static fromParser(parser: BinaryParser): Amount { 138 | const isXRP = parser.peek() & 0x80; 139 | const numBytes = isXRP ? 48 : 8; 140 | return new Amount(parser.read(numBytes)); 141 | } 142 | 143 | /** 144 | * Get the JSON representation of this Amount 145 | * 146 | * @returns the JSON interpretation of this.bytes 147 | */ 148 | toJSON(): AmountObject | string { 149 | if (this.isNative()) { 150 | const bytes = this.bytes; 151 | const isPositive = bytes[0] & 0x40; 152 | const sign = isPositive ? "" : "-"; 153 | bytes[0] &= 0x3f; 154 | 155 | const msb = bigInt(bytes.slice(0, 4).readUInt32BE(0)); 156 | const lsb = bigInt(bytes.slice(4).readUInt32BE(0)); 157 | const num = msb.shiftLeft(32).or(lsb); 158 | 159 | return `${sign}${num.toString()}`; 160 | } else { 161 | const parser = new BinaryParser(this.toString()); 162 | const mantissa = parser.read(8); 163 | const currency = Currency.fromParser(parser) as Currency; 164 | const issuer = AccountID.fromParser(parser) as AccountID; 165 | 166 | const b1 = mantissa[0]; 167 | const b2 = mantissa[1]; 168 | 169 | const isPositive = b1 & 0x40; 170 | const sign = isPositive ? "" : "-"; 171 | const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97; 172 | 173 | mantissa[0] = 0; 174 | mantissa[1] &= 0x3f; 175 | const value = new Decimal(`${sign}0x${mantissa.toString("hex")}`).times( 176 | `1e${exponent}` 177 | ); 178 | Amount.assertIouIsValid(value); 179 | 180 | return { 181 | value: value.toString(), 182 | currency: currency.toJSON(), 183 | issuer: issuer.toJSON(), 184 | }; 185 | } 186 | } 187 | 188 | /** 189 | * Validate XRP amount 190 | * 191 | * @param amount String representing XRP amount 192 | * @returns void, but will throw if invalid amount 193 | */ 194 | private static assertXrpIsValid(amount: string): void { 195 | if (amount.indexOf(".") !== -1) { 196 | throw new Error(`${amount.toString()} is an illegal amount`); 197 | } 198 | 199 | const decimal = new Decimal(amount); 200 | if (!decimal.isZero()) { 201 | if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) { 202 | throw new Error(`${amount.toString()} is an illegal amount`); 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * Validate IOU.value amount 209 | * 210 | * @param decimal Decimal.js object representing IOU.value 211 | * @returns void, but will throw if invalid amount 212 | */ 213 | private static assertIouIsValid(decimal: Decimal): void { 214 | if (!decimal.isZero()) { 215 | const p = decimal.precision(); 216 | const e = decimal.e - 15; 217 | if ( 218 | p > MAX_IOU_PRECISION || 219 | e > MAX_IOU_EXPONENT || 220 | e < MIN_IOU_EXPONENT 221 | ) { 222 | throw new Error("Decimal precision out of range"); 223 | } 224 | this.verifyNoDecimal(decimal); 225 | } 226 | } 227 | 228 | /** 229 | * Ensure that the value after being multiplied by the exponent does not 230 | * contain a decimal. 231 | * 232 | * @param decimal a Decimal object 233 | * @returns a string of the object without a decimal 234 | */ 235 | private static verifyNoDecimal(decimal: Decimal): void { 236 | const integerNumberString = decimal 237 | .times(`1e${-(decimal.e - 15)}`) 238 | .abs() 239 | .toString(); 240 | 241 | if (integerNumberString.indexOf(".") !== -1) { 242 | throw new Error("Decimal place found in integerNumberString"); 243 | } 244 | } 245 | 246 | /** 247 | * Test if this amount is in units of Native Currency(XRP) 248 | * 249 | * @returns true if Native (XRP) 250 | */ 251 | private isNative(): boolean { 252 | return (this.bytes[0] & 0x80) === 0; 253 | } 254 | } 255 | 256 | export { Amount, AmountObject }; 257 | -------------------------------------------------------------------------------- /src/types/blob.ts: -------------------------------------------------------------------------------- 1 | import { SerializedType } from "./serialized-type"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Variable length encoded type 7 | */ 8 | class Blob extends SerializedType { 9 | constructor(bytes: Buffer) { 10 | super(bytes); 11 | } 12 | 13 | /** 14 | * Defines how to read a Blob from a BinaryParser 15 | * 16 | * @param parser The binary parser to read the Blob from 17 | * @param hint The length of the blob, computed by readVariableLengthLength() and passed in 18 | * @returns A Blob object 19 | */ 20 | static fromParser(parser: BinaryParser, hint: number): Blob { 21 | return new Blob(parser.read(hint)); 22 | } 23 | 24 | /** 25 | * Create a Blob object from a hex-string 26 | * 27 | * @param value existing Blob object or a hex-string 28 | * @returns A Blob object 29 | */ 30 | static from(value: T): Blob { 31 | if (value instanceof Blob) { 32 | return value; 33 | } 34 | 35 | if (typeof value === "string") { 36 | return new Blob(Buffer.from(value, "hex")); 37 | } 38 | 39 | throw new Error("Cannot construct Blob from value given"); 40 | } 41 | } 42 | 43 | export { Blob }; 44 | -------------------------------------------------------------------------------- /src/types/currency.ts: -------------------------------------------------------------------------------- 1 | import { Hash160 } from "./hash-160"; 2 | import { Buffer } from "buffer/"; 3 | 4 | const ISO_REGEX = /^[A-Z0-9]{3}$/; 5 | const HEX_REGEX = /^[A-F0-9]{40}$/; 6 | 7 | /** 8 | * Convert an ISO code to a currency bytes representation 9 | */ 10 | function isoToBytes(iso: string): Buffer { 11 | const bytes = Buffer.alloc(20); 12 | if (iso !== "XRP") { 13 | const isoBytes = iso.split("").map((c) => c.charCodeAt(0)); 14 | bytes.set(isoBytes, 12); 15 | } 16 | return bytes; 17 | } 18 | 19 | /** 20 | * Tests if ISO is a valid iso code 21 | */ 22 | function isIsoCode(iso: string): boolean { 23 | return ISO_REGEX.test(iso); 24 | } 25 | 26 | function isoCodeFromHex(code: Buffer): string | null { 27 | const iso = code.toString(); 28 | if (iso === "XRP") { 29 | throw new Error( 30 | "Disallowed currency code: to indicate the currency XRP you must use 20 bytes of 0s" 31 | ); 32 | } 33 | if (isIsoCode(iso)) { 34 | return iso; 35 | } 36 | return null; 37 | } 38 | 39 | /** 40 | * Tests if hex is a valid hex-string 41 | */ 42 | function isHex(hex: string): boolean { 43 | return HEX_REGEX.test(hex); 44 | } 45 | 46 | /** 47 | * Tests if a string is a valid representation of a currency 48 | */ 49 | function isStringRepresentation(input: string): boolean { 50 | return input.length === 3 || isHex(input); 51 | } 52 | 53 | /** 54 | * Tests if a Buffer is a valid representation of a currency 55 | */ 56 | function isBytesArray(bytes: Buffer): boolean { 57 | return bytes.byteLength === 20; 58 | } 59 | 60 | /** 61 | * Ensures that a value is a valid representation of a currency 62 | */ 63 | function isValidRepresentation(input: Buffer | string): boolean { 64 | return input instanceof Buffer 65 | ? isBytesArray(input) 66 | : isStringRepresentation(input); 67 | } 68 | 69 | /** 70 | * Generate bytes from a string or buffer representation of a currency 71 | */ 72 | function bytesFromRepresentation(input: string): Buffer { 73 | if (!isValidRepresentation(input)) { 74 | throw new Error(`Unsupported Currency representation: ${input}`); 75 | } 76 | return input.length === 3 ? isoToBytes(input) : Buffer.from(input, "hex"); 77 | } 78 | 79 | /** 80 | * Class defining how to encode and decode Currencies 81 | */ 82 | class Currency extends Hash160 { 83 | static readonly XRP = new Currency(Buffer.alloc(20)); 84 | private readonly _iso: string | null; 85 | 86 | constructor(byteBuf: Buffer) { 87 | super(byteBuf ?? Currency.XRP.bytes); 88 | const code = this.bytes.slice(12, 15); 89 | 90 | if (this.bytes[0] !== 0) { 91 | this._iso = null; 92 | } else if (code.toString("hex") === "000000") { 93 | this._iso = "XRP"; 94 | } else { 95 | this._iso = isoCodeFromHex(code); 96 | } 97 | } 98 | 99 | /** 100 | * Return the ISO code of this currency 101 | * 102 | * @returns ISO code if it exists, else null 103 | */ 104 | iso(): string | null { 105 | return this._iso; 106 | } 107 | 108 | /** 109 | * Constructs a Currency object 110 | * 111 | * @param val Currency object or a string representation of a currency 112 | */ 113 | static from(value: T): Currency { 114 | if (value instanceof Currency) { 115 | return value; 116 | } 117 | 118 | if (typeof value === "string") { 119 | return new Currency(bytesFromRepresentation(value)); 120 | } 121 | 122 | throw new Error("Cannot construct Currency from value given"); 123 | } 124 | 125 | /** 126 | * Gets the JSON representation of a currency 127 | * 128 | * @returns JSON representation 129 | */ 130 | toJSON(): string { 131 | const iso = this.iso(); 132 | if (iso !== null) { 133 | return iso; 134 | } 135 | return this.bytes.toString("hex").toUpperCase(); 136 | } 137 | } 138 | 139 | export { Currency }; 140 | -------------------------------------------------------------------------------- /src/types/hash-128.ts: -------------------------------------------------------------------------------- 1 | import { Hash } from "./hash"; 2 | import { Buffer } from "buffer/"; 3 | 4 | /** 5 | * Hash with a width of 128 bits 6 | */ 7 | class Hash128 extends Hash { 8 | static readonly width = 16; 9 | static readonly ZERO_128: Hash128 = new Hash128(Buffer.alloc(Hash128.width)); 10 | 11 | constructor(bytes: Buffer) { 12 | super(bytes ?? Hash128.ZERO_128.bytes); 13 | } 14 | } 15 | 16 | export { Hash128 }; 17 | -------------------------------------------------------------------------------- /src/types/hash-160.ts: -------------------------------------------------------------------------------- 1 | import { Hash } from "./hash"; 2 | import { Buffer } from "buffer/"; 3 | 4 | /** 5 | * Hash with a width of 160 bits 6 | */ 7 | class Hash160 extends Hash { 8 | static readonly width = 20; 9 | static readonly ZERO_160: Hash160 = new Hash160(Buffer.alloc(Hash160.width)); 10 | 11 | constructor(bytes?: Buffer) { 12 | if (bytes && bytes.byteLength === 0) { 13 | bytes = Hash160.ZERO_160.bytes; 14 | } 15 | 16 | super(bytes ?? Hash160.ZERO_160.bytes); 17 | } 18 | } 19 | 20 | export { Hash160 }; 21 | -------------------------------------------------------------------------------- /src/types/hash-256.ts: -------------------------------------------------------------------------------- 1 | import { Hash } from "./hash"; 2 | import { Buffer } from "buffer/"; 3 | 4 | /** 5 | * Hash with a width of 256 bits 6 | */ 7 | class Hash256 extends Hash { 8 | static readonly width = 32; 9 | static readonly ZERO_256 = new Hash256(Buffer.alloc(Hash256.width)); 10 | 11 | constructor(bytes: Buffer) { 12 | super(bytes ?? Hash256.ZERO_256.bytes); 13 | } 14 | } 15 | 16 | export { Hash256 }; 17 | -------------------------------------------------------------------------------- /src/types/hash.ts: -------------------------------------------------------------------------------- 1 | import { Comparable } from "./serialized-type"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Base class defining how to encode and decode hashes 7 | */ 8 | class Hash extends Comparable { 9 | static readonly width: number; 10 | 11 | constructor(bytes: Buffer) { 12 | super(bytes); 13 | if (this.bytes.byteLength !== (this.constructor as typeof Hash).width) { 14 | throw new Error(`Invalid Hash length ${this.bytes.byteLength}`); 15 | } 16 | } 17 | 18 | /** 19 | * Construct a Hash object from an existing Hash object or a hex-string 20 | * 21 | * @param value A hash object or hex-string of a hash 22 | */ 23 | static from(value: T): Hash { 24 | if (value instanceof this) { 25 | return value; 26 | } 27 | 28 | if (typeof value === "string") { 29 | return new this(Buffer.from(value, "hex")); 30 | } 31 | 32 | throw new Error("Cannot construct Hash from given value"); 33 | } 34 | 35 | /** 36 | * Read a Hash object from a BinaryParser 37 | * 38 | * @param parser BinaryParser to read the hash from 39 | * @param hint length of the bytes to read, optional 40 | */ 41 | static fromParser(parser: BinaryParser, hint?: number): Hash { 42 | return new this(parser.read(hint ?? this.width)); 43 | } 44 | 45 | /** 46 | * Overloaded operator for comparing two hash objects 47 | * 48 | * @param other The Hash to compare this to 49 | */ 50 | compareTo(other: Hash): number { 51 | return this.bytes.compare( 52 | (this.constructor as typeof Hash).from(other).bytes 53 | ); 54 | } 55 | 56 | /** 57 | * @returns the hex-string representation of this Hash 58 | */ 59 | toString(): string { 60 | return this.toHex(); 61 | } 62 | 63 | /** 64 | * Returns four bits at the specified depth within a hash 65 | * 66 | * @param depth The depth of the four bits 67 | * @returns The number represented by the four bits 68 | */ 69 | nibblet(depth: number): number { 70 | const byteIx = depth > 0 ? (depth / 2) | 0 : 0; 71 | let b = this.bytes[byteIx]; 72 | if (depth % 2 === 0) { 73 | b = (b & 0xf0) >>> 4; 74 | } else { 75 | b = b & 0x0f; 76 | } 77 | return b; 78 | } 79 | } 80 | 81 | export { Hash }; 82 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Field, 3 | TransactionResult, 4 | TransactionType, 5 | LedgerEntryType, 6 | } from "../enums"; 7 | import { AccountID } from "./account-id"; 8 | import { Amount } from "./amount"; 9 | import { Blob } from "./blob"; 10 | import { Currency } from "./currency"; 11 | import { Hash128 } from "./hash-128"; 12 | import { Hash160 } from "./hash-160"; 13 | import { Hash256 } from "./hash-256"; 14 | import { PathSet } from "./path-set"; 15 | import { STArray } from "./st-array"; 16 | import { STObject } from "./st-object"; 17 | import { UInt16 } from "./uint-16"; 18 | import { UInt32 } from "./uint-32"; 19 | import { UInt64 } from "./uint-64"; 20 | import { UInt8 } from "./uint-8"; 21 | import { Vector256 } from "./vector-256"; 22 | 23 | const coreTypes = { 24 | AccountID, 25 | Amount, 26 | Blob, 27 | Currency, 28 | Hash128, 29 | Hash160, 30 | Hash256, 31 | PathSet, 32 | STArray, 33 | STObject, 34 | UInt8, 35 | UInt16, 36 | UInt32, 37 | UInt64, 38 | Vector256, 39 | }; 40 | 41 | Object.values(Field).forEach((field) => { 42 | field.associatedType = coreTypes[field.type.name]; 43 | }); 44 | 45 | Field["TransactionType"].associatedType = TransactionType; 46 | Field["TransactionResult"].associatedType = TransactionResult; 47 | Field["LedgerEntryType"].associatedType = LedgerEntryType; 48 | 49 | export { coreTypes }; 50 | -------------------------------------------------------------------------------- /src/types/path-set.ts: -------------------------------------------------------------------------------- 1 | import { AccountID } from "./account-id"; 2 | import { Currency } from "./currency"; 3 | import { BinaryParser } from "../serdes/binary-parser"; 4 | import { SerializedType, JsonObject } from "./serialized-type"; 5 | import { Buffer } from "buffer/"; 6 | 7 | /** 8 | * Constants for separating Paths in a PathSet 9 | */ 10 | const PATHSET_END_BYTE = 0x00; 11 | const PATH_SEPARATOR_BYTE = 0xff; 12 | 13 | /** 14 | * Constant for masking types of a Hop 15 | */ 16 | const TYPE_ACCOUNT = 0x01; 17 | const TYPE_CURRENCY = 0x10; 18 | const TYPE_ISSUER = 0x20; 19 | 20 | /** 21 | * The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency 22 | */ 23 | interface HopObject extends JsonObject { 24 | issuer?: string; 25 | account?: string; 26 | currency?: string; 27 | } 28 | 29 | /** 30 | * TypeGuard for HopObject 31 | */ 32 | function isHopObject(arg): arg is HopObject { 33 | return ( 34 | arg.issuer !== undefined || 35 | arg.account !== undefined || 36 | arg.currency !== undefined 37 | ); 38 | } 39 | 40 | /** 41 | * TypeGuard for PathSet 42 | */ 43 | function isPathSet(arg): arg is Array> { 44 | return ( 45 | (Array.isArray(arg) && arg.length === 0) || 46 | (Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 0) || 47 | (Array.isArray(arg) && Array.isArray(arg[0]) && isHopObject(arg[0][0])) 48 | ); 49 | } 50 | 51 | /** 52 | * Serialize and Deserialize a Hop 53 | */ 54 | class Hop extends SerializedType { 55 | /** 56 | * Create a Hop from a HopObject 57 | * 58 | * @param value Either a hop or HopObject to create a hop with 59 | * @returns a Hop 60 | */ 61 | static from(value: Hop | HopObject): Hop { 62 | if (value instanceof Hop) { 63 | return value; 64 | } 65 | 66 | const bytes: Array = [Buffer.from([0])]; 67 | 68 | if (value.account) { 69 | bytes.push(AccountID.from(value.account).toBytes()); 70 | bytes[0][0] |= TYPE_ACCOUNT; 71 | } 72 | 73 | if (value.currency) { 74 | bytes.push(Currency.from(value.currency).toBytes()); 75 | bytes[0][0] |= TYPE_CURRENCY; 76 | } 77 | 78 | if (value.issuer) { 79 | bytes.push(AccountID.from(value.issuer).toBytes()); 80 | bytes[0][0] |= TYPE_ISSUER; 81 | } 82 | 83 | return new Hop(Buffer.concat(bytes)); 84 | } 85 | 86 | /** 87 | * Construct a Hop from a BinaryParser 88 | * 89 | * @param parser BinaryParser to read the Hop from 90 | * @returns a Hop 91 | */ 92 | static fromParser(parser: BinaryParser): Hop { 93 | const type = parser.readUInt8(); 94 | const bytes: Array = [Buffer.from([type])]; 95 | 96 | if (type & TYPE_ACCOUNT) { 97 | bytes.push(parser.read(AccountID.width)); 98 | } 99 | 100 | if (type & TYPE_CURRENCY) { 101 | bytes.push(parser.read(Currency.width)); 102 | } 103 | 104 | if (type & TYPE_ISSUER) { 105 | bytes.push(parser.read(AccountID.width)); 106 | } 107 | 108 | return new Hop(Buffer.concat(bytes)); 109 | } 110 | 111 | /** 112 | * Get the JSON interpretation of this hop 113 | * 114 | * @returns a HopObject, an JS object with optional account, issuer, and currency 115 | */ 116 | toJSON(): HopObject { 117 | const hopParser = new BinaryParser(this.bytes.toString("hex")); 118 | const type = hopParser.readUInt8(); 119 | 120 | let account, currency, issuer; 121 | if (type & TYPE_ACCOUNT) { 122 | account = (AccountID.fromParser(hopParser) as AccountID).toJSON(); 123 | } 124 | 125 | if (type & TYPE_CURRENCY) { 126 | currency = (Currency.fromParser(hopParser) as Currency).toJSON(); 127 | } 128 | 129 | if (type & TYPE_ISSUER) { 130 | issuer = (AccountID.fromParser(hopParser) as AccountID).toJSON(); 131 | } 132 | 133 | const result: HopObject = {}; 134 | if (account) { 135 | result.account = account; 136 | } 137 | 138 | if (issuer) { 139 | result.issuer = issuer; 140 | } 141 | 142 | if (currency) { 143 | result.currency = currency; 144 | } 145 | 146 | return result; 147 | } 148 | 149 | /** 150 | * get a number representing the type of this hop 151 | * 152 | * @returns a number to be bitwise and-ed with TYPE_ constants to describe the types in the hop 153 | */ 154 | type(): number { 155 | return this.bytes[0]; 156 | } 157 | } 158 | 159 | /** 160 | * Class for serializing/deserializing Paths 161 | */ 162 | class Path extends SerializedType { 163 | /** 164 | * construct a Path from an array of Hops 165 | * 166 | * @param value Path or array of HopObjects to construct a Path 167 | * @returns the Path 168 | */ 169 | static from(value: Path | Array): Path { 170 | if (value instanceof Path) { 171 | return value; 172 | } 173 | 174 | const bytes: Array = []; 175 | value.forEach((hop: HopObject) => { 176 | bytes.push(Hop.from(hop).toBytes()); 177 | }); 178 | 179 | return new Path(Buffer.concat(bytes)); 180 | } 181 | 182 | /** 183 | * Read a Path from a BinaryParser 184 | * 185 | * @param parser BinaryParser to read Path from 186 | * @returns the Path represented by the bytes read from the BinaryParser 187 | */ 188 | static fromParser(parser: BinaryParser): Path { 189 | const bytes: Array = []; 190 | while (!parser.end()) { 191 | bytes.push(Hop.fromParser(parser).toBytes()); 192 | 193 | if ( 194 | parser.peek() === PATHSET_END_BYTE || 195 | parser.peek() === PATH_SEPARATOR_BYTE 196 | ) { 197 | break; 198 | } 199 | } 200 | return new Path(Buffer.concat(bytes)); 201 | } 202 | 203 | /** 204 | * Get the JSON representation of this Path 205 | * 206 | * @returns an Array of HopObject constructed from this.bytes 207 | */ 208 | toJSON(): Array { 209 | const json: Array = []; 210 | const pathParser = new BinaryParser(this.toString()); 211 | 212 | while (!pathParser.end()) { 213 | json.push(Hop.fromParser(pathParser).toJSON()); 214 | } 215 | 216 | return json; 217 | } 218 | } 219 | 220 | /** 221 | * Deserialize and Serialize the PathSet type 222 | */ 223 | class PathSet extends SerializedType { 224 | /** 225 | * Construct a PathSet from an Array of Arrays representing paths 226 | * 227 | * @param value A PathSet or Array of Array of HopObjects 228 | * @returns the PathSet constructed from value 229 | */ 230 | static from>>(value: T): PathSet { 231 | if (value instanceof PathSet) { 232 | return value; 233 | } 234 | 235 | if (isPathSet(value)) { 236 | const bytes: Array = []; 237 | 238 | value.forEach((path: Array) => { 239 | bytes.push(Path.from(path).toBytes()); 240 | bytes.push(Buffer.from([PATH_SEPARATOR_BYTE])); 241 | }); 242 | 243 | bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE]); 244 | 245 | return new PathSet(Buffer.concat(bytes)); 246 | } 247 | 248 | throw new Error("Cannot construct PathSet from given value"); 249 | } 250 | 251 | /** 252 | * Construct a PathSet from a BinaryParser 253 | * 254 | * @param parser A BinaryParser to read PathSet from 255 | * @returns the PathSet read from parser 256 | */ 257 | static fromParser(parser: BinaryParser): PathSet { 258 | const bytes: Array = []; 259 | 260 | while (!parser.end()) { 261 | bytes.push(Path.fromParser(parser).toBytes()); 262 | bytes.push(parser.read(1)); 263 | 264 | if (bytes[bytes.length - 1][0] == PATHSET_END_BYTE) { 265 | break; 266 | } 267 | } 268 | 269 | return new PathSet(Buffer.concat(bytes)); 270 | } 271 | 272 | /** 273 | * Get the JSON representation of this PathSet 274 | * 275 | * @returns an Array of Array of HopObjects, representing this PathSet 276 | */ 277 | toJSON(): Array> { 278 | const json: Array> = []; 279 | const pathParser = new BinaryParser(this.toString()); 280 | 281 | while (!pathParser.end()) { 282 | json.push(Path.fromParser(pathParser).toJSON()); 283 | pathParser.skip(1); 284 | } 285 | 286 | return json; 287 | } 288 | } 289 | 290 | export { PathSet }; 291 | -------------------------------------------------------------------------------- /src/types/serialized-type.ts: -------------------------------------------------------------------------------- 1 | import { BytesList } from "../serdes/binary-serializer"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import * as bigInt from "big-integer"; 4 | import { Buffer } from "buffer/"; 5 | 6 | type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject; 7 | 8 | type JsonObject = { [key: string]: JSON }; 9 | 10 | /** 11 | * The base class for all binary-codec types 12 | */ 13 | class SerializedType { 14 | protected readonly bytes: Buffer = Buffer.alloc(0); 15 | 16 | constructor(bytes: Buffer) { 17 | this.bytes = bytes ?? Buffer.alloc(0); 18 | } 19 | 20 | static fromParser(parser: BinaryParser, hint?: number): SerializedType { 21 | throw new Error("fromParser not implemented"); 22 | return this.fromParser(parser, hint); 23 | } 24 | 25 | static from( 26 | value: SerializedType | JSON | bigInt.BigInteger 27 | ): SerializedType { 28 | throw new Error("from not implemented"); 29 | return this.from(value); 30 | } 31 | 32 | /** 33 | * Write the bytes representation of a SerializedType to a BytesList 34 | * 35 | * @param list The BytesList to write SerializedType bytes to 36 | */ 37 | toBytesSink(list: BytesList): void { 38 | list.put(this.bytes); 39 | } 40 | 41 | /** 42 | * Get the hex representation of a SerializedType's bytes 43 | * 44 | * @returns hex String of this.bytes 45 | */ 46 | toHex(): string { 47 | return this.toBytes().toString("hex").toUpperCase(); 48 | } 49 | 50 | /** 51 | * Get the bytes representation of a SerializedType 52 | * 53 | * @returns A buffer of the bytes 54 | */ 55 | toBytes(): Buffer { 56 | if (this.bytes) { 57 | return this.bytes; 58 | } 59 | const bytes = new BytesList(); 60 | this.toBytesSink(bytes); 61 | return bytes.toBytes(); 62 | } 63 | 64 | /** 65 | * Return the JSON representation of a SerializedType 66 | * 67 | * @returns any type, if not overloaded returns hexString representation of bytes 68 | */ 69 | toJSON(): JSON { 70 | return this.toHex(); 71 | } 72 | 73 | /** 74 | * @returns hexString representation of this.bytes 75 | */ 76 | toString(): string { 77 | return this.toHex(); 78 | } 79 | } 80 | 81 | /** 82 | * Base class for SerializedTypes that are comparable 83 | */ 84 | class Comparable extends SerializedType { 85 | lt(other: Comparable): boolean { 86 | return this.compareTo(other) < 0; 87 | } 88 | 89 | eq(other: Comparable): boolean { 90 | return this.compareTo(other) === 0; 91 | } 92 | 93 | gt(other: Comparable): boolean { 94 | return this.compareTo(other) > 0; 95 | } 96 | 97 | gte(other: Comparable): boolean { 98 | return this.compareTo(other) > -1; 99 | } 100 | 101 | lte(other: Comparable): boolean { 102 | return this.compareTo(other) < 1; 103 | } 104 | 105 | /** 106 | * Overload this method to define how two Comparable SerializedTypes are compared 107 | * 108 | * @param other The comparable object to compare this to 109 | * @returns A number denoting the relationship of this and other 110 | */ 111 | compareTo(other: Comparable): number { 112 | throw new Error( 113 | `cannot compare ${this.toString()} and ${other.toString()}` 114 | ); 115 | } 116 | } 117 | 118 | export { SerializedType, Comparable, JSON, JsonObject }; 119 | -------------------------------------------------------------------------------- /src/types/st-array.ts: -------------------------------------------------------------------------------- 1 | import { SerializedType, JsonObject } from "./serialized-type"; 2 | import { STObject } from "./st-object"; 3 | import { BinaryParser } from "../serdes/binary-parser"; 4 | import { Buffer } from "buffer/"; 5 | 6 | const ARRAY_END_MARKER = Buffer.from([0xf1]); 7 | const ARRAY_END_MARKER_NAME = "ArrayEndMarker"; 8 | 9 | const OBJECT_END_MARKER = Buffer.from([0xe1]); 10 | 11 | /** 12 | * TypeGuard for Array 13 | */ 14 | function isObjects(args): args is Array { 15 | return ( 16 | Array.isArray(args) && (args.length === 0 || typeof args[0] === "object") 17 | ); 18 | } 19 | 20 | /** 21 | * Class for serializing and deserializing Arrays of Objects 22 | */ 23 | class STArray extends SerializedType { 24 | /** 25 | * Construct an STArray from a BinaryParser 26 | * 27 | * @param parser BinaryParser to parse an STArray from 28 | * @returns An STArray Object 29 | */ 30 | static fromParser(parser: BinaryParser): STArray { 31 | const bytes: Array = []; 32 | 33 | while (!parser.end()) { 34 | const field = parser.readField(); 35 | if (field.name === ARRAY_END_MARKER_NAME) { 36 | break; 37 | } 38 | 39 | bytes.push( 40 | field.header, 41 | parser.readFieldValue(field).toBytes(), 42 | OBJECT_END_MARKER 43 | ); 44 | } 45 | 46 | bytes.push(ARRAY_END_MARKER); 47 | return new STArray(Buffer.concat(bytes)); 48 | } 49 | 50 | /** 51 | * Construct an STArray from an Array of JSON Objects 52 | * 53 | * @param value STArray or Array of Objects to parse into an STArray 54 | * @returns An STArray object 55 | */ 56 | static from>(value: T): STArray { 57 | if (value instanceof STArray) { 58 | return value; 59 | } 60 | 61 | if (isObjects(value)) { 62 | const bytes: Array = []; 63 | value.forEach((obj) => { 64 | bytes.push(STObject.from(obj).toBytes()); 65 | }); 66 | 67 | bytes.push(ARRAY_END_MARKER); 68 | return new STArray(Buffer.concat(bytes)); 69 | } 70 | 71 | throw new Error("Cannot construct STArray from value given"); 72 | } 73 | 74 | /** 75 | * Return the JSON representation of this.bytes 76 | * 77 | * @returns An Array of JSON objects 78 | */ 79 | toJSON(): Array { 80 | const result: Array = []; 81 | 82 | const arrayParser = new BinaryParser(this.toString()); 83 | 84 | while (!arrayParser.end()) { 85 | const field = arrayParser.readField(); 86 | if (field.name === ARRAY_END_MARKER_NAME) { 87 | break; 88 | } 89 | 90 | const outer = {}; 91 | outer[field.name] = STObject.fromParser(arrayParser).toJSON(); 92 | result.push(outer); 93 | } 94 | 95 | return result; 96 | } 97 | } 98 | 99 | export { STArray }; 100 | -------------------------------------------------------------------------------- /src/types/st-object.ts: -------------------------------------------------------------------------------- 1 | import { Field, FieldInstance } from "../enums"; 2 | import { SerializedType, JsonObject } from "./serialized-type"; 3 | import { 4 | xAddressToClassicAddress, 5 | isValidXAddress, 6 | } from "ripple-address-codec"; 7 | import { BinaryParser } from "../serdes/binary-parser"; 8 | import { BinarySerializer, BytesList } from "../serdes/binary-serializer"; 9 | import { Buffer } from "buffer/"; 10 | 11 | const OBJECT_END_MARKER_BYTE = Buffer.from([0xe1]); 12 | const OBJECT_END_MARKER = "ObjectEndMarker"; 13 | const ST_OBJECT = "STObject"; 14 | const DESTINATION = "Destination"; 15 | const ACCOUNT = "Account"; 16 | const SOURCE_TAG = "SourceTag"; 17 | const DEST_TAG = "DestinationTag"; 18 | 19 | /** 20 | * Break down an X-Address into an account and a tag 21 | * 22 | * @param field Name of field 23 | * @param xAddress X-Address corresponding to the field 24 | */ 25 | function handleXAddress(field: string, xAddress: string): JsonObject { 26 | const decoded = xAddressToClassicAddress(xAddress); 27 | 28 | let tagName; 29 | if (field === DESTINATION) tagName = DEST_TAG; 30 | else if (field === ACCOUNT) tagName = SOURCE_TAG; 31 | else if (decoded.tag !== false) 32 | throw new Error(`${field} cannot have an associated tag`); 33 | 34 | return decoded.tag !== false 35 | ? { [field]: decoded.classicAddress, [tagName]: decoded.tag } 36 | : { [field]: decoded.classicAddress }; 37 | } 38 | 39 | /** 40 | * Validate that two objects don't both have the same tag fields 41 | * 42 | * @param obj1 First object to check for tags 43 | * @param obj2 Second object to check for tags 44 | * @throws When both objects have SourceTag or DestinationTag 45 | */ 46 | function checkForDuplicateTags(obj1: JsonObject, obj2: JsonObject): void { 47 | if (!(obj1[SOURCE_TAG] === undefined || obj2[SOURCE_TAG] === undefined)) 48 | throw new Error("Cannot have Account X-Address and SourceTag"); 49 | if (!(obj1[DEST_TAG] === undefined || obj2[DEST_TAG] === undefined)) 50 | throw new Error("Cannot have Destination X-Address and DestinationTag"); 51 | } 52 | 53 | /** 54 | * Class for Serializing/Deserializing objects 55 | */ 56 | class STObject extends SerializedType { 57 | /** 58 | * Construct a STObject from a BinaryParser 59 | * 60 | * @param parser BinaryParser to read STObject from 61 | * @returns A STObject object 62 | */ 63 | static fromParser(parser: BinaryParser): STObject { 64 | const list: BytesList = new BytesList(); 65 | const bytes: BinarySerializer = new BinarySerializer(list); 66 | 67 | while (!parser.end()) { 68 | const field = parser.readField(); 69 | if (field.name === OBJECT_END_MARKER) { 70 | break; 71 | } 72 | 73 | const associatedValue = parser.readFieldValue(field); 74 | 75 | bytes.writeFieldAndValue(field, associatedValue); 76 | if (field.type.name === ST_OBJECT) { 77 | bytes.put(OBJECT_END_MARKER_BYTE); 78 | } 79 | } 80 | 81 | return new STObject(list.toBytes()); 82 | } 83 | 84 | /** 85 | * Construct a STObject from a JSON object 86 | * 87 | * @param value An object to include 88 | * @param filter optional, denote which field to include in serialized object 89 | * @returns a STObject object 90 | */ 91 | static from( 92 | value: T, 93 | filter?: (...any) => boolean 94 | ): STObject { 95 | if (value instanceof STObject) { 96 | return value; 97 | } 98 | 99 | const list: BytesList = new BytesList(); 100 | const bytes: BinarySerializer = new BinarySerializer(list); 101 | 102 | const xAddressDecoded = Object.entries(value).reduce((acc, [key, val]) => { 103 | let handled: JsonObject | undefined = undefined; 104 | if (isValidXAddress(val)) { 105 | handled = handleXAddress(key, val); 106 | checkForDuplicateTags(handled, value as JsonObject); 107 | } 108 | return Object.assign(acc, handled ?? { [key]: val }); 109 | }, {}); 110 | 111 | let sorted = Object.keys(xAddressDecoded) 112 | .map((f: string): FieldInstance => Field[f] as FieldInstance) 113 | .filter( 114 | (f: FieldInstance): boolean => 115 | f !== undefined && 116 | xAddressDecoded[f.name] !== undefined && 117 | f.isSerialized 118 | ) 119 | .sort((a, b) => { 120 | return a.ordinal - b.ordinal; 121 | }); 122 | 123 | if (filter !== undefined) { 124 | sorted = sorted.filter(filter); 125 | } 126 | 127 | sorted.forEach((field) => { 128 | const associatedValue = field.associatedType.from( 129 | xAddressDecoded[field.name] 130 | ); 131 | 132 | bytes.writeFieldAndValue(field, associatedValue); 133 | if (field.type.name === ST_OBJECT) { 134 | bytes.put(OBJECT_END_MARKER_BYTE); 135 | } 136 | }); 137 | 138 | return new STObject(list.toBytes()); 139 | } 140 | 141 | /** 142 | * Get the JSON interpretation of this.bytes 143 | * 144 | * @returns a JSON object 145 | */ 146 | toJSON(): JsonObject { 147 | const objectParser = new BinaryParser(this.toString()); 148 | const accumulator = {}; 149 | 150 | while (!objectParser.end()) { 151 | const field = objectParser.readField(); 152 | if (field.name === OBJECT_END_MARKER) { 153 | break; 154 | } 155 | accumulator[field.name] = objectParser.readFieldValue(field).toJSON(); 156 | } 157 | 158 | return accumulator; 159 | } 160 | } 161 | 162 | export { STObject }; 163 | -------------------------------------------------------------------------------- /src/types/uint-16.ts: -------------------------------------------------------------------------------- 1 | import { UInt } from "./uint"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Derived UInt class for serializing/deserializing 16 bit UInt 7 | */ 8 | class UInt16 extends UInt { 9 | protected static readonly width: number = 16 / 8; // 2 10 | static readonly defaultUInt16: UInt16 = new UInt16( 11 | Buffer.alloc(UInt16.width) 12 | ); 13 | 14 | constructor(bytes: Buffer) { 15 | super(bytes ?? UInt16.defaultUInt16.bytes); 16 | } 17 | 18 | static fromParser(parser: BinaryParser): UInt { 19 | return new UInt16(parser.read(UInt16.width)); 20 | } 21 | 22 | /** 23 | * Construct a UInt16 object from a number 24 | * 25 | * @param val UInt16 object or number 26 | */ 27 | static from(val: T): UInt16 { 28 | if (val instanceof UInt16) { 29 | return val; 30 | } 31 | 32 | if (typeof val === "number") { 33 | const buf = Buffer.alloc(UInt16.width); 34 | buf.writeUInt16BE(val, 0); 35 | return new UInt16(buf); 36 | } 37 | 38 | throw new Error("Can not construct UInt16 with given value"); 39 | } 40 | 41 | /** 42 | * get the value of a UInt16 object 43 | * 44 | * @returns the number represented by this.bytes 45 | */ 46 | valueOf(): number { 47 | return this.bytes.readUInt16BE(0); 48 | } 49 | } 50 | 51 | export { UInt16 }; 52 | -------------------------------------------------------------------------------- /src/types/uint-32.ts: -------------------------------------------------------------------------------- 1 | import { UInt } from "./uint"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Derived UInt class for serializing/deserializing 32 bit UInt 7 | */ 8 | class UInt32 extends UInt { 9 | protected static readonly width: number = 32 / 8; // 4 10 | static readonly defaultUInt32: UInt32 = new UInt32( 11 | Buffer.alloc(UInt32.width) 12 | ); 13 | 14 | constructor(bytes: Buffer) { 15 | super(bytes ?? UInt32.defaultUInt32.bytes); 16 | } 17 | 18 | static fromParser(parser: BinaryParser): UInt { 19 | return new UInt32(parser.read(UInt32.width)); 20 | } 21 | 22 | /** 23 | * Construct a UInt32 object from a number 24 | * 25 | * @param val UInt32 object or number 26 | */ 27 | static from(val: T): UInt32 { 28 | if (val instanceof UInt32) { 29 | return val; 30 | } 31 | 32 | const buf = Buffer.alloc(UInt32.width); 33 | 34 | if (typeof val === "string") { 35 | const num = Number.parseInt(val); 36 | buf.writeUInt32BE(num, 0); 37 | return new UInt32(buf); 38 | } 39 | 40 | if (typeof val === "number") { 41 | buf.writeUInt32BE(val, 0); 42 | return new UInt32(buf); 43 | } 44 | 45 | throw new Error("Cannot construct UInt32 from given value"); 46 | } 47 | 48 | /** 49 | * get the value of a UInt32 object 50 | * 51 | * @returns the number represented by this.bytes 52 | */ 53 | valueOf(): number { 54 | return this.bytes.readUInt32BE(0); 55 | } 56 | } 57 | 58 | export { UInt32 }; 59 | -------------------------------------------------------------------------------- /src/types/uint-64.ts: -------------------------------------------------------------------------------- 1 | import { UInt } from "./uint"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import * as bigInt from "big-integer"; 4 | import { isInstance } from "big-integer"; 5 | import { Buffer } from "buffer/"; 6 | 7 | const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/; 8 | const mask = bigInt(0x00000000ffffffff); 9 | 10 | /** 11 | * Derived UInt class for serializing/deserializing 64 bit UInt 12 | */ 13 | class UInt64 extends UInt { 14 | protected static readonly width: number = 64 / 8; // 8 15 | static readonly defaultUInt64: UInt64 = new UInt64( 16 | Buffer.alloc(UInt64.width) 17 | ); 18 | 19 | constructor(bytes: Buffer) { 20 | super(bytes ?? UInt64.defaultUInt64.bytes); 21 | } 22 | 23 | static fromParser(parser: BinaryParser): UInt { 24 | return new UInt64(parser.read(UInt64.width)); 25 | } 26 | 27 | /** 28 | * Construct a UInt64 object 29 | * 30 | * @param val A UInt64, hex-string, bigInt, or number 31 | * @returns A UInt64 object 32 | */ 33 | static from( 34 | val: T 35 | ): UInt64 { 36 | if (val instanceof UInt64) { 37 | return val; 38 | } 39 | 40 | let buf = Buffer.alloc(UInt64.width); 41 | 42 | if (typeof val === "number") { 43 | if (val < 0) { 44 | throw new Error("value must be an unsigned integer"); 45 | } 46 | 47 | const number = bigInt(val); 48 | 49 | const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]; 50 | intBuf[0].writeUInt32BE(Number(number.shiftRight(32)), 0); 51 | intBuf[1].writeUInt32BE(Number(number.and(mask)), 0); 52 | 53 | return new UInt64(Buffer.concat(intBuf)); 54 | } 55 | 56 | if (typeof val === "string") { 57 | if (!HEX_REGEX.test(val)) { 58 | throw new Error(`${val} is not a valid hex-string`); 59 | } 60 | 61 | const strBuf = val.padStart(16, "0"); 62 | buf = Buffer.from(strBuf, "hex"); 63 | return new UInt64(buf); 64 | } 65 | 66 | if (isInstance(val)) { 67 | const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]; 68 | intBuf[0].writeUInt32BE(Number(val.shiftRight(bigInt(32))), 0); 69 | intBuf[1].writeUInt32BE(Number(val.and(mask)), 0); 70 | 71 | return new UInt64(Buffer.concat(intBuf)); 72 | } 73 | 74 | throw new Error("Cannot construct UInt64 from given value"); 75 | } 76 | 77 | /** 78 | * The JSON representation of a UInt64 object 79 | * 80 | * @returns a hex-string 81 | */ 82 | toJSON(): string { 83 | return this.bytes.toString("hex").toUpperCase(); 84 | } 85 | 86 | /** 87 | * Get the value of the UInt64 88 | * 89 | * @returns the number represented buy this.bytes 90 | */ 91 | valueOf(): bigInt.BigInteger { 92 | const msb = bigInt(this.bytes.slice(0, 4).readUInt32BE(0)); 93 | const lsb = bigInt(this.bytes.slice(4).readUInt32BE(0)); 94 | return msb.shiftLeft(bigInt(32)).or(lsb); 95 | } 96 | 97 | /** 98 | * Get the bytes representation of the UInt64 object 99 | * 100 | * @returns 8 bytes representing the UInt64 101 | */ 102 | toBytes(): Buffer { 103 | return this.bytes; 104 | } 105 | } 106 | 107 | export { UInt64 }; 108 | -------------------------------------------------------------------------------- /src/types/uint-8.ts: -------------------------------------------------------------------------------- 1 | import { UInt } from "./uint"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Derived UInt class for serializing/deserializing 8 bit UInt 7 | */ 8 | class UInt8 extends UInt { 9 | protected static readonly width: number = 8 / 8; // 1 10 | static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width)); 11 | 12 | constructor(bytes: Buffer) { 13 | super(bytes ?? UInt8.defaultUInt8.bytes); 14 | } 15 | 16 | static fromParser(parser: BinaryParser): UInt { 17 | return new UInt8(parser.read(UInt8.width)); 18 | } 19 | 20 | /** 21 | * Construct a UInt8 object from a number 22 | * 23 | * @param val UInt8 object or number 24 | */ 25 | static from(val: T): UInt8 { 26 | if (val instanceof UInt8) { 27 | return val; 28 | } 29 | 30 | if (typeof val === "number") { 31 | const buf = Buffer.alloc(UInt8.width); 32 | buf.writeUInt8(val, 0); 33 | return new UInt8(buf); 34 | } 35 | 36 | throw new Error("Cannot construct UInt8 from given value"); 37 | } 38 | 39 | /** 40 | * get the value of a UInt8 object 41 | * 42 | * @returns the number represented by this.bytes 43 | */ 44 | valueOf(): number { 45 | return this.bytes.readUInt8(0); 46 | } 47 | } 48 | 49 | export { UInt8 }; 50 | -------------------------------------------------------------------------------- /src/types/uint.ts: -------------------------------------------------------------------------------- 1 | import * as bigInt from "big-integer"; 2 | import { Comparable } from "./serialized-type"; 3 | import { Buffer } from "buffer/"; 4 | 5 | /** 6 | * Compare numbers and bigInts n1 and n2 7 | * 8 | * @param n1 First object to compare 9 | * @param n2 Second object to compare 10 | * @returns -1, 0, or 1, depending on how the two objects compare 11 | */ 12 | function compare( 13 | n1: number | bigInt.BigInteger, 14 | n2: number | bigInt.BigInteger 15 | ): number { 16 | return n1 < n2 ? -1 : n1 == n2 ? 0 : 1; 17 | } 18 | 19 | /** 20 | * Base class for serializing and deserializing unsigned integers. 21 | */ 22 | abstract class UInt extends Comparable { 23 | protected static width: number; 24 | 25 | constructor(bytes: Buffer) { 26 | super(bytes); 27 | } 28 | 29 | /** 30 | * Overload of compareTo for Comparable 31 | * 32 | * @param other other UInt to compare this to 33 | * @returns -1, 0, or 1 depending on how the objects relate to each other 34 | */ 35 | compareTo(other: UInt): number { 36 | return compare(this.valueOf(), other.valueOf()); 37 | } 38 | 39 | /** 40 | * Convert a UInt object to JSON 41 | * 42 | * @returns number or string represented by this.bytes 43 | */ 44 | toJSON(): number | string { 45 | const val = this.valueOf(); 46 | return typeof val === "number" ? val : val.toString(); 47 | } 48 | 49 | /** 50 | * Get the value of the UInt represented by this.bytes 51 | * 52 | * @returns the value 53 | */ 54 | abstract valueOf(): number | bigInt.BigInteger; 55 | } 56 | 57 | export { UInt }; 58 | -------------------------------------------------------------------------------- /src/types/vector-256.ts: -------------------------------------------------------------------------------- 1 | import { SerializedType } from "./serialized-type"; 2 | import { BinaryParser } from "../serdes/binary-parser"; 3 | import { Hash256 } from "./hash-256"; 4 | import { BytesList } from "../serdes/binary-serializer"; 5 | import { Buffer } from "buffer/"; 6 | 7 | /** 8 | * TypeGuard for Array 9 | */ 10 | function isStrings(arg): arg is Array { 11 | return Array.isArray(arg) && (arg.length === 0 || typeof arg[0] === "string"); 12 | } 13 | 14 | /** 15 | * Class for serializing and deserializing vectors of Hash256 16 | */ 17 | class Vector256 extends SerializedType { 18 | constructor(bytes: Buffer) { 19 | super(bytes); 20 | } 21 | 22 | /** 23 | * Construct a Vector256 from a BinaryParser 24 | * 25 | * @param parser BinaryParser to 26 | * @param hint length of the vector, in bytes, optional 27 | * @returns a Vector256 object 28 | */ 29 | static fromParser(parser: BinaryParser, hint?: number): Vector256 { 30 | const bytesList = new BytesList(); 31 | const bytes = hint ?? parser.size(); 32 | const hashes = bytes / 32; 33 | for (let i = 0; i < hashes; i++) { 34 | Hash256.fromParser(parser).toBytesSink(bytesList); 35 | } 36 | return new Vector256(bytesList.toBytes()); 37 | } 38 | 39 | /** 40 | * Construct a Vector256 object from an array of hashes 41 | * 42 | * @param value A Vector256 object or array of hex-strings representing Hash256's 43 | * @returns a Vector256 object 44 | */ 45 | static from>(value: T): Vector256 { 46 | if (value instanceof Vector256) { 47 | return value; 48 | } 49 | 50 | if (isStrings(value)) { 51 | const bytesList = new BytesList(); 52 | value.forEach((hash) => { 53 | Hash256.from(hash).toBytesSink(bytesList); 54 | }); 55 | return new Vector256(bytesList.toBytes()); 56 | } 57 | 58 | throw new Error("Cannot construct Vector256 from given value"); 59 | } 60 | 61 | /** 62 | * Return an Array of hex-strings represented by this.bytes 63 | * 64 | * @returns An Array of strings representing the Hash256 objects 65 | */ 66 | toJSON(): Array { 67 | if (this.bytes.byteLength % 32 !== 0) { 68 | throw new Error("Invalid bytes for Vector256"); 69 | } 70 | 71 | const result: Array = []; 72 | for (let i = 0; i < this.bytes.byteLength; i += 32) { 73 | result.push( 74 | this.bytes 75 | .slice(i, i + 32) 76 | .toString("hex") 77 | .toUpperCase() 78 | ); 79 | } 80 | return result; 81 | } 82 | } 83 | 84 | export { Vector256 }; 85 | -------------------------------------------------------------------------------- /test/amount.test.js: -------------------------------------------------------------------------------- 1 | const { loadFixture } = require("./utils"); 2 | const { coreTypes } = require("../dist/types"); 3 | const { Amount } = coreTypes; 4 | const fixtures = loadFixture("data-driven-tests.json"); 5 | 6 | function amountErrorTests() { 7 | fixtures.values_tests 8 | .filter((obj) => obj.type === "Amount") 9 | .forEach((f) => { 10 | // We only want these with errors 11 | if (!f.error) { 12 | return; 13 | } 14 | const testName = 15 | `${JSON.stringify(f.test_json)}\n\tis invalid ` + `because: ${f.error}`; 16 | it(testName, () => { 17 | expect(() => { 18 | Amount.from(f.test_json); 19 | JSON.stringify(f.test_json); 20 | }).toThrow(); 21 | }); 22 | }); 23 | } 24 | 25 | describe("Amount", function () { 26 | it("can be parsed from", function () { 27 | expect(Amount.from("1000000") instanceof Amount).toBe(true); 28 | expect(Amount.from("1000000").toJSON()).toEqual("1000000"); 29 | const fixture = { 30 | value: "1", 31 | issuer: "0000000000000000000000000000000000000000", 32 | currency: "USD", 33 | }; 34 | const amt = Amount.from(fixture); 35 | const rewritten = { 36 | value: "1", 37 | issuer: "rrrrrrrrrrrrrrrrrrrrrhoLvTp", 38 | currency: "USD", 39 | }; 40 | expect(amt.toJSON()).toEqual(rewritten); 41 | }); 42 | amountErrorTests(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/binary-json.test.js: -------------------------------------------------------------------------------- 1 | const fixtures = require("./fixtures/codec-fixtures.json"); 2 | const { decode, encode, decodeLedgerData } = require("../dist"); 3 | 4 | function json(object) { 5 | return JSON.stringify(object); 6 | } 7 | 8 | function truncateForDisplay(longStr) { 9 | return `${longStr.slice(0, 10)} ... ${longStr.slice(-10)}`; 10 | } 11 | 12 | describe("ripple-binary-codec", function () { 13 | function makeSuite(name, entries) { 14 | describe(name, function () { 15 | entries.forEach((t, testN) => { 16 | // eslint-disable-next-line max-len 17 | test(`${name}[${testN}] can encode ${truncateForDisplay( 18 | json(t.json) 19 | )} to ${truncateForDisplay(t.binary)}`, () => { 20 | expect(encode(t.json)).toEqual(t.binary); 21 | }); 22 | // eslint-disable-next-line max-len 23 | test(`${name}[${testN}] can decode ${truncateForDisplay( 24 | t.binary 25 | )} to ${truncateForDisplay(json(t.json))}`, () => { 26 | const decoded = decode(t.binary); 27 | expect(decoded).toEqual(t.json); 28 | }); 29 | }); 30 | }); 31 | } 32 | makeSuite("transactions", fixtures.transactions); 33 | makeSuite("accountState", fixtures.accountState); 34 | 35 | describe("ledgerData", function () { 36 | if (fixtures.ledgerData) { 37 | fixtures.ledgerData.forEach((t, testN) => { 38 | test(`ledgerData[${testN}] can decode ${t.binary} to ${json( 39 | t.json 40 | )}`, () => { 41 | const decoded = decodeLedgerData(t.binary); 42 | expect(t.json).toEqual(decoded); 43 | }); 44 | }); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/binary-parser.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-style */ 2 | 3 | const { coreTypes } = require("../dist/types"); 4 | const Decimal = require("decimal.js"); 5 | 6 | const { encodeAccountID } = require("ripple-address-codec"); 7 | const { binary } = require("../dist/coretypes"); 8 | const { Amount, Hash160 } = coreTypes; 9 | const { makeParser, readJSON } = binary; 10 | const { Field, TransactionType } = require("./../dist/enums"); 11 | const { parseHexOnly, hexOnly, loadFixture } = require("./utils"); 12 | const fixtures = loadFixture("data-driven-tests.json"); 13 | const { BytesList } = require("../dist/serdes/binary-serializer"); 14 | const { Buffer } = require("buffer/"); 15 | 16 | const __ = hexOnly; 17 | function toJSON(v) { 18 | return v.toJSON ? v.toJSON() : v; 19 | } 20 | 21 | function assertEqualAmountJSON(actual, expected) { 22 | expect(typeof actual === typeof expected).toBe(true); 23 | if (typeof actual === "string") { 24 | expect(actual).toEqual(expected); 25 | return; 26 | } 27 | expect(actual.currency).toEqual(expected.currency); 28 | expect(actual.issuer).toEqual(expected.issuer); 29 | expect( 30 | actual.value === expected.value || 31 | new Decimal(actual.value).equals(new Decimal(expected.value)) 32 | ).toBe(true); 33 | } 34 | 35 | function basicApiTests() { 36 | const bytes = parseHexOnly("00,01020304,0506", Uint8Array); 37 | test("can read slices of bytes", () => { 38 | const parser = makeParser(bytes); 39 | expect(parser.bytes instanceof Buffer).toBe(true); 40 | const read1 = parser.read(1); 41 | expect(read1 instanceof Buffer).toBe(true); 42 | expect(read1).toEqual(Buffer.from([0])); 43 | expect(parser.read(4)).toEqual(Buffer.from([1, 2, 3, 4])); 44 | expect(parser.read(2)).toEqual(Buffer.from([5, 6])); 45 | expect(() => parser.read(1)).toThrow(); 46 | }); 47 | test("can read a Uint32 at full", () => { 48 | const parser = makeParser("FFFFFFFF"); 49 | expect(parser.readUInt32()).toEqual(0xffffffff); 50 | }); 51 | } 52 | 53 | function transactionParsingTests() { 54 | const transaction = { 55 | json: { 56 | Account: "raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3", 57 | Fee: "10", 58 | Flags: 0, 59 | Sequence: 103929, 60 | SigningPubKey: 61 | "028472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F418D6A7166", 62 | TakerGets: { 63 | currency: "ILS", 64 | issuer: "rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9", 65 | value: "1694.768", 66 | }, 67 | TakerPays: "98957503520", 68 | TransactionType: "OfferCreate", 69 | TxnSignature: __(` 70 | 304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF 71 | A5733109EB3C077077520022100DB335EE97386E4C0591CAC02 72 | 4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`), 73 | }, 74 | binary: __(` 75 | 120007220000000024000195F964400000170A53AC2065D5460561E 76 | C9DE000000000000000000000000000494C53000000000092D70596 77 | 8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210 78 | 28472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F 79 | 418D6A71667447304502202ABE08D5E78D1E74A4C18F2714F64E87B 80 | 8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C059 81 | 1CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C811439408 82 | A69F0895E62149CFCC006FB89FA7D1E6E5D`), 83 | }; 84 | 85 | const tx_json = transaction.json; 86 | // These tests are basically development logs 87 | 88 | test("can be done with low level apis", () => { 89 | const parser = makeParser(transaction.binary); 90 | 91 | expect(parser.readField()).toEqual(Field.TransactionType); 92 | expect(parser.readUInt16()).toEqual(7); 93 | expect(parser.readField()).toEqual(Field.Flags); 94 | expect(parser.readUInt32()).toEqual(0); 95 | expect(parser.readField()).toEqual(Field.Sequence); 96 | expect(parser.readUInt32()).toEqual(103929); 97 | expect(parser.readField()).toEqual(Field.TakerPays); 98 | parser.read(8); 99 | expect(parser.readField()).toEqual(Field.TakerGets); 100 | // amount value 101 | expect(parser.read(8)).not.toBe([]); 102 | // amount currency 103 | expect(Hash160.fromParser(parser)).not.toBe([]); 104 | expect(encodeAccountID(parser.read(20))).toEqual(tx_json.TakerGets.issuer); 105 | expect(parser.readField()).toEqual(Field.Fee); 106 | expect(parser.read(8)).not.toEqual([]); 107 | expect(parser.readField()).toEqual(Field.SigningPubKey); 108 | expect(parser.readVariableLengthLength()).toBe(33); 109 | expect(parser.read(33).toString("hex").toUpperCase()).toEqual( 110 | tx_json.SigningPubKey 111 | ); 112 | expect(parser.readField()).toEqual(Field.TxnSignature); 113 | expect(parser.readVariableLength().toString("hex").toUpperCase()).toEqual( 114 | tx_json.TxnSignature 115 | ); 116 | expect(parser.readField()).toEqual(Field.Account); 117 | expect(encodeAccountID(parser.readVariableLength())).toEqual( 118 | tx_json.Account 119 | ); 120 | expect(parser.end()).toBe(true); 121 | }); 122 | 123 | test("can be done with high level apis", () => { 124 | const parser = makeParser(transaction.binary); 125 | function readField() { 126 | return parser.readFieldAndValue(); 127 | } 128 | { 129 | const [field, value] = readField(); 130 | expect(field).toEqual(Field.TransactionType); 131 | expect(value).toEqual(TransactionType.OfferCreate); 132 | } 133 | { 134 | const [field, value] = readField(); 135 | expect(field).toEqual(Field.Flags); 136 | expect(value.valueOf()).toEqual(0); 137 | } 138 | { 139 | const [field, value] = readField(); 140 | expect(field).toEqual(Field.Sequence); 141 | expect(value.valueOf()).toEqual(103929); 142 | } 143 | { 144 | const [field, value] = readField(); 145 | expect(field).toEqual(Field.TakerPays); 146 | expect(value.isNative()).toEqual(true); 147 | expect(value.toJSON()).toEqual("98957503520"); 148 | } 149 | { 150 | const [field, value] = readField(); 151 | expect(field).toEqual(Field.TakerGets); 152 | expect(value.isNative()).toEqual(false); 153 | expect(value.toJSON().issuer).toEqual(tx_json.TakerGets.issuer); 154 | } 155 | { 156 | const [field, value] = readField(); 157 | expect(field).toEqual(Field.Fee); 158 | expect(value.isNative()).toEqual(true); 159 | } 160 | { 161 | const [field, value] = readField(); 162 | expect(field).toEqual(Field.SigningPubKey); 163 | expect(value.toJSON()).toEqual(tx_json.SigningPubKey); 164 | } 165 | { 166 | const [field, value] = readField(); 167 | expect(field).toEqual(Field.TxnSignature); 168 | expect(value.toJSON()).toEqual(tx_json.TxnSignature); 169 | } 170 | { 171 | const [field, value] = readField(); 172 | expect(field).toEqual(Field.Account); 173 | expect(value.toJSON()).toEqual(tx_json.Account); 174 | } 175 | expect(parser.end()).toBe(true); 176 | }); 177 | 178 | test("can be done with higher level apis", () => { 179 | const parser = makeParser(transaction.binary); 180 | const jsonFromBinary = readJSON(parser); 181 | expect(jsonFromBinary).toEqual(tx_json); 182 | }); 183 | 184 | test("readJSON (binary.decode) does not return STObject ", () => { 185 | const parser = makeParser(transaction.binary); 186 | const jsonFromBinary = readJSON(parser); 187 | expect(jsonFromBinary instanceof coreTypes.STObject).toBe(false); 188 | expect(jsonFromBinary instanceof Object).toBe(true); 189 | expect(jsonFromBinary.prototype).toBe(undefined); 190 | }); 191 | } 192 | 193 | function amountParsingTests() { 194 | fixtures.values_tests 195 | .filter((obj) => obj.type === "Amount") 196 | .forEach((f, i) => { 197 | if (f.error) { 198 | return; 199 | } 200 | const parser = makeParser(f.expected_hex); 201 | const testName = `values_tests[${i}] parses ${f.expected_hex.slice( 202 | 0, 203 | 16 204 | )}... 205 | as ${JSON.stringify(f.test_json)}`; 206 | test(testName, () => { 207 | const value = parser.readType(Amount); 208 | // May not actually be in canonical form. The fixtures are to be used 209 | // also for json -> binary; 210 | const json = toJSON(value); 211 | assertEqualAmountJSON(json, f.test_json); 212 | if (f.exponent) { 213 | const exponent = new Decimal(json.value); 214 | expect(exponent.e - 15).toEqual(f.exponent); 215 | } 216 | }); 217 | }); 218 | } 219 | 220 | function fieldParsingTests() { 221 | fixtures.fields_tests.forEach((f, i) => { 222 | const parser = makeParser(f.expected_hex); 223 | test(`fields[${i}]: parses ${f.expected_hex} as ${f.name}`, () => { 224 | const field = parser.readField(); 225 | expect(field.name).toEqual(f.name); 226 | expect(field.type.name).toEqual(f.type_name); 227 | }); 228 | }); 229 | test("Field throws when type code out of range", () => { 230 | const parser = makeParser("0101"); 231 | expect(() => parser.readField()).toThrow( 232 | new Error("Cannot read FieldOrdinal, type_code out of range") 233 | ); 234 | }); 235 | test("Field throws when field code out of range", () => { 236 | const parser = makeParser("1001"); 237 | expect(() => parser.readFieldOrdinal()).toThrowError( 238 | new Error("Cannot read FieldOrdinal, field_code out of range") 239 | ); 240 | }); 241 | test("Field throws when both type and field code out of range", () => { 242 | const parser = makeParser("000101"); 243 | expect(() => parser.readFieldOrdinal()).toThrowError( 244 | new Error("Cannot read FieldOrdinal, type_code out of range") 245 | ); 246 | }); 247 | } 248 | 249 | function assertRecyclable(json, forField) { 250 | const Type = forField.associatedType; 251 | const recycled = Type.from(json).toJSON(); 252 | expect(recycled).toEqual(json); 253 | const sink = new BytesList(); 254 | Type.from(recycled).toBytesSink(sink); 255 | const recycledAgain = makeParser(sink.toHex()).readType(Type).toJSON(); 256 | expect(recycledAgain).toEqual(json); 257 | } 258 | 259 | function nestedObjectTests() { 260 | fixtures.whole_objects.forEach((f, i) => { 261 | test(`whole_objects[${i}]: can parse blob into 262 | ${JSON.stringify( 263 | f.tx_json 264 | )}`, /* */ () => { 265 | const parser = makeParser(f.blob_with_no_signing); 266 | let ix = 0; 267 | while (!parser.end()) { 268 | const [field, value] = parser.readFieldAndValue(); 269 | const expected = f.fields[ix]; 270 | const expectedJSON = expected[1].json; 271 | const expectedField = expected[0]; 272 | const actual = toJSON(value); 273 | 274 | try { 275 | expect(actual).toEqual(expectedJSON); 276 | } catch (e) { 277 | throw new Error(`${e} ${field} a: ${actual} e: ${expectedJSON}`); 278 | } 279 | expect(field.name).toEqual(expectedField); 280 | assertRecyclable(actual, field); 281 | ix++; 282 | } 283 | }); 284 | }); 285 | } 286 | 287 | function pathSetBinaryTests() { 288 | const bytes = __( 289 | `1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000 290 | 0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0 291 | 6594D168400000000000000A69D446F8038585E9400000000000000000000000 292 | 00425443000000000078CA21A6014541AB7B26C3929B9E0CD8C284D61C732103 293 | A4665B1F0B7AE2BCA12E2DB80A192125BBEA660F80E9CEE137BA444C1B0769EC 294 | 7447304502205A964536805E35785C659D1F9670D057749AE39668175D6AA75D 295 | 25B218FE682E0221009252C0E5DDD5F2712A48F211669DE17B54113918E0D2C2 296 | 66F818095E9339D7D3811478CA21A6014541AB7B26C3929B9E0CD8C284D61C83 297 | 140A20B3C85F482532A9578DBB3950B85CA06594D1011231585E1F3BD02A15D6 298 | 185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000 299 | 585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C 300 | 8531CDEECBE84F33670000000000000000000000004254430000000000E4FE68 301 | 7C90257D3D2D694C8531CDEECBE84F3367310A20B3C85F482532A9578DBB3950 302 | B85CA06594D100000000000000000000000042544300000000000A20B3C85F48 303 | 2532A9578DBB3950B85CA06594D1300000000000000000000000005553440000 304 | 0000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15 305 | D6185F8BB9B57CC60DEDDB37C100000000000000000000000042544300000000 306 | 00585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D69 307 | 4C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE 308 | 687C90257D3D2D694C8531CDEECBE84F33673115036E2D3F5437A83E5AC3CAEE 309 | 34FF2C21DEB618000000000000000000000000425443000000000015036E2D3F 310 | 5437A83E5AC3CAEE34FF2C21DEB6183000000000000000000000000055534400 311 | 000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A 312 | 15D6185F8BB9B57CC60DEDDB37C1000000000000000000000000425443000000 313 | 0000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C13157180C769B66D942EE 314 | 69E6DCC940CA48D82337AD000000000000000000000000425443000000000057 315 | 180C769B66D942EE69E6DCC940CA48D82337AD10000000000000000000000000 316 | 00000000000000003000000000000000000000000055534400000000000A20B3 317 | C85F482532A9578DBB3950B85CA06594D100` 318 | ); 319 | 320 | const expectedJSON = [ 321 | [ 322 | { 323 | account: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 324 | currency: "BTC", 325 | issuer: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 326 | }, 327 | { 328 | account: "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", 329 | currency: "BTC", 330 | issuer: "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", 331 | }, 332 | { 333 | account: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 334 | currency: "BTC", 335 | issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 336 | }, 337 | { 338 | currency: "USD", 339 | issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 340 | }, 341 | ], 342 | [ 343 | { 344 | account: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 345 | currency: "BTC", 346 | issuer: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 347 | }, 348 | { 349 | account: "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", 350 | currency: "BTC", 351 | issuer: "rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo", 352 | }, 353 | { 354 | account: "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi", 355 | currency: "BTC", 356 | issuer: "rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi", 357 | }, 358 | { 359 | currency: "USD", 360 | issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 361 | }, 362 | ], 363 | [ 364 | { 365 | account: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 366 | currency: "BTC", 367 | issuer: "r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K", 368 | }, 369 | { 370 | account: "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn", 371 | currency: "BTC", 372 | issuer: "r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn", 373 | }, 374 | { currency: "XRP" }, 375 | { 376 | currency: "USD", 377 | issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 378 | }, 379 | ], 380 | ]; 381 | 382 | test("works with long paths", () => { 383 | const parser = makeParser(bytes); 384 | const txn = readJSON(parser); 385 | expect(txn.Paths).toEqual(expectedJSON); 386 | // TODO: this should go elsewhere 387 | expect(coreTypes.PathSet.from(txn.Paths).toJSON()).toEqual(expectedJSON); 388 | }); 389 | } 390 | 391 | describe("Binary Parser", function () { 392 | describe("pathSetBinaryTests", () => pathSetBinaryTests()); 393 | describe("nestedObjectTests", () => nestedObjectTests()); 394 | describe("fieldParsingTests", () => fieldParsingTests()); 395 | describe("amountParsingTests", () => amountParsingTests()); 396 | describe("transactionParsingTests", () => transactionParsingTests()); 397 | describe("basicApiTests", () => basicApiTests()); 398 | }); 399 | -------------------------------------------------------------------------------- /test/binary-serializer.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-style */ 2 | 3 | const { binary } = require("../dist/coretypes"); 4 | const { encode, decode } = require("../dist"); 5 | const { makeParser, BytesList, BinarySerializer } = binary; 6 | const { coreTypes } = require("../dist/types"); 7 | const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes; 8 | const bigInt = require("big-integer"); 9 | const { Buffer } = require("buffer/"); 10 | 11 | const { loadFixture } = require("./utils"); 12 | const fixtures = loadFixture("data-driven-tests.json"); 13 | const deliverMinTx = require("./fixtures/delivermin-tx.json"); 14 | const deliverMinTxBinary = require("./fixtures/delivermin-tx-binary.json"); 15 | const SignerListSet = { 16 | tx: require("./fixtures/signerlistset-tx.json"), 17 | binary: require("./fixtures/signerlistset-tx-binary.json"), 18 | meta: require("./fixtures/signerlistset-tx-meta-binary.json"), 19 | }; 20 | const DepositPreauth = { 21 | tx: require("./fixtures/deposit-preauth-tx.json"), 22 | binary: require("./fixtures/deposit-preauth-tx-binary.json"), 23 | meta: require("./fixtures/deposit-preauth-tx-meta-binary.json"), 24 | }; 25 | const Escrow = { 26 | create: { 27 | tx: require("./fixtures/escrow-create-tx.json"), 28 | binary: require("./fixtures/escrow-create-binary.json"), 29 | }, 30 | finish: { 31 | tx: require("./fixtures/escrow-finish-tx.json"), 32 | binary: require("./fixtures/escrow-finish-binary.json"), 33 | meta: require("./fixtures/escrow-finish-meta-binary.json"), 34 | }, 35 | cancel: { 36 | tx: require("./fixtures/escrow-cancel-tx.json"), 37 | binary: require("./fixtures/escrow-cancel-binary.json"), 38 | }, 39 | }; 40 | const PaymentChannel = { 41 | create: { 42 | tx: require("./fixtures/payment-channel-create-tx.json"), 43 | binary: require("./fixtures/payment-channel-create-binary.json"), 44 | }, 45 | fund: { 46 | tx: require("./fixtures/payment-channel-fund-tx.json"), 47 | binary: require("./fixtures/payment-channel-fund-binary.json"), 48 | }, 49 | claim: { 50 | tx: require("./fixtures/payment-channel-claim-tx.json"), 51 | binary: require("./fixtures/payment-channel-claim-binary.json"), 52 | }, 53 | }; 54 | 55 | const Ticket = { 56 | create: { 57 | tx: require("./fixtures/ticket-create-tx.json"), 58 | binary: require("./fixtures/ticket-create-binary.json"), 59 | }, 60 | }; 61 | 62 | let json_undefined = { 63 | TakerPays: "223174650", 64 | Account: "rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp", 65 | TransactionType: "OfferCreate", 66 | Memos: [ 67 | { 68 | Memo: { 69 | MemoType: "584D4D2076616C7565", 70 | MemoData: "322E3230393635", 71 | MemoFormat: undefined, 72 | }, 73 | }, 74 | ], 75 | Fee: "15", 76 | OfferSequence: undefined, 77 | TakerGets: { 78 | currency: "XMM", 79 | value: "100", 80 | issuer: "rExAPEZvbkZqYPuNcZ7XEBLENEshsWDQc8", 81 | }, 82 | Flags: 524288, 83 | Sequence: undefined, 84 | LastLedgerSequence: 6220135, 85 | }; 86 | 87 | let json_omitted = { 88 | TakerPays: "223174650", 89 | Account: "rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp", 90 | TransactionType: "OfferCreate", 91 | Memos: [ 92 | { 93 | Memo: { 94 | MemoType: "584D4D2076616C7565", 95 | MemoData: "322E3230393635", 96 | }, 97 | }, 98 | ], 99 | Fee: "15", 100 | TakerGets: { 101 | currency: "XMM", 102 | value: "100", 103 | issuer: "rExAPEZvbkZqYPuNcZ7XEBLENEshsWDQc8", 104 | }, 105 | Flags: 524288, 106 | LastLedgerSequence: 6220135, 107 | }; 108 | 109 | const NegativeUNL = require("./fixtures/negative-unl.json"); 110 | 111 | function bytesListTest() { 112 | const list = new BytesList() 113 | .put(Buffer.from([0])) 114 | .put(Buffer.from([2, 3])) 115 | .put(Buffer.from([4, 5])); 116 | test("is an Array", function () { 117 | expect(Array.isArray(list.bytesArray)).toBe(true); 118 | expect(list.bytesArray[0] instanceof Buffer).toBe(true); 119 | }); 120 | test("keeps track of the length itself", function () { 121 | expect(list.getLength()).toBe(5); 122 | }); 123 | test("can join all arrays into one via toBytes", function () { 124 | const joined = list.toBytes(); 125 | expect(joined).toHaveLength(5); 126 | expect(joined).toEqual(Buffer.from([0, 2, 3, 4, 5])); 127 | }); 128 | } 129 | 130 | function assertRecycles(blob) { 131 | const parser = makeParser(blob); 132 | const so = parser.readType(STObject); 133 | const out = new BytesList(); 134 | so.toBytesSink(out); 135 | const hex = out.toHex(); 136 | expect(hex).toEqual(blob); 137 | expect(hex + ":").not.toEqual(blob); 138 | } 139 | 140 | function nestedObjectTests() { 141 | fixtures.whole_objects.forEach((f, i) => { 142 | /*eslint-disable jest/expect-expect*/ 143 | test(`whole_objects[${i}]: can parse blob and dump out same blob`, () => { 144 | assertRecycles(f.blob_with_no_signing); 145 | }); 146 | /*eslint-enable jest/expect-expect*/ 147 | }); 148 | } 149 | 150 | function check(type, n, expected) { 151 | test(`Uint${type.width * 8} serializes ${n} as ${expected}`, function () { 152 | const bl = new BytesList(); 153 | const serializer = new BinarySerializer(bl); 154 | if (expected === "throws") { 155 | expect(() => serializer.writeType(type, n)).toThrow(); 156 | return; 157 | } 158 | serializer.writeType(type, n); 159 | expect(bl.toBytes()).toEqual(Buffer.from(expected)); 160 | }); 161 | } 162 | 163 | check(UInt8, 5, [5]); 164 | check(UInt16, 5, [0, 5]); 165 | check(UInt32, 5, [0, 0, 0, 5]); 166 | check(UInt32, 0xffffffff, [255, 255, 255, 255]); 167 | check(UInt8, 0xfeffffff, "throws"); 168 | check(UInt16, 0xfeffffff, "throws"); 169 | check(UInt16, 0xfeffffff, "throws"); 170 | check(UInt64, 0xfeffffff, [0, 0, 0, 0, 254, 255, 255, 255]); 171 | check(UInt64, -1, "throws"); 172 | check(UInt64, 0, [0, 0, 0, 0, 0, 0, 0, 0]); 173 | check(UInt64, 1, [0, 0, 0, 0, 0, 0, 0, 1]); 174 | check(UInt64, bigInt(1), [0, 0, 0, 0, 0, 0, 0, 1]); 175 | 176 | function deliverMinTest() { 177 | test("can serialize DeliverMin", () => { 178 | expect(encode(deliverMinTx)).toEqual(deliverMinTxBinary); 179 | }); 180 | } 181 | 182 | function SignerListSetTest() { 183 | test("can serialize SignerListSet", () => { 184 | expect(encode(SignerListSet.tx)).toEqual(SignerListSet.binary); 185 | }); 186 | test("can serialize SignerListSet metadata", () => { 187 | expect(encode(SignerListSet.tx.meta)).toEqual(SignerListSet.meta); 188 | }); 189 | } 190 | 191 | function DepositPreauthTest() { 192 | test("can serialize DepositPreauth", () => { 193 | expect(encode(DepositPreauth.tx)).toEqual(DepositPreauth.binary); 194 | }); 195 | test("can serialize DepositPreauth metadata", () => { 196 | expect(encode(DepositPreauth.tx.meta)).toEqual(DepositPreauth.meta); 197 | }); 198 | } 199 | 200 | function EscrowTest() { 201 | test("can serialize EscrowCreate", () => { 202 | expect(encode(Escrow.create.tx)).toEqual(Escrow.create.binary); 203 | }); 204 | test("can serialize EscrowFinish", () => { 205 | expect(encode(Escrow.finish.tx)).toEqual(Escrow.finish.binary); 206 | expect(encode(Escrow.finish.tx.meta)).toEqual(Escrow.finish.meta); 207 | }); 208 | test("can serialize EscrowCancel", () => { 209 | expect(encode(Escrow.cancel.tx)).toEqual(Escrow.cancel.binary); 210 | }); 211 | } 212 | 213 | function PaymentChannelTest() { 214 | test("can serialize PaymentChannelCreate", () => { 215 | expect(encode(PaymentChannel.create.tx)).toEqual( 216 | PaymentChannel.create.binary 217 | ); 218 | }); 219 | test("can serialize PaymentChannelFund", () => { 220 | expect(encode(PaymentChannel.fund.tx)).toEqual(PaymentChannel.fund.binary); 221 | }); 222 | test("can serialize PaymentChannelClaim", () => { 223 | expect(encode(PaymentChannel.claim.tx)).toEqual( 224 | PaymentChannel.claim.binary 225 | ); 226 | }); 227 | } 228 | 229 | function NegativeUNLTest() { 230 | test("can serialize NegativeUNL", () => { 231 | expect(encode(NegativeUNL.tx)).toEqual(NegativeUNL.binary); 232 | }); 233 | test("can deserialize NegativeUNL", () => { 234 | expect(decode(NegativeUNL.binary)).toEqual(NegativeUNL.tx); 235 | }); 236 | } 237 | 238 | function omitUndefinedTest() { 239 | test("omits fields with undefined value", () => { 240 | let encodedOmitted = encode(json_omitted); 241 | let encodedUndefined = encode(json_undefined); 242 | expect(encodedOmitted).toEqual(encodedUndefined); 243 | expect(decode(encodedOmitted)).toEqual(decode(encodedUndefined)); 244 | }); 245 | } 246 | 247 | function ticketTest() { 248 | test("can serialize TicketCreate", () => { 249 | expect(encode(Ticket.create.tx)).toEqual(Ticket.create.binary); 250 | }); 251 | } 252 | 253 | describe("Binary Serialization", function () { 254 | describe("nestedObjectTests", () => nestedObjectTests()); 255 | describe("BytesList", () => bytesListTest()); 256 | describe("DeliverMin", () => deliverMinTest()); 257 | describe("DepositPreauth", () => DepositPreauthTest()); 258 | describe("SignerListSet", () => SignerListSetTest()); 259 | describe("Escrow", () => EscrowTest()); 260 | describe("PaymentChannel", () => PaymentChannelTest()); 261 | describe("NegativeUNLTest", () => NegativeUNLTest()); 262 | describe("OmitUndefined", () => omitUndefinedTest()); 263 | describe("TicketTest", () => ticketTest()); 264 | }); 265 | -------------------------------------------------------------------------------- /test/fixtures/account-tx-transactions.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-binary-codec/e349cb87b236eedf175ae70cf56b143cdac1a1a4/test/fixtures/account-tx-transactions.db -------------------------------------------------------------------------------- /test/fixtures/delivermin-tx-binary.json: -------------------------------------------------------------------------------- 1 | "1200002280020000240000689E201B010BF0E361D4950EA99C657EF800000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1684000000000002AF8694000000000003A986AD40485B690F28E8000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210254D771E2A30552D1F347F5B88EC87513843F8BC1A408E70A4175B2E3C325FD3C7446304402202A4965FCF0571B7308971956864B1949C2BD924B5A41B5E8DAF00C91C64F964502207FD3BEB7C165BD1F10E6E7C443742BD686F8E102A89B502A4A495F4C29EC5C488114EAAA52373B59DCFBFD3476049AA6408AA22EAA898314EAAA52373B59DCFBFD3476049AA6408AA22EAA8901123000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF01FDF050193BEDEAA9074764B961405D31E66AC0E9300000000000000000000000005553440000000000FDF050193BEDEAA9074764B961405D31E66AC0E901FDF050193BEDEAA9074764B961405D31E66AC0E9FF300000000000000000000000005553440000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD301DD39C650A96EDA48334E70CC4A85B8B2E8502CD3017C44F934D7A5FEEBD1530570CDB83D1D8EF1F37E00" 2 | -------------------------------------------------------------------------------- /test/fixtures/delivermin-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7", 3 | "Amount": { 4 | "currency": "USD", 5 | "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 6 | "value": "5.927096147083" 7 | }, 8 | "DeliverMin": { 9 | "currency": "USD", 10 | "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 11 | "value": "0.012729190692" 12 | }, 13 | "Destination": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7", 14 | "Fee": "11000", 15 | "Flags": 2147614720, 16 | "LastLedgerSequence": 17559779, 17 | "Paths": [ 18 | [ 19 | { 20 | "currency": "USD", 21 | "issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 22 | "type": 48, 23 | "type_hex": "0000000000000030" 24 | } 25 | ], 26 | [ 27 | { 28 | "account": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R", 29 | "type": 1, 30 | "type_hex": "0000000000000001" 31 | }, 32 | { 33 | "currency": "USD", 34 | "issuer": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R", 35 | "type": 48, 36 | "type_hex": "0000000000000030" 37 | }, 38 | { 39 | "account": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R", 40 | "type": 1, 41 | "type_hex": "0000000000000001" 42 | } 43 | ], 44 | [ 45 | { 46 | "currency": "USD", 47 | "issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", 48 | "type": 48, 49 | "type_hex": "0000000000000030" 50 | }, 51 | { 52 | "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", 53 | "type": 1, 54 | "type_hex": "0000000000000001" 55 | }, 56 | { 57 | "account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq", 58 | "type": 1, 59 | "type_hex": "0000000000000001" 60 | } 61 | ] 62 | ], 63 | "SendMax": "15000", 64 | "Sequence": 26782, 65 | "SigningPubKey": "0254D771E2A30552D1F347F5B88EC87513843F8BC1A408E70A4175B2E3C325FD3C", 66 | "TransactionType": "Payment", 67 | "TxnSignature": "304402202A4965FCF0571B7308971956864B1949C2BD924B5A41B5E8DAF00C91C64F964502207FD3BEB7C165BD1F10E6E7C443742BD686F8E102A89B502A4A495F4C29EC5C48", 68 | "date": 502912010, 69 | "hash": "0FB10DF664F33840ABC68A8BBE78178359C55AC1AFC83DB468CE69C4A86E3EAC", 70 | "inLedger": 17559773, 71 | "ledger_index": 17559773, 72 | "meta": { 73 | "AffectedNodes": [ 74 | { 75 | "ModifiedNode": { 76 | "FinalFields": { 77 | "Account": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7", 78 | "Balance": "1064773000", 79 | "Flags": 65536, 80 | "OwnerCount": 9, 81 | "Sequence": 26783 82 | }, 83 | "LedgerEntryType": "AccountRoot", 84 | "LedgerIndex": "0D7F8ADAA3269E9C8B2AA9CDE31BC57E2E52133F36516D3DF576DBCE6E405BDC", 85 | "PreviousFields": { 86 | "Balance": "1064784000", 87 | "Sequence": 26782 88 | }, 89 | "PreviousTxnID": "E028797D6C7AE9B84C6930452D427D5E40CE23E199E8DF0534DAAE5277A76CC1", 90 | "PreviousTxnLgrSeq": 17537115 91 | } 92 | } 93 | ], 94 | "TransactionIndex": 12, 95 | "TransactionResult": "tecPATH_PARTIAL" 96 | }, 97 | "validated": true 98 | } 99 | -------------------------------------------------------------------------------- /test/fixtures/deposit-preauth-tx-binary.json: -------------------------------------------------------------------------------- 1 | "1200132280000000240000004168400000000000000A732103EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A5574473045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D81148A928D14A643F388AC0D26BAF9755B07EB0A2B44851486FFE2A17E861BA0FE9A3ED8352F895D80E789E0" -------------------------------------------------------------------------------- /test/fixtures/deposit-preauth-tx-meta-binary.json: -------------------------------------------------------------------------------- 1 | "201C00000000F8E51100612500AE5F59558107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E56146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2E624000000412D00000007624000000249CB5DD0E1E7220000000024000000422D00000008624000000249CB5DC681148A928D14A643F388AC0D26BAF9755B07EB0A2B44E1E1E311007056C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3AE881148A928D14A643F388AC0D26BAF9755B07EB0A2B44851486FFE2A17E861BA0FE9A3ED8352F895D80E789E0E1E1E511006456CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82ECE7220000000058CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC82148A928D14A643F388AC0D26BAF9755B07EB0A2B44E1E1F1031000" -------------------------------------------------------------------------------- /test/fixtures/deposit-preauth-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", 3 | "Authorize": "rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM", 4 | "Fee": "10", 5 | "Flags": 2147483648, 6 | "Sequence": 65, 7 | "SigningPubKey": "03EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A55", 8 | "TransactionType": "DepositPreauth", 9 | "TxnSignature": "3045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D", 10 | "hash": "B5D94C027C846171B2F5D4C2D126E88580BF369986A155C1890352F5BC4D7AF9", 11 | "meta": { 12 | "AffectedNodes": [ 13 | { 14 | "ModifiedNode": { 15 | "FinalFields": { 16 | "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", 17 | "Balance": "9827999174", 18 | "Flags": 0, 19 | "OwnerCount": 8, 20 | "Sequence": 66 21 | }, 22 | "LedgerEntryType": "AccountRoot", 23 | "LedgerIndex": "146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2", 24 | "PreviousFields": { 25 | "Balance": "9827999184", 26 | "OwnerCount": 7, 27 | "Sequence": 65 28 | }, 29 | "PreviousTxnID": "8107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E", 30 | "PreviousTxnLgrSeq": 11427673 31 | } 32 | }, 33 | { 34 | "CreatedNode": { 35 | "LedgerEntryType": "DepositPreauth", 36 | "LedgerIndex": "C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3A", 37 | "NewFields": { 38 | "Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", 39 | "Authorize": "rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM" 40 | } 41 | } 42 | }, 43 | { 44 | "ModifiedNode": { 45 | "FinalFields": { 46 | "Flags": 0, 47 | "Owner": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM", 48 | "RootIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" 49 | }, 50 | "LedgerEntryType": "DirectoryNode", 51 | "LedgerIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC" 52 | } 53 | } 54 | ], 55 | "TransactionIndex": 0, 56 | "TransactionResult": "tesSUCCESS" 57 | } 58 | } -------------------------------------------------------------------------------- /test/fixtures/escrow-cancel-binary.json: -------------------------------------------------------------------------------- 1 | "1200042019000000198114EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA8214EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA" 2 | -------------------------------------------------------------------------------- /test/fixtures/escrow-cancel-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account" : "r4jQDHCUvgcBAa5EzcB1D8BHGcjYP9eBC2", 3 | "OfferSequence" : 25, 4 | "Owner" : "r4jQDHCUvgcBAa5EzcB1D8BHGcjYP9eBC2", 5 | "TransactionType" : "EscrowCancel" 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/escrow-create-binary.json: -------------------------------------------------------------------------------- 1 | "1200012E00005BB82024258D09812025258D0980614000000000000064701127A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B8558101008114EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA8314B5F762798A53D543A014CAF8B297CFF8F2F937E8" 2 | -------------------------------------------------------------------------------- /test/fixtures/escrow-create-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account" : "r4jQDHCUvgcBAa5EzcB1D8BHGcjYP9eBC2", 3 | "Amount" : "100", 4 | "CancelAfter" : 630000001, 5 | "Condition" : "A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100", 6 | "Destination" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 7 | "DestinationTag" : 23480, 8 | "FinishAfter" : 630000000, 9 | "TransactionType": "EscrowCreate" 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/escrow-finish-binary.json: -------------------------------------------------------------------------------- 1 | "1200022280000000240000000320190000000268400000000000000A7321028DB04DA8B702272B2A29FAF3DBCF40D01386208E6A9541DB497BE962FEF96A9C74473045022100E3C860D54E88AFA8584FBED99AA38BCB512AB1D69D87F3F71EF38ACB6EA4B7A202204CC1BB34DAA221416DB8946B583D4F33C7E22C130F9FA49353FB6ADB2DF6C69D8114E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A18214E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A1" 2 | -------------------------------------------------------------------------------- /test/fixtures/escrow-finish-meta-binary.json: -------------------------------------------------------------------------------- 1 | "201C0000000BF8E511006125020B814F55F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F95644A462A2806A513D7480CA71059D3D33637B65458017A8828A8F95AF17272501E6624000000010DC67D0E1E7220000000024000000052D00000000624000000013C074F08114DC0BCC71D87BB4E684B35721DC12F2C4E1ABABA4E1E1E4110075569766AE124E5053BCFDE253F6E3DDBAC13858CC0700DDECDCD57FF2FA777BEF7DE7220000000025020B814F202521A0C1B834000000000000000039000000000000000055F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F9614000000002E40D208114E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A18314DC0BCC71D87BB4E684B35721DC12F2C4E1ABABA4E1E1E511006456BA4B47767C25E21CCDA3553BD45BE3699B34B508B459FE0472C70C51660058E0E7220000000058BA4B47767C25E21CCDA3553BD45BE3699B34B508B459FE0472C70C51660058E08214E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A1E1E1E511006125020B814F55F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F956E4DA2A510F0C7FFD0474FAA4C7308A83828E0B3DD09EAA9CFDFFC067E3719D2EE624000000032D00000001624000000002FAF06CE1E7220000000024000000042D00000000624000000002FAF0628114E151CA3207BAB5B91D2F0E4D35ECDFD4551C69A1E1E1E511006456ECE79D27042E87B02DF3A263DB6BB6FCD96E69E20E0955F84D47D164C37546A1E7220000000058ECE79D27042E87B02DF3A263DB6BB6FCD96E69E20E0955F84D47D164C37546A18214DC0BCC71D87BB4E684B35721DC12F2C4E1ABABA4E1E1F1031000" 2 | -------------------------------------------------------------------------------- /test/fixtures/escrow-finish-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "rMYPppnVNQ7crMizv8D6wF45kYuSupygyr", 3 | "Fee": "10", 4 | "Flags": 2147483648, 5 | "OfferSequence": 2, 6 | "Owner": "rMYPppnVNQ7crMizv8D6wF45kYuSupygyr", 7 | "Sequence": 3, 8 | "SigningPubKey": "028DB04DA8B702272B2A29FAF3DBCF40D01386208E6A9541DB497BE962FEF96A9C", 9 | "TransactionType": "EscrowFinish", 10 | "TxnSignature": "3045022100E3C860D54E88AFA8584FBED99AA38BCB512AB1D69D87F3F71EF38ACB6EA4B7A202204CC1BB34DAA221416DB8946B583D4F33C7E22C130F9FA49353FB6ADB2DF6C69D", 11 | "hash": "1A76D4BA47A53A66B539D4BD4C30826A5E51C78D0B7344758EACAC77A6753C3C", 12 | "meta": { 13 | "AffectedNodes": [ 14 | { 15 | "ModifiedNode": { 16 | "FinalFields": { 17 | "Account": "rMhV8zfKJRqEspcayRfqa4FuzQg8kW5LdB", 18 | "Balance": "331379952", 19 | "Flags": 0, 20 | "OwnerCount": 0, 21 | "Sequence": 5 22 | }, 23 | "LedgerEntryType": "AccountRoot", 24 | "LedgerIndex": "44A462A2806A513D7480CA71059D3D33637B65458017A8828A8F95AF17272501", 25 | "PreviousFields": { 26 | "Balance": "282879952" 27 | }, 28 | "PreviousTxnID": "F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F9", 29 | "PreviousTxnLgrSeq": 34308431 30 | } 31 | }, 32 | { 33 | "DeletedNode": { 34 | "FinalFields": { 35 | "Account": "rMYPppnVNQ7crMizv8D6wF45kYuSupygyr", 36 | "Amount": "48500000", 37 | "Destination": "rMhV8zfKJRqEspcayRfqa4FuzQg8kW5LdB", 38 | "DestinationNode": "0000000000000000", 39 | "FinishAfter": 564183480, 40 | "Flags": 0, 41 | "OwnerNode": "0000000000000000", 42 | "PreviousTxnID": "F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F9", 43 | "PreviousTxnLgrSeq": 34308431 44 | }, 45 | "LedgerEntryType": "Escrow", 46 | "LedgerIndex": "9766AE124E5053BCFDE253F6E3DDBAC13858CC0700DDECDCD57FF2FA777BEF7D" 47 | } 48 | }, 49 | { 50 | "ModifiedNode": { 51 | "FinalFields": { 52 | "Flags": 0, 53 | "Owner": "rMYPppnVNQ7crMizv8D6wF45kYuSupygyr", 54 | "RootIndex": "BA4B47767C25E21CCDA3553BD45BE3699B34B508B459FE0472C70C51660058E0" 55 | }, 56 | "LedgerEntryType": "DirectoryNode", 57 | "LedgerIndex": "BA4B47767C25E21CCDA3553BD45BE3699B34B508B459FE0472C70C51660058E0" 58 | } 59 | }, 60 | { 61 | "ModifiedNode": { 62 | "FinalFields": { 63 | "Account": "rMYPppnVNQ7crMizv8D6wF45kYuSupygyr", 64 | "Balance": "49999970", 65 | "Flags": 0, 66 | "OwnerCount": 0, 67 | "Sequence": 4 68 | }, 69 | "LedgerEntryType": "AccountRoot", 70 | "LedgerIndex": "E4DA2A510F0C7FFD0474FAA4C7308A83828E0B3DD09EAA9CFDFFC067E3719D2E", 71 | "PreviousFields": { 72 | "Balance": "49999980", 73 | "OwnerCount": 1, 74 | "Sequence": 3 75 | }, 76 | "PreviousTxnID": "F72706F8C7B9C07D83332BE330D77B5CE6A246FE4FA04DD47EBC88719A35B5F9", 77 | "PreviousTxnLgrSeq": 34308431 78 | } 79 | }, 80 | { 81 | "ModifiedNode": { 82 | "FinalFields": { 83 | "Flags": 0, 84 | "Owner": "rMhV8zfKJRqEspcayRfqa4FuzQg8kW5LdB", 85 | "RootIndex": "ECE79D27042E87B02DF3A263DB6BB6FCD96E69E20E0955F84D47D164C37546A1" 86 | }, 87 | "LedgerEntryType": "DirectoryNode", 88 | "LedgerIndex": "ECE79D27042E87B02DF3A263DB6BB6FCD96E69E20E0955F84D47D164C37546A1" 89 | } 90 | } 91 | ], 92 | "TransactionIndex": 11, 93 | "TransactionResult": "tesSUCCESS" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /test/fixtures/negative-unl.json: -------------------------------------------------------------------------------- 1 | { 2 | "binary": "120066240000000026000003006840000000000000007300701321ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE8114000000000000000000000000000000000000000000101101", 3 | "tx": { 4 | "UNLModifyDisabling": 1, 5 | "LedgerSequence": 768, 6 | "UNLModifyValidator": "ED9D593004CC501CACD261BD8E31E863F2B3F6CA69505E7FD54DA8F5690BEFB7AE", 7 | "TransactionType": "UNLModify", 8 | "Account": "rrrrrrrrrrrrrrrrrrrrrhoLvTp", 9 | "Sequence": 0, 10 | "Fee": "0", 11 | "SigningPubKey": ""} 12 | } -------------------------------------------------------------------------------- /test/fixtures/payment-channel-claim-binary.json: -------------------------------------------------------------------------------- 1 | "12000F5016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA61986140000000000F42406240000000000F4240712132D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A764630440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B" 2 | -------------------------------------------------------------------------------- /test/fixtures/payment-channel-claim-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "TransactionType": "PaymentChannelClaim", 3 | "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", 4 | "Balance": "1000000", 5 | "Amount": "1000000", 6 | "Signature": "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA3CD7B9B", 7 | "PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A" 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/payment-channel-create-binary.json: -------------------------------------------------------------------------------- 1 | "12000D2300002DE32E00005BB820241FC78D66202700015180614000000000002710712132D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A81144B4E9C06F24296074F7BC48F92A97916C6DC5EA98314204288D2E47F8EF6C99BCC457966320D12409711" 2 | -------------------------------------------------------------------------------- /test/fixtures/payment-channel-create-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 3 | "TransactionType": "PaymentChannelCreate", 4 | "Amount": "10000", 5 | "Destination": "rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW", 6 | "SettleDelay": 86400, 7 | "PublicKey": "32D2471DB72B27E3310F355BB33E339BF26F8392D5A93D3BC0FC3B566612DA0F0A", 8 | "CancelAfter": 533171558, 9 | "DestinationTag": 23480, 10 | "SourceTag": 11747 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/payment-channel-fund-binary.json: -------------------------------------------------------------------------------- 1 | "12000E2A206023E65016C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198614000000000030D4081144B4E9C06F24296074F7BC48F92A97916C6DC5EA9" 2 | -------------------------------------------------------------------------------- /test/fixtures/payment-channel-fund-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", 3 | "TransactionType": "PaymentChannelFund", 4 | "Channel": "C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198", 5 | "Amount": "200000", 6 | "Expiration": 543171558 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/signerlistset-tx-binary.json: -------------------------------------------------------------------------------- 1 | "12000C22800000002400003B49201B01724E3520230000000368400000000000000B73210315B15964B3704B171E860DE1FC914D283395EE825C8546AEEBF0D24A5802BBC574463044022069FC98D0BC32F510D4F94ECC726613E957D290050E428DD86EDA2C2515A1732D02207064EF085437B3F12A744AC6528D9E0C59FAA5A9FE903DF3639D2F09B522175F81144901F90028CEAD8AF389AC6FA0F83643DB67E95BF4EB1300018114EA97C2F7C88AE5735A59811F6F89E825B478982FE1EB1300018114B4AC72AF6C0EE5A1B8C94A3C20BE09599BBB57EEE1EB1300018114FA7420343B9EA7C9B294C3AF802AE80103F0B11BE1F1" 2 | -------------------------------------------------------------------------------- /test/fixtures/signerlistset-tx-meta-binary.json: -------------------------------------------------------------------------------- 1 | "201C00000008F8E51100612501724363554CD30C2526418A76ABED74713DF144B19F1B29BD8F7BC9EADCF16D33D7EC83D8560DAF42BEE40F0EEFCFD8D54E81ECACD9371D281CDD9B77384FCDAF0E16560A44E62400003B4962400000010F99515AE1E722000000002400003B4A2D0000001962400000010F99514F81144901F90028CEAD8AF389AC6FA0F83643DB67E95B8814CAA26A38FC8F4E5D6D142432B0D7D94A2880DDDFE1E1E51100535616F6AEEC6B85C9658B4BF604671677B7A7B2FAAFDB2FA5A0B72CBB49CAE80924E72200000000202300000003202600000000340000000000000000F4EB1300018114B4AC72AF6C0EE5A1B8C94A3C20BE09599BBB57EEE1EB1300018114EA97C2F7C88AE5735A59811F6F89E825B478982FE1EB1300018114FA7420343B9EA7C9B294C3AF802AE80103F0B11BE1F1E1E1F1031000" 2 | -------------------------------------------------------------------------------- /test/fixtures/signerlistset-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "Account": "rfCp6hiUS4qqN1i4hTyX4ogA49MEbXgCau", 3 | "Fee": "11", 4 | "Flags": 2147483648, 5 | "LastLedgerSequence": 24268341, 6 | "Sequence": 15177, 7 | "SignerEntries": [ 8 | { 9 | "SignerEntry": { 10 | "Account": "r4PQv7BCpp4SAJx3isNpQM8T2BuGrMQs5U", 11 | "SignerWeight": 1 12 | } 13 | }, 14 | { 15 | "SignerEntry": { 16 | "Account": "rH7KDR67MZR7LDV7gesmEMXtaqU3FaK7Lr", 17 | "SignerWeight": 1 18 | } 19 | }, 20 | { 21 | "SignerEntry": { 22 | "Account": "rPqHsX34XApKSfE4UxKbqVXb3WRmmgMY2u", 23 | "SignerWeight": 1 24 | } 25 | } 26 | ], 27 | "SignerQuorum": 3, 28 | "SigningPubKey": "0315B15964B3704B171E860DE1FC914D283395EE825C8546AEEBF0D24A5802BBC5", 29 | "TransactionType": "SignerListSet", 30 | "TxnSignature": "3044022069FC98D0BC32F510D4F94ECC726613E957D290050E428DD86EDA2C2515A1732D02207064EF085437B3F12A744AC6528D9E0C59FAA5A9FE903DF3639D2F09B522175F", 31 | "date": 527847001, 32 | "hash": "98C33CABFAE9F830CE842C260E34C25B0F987EE691941C0C9225AD476871B73D", 33 | "inLedger": 24268339, 34 | "ledger_index": 24268339, 35 | "meta": { 36 | "AffectedNodes": [ 37 | { 38 | "ModifiedNode": { 39 | "FinalFields": { 40 | "Account": "rfCp6hiUS4qqN1i4hTyX4ogA49MEbXgCau", 41 | "Balance": "4556673359", 42 | "Flags": 0, 43 | "OwnerCount": 25, 44 | "RegularKey": "rK7ShY9CeDMBHLNFSMtTrSAUd9uzwcymcL", 45 | "Sequence": 15178 46 | }, 47 | "LedgerEntryType": "AccountRoot", 48 | "LedgerIndex": "0DAF42BEE40F0EEFCFD8D54E81ECACD9371D281CDD9B77384FCDAF0E16560A44", 49 | "PreviousFields": { 50 | "Balance": "4556673370", 51 | "Sequence": 15177 52 | }, 53 | "PreviousTxnID": "4CD30C2526418A76ABED74713DF144B19F1B29BD8F7BC9EADCF16D33D7EC83D8", 54 | "PreviousTxnLgrSeq": 24265571 55 | } 56 | }, 57 | { 58 | "ModifiedNode": { 59 | "FinalFields": { 60 | "Flags": 0, 61 | "OwnerNode": "0000000000000000", 62 | "SignerEntries": [ 63 | { 64 | "SignerEntry": { 65 | "Account": "rH7KDR67MZR7LDV7gesmEMXtaqU3FaK7Lr", 66 | "SignerWeight": 1 67 | } 68 | }, 69 | { 70 | "SignerEntry": { 71 | "Account": "r4PQv7BCpp4SAJx3isNpQM8T2BuGrMQs5U", 72 | "SignerWeight": 1 73 | } 74 | }, 75 | { 76 | "SignerEntry": { 77 | "Account": "rPqHsX34XApKSfE4UxKbqVXb3WRmmgMY2u", 78 | "SignerWeight": 1 79 | } 80 | } 81 | ], 82 | "SignerListID": 0, 83 | "SignerQuorum": 3 84 | }, 85 | "LedgerEntryType": "SignerList", 86 | "LedgerIndex": "16F6AEEC6B85C9658B4BF604671677B7A7B2FAAFDB2FA5A0B72CBB49CAE80924" 87 | } 88 | } 89 | ], 90 | "TransactionIndex": 8, 91 | "TransactionResult": "tesSUCCESS" 92 | }, 93 | "validated": true 94 | } 95 | -------------------------------------------------------------------------------- /test/fixtures/ticket-create-binary.json: -------------------------------------------------------------------------------- 1 | "12000A240000000E2028000000016840000000000027108114D5024F157225CA9F8F4C73094D61A8FDD746E0DB" 2 | -------------------------------------------------------------------------------- /test/fixtures/ticket-create-tx.json: -------------------------------------------------------------------------------- 1 | { 2 | "TransactionType": "TicketCreate", 3 | "TicketCount": 1, 4 | "Account": "rLRH6HciuPv5rQBNFBYH1iPPAuVFERtNXZ", 5 | "Sequence": 14, 6 | "Fee": "10000" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/x-codec-fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "transactions": [{ 3 | "rjson": { 4 | "Account": "r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV", 5 | "Destination": "rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj", 6 | "TransactionType": "Payment", 7 | "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", 8 | "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", 9 | "Amount": "10000000000", 10 | "DestinationTag": 1010, 11 | "SourceTag": 84854, 12 | "Fee": "10", 13 | "Flags": 0, 14 | "Sequence": 62 15 | }, 16 | "xjson": { 17 | "Account": "X7tFPvjMH7nDxP8nTGkeeggcUpCZj8UbyT2QoiRHGDfjqrB", 18 | "Destination": "XVYmGpJqHS95ir411XvanwY1xt5Z2314WsamHPVgUNABUGV", 19 | "TransactionType": "Payment", 20 | "TxnSignature": "3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639", 21 | "SigningPubKey": "034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E", 22 | "Amount": "10000000000", 23 | "Fee": "10", 24 | "Flags": 0, 25 | "Sequence": 62 26 | } 27 | }, 28 | { 29 | "rjson": { 30 | "Account": "r4DymtkgUAh2wqRxVfdd3Xtswzim6eC6c5", 31 | "Amount": "199000000", 32 | "Destination": "rsekGH9p9neiPxym2TMJhqaCzHFuokenTU", 33 | "DestinationTag": 3663729509, 34 | "Fee": "6335", 35 | "Flags": 2147483648, 36 | "LastLedgerSequence": 57313352, 37 | "Sequence": 105791, 38 | "SigningPubKey": "02053A627976CE1157461336AC65290EC1571CAAD1B327339980F7BF65EF776F83", 39 | "TransactionType": "Payment", 40 | "TxnSignature": "30440220086D3330CD6CE01D891A26BA0355D8D5A5D28A5C9A1D0C5E06E321C81A02318A0220027C3F6606E41FEA35103EDE5224CC489B6514ACFE27543185B0419DD02E301C" 41 | }, 42 | "xjson": { 43 | "Account": "r4DymtkgUAh2wqRxVfdd3Xtswzim6eC6c5", 44 | "Amount": "199000000", 45 | "Destination": "X7cBoj6a5xSEfPCr6AStN9YPhbMAA2yaN2XYWwRJKAKb3y5", 46 | "Fee": "6335", 47 | "Flags": 2147483648, 48 | "LastLedgerSequence": 57313352, 49 | "Sequence": 105791, 50 | "SigningPubKey": "02053A627976CE1157461336AC65290EC1571CAAD1B327339980F7BF65EF776F83", 51 | "TransactionType": "Payment", 52 | "TxnSignature": "30440220086D3330CD6CE01D891A26BA0355D8D5A5D28A5C9A1D0C5E06E321C81A02318A0220027C3F6606E41FEA35103EDE5224CC489B6514ACFE27543185B0419DD02E301C" 53 | } 54 | }, 55 | { 56 | "rjson": { 57 | "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", 58 | "Amount": "105302107", 59 | "Destination": "r33hypJXDs47LVpmvta7hMW9pR8DYeBtkW", 60 | "DestinationTag": 1658156118, 61 | "Fee": "60000", 62 | "Flags": 2147483648, 63 | "LastLedgerSequence": 57313566, 64 | "Sequence": 1113196, 65 | "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", 66 | "TransactionType": "Payment", 67 | "TxnSignature": "3045022100FCA10FBAC65EA60C115A970CD52E6A526B1F9DDB6C4F843DA3DE7A97DFF9492D022037824D0FC6F663FB08BE0F2812CBADE1F61836528D44945FC37F10CC03215111" 68 | }, 69 | "xjson": { 70 | "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", 71 | "Amount": "105302107", 72 | "Destination": "X7ikFY5asEwp6ikt2AJdTfBLALEs5JN35kkeqKVeT1GdvY1", 73 | "Fee": "60000", 74 | "Flags": 2147483648, 75 | "LastLedgerSequence": 57313566, 76 | "Sequence": 1113196, 77 | "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", 78 | "TransactionType": "Payment", 79 | "TxnSignature": "3045022100FCA10FBAC65EA60C115A970CD52E6A526B1F9DDB6C4F843DA3DE7A97DFF9492D022037824D0FC6F663FB08BE0F2812CBADE1F61836528D44945FC37F10CC03215111" 80 | } 81 | }, 82 | { 83 | "rjson": { 84 | "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", 85 | "Amount": "3899911571", 86 | "Destination": "rU2mEJSLqBRkYLVTv55rFTgQajkLTnT6mA", 87 | "DestinationTag": 255406, 88 | "Fee": "60000", 89 | "Flags": 2147483648, 90 | "LastLedgerSequence": 57313566, 91 | "Sequence": 1113197, 92 | "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", 93 | "TransactionType": "Payment", 94 | "TxnSignature": "3044022077642D94BB3C49BF3CB4C804255EC830D2C6009EA4995E38A84602D579B8AAD702206FAD977C49980226E8B495BF03C8D9767380F1546BBF5A4FD47D604C0D2CCF9B" 95 | }, 96 | "xjson": { 97 | "Account": "rDsbeomae4FXwgQTJp9Rs64Qg9vDiTCdBv", 98 | "Amount": "3899911571", 99 | "Destination": "XVfH8gwNWVbB5Kft16jmTNgGTqgw1dzA8ZTBkNjSLw6JdXS", 100 | "Fee": "60000", 101 | "Flags": 2147483648, 102 | "LastLedgerSequence": 57313566, 103 | "Sequence": 1113197, 104 | "SigningPubKey": "03D847C2DBED3ABF0453F71DCD7641989136277218DF516AD49519C9693F32727E", 105 | "TransactionType": "Payment", 106 | "TxnSignature": "3044022077642D94BB3C49BF3CB4C804255EC830D2C6009EA4995E38A84602D579B8AAD702206FAD977C49980226E8B495BF03C8D9767380F1546BBF5A4FD47D604C0D2CCF9B" 107 | } 108 | }, 109 | { 110 | "rjson": { 111 | "Account": "r4eEbLKZGbVSBHnSUBZW8i5XaMjGLdqT4a", 112 | "Amount": "820370849", 113 | "Destination": "rDhmyBh4JwDAtXyRZDarNgg52UcLLRoGje", 114 | "DestinationTag": 2017780486, 115 | "Fee": "6000", 116 | "Flags": 2147483648, 117 | "LastLedgerSequence": 57315579, 118 | "Sequence": 234254, 119 | "SigningPubKey": "038CF47114672A12B269AEE015BF7A8438609B994B0640E4B28B2F56E93D948B15", 120 | "TransactionType": "Payment", 121 | "TxnSignature": "3044022015004653B1CBDD5CCA1F7B38555F1B37FE3F811E9D5070281CCC6C8A93460D870220679E9899184901EA69750C8A9325768490B1B9C1A733842446727653FF3D1DC0" 122 | }, 123 | "xjson": { 124 | "Account": "r4eEbLKZGbVSBHnSUBZW8i5XaMjGLdqT4a", 125 | "Amount": "820370849", 126 | "Destination": "XV31huWNJQXsAJFwgE6rnC8uf8jRx4H4waq4MyGUxz5CXzS", 127 | "Fee": "6000", 128 | "Flags": 2147483648, 129 | "LastLedgerSequence": 57315579, 130 | "Sequence": 234254, 131 | "SigningPubKey": "038CF47114672A12B269AEE015BF7A8438609B994B0640E4B28B2F56E93D948B15", 132 | "TransactionType": "Payment", 133 | "TxnSignature": "3044022015004653B1CBDD5CCA1F7B38555F1B37FE3F811E9D5070281CCC6C8A93460D870220679E9899184901EA69750C8A9325768490B1B9C1A733842446727653FF3D1DC0" 134 | } 135 | }, 136 | { 137 | "rjson": { 138 | "Account": "rsGeDwS4rpocUumu9smpXomzaaeG4Qyifz", 139 | "Amount": "1500000000", 140 | "Destination": "rDxfhNRgCDNDckm45zT5ayhKDC4Ljm7UoP", 141 | "DestinationTag": 1000635172, 142 | "Fee": "5000", 143 | "Flags": 2147483648, 144 | "Sequence": 55741075, 145 | "SigningPubKey": "02ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C36", 146 | "SourceTag": 1000635172, 147 | "TransactionType": "Payment", 148 | "TxnSignature": "304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A" 149 | }, 150 | "xjson": { 151 | "Account": "rsGeDwS4rpocUumu9smpXomzaaeG4Qyifz", 152 | "Amount": "1500000000", 153 | "Destination": "XVBkK1yLutMqFGwTm6hykn7YXGDUrjsZSkpzMgRveZrMbHs", 154 | "Fee": "5000", 155 | "Flags": 2147483648, 156 | "Sequence": 55741075, 157 | "SigningPubKey": "02ECB814477DF9D8351918878E235EE6AF147A2A5C20F1E71F291F0F3303357C36", 158 | "SourceTag": 1000635172, 159 | "TransactionType": "Payment", 160 | "TxnSignature": "304402202A90972E21823214733082E1977F9EA2D6B5101902F108E7BDD7D128CEEA7AF3022008852C8DAD746A7F18E66A47414FABF551493674783E8EA7409C501D3F05F99A" 161 | } 162 | }, 163 | { 164 | "rjson": { 165 | "Account": "rHWcuuZoFvDS6gNbmHSdpb7u1hZzxvCoMt", 166 | "Amount": "48918500000", 167 | "Destination": "rEb8TK3gBgk5auZkwc6sHnwrGVJH8DuaLh", 168 | "DestinationTag": 105959914, 169 | "Fee": "10", 170 | "Flags": 2147483648, 171 | "Sequence": 32641, 172 | "SigningPubKey": "02E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D33429", 173 | "TransactionType": "Payment", 174 | "TxnSignature": "304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A524" 175 | }, 176 | "xjson": { 177 | "Account": "rHWcuuZoFvDS6gNbmHSdpb7u1hZzxvCoMt", 178 | "Amount": "48918500000", 179 | "Destination": "XVH3aqvbYGhRhrD1FYSzGooNuxdzbG3VR2fuM47oqbXxQr7", 180 | "Fee": "10", 181 | "Flags": 2147483648, 182 | "Sequence": 32641, 183 | "SigningPubKey": "02E98DA545CCCC5D14C82594EE9E6CCFCF5171108E2410B3E784183E1068D33429", 184 | "TransactionType": "Payment", 185 | "TxnSignature": "304502210091DCA7AF189CD9DC93BDE24DEAE87381FBF16789C43113EE312241D648982B2402201C6055FEFFF1F119640AAC0B32C4F37375B0A96033E0527A21C1366920D6A524" 186 | } 187 | }] 188 | } -------------------------------------------------------------------------------- /test/hash.test.js: -------------------------------------------------------------------------------- 1 | const { coreTypes } = require("../dist/types"); 2 | const { Hash160, Hash256, AccountID, Currency } = coreTypes; 3 | const { Buffer } = require("buffer/"); 4 | 5 | describe("Hash160", function () { 6 | test("has a static width member", function () { 7 | expect(Hash160.width).toBe(20); 8 | }); 9 | test("inherited by subclasses", function () { 10 | expect(AccountID.width).toBe(20); 11 | expect(Currency.width).toBe(20); 12 | }); 13 | test("can be compared against another", function () { 14 | const h1 = Hash160.from("1000000000000000000000000000000000000000"); 15 | const h2 = Hash160.from("2000000000000000000000000000000000000000"); 16 | const h3 = Hash160.from("0000000000000000000000000000000000000003"); 17 | expect(h1.lt(h2)).toBe(true); 18 | expect(h3.lt(h2)).toBe(true); 19 | }); 20 | test("throws when constructed from invalid hash length", () => { 21 | expect(() => 22 | Hash160.from("10000000000000000000000000000000000000") 23 | ).toThrow("Invalid Hash length 19"); 24 | expect(() => 25 | Hash160.from("100000000000000000000000000000000000000000") 26 | ).toThrow("Invalid Hash length 21"); 27 | }); 28 | }); 29 | 30 | describe("Hash256", function () { 31 | test("has a static width member", function () { 32 | expect(Hash256.width).toBe(32); 33 | }); 34 | test("has a ZERO_256 member", function () { 35 | expect(Hash256.ZERO_256.toJSON()).toBe( 36 | "0000000000000000000000000000000000000000000000000000000000000000" 37 | ); 38 | }); 39 | test("supports getting the nibblet values at given positions", function () { 40 | const h = Hash256.from( 41 | "1359BD0000000000000000000000000000000000000000000000000000000000" 42 | ); 43 | expect(h.nibblet(0)).toBe(0x1); 44 | expect(h.nibblet(1)).toBe(0x3); 45 | expect(h.nibblet(2)).toBe(0x5); 46 | expect(h.nibblet(3)).toBe(0x9); 47 | expect(h.nibblet(4)).toBe(0x0b); 48 | expect(h.nibblet(5)).toBe(0xd); 49 | }); 50 | }); 51 | 52 | describe("Currency", function () { 53 | test("Will throw an error for dodgy XRP ", function () { 54 | expect(() => 55 | Currency.from("0000000000000000000000005852500000000000") 56 | ).toThrow(); 57 | }); 58 | test("Currency with lowercase letters decode to hex", () => { 59 | expect(Currency.from("xRp").toJSON()).toBe( 60 | "0000000000000000000000007852700000000000" 61 | ); 62 | }); 63 | test("Currency codes with symbols decode to hex", () => { 64 | expect(Currency.from("x|p").toJSON()).toBe( 65 | "000000000000000000000000787C700000000000" 66 | ); 67 | }); 68 | test("Currency codes with uppercase and 0-9 decode to ISO codes", () => { 69 | expect(Currency.from("X8P").toJSON()).toBe("X8P"); 70 | expect(Currency.from("USD").toJSON()).toBe("USD"); 71 | }); 72 | test("can be constructed from a Buffer", function () { 73 | const xrp = new Currency(Buffer.alloc(20)); 74 | expect(xrp.iso()).toBe("XRP"); 75 | }); 76 | test("Can handle non-standard currency codes", () => { 77 | const currency = "015841551A748AD2C1F76FF6ECB0CCCD00000000"; 78 | expect(Currency.from(currency).toJSON()).toBe(currency); 79 | }); 80 | test("throws on invalid reprs", function () { 81 | expect(() => Currency.from(Buffer.alloc(19))).toThrow(); 82 | expect(() => Currency.from(1)).toThrow(); 83 | expect(() => 84 | Currency.from("00000000000000000000000000000000000000m") 85 | ).toThrow(); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/ledger.test.js: -------------------------------------------------------------------------------- 1 | const { loadFixture } = require("./utils"); 2 | const { 3 | transactionTreeHash, 4 | ledgerHash, 5 | accountStateHash, 6 | } = require("../dist/ledger-hashes"); 7 | 8 | describe("Ledger Hashes", function () { 9 | function testFactory(ledgerFixture) { 10 | describe(`can calculate hashes for ${ledgerFixture}`, function () { 11 | const ledger = loadFixture(ledgerFixture); 12 | test("computes correct account state hash", function () { 13 | expect(accountStateHash(ledger.accountState).toHex()).toBe( 14 | ledger.account_hash 15 | ); 16 | }); 17 | test("computes correct transaction tree hash", function () { 18 | expect(transactionTreeHash(ledger.transactions).toHex()).toBe( 19 | ledger.transaction_hash 20 | ); 21 | }); 22 | test("computes correct ledger header hash", function () { 23 | expect(ledgerHash(ledger).toHex()).toBe(ledger.hash); 24 | }); 25 | }); 26 | } 27 | testFactory("ledger-full-40000.json"); 28 | testFactory("ledger-full-38129.json"); 29 | }); 30 | -------------------------------------------------------------------------------- /test/lower-case-hex.test.js: -------------------------------------------------------------------------------- 1 | const { encode, decode } = require("../dist"); 2 | 3 | let str = 4 | "1100612200000000240000000125000068652D0000000055B6632D6376A2D9319F20A1C6DCCB486432D1E4A79951229D4C3DE2946F51D56662400009184E72A00081140DD319918CD5AE792BF7EC80D63B0F01B4573BBC"; 5 | let lower = str.toLowerCase(); 6 | 7 | let bin = 8 | "1100612200000000240000000125000000082D00000000550735A0B32B2A3F4C938B76D6933003E29447DB8C7CE382BBE089402FF12A03E56240000002540BE400811479927BAFFD3D04A26096C0C97B1B0D45B01AD3C0"; 9 | let json = { 10 | OwnerCount: 0, 11 | Account: "rUnFEsHjxqTswbivzL2DNHBb34rhAgZZZK", 12 | PreviousTxnLgrSeq: 8, 13 | LedgerEntryType: "AccountRoot", 14 | PreviousTxnID: 15 | "0735A0B32B2A3F4C938B76D6933003E29447DB8C7CE382BBE089402FF12A03E5".toLowerCase(), 16 | Flags: 0, 17 | Sequence: 1, 18 | Balance: "10000000000", 19 | }; 20 | 21 | let jsonUpper = { 22 | OwnerCount: 0, 23 | Account: "rUnFEsHjxqTswbivzL2DNHBb34rhAgZZZK", 24 | PreviousTxnLgrSeq: 8, 25 | LedgerEntryType: "AccountRoot", 26 | PreviousTxnID: 27 | "0735A0B32B2A3F4C938B76D6933003E29447DB8C7CE382BBE089402FF12A03E5", 28 | Flags: 0, 29 | Sequence: 1, 30 | Balance: "10000000000", 31 | }; 32 | 33 | describe("Lowercase hex test", () => { 34 | test("Correctly decodes", () => { 35 | expect(decode(lower)).toEqual(decode(str)); 36 | }); 37 | test("Re-encodes to uppercase hex", () => { 38 | expect(encode(decode(lower))).toEqual(str); 39 | }); 40 | test("Encode when hex field lowercase", () => { 41 | expect(encode(json)).toBe(bin); 42 | }); 43 | test("Re-decodes to uppercase hex", () => { 44 | expect(decode(encode(json))).toEqual(jsonUpper); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/pseudo-transaction.test.js: -------------------------------------------------------------------------------- 1 | const { encode, decode } = require("../dist"); 2 | 3 | let json = { 4 | Account: "rrrrrrrrrrrrrrrrrrrrrhoLvTp", 5 | Sequence: 0, 6 | Fee: "0", 7 | SigningPubKey: "", 8 | Signature: "", 9 | }; 10 | 11 | let json_blank_acct = { 12 | Account: "", 13 | Sequence: 0, 14 | Fee: "0", 15 | SigningPubKey: "", 16 | Signature: "", 17 | }; 18 | 19 | let binary = 20 | "24000000006840000000000000007300760081140000000000000000000000000000000000000000"; 21 | 22 | describe("Can encode Pseudo Transactions", () => { 23 | test("Correctly encodes Pseudo Transaciton", () => { 24 | expect(encode(json)).toEqual(binary); 25 | }); 26 | 27 | test("Can decode account objects", () => { 28 | expect(decode(encode(json))).toEqual(json); 29 | }); 30 | 31 | test("Blank AccountID is ACCOUNT_ZERO", () => { 32 | expect(encode(json_blank_acct)).toEqual(binary); 33 | }); 34 | 35 | test("Decodes Blank AccountID", () => { 36 | expect(decode(encode(json_blank_acct))).toEqual(json); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/quality.test.js: -------------------------------------------------------------------------------- 1 | const { quality } = require("../dist/coretypes"); 2 | 3 | describe("Quality encode/decode", function () { 4 | const bookDirectory = 5 | "4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D06F4C3362FE1D0"; 6 | const expectedQuality = "195796912.5171664"; 7 | test("can decode", function () { 8 | const decimal = quality.decode(bookDirectory); 9 | expect(decimal.toString()).toBe(expectedQuality); 10 | }); 11 | test("can encode", function () { 12 | const bytes = quality.encode(expectedQuality); 13 | expect(bytes.toString("hex").toUpperCase()).toBe(bookDirectory.slice(-16)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/shamap.test.js: -------------------------------------------------------------------------------- 1 | const { ShaMap } = require("../dist/shamap.js"); 2 | const { binary, HashPrefix } = require("../dist/coretypes"); 3 | const { coreTypes } = require("../dist/types"); 4 | const { loadFixture } = require("./utils"); 5 | const { Buffer } = require("buffer/"); 6 | 7 | function now() { 8 | return Number(Date.now()) / 1000; 9 | } 10 | 11 | const ZERO = "0000000000000000000000000000000000000000000000000000000000000000"; 12 | 13 | function makeItem(indexArg) { 14 | let str = indexArg; 15 | while (str.length < 64) { 16 | str += "0"; 17 | } 18 | const index = coreTypes.Hash256.from(str); 19 | const item = { 20 | toBytesSink(sink) { 21 | index.toBytesSink(sink); 22 | }, 23 | hashPrefix() { 24 | return Buffer.from([1, 3, 3, 7]); 25 | }, 26 | }; 27 | return [index, item]; 28 | } 29 | 30 | describe("ShaMap", () => { 31 | now(); 32 | 33 | test("hashes to zero when empty", () => { 34 | const map = new ShaMap(); 35 | expect(map.hash().toHex()).toBe(ZERO); 36 | }); 37 | test("creates the same hash no matter which order items are added", () => { 38 | let map = new ShaMap(); 39 | const items = [ 40 | "0", 41 | "1", 42 | "11", 43 | "7000DE445E22CB9BB7E1717589FA858736BAA5FD192310E20000000000000000", 44 | "7000DE445E22CB9BB7E1717589FA858736BAA5FD192310E21000000000000000", 45 | "7000DE445E22CB9BB7E1717589FA858736BAA5FD192310E22000000000000000", 46 | "7000DE445E22CB9BB7E1717589FA858736BAA5FD192310E23000000000000000", 47 | "12", 48 | "122", 49 | ]; 50 | items.forEach((i) => map.addItem(...makeItem(i))); 51 | const h1 = map.hash(); 52 | expect(h1.eq(h1)).toBe(true); 53 | map = new ShaMap(); 54 | items.reverse().forEach((i) => map.addItem(...makeItem(i))); 55 | expect(map.hash()).toStrictEqual(h1); 56 | }); 57 | function factory(fixture) { 58 | test(`recreate account state hash from ${fixture}`, () => { 59 | const map = new ShaMap(); 60 | const ledger = loadFixture(fixture); 61 | // const t = now(); 62 | const leafNodePrefix = HashPrefix.accountStateEntry; 63 | ledger.accountState 64 | .map((e, i) => { 65 | if ((i > 1000) & (i % 1000 === 0)) { 66 | console.log(e.index); 67 | console.log(i); 68 | } 69 | const bytes = binary.serializeObject(e); 70 | return { 71 | index: coreTypes.Hash256.from(e.index), 72 | hashPrefix() { 73 | return leafNodePrefix; 74 | }, 75 | toBytesSink(sink) { 76 | sink.put(bytes); 77 | }, 78 | }; 79 | }) 80 | .forEach((so) => map.addItem(so.index, so)); 81 | expect(map.hash().toHex()).toBe(ledger.account_hash); 82 | // console.log('took seconds: ', (now() - t)); 83 | }); 84 | } 85 | factory("ledger-full-38129.json"); 86 | factory("ledger-full-40000.json"); 87 | // factory('ledger-4320277.json'); 88 | // factory('14280680.json'); 89 | }); 90 | -------------------------------------------------------------------------------- /test/signing-data-encoding.test.js: -------------------------------------------------------------------------------- 1 | const { 2 | encodeForSigning, 3 | encodeForSigningClaim, 4 | encodeForMultisigning, 5 | } = require("../dist"); 6 | 7 | const tx_json = { 8 | Account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", 9 | Amount: "1000", 10 | Destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 11 | Fee: "10", 12 | Flags: 2147483648, 13 | Sequence: 1, 14 | TransactionType: "Payment", 15 | TxnSignature: 16 | "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1" + 17 | "E721B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80" + 18 | "ECA3CD7B9B", 19 | Signature: 20 | "30440220718D264EF05CAED7C781FF6DE298DCAC68D002562C9BF3A07C1E72" + 21 | "1B420C0DAB02203A5A4779EF4D2CCC7BC3EF886676D803A9981B928D3B8ACA483B80ECA" + 22 | "3CD7B9B", 23 | SigningPubKey: 24 | "ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A", 25 | }; 26 | 27 | describe("Signing data", function () { 28 | test("can create single signing blobs", function () { 29 | const actual = encodeForSigning(tx_json); 30 | expect(actual).toBe( 31 | [ 32 | "53545800", // signingPrefix 33 | // TransactionType 34 | "12", 35 | "0000", 36 | // Flags 37 | "22", 38 | "80000000", 39 | // Sequence 40 | "24", 41 | "00000001", 42 | // Amount 43 | "61", 44 | // native amount 45 | "40000000000003E8", 46 | // Fee 47 | "68", 48 | // native amount 49 | "400000000000000A", 50 | // SigningPubKey 51 | "73", 52 | // VLLength 53 | "21", 54 | "ED5F5AC8B98974A3CA843326D9B88CEBD0560177B973EE0B149F782CFAA06DC66A", 55 | // Account 56 | "81", 57 | // VLLength 58 | "14", 59 | "5B812C9D57731E27A2DA8B1830195F88EF32A3B6", 60 | // Destination 61 | "83", 62 | // VLLength 63 | "14", 64 | "B5F762798A53D543A014CAF8B297CFF8F2F937E8", 65 | ].join("") 66 | ); 67 | }); 68 | test("can create multi signing blobs", function () { 69 | const signingAccount = "rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN"; 70 | const signingJson = Object.assign({}, tx_json, { SigningPubKey: "" }); 71 | const actual = encodeForMultisigning(signingJson, signingAccount); 72 | expect(actual).toBe( 73 | [ 74 | "534D5400", // signingPrefix 75 | // TransactionType 76 | "12", 77 | "0000", 78 | // Flags 79 | "22", 80 | "80000000", 81 | // Sequence 82 | "24", 83 | "00000001", 84 | // Amount 85 | "61", 86 | // native amount 87 | "40000000000003E8", 88 | // Fee 89 | "68", 90 | // native amount 91 | "400000000000000A", 92 | // SigningPubKey 93 | "73", 94 | // VLLength 95 | "00", 96 | // '', 97 | // Account 98 | "81", 99 | // VLLength 100 | "14", 101 | "5B812C9D57731E27A2DA8B1830195F88EF32A3B6", 102 | // Destination 103 | "83", 104 | // VLLength 105 | "14", 106 | "B5F762798A53D543A014CAF8B297CFF8F2F937E8", 107 | // signingAccount suffix 108 | "C0A5ABEF242802EFED4B041E8F2D4A8CC86AE3D1", 109 | ].join("") 110 | ); 111 | }); 112 | test("can create claim blob", function () { 113 | const channel = 114 | "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1"; 115 | const amount = "1000"; 116 | const json = { channel, amount }; 117 | const actual = encodeForSigningClaim(json); 118 | expect(actual).toBe( 119 | [ 120 | // hash prefix 121 | "434C4D00", 122 | // channel ID 123 | "43904CBFCDCEC530B4037871F86EE90BF799DF8D2E0EA564BC8A3F332E4F5FB1", 124 | // amount as a uint64 125 | "00000000000003E8", 126 | ].join("") 127 | ); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/tx-encode-decode.test.js: -------------------------------------------------------------------------------- 1 | const { encode, decode } = require("../dist"); 2 | 3 | // Notice: no Amount or Fee 4 | const tx_json = { 5 | Account: "r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ", 6 | // Amount: '1000', 7 | Destination: "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", 8 | // Fee: '10', 9 | 10 | // JavaScript converts operands to 32-bit signed ints after doing bitwise 11 | // operations. We need to convert it back to an unsigned int with >>> 0. 12 | Flags: (1 << 31) >>> 0, // tfFullyCanonicalSig 13 | 14 | Sequence: 1, 15 | TransactionType: "Payment", 16 | // TxnSignature, 17 | // Signature, 18 | // SigningPubKey 19 | }; 20 | 21 | describe("encoding and decoding tx_json", function () { 22 | test("can encode tx_json without Amount or Fee", function () { 23 | const encoded = encode(tx_json); 24 | const decoded = decode(encoded); 25 | expect(tx_json).toEqual(decoded); 26 | }); 27 | test("can encode tx_json with Amount and Fee", function () { 28 | const my_tx = Object.assign({}, tx_json, { 29 | Amount: "1000", 30 | Fee: "10", 31 | }); 32 | const encoded = encode(my_tx); 33 | const decoded = decode(encoded); 34 | expect(my_tx).toEqual(decoded); 35 | }); 36 | test("can encode tx_json with TicketCount", function () { 37 | const my_tx = Object.assign({}, tx_json, { 38 | TicketCount: 2, 39 | }); 40 | const encoded = encode(my_tx); 41 | const decoded = decode(encoded); 42 | expect(my_tx).toEqual(decoded); 43 | }); 44 | test("can encode tx_json with TicketSequence", function () { 45 | const my_tx = Object.assign({}, tx_json, { 46 | Sequence: 0, 47 | TicketSequence: 2, 48 | }); 49 | const encoded = encode(my_tx); 50 | const decoded = decode(encoded); 51 | expect(my_tx).toEqual(decoded); 52 | }); 53 | test("throws when Amount is invalid", function () { 54 | const my_tx = Object.assign({}, tx_json, { 55 | Amount: "1000.001", 56 | Fee: "10", 57 | }); 58 | expect(() => { 59 | encode(my_tx); 60 | }).toThrow(); 61 | }); 62 | test("throws when Fee is invalid", function () { 63 | const my_tx = Object.assign({}, tx_json, { 64 | Amount: "1000", 65 | Fee: "10.123", 66 | }); 67 | expect(() => { 68 | encode(my_tx); 69 | }).toThrow(); 70 | }); 71 | test("throws when Amount and Fee are invalid", function () { 72 | const my_tx = Object.assign({}, tx_json, { 73 | Amount: "1000.789", 74 | Fee: "10.123", 75 | }); 76 | expect(() => { 77 | encode(my_tx); 78 | }).toThrow(); 79 | }); 80 | test("throws when Amount is a number instead of a string-encoded integer", function () { 81 | const my_tx = Object.assign({}, tx_json, { 82 | Amount: 1000.789, 83 | }); 84 | expect(() => { 85 | encode(my_tx); 86 | }).toThrow(); 87 | }); 88 | 89 | test("throws when Fee is a number instead of a string-encoded integer", function () { 90 | const my_tx = Object.assign({}, tx_json, { 91 | Amount: 1234.56, 92 | }); 93 | expect(() => { 94 | encode(my_tx); 95 | }).toThrow(); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/types.test.js: -------------------------------------------------------------------------------- 1 | const { coreTypes } = require("../dist/types"); 2 | const { SerializedType } = require("../dist/types/serialized-type"); 3 | 4 | describe("SerializedType interfaces", () => { 5 | Object.entries(coreTypes).forEach(([name, Value]) => { 6 | test(`${name} has a \`from\` static constructor`, () => { 7 | expect(Value.from && Value.from !== Array.from).toBe(true); 8 | }); 9 | test(`${name} has a default constructor`, () => { 10 | expect(new Value()).not.toBe(undefined); 11 | }); 12 | test(`${name}.from will return the same object`, () => { 13 | const instance = new Value(); 14 | expect(Value.from(instance) === instance).toBe(true); 15 | }); 16 | test(`${name} instances have toBytesSink`, () => { 17 | expect(new Value().toBytesSink).not.toBe(undefined); 18 | }); 19 | test(`${name} instances have toJSON`, () => { 20 | expect(new Value().toJSON).not.toBe(undefined); 21 | }); 22 | test(`${name}.from(json).toJSON() == json`, () => { 23 | const newJSON = new Value().toJSON(); 24 | expect(Value.from(newJSON).toJSON()).toEqual(newJSON); 25 | }); 26 | describe(`${name} supports all methods of the SerializedType mixin`, () => { 27 | Object.keys(SerializedType.prototype).forEach((k) => { 28 | test(`new ${name}.prototype.${k} !== undefined`, () => { 29 | expect(Value.prototype[k]).not.toBe(undefined); 30 | }); 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/uint.test.js: -------------------------------------------------------------------------------- 1 | const { coreTypes } = require("../dist/types"); 2 | const { UInt8, UInt64 } = coreTypes; 3 | 4 | const { encode } = require("../dist"); 5 | 6 | const binary = 7 | "11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F"; 8 | const json = { 9 | Balance: { 10 | currency: "USD", 11 | issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji", 12 | value: "0", 13 | }, 14 | Flags: 196608, 15 | HighLimit: { 16 | currency: "USD", 17 | issuer: "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK", 18 | value: "1000", 19 | }, 20 | HighNode: "0", 21 | LedgerEntryType: "RippleState", 22 | LowLimit: { 23 | currency: "USD", 24 | issuer: "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn", 25 | value: "1000", 26 | }, 27 | LowNode: "0", 28 | }; 29 | 30 | const binaryEntry0 = 31 | "11007222001100002501EC24873700000000000000003800000000000000A35506FC7DE374089D50F81AAE13E7BBF3D0E694769331E14F55351B38D0148018EA62D44BF89AC2A40B800000000000000000000000004A50590000000000000000000000000000000000000000000000000166D6C38D7EA4C680000000000000000000000000004A5059000000000047C1258B4B79774B28176324068F759EDE226F686780000000000000000000000000000000000000004A505900000000005BBC0F22F61D9224A110650CFE21CC0C4BE13098"; 32 | const jsonEntry0 = { 33 | Balance: { 34 | currency: "JPY", 35 | issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji", 36 | value: "0.3369568318", 37 | }, 38 | Flags: 1114112, 39 | HighLimit: { 40 | currency: "JPY", 41 | issuer: "r94s8px6kSw1uZ1MV98dhSRTvc6VMPoPcN", 42 | value: "0", 43 | }, 44 | HighNode: "a3", 45 | LedgerEntryType: "RippleState", 46 | LowLimit: { 47 | currency: "JPY", 48 | issuer: "rfYQMgj3g3Qp8VLoZNvvU35mEuuJC8nCmY", 49 | value: "1000000000", 50 | }, 51 | LowNode: "0", 52 | PreviousTxnID: 53 | "06FC7DE374089D50F81AAE13E7BBF3D0E694769331E14F55351B38D0148018EA", 54 | PreviousTxnLgrSeq: 32253063, 55 | index: "000319BAE0A618A7D3BB492F17E98E5D92EA0C6458AFEBED44206B5B4798A840", 56 | }; 57 | 58 | const binaryEntry1 = 59 | "1100642200000000320000000000000002580CB3C1AD2C371136AEA434246D971C5FCCD32CBF520667E131AB7B10D706E7528214BA53D10260FFCC968ACD16BA30F7CEABAD6E5D92011340A3454ACED87177146EABD5E4A256021D836D1E3617618B1EB362D10B0D1BAC6AE1ED9E8D280BBE0B6656748FD647231851C6C650794D5E6852DFA1E35E68630F"; 60 | const jsonEntry1 = { 61 | Flags: 0, 62 | IndexPrevious: "2", 63 | Indexes: [ 64 | "A3454ACED87177146EABD5E4A256021D836D1E3617618B1EB362D10B0D1BAC6A", 65 | "E1ED9E8D280BBE0B6656748FD647231851C6C650794D5E6852DFA1E35E68630F", 66 | ], 67 | LedgerEntryType: "DirectoryNode", 68 | Owner: "rHzDaMNybxQppiE3uWyt2N265KvAKdiRdP", 69 | RootIndex: "0CB3C1AD2C371136AEA434246D971C5FCCD32CBF520667E131AB7B10D706E752", 70 | index: "0B4A2E68C111F7E42FAEEE405F7344560C8240840B151D9D04131EB79D080167", 71 | }; 72 | 73 | const binaryEntry2 = 74 | "1100722200210000250178D1CA37000000000000000038000000000000028355C0C37CE200B509E0A529880634F7841A9EF4CB65F03C12E6004CFAD9718D66946280000000000000000000000000000000000000004743420000000000000000000000000000000000000000000000000166D6071AFD498D000000000000000000000000000047434200000000002599D1D255BCA61189CA64C84528F2FCBE4BFC3867800000000000000000000000000000000000000047434200000000006EEBB1D1852CE667876A0B3630861FB6C6AB358E"; 75 | const jsonEntry2 = { 76 | Balance: { 77 | currency: "GCB", 78 | issuer: "rrrrrrrrrrrrrrrrrrrrBZbvji", 79 | value: "0", 80 | }, 81 | Flags: 2162688, 82 | HighLimit: { 83 | currency: "GCB", 84 | issuer: "rBfVgTnsdh8ckC19RM8aVGNuMZnpwrMP6n", 85 | value: "0", 86 | }, 87 | HighNode: "283", 88 | LedgerEntryType: "RippleState", 89 | LowLimit: { 90 | currency: "GCB", 91 | issuer: "rhRFGCy2RJTA8oxkjjtYTvofPVGqcgvXWj", 92 | value: "2000000", 93 | }, 94 | LowNode: "0", 95 | PreviousTxnID: 96 | "C0C37CE200B509E0A529880634F7841A9EF4CB65F03C12E6004CFAD9718D6694", 97 | PreviousTxnLgrSeq: 24695242, 98 | index: "0000041EFD027808D3F78C8352F97E324CB816318E00B977C74ECDDC7CD975B2", 99 | }; 100 | 101 | test("compareToTests[0]", () => { 102 | expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0); 103 | }); 104 | 105 | test("compareToTest[1]", () => { 106 | expect(UInt64.from(124).compareTo(UInt8.from(124))).toBe(0); 107 | }); 108 | 109 | test("compareToTest[2]", () => { 110 | expect(UInt64.from(124).compareTo(UInt8.from(123))).toBe(1); 111 | }); 112 | 113 | test("compareToTest[3]", () => { 114 | expect(UInt8.from(124).compareTo(UInt8.from(13))).toBe(1); 115 | }); 116 | 117 | test("compareToTest[4]", () => { 118 | expect(UInt8.from(124).compareTo(124)).toBe(0); 119 | }); 120 | 121 | test("compareToTest[5]", () => { 122 | expect(UInt64.from(124).compareTo(124)).toBe(0); 123 | }); 124 | 125 | test("compareToTest[6]", () => { 126 | expect(UInt64.from(124).compareTo(123)).toBe(1); 127 | }); 128 | 129 | test("compareToTest[7]", () => { 130 | expect(UInt8.from(124).compareTo(13)).toBe(1); 131 | }); 132 | 133 | test("UInt64 from string zero", () => { 134 | expect(UInt64.from("0")).toEqual(UInt64.from(0)); 135 | expect(encode(json)).toEqual(binary); 136 | }); 137 | 138 | test("UInt64 from non 16 length hex", () => { 139 | expect(encode(jsonEntry0)).toEqual(binaryEntry0); 140 | expect(encode(jsonEntry1)).toEqual(binaryEntry1); 141 | expect(encode(jsonEntry2)).toEqual(binaryEntry2); 142 | }); 143 | 144 | test("valueOfTests", () => { 145 | let val = UInt8.from(1); 146 | val |= 0x2; 147 | expect(val).toBe(3); 148 | }); 149 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { Buffer } = require('buffer/') 3 | 4 | function hexOnly(hex) { 5 | return hex.replace(/[^a-fA-F0-9]/g, ""); 6 | } 7 | 8 | function unused() {} 9 | 10 | function parseHexOnly(hex) { 11 | return Buffer.from(hexOnly(hex), "hex"); 12 | } 13 | 14 | function loadFixture(relativePath) { 15 | const fn = __dirname + "/fixtures/" + relativePath; 16 | return require(fn); 17 | } 18 | 19 | function loadFixtureText(relativePath) { 20 | const fn = __dirname + "/fixtures/" + relativePath; 21 | return fs.readFileSync(fn).toString("utf8"); 22 | } 23 | 24 | module.exports = { 25 | hexOnly, 26 | parseHexOnly, 27 | loadFixture, 28 | loadFixtureText, 29 | unused, 30 | }; 31 | -------------------------------------------------------------------------------- /test/x-address.test.js: -------------------------------------------------------------------------------- 1 | const { encode, decode } = require("./../dist/index"); 2 | const fixtures = require("./fixtures/x-codec-fixtures.json"); 3 | 4 | let json_x1 = { 5 | OwnerCount: 0, 6 | Account: "XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT", 7 | PreviousTxnLgrSeq: 7, 8 | LedgerEntryType: "AccountRoot", 9 | PreviousTxnID: 10 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 11 | Flags: 0, 12 | Sequence: 1, 13 | Balance: "10000000000", 14 | }; 15 | 16 | let json_r1 = { 17 | OwnerCount: 0, 18 | Account: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 19 | PreviousTxnLgrSeq: 7, 20 | LedgerEntryType: "AccountRoot", 21 | PreviousTxnID: 22 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 23 | Flags: 0, 24 | Sequence: 1, 25 | Balance: "10000000000", 26 | SourceTag: 12345, 27 | }; 28 | 29 | let json_null_x = { 30 | OwnerCount: 0, 31 | Account: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 32 | Destination: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 33 | Issuer: "XVXdn5wEVm5G4UhEHWDPqjvdeH361P4GETfNyyXGaoqBj71", 34 | PreviousTxnLgrSeq: 7, 35 | LedgerEntryType: "AccountRoot", 36 | PreviousTxnID: 37 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 38 | Flags: 0, 39 | Sequence: 1, 40 | Balance: "10000000000", 41 | }; 42 | 43 | let json_invalid_x = { 44 | OwnerCount: 0, 45 | Account: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 46 | Destination: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 47 | Issuer: "XVXdn5wEVm5g4UhEHWDPqjvdeH361P4GETfNyyXGaoqBj71", 48 | PreviousTxnLgrSeq: 7, 49 | LedgerEntryType: "AccountRoot", 50 | PreviousTxnID: 51 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 52 | Flags: 0, 53 | Sequence: 1, 54 | Balance: "10000000000", 55 | }; 56 | 57 | let json_null_r = { 58 | OwnerCount: 0, 59 | Account: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 60 | Destination: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 61 | Issuer: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 62 | PreviousTxnLgrSeq: 7, 63 | LedgerEntryType: "AccountRoot", 64 | PreviousTxnID: 65 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 66 | Flags: 0, 67 | Sequence: 1, 68 | Balance: "10000000000", 69 | }; 70 | 71 | let invalid_json_issuer_tagged = { 72 | OwnerCount: 0, 73 | Account: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 74 | Destination: "rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv", 75 | Issuer: "XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT", 76 | PreviousTxnLgrSeq: 7, 77 | LedgerEntryType: "AccountRoot", 78 | PreviousTxnID: 79 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 80 | Flags: 0, 81 | Sequence: 1, 82 | Balance: "10000000000", 83 | }; 84 | 85 | let invalid_json_x_and_tagged = { 86 | OwnerCount: 0, 87 | Account: "XVXdn5wEVm5G4UhEHWDPqjvdeH361P7BsapL4m2D2XnPSwT", 88 | PreviousTxnLgrSeq: 7, 89 | LedgerEntryType: "AccountRoot", 90 | PreviousTxnID: 91 | "DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF", 92 | Flags: 0, 93 | Sequence: 1, 94 | Balance: "10000000000", 95 | SourceTag: 12345, 96 | }; 97 | 98 | let json_issued_x = { 99 | TakerPays: { 100 | currency: "USD", 101 | issuer: "X7WZKEeNVS2p9Tire9DtNFkzWBZbFtJHWxDjN9fCrBGqVA4", 102 | value: "7072.8", 103 | }, 104 | }; 105 | 106 | let json_issued_r = { 107 | TakerPays: { 108 | currency: "USD", 109 | issuer: "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 110 | value: "7072.8", 111 | }, 112 | }; 113 | 114 | let json_issued_with_tag = { 115 | TakerPays: { 116 | currency: "USD", 117 | issuer: "X7WZKEeNVS2p9Tire9DtNFkzWBZbFtSiS2eDBib7svZXuc2", 118 | value: "7072.8", 119 | }, 120 | }; 121 | 122 | describe("X-Address Account is equivalent to a classic address w/ SourceTag", () => { 123 | let encoded_x = encode(json_x1); 124 | let encoded_r = encode(json_r1); 125 | test("Can encode with x-Address", () => { 126 | expect(encoded_x).toEqual(encoded_r); 127 | }); 128 | 129 | test("decoded X-address is object w/ source and tag", () => { 130 | let decoded_x = decode(encoded_x); 131 | expect(decoded_x).toEqual(json_r1); 132 | }); 133 | 134 | test("Encoding issuer X-Address w/ undefined destination tag", () => { 135 | expect(encode(json_null_x)).toEqual(encode(json_null_r)); 136 | }); 137 | 138 | test("Throws when X-Address is invalid", () => { 139 | expect(() => encode(json_invalid_x)).toThrow("checksum_invalid"); 140 | }); 141 | 142 | test("Encodes issued currency w/ x-address", () => { 143 | expect(encode(json_issued_x)).toEqual(encode(json_issued_r)); 144 | }); 145 | }); 146 | 147 | describe("Invalid X-Address behavior", () => { 148 | test("X-Address with tag throws value for invalid field", () => { 149 | expect(() => encode(invalid_json_issuer_tagged)).toThrow( 150 | new Error("Issuer cannot have an associated tag") 151 | ); 152 | }); 153 | 154 | test("Throws when Account has both X-Addr and Destination Tag", () => { 155 | expect(() => encode(invalid_json_x_and_tagged)).toThrow( 156 | new Error("Cannot have Account X-Address and SourceTag") 157 | ); 158 | }); 159 | 160 | test("Throws when issued currency has tag", () => { 161 | expect(() => encode(json_issued_with_tag)).toThrow( 162 | "Only allowed to have tag on Account or Destination" 163 | ); 164 | }); 165 | }); 166 | 167 | describe("ripple-binary-codec x-address test", function () { 168 | function makeSuite(name, entries) { 169 | describe(name, function () { 170 | entries.forEach((t, testN) => { 171 | test(`${name}[${testN}] encodes X-address json equivalent to classic address json`, () => { 172 | expect(encode(t.rjson)).toEqual(encode(t.xjson)); 173 | }); 174 | test(`${name}[${testN}] decodes X-address json equivalent to classic address json`, () => { 175 | expect(decode(encode(t.xjson))).toEqual(t.rjson); 176 | }); 177 | }); 178 | }); 179 | } 180 | makeSuite("transactions", fixtures.transactions); 181 | }); 182 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | // We need this file to run ESLint on our tests 2 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#configuration 3 | { 4 | // extend your base config so you don't have to redefine your compilerOptions 5 | "extends": "./tsconfig.json", 6 | "include": ["src/**/*.ts", "test/**/*.js"] 7 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "es2017" 6 | ], 7 | "outDir": "dist", 8 | "rootDir": "src", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "resolveJsonModule": true, 14 | "noImplicitThis": false, 15 | "noImplicitAny": false, 16 | "removeComments": false, 17 | "preserveConstEnums": false, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "sourceMap": true, 20 | "skipLibCheck": true, 21 | "declaration": true, 22 | "strict": true 23 | }, 24 | "include": [ 25 | "src/**/*.ts" 26 | ] 27 | } --------------------------------------------------------------------------------