diff --git a/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index 291f87b730..673c1e3505 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -178,3 +178,13 @@ PriceOracle fixEmptyDID fixXChainRewardRounding fixPreviousTxnID +fixAMMv1_1 +# 2.3.0 Amendments +fixAMMv1_2 +Credentials +NFTokenMintOffer +MPTokensV1 +fixNFTokenPageLinks +fixInnerObjTemplate2 +fixEnforceNFTokenTrustline +fixReducedOffersV2 diff --git a/.coderabbit.yaml b/.coderabbit.yaml index f4532da994..c7eaae931f 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -13,6 +13,8 @@ reviews: review_status: true # Generate walkthrough in a markdown collapsible section. collapse_walkthrough: false + # Generate sequence diagrams in the walkthrough. + sequence_diagrams: false # Abort the in-progress review if the pull request is closed or merged. abort_on_close: true auto_review: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9bf9fc6b16..3670396249 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -33,11 +33,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -48,7 +48,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -62,4 +62,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 176acc8cb8..012537fa3f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -4,7 +4,7 @@ name: Node.js CI env: - RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.2.0-b3 + RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.3.0-rc1 on: push: @@ -22,19 +22,19 @@ jobs: node-version: [18.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - name: Setup npm version 9 + - name: Setup npm version 10 run: | - npm i -g npm@9 --registry=https://registry.npmjs.org + npm i -g npm@10 --registry=https://registry.npmjs.org - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -45,7 +45,6 @@ jobs: key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.node-version }}- - ${{ runner.os }}-deps- - name: Install Dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' @@ -60,22 +59,22 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - name: Setup npm version 9 + - name: Setup npm version 10 run: | - npm i -g npm@9 --registry=https://registry.npmjs.org + npm i -g npm@10 --registry=https://registry.npmjs.org - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -86,7 +85,6 @@ jobs: key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.node-version }}- - ${{ runner.os }}-deps- - name: Install Dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' @@ -101,27 +99,27 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run docker in background run: | - docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/ripple/etc/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true ${{ env.RIPPLED_DOCKER_IMAGE }} /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg + docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a" - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - name: Setup npm version 9 + - name: Setup npm version 10 run: | - npm i -g npm@9 --registry=https://registry.npmjs.org + npm i -g npm@10 --registry=https://registry.npmjs.org - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -132,7 +130,6 @@ jobs: key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.node-version }}- - ${{ runner.os }}-deps- - name: Install Dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' @@ -156,24 +153,24 @@ jobs: node-version: [18.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Run docker in background run: | - docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/ripple/etc/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true ${{ env.RIPPLED_DOCKER_IMAGE }} /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg + docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a" - - name: Setup npm version 9 + - name: Setup npm version 10 run: | - npm i -g npm@9 --registry=https://registry.npmjs.org + npm i -g npm@10 --registry=https://registry.npmjs.org - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -184,7 +181,6 @@ jobs: key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.node-version }}- - ${{ runner.os }}-deps- - name: Install Dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' @@ -205,22 +201,22 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x] + node-version: [18.x, 20.x, 22.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - - name: Setup npm version 9 + - name: Setup npm version 10 run: | - npm i -g npm@9 --registry=https://registry.npmjs.org + npm i -g npm@10 --registry=https://registry.npmjs.org - name: Cache node modules id: cache-nodemodules - uses: actions/cache@v3 + uses: actions/cache@v4 env: cache-name: cache-node-modules with: @@ -231,7 +227,6 @@ jobs: key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-deps-${{ matrix.node-version }}- - ${{ runner.os }}-deps- - name: Install Dependencies if: steps.cache-nodemodules.outputs.cache-hit != 'true' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 38b08ab0fc..2b3f978dce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,18 +64,20 @@ From the top-level xrpl.js folder (one level above `packages`), run the followin ```bash npm install # sets up the rippled standalone Docker container - you can skip this step if you already have it set up -docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.0.0-b4 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg +docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a' npm run build npm run test:integration ``` Breaking down the command: * `docker run -p 6006:6006` starts a Docker container with an open port for admin WebSocket requests. -* `--interactive` allows you to interact with the container. -* `-t` starts a terminal in the container for you to send commands to. -* `--volume $PWD/.ci-config:/config/` identifies the `rippled.cfg` and `validators.txt` to import. It must be an absolute path, so we use `$PWD` instead of `./`. + `--rm` tells docker to close the container after processes are done running. +* `-it` allows you to interact with the container. + `--name rippled_standalone` is an instance name for clarity +* `--volume $PWD/.ci-config:/etc/opt/ripple/` identifies the `rippled.cfg` and `validators.txt` to import. It must be an absolute path, so we use `$PWD` instead of `./`. * `rippleci/rippled` is an image that is regularly updated with the latest `rippled` releases -* `/opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg` starts `rippled` in standalone mode +* `--entrypoint bash rippleci/rippled:2.3.0-rc1` manually overrides the entrypoint (for versions of rippled >= 2.3.0) +* `-c 'rippled -a'` provides the bash command to start `rippled` in standalone mode from the manual entrypoint ### Browser Tests @@ -90,7 +92,7 @@ This should be run from the `xrpl.js` top level folder (one above the `packages` ```bash npm run build # sets up the rippled standalone Docker container - you can skip this step if you already have it set up -docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg +docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a' npm run test:browser ``` @@ -130,13 +132,13 @@ For every file in `src`, we try to have a corresponding file in `test` with unit The goal is to maintain above 80% code coverage, and generally any new feature or bug fix should be accompanied by unit tests, and integration tests if applicable. -For an example of a unit test, check out the [autofill tests here](./packages/xrpl/test/client/autofill.ts). +For an example of a unit test, check out the [autofill tests here](./packages/xrpl/test/client/autofill.test.ts). If your code connects to the ledger (ex. Adding a new transaction type) it's handy to write integration tests to ensure that you can successfully interact with the ledger. Integration tests are generally run against a docker instance of rippled which contains the latest updates. Since standalone mode allows us to manually close ledgers, this allows us to run integration tests at a much faster rate than if we had to wait 4-5 seconds per transaction for the ledger to validate the transaction. [See above](#running-tests) for how to start up the docker container to run integration tests. All integration tests should be written in the `test/integration` folder, with new `Requests` and `Transactions` tests being in their respective folders. -For an example of how to write an integration test for `xrpl.js`, you can look at the [Payment integration test](./packages/xrpl/test/integration/transactions/payment.ts). +For an example of how to write an integration test for `xrpl.js`, you can look at the [Payment integration test](./packages/xrpl/test/integration/transactions/payment.test.ts). ## Generate reference docs diff --git a/README.md b/README.md index b9c031482b..aefb3169a7 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ All of which works in Node.js (tested for v18+) & web browsers (tested for Chrom ### Requirements -+ **[Node.js v18](https://nodejs.org/)** is recommended. We also support v20. Other versions may work but are not frequently tested. ++ **[Node.js v18](https://nodejs.org/)** is recommended. We also support v20 and v22. Other versions may work but are not frequently tested. ### Installing xrpl.js diff --git a/package-lock.json b/package-lock.json index 9a32dfacff..a3f4f09e15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,11 +53,11 @@ "typescript": "^5.1.6", "webpack": "^5.81.0", "webpack-bundle-analyzer": "^4.1.0", - "webpack-cli": "^5.0.1" + "webpack-cli": "^6.0.1" }, "engines": { "node": ">=18.0.0", - "npm": ">=7.10.0 < 10.0.0" + "npm": ">=7.10.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -799,6 +799,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@gerrit0/mini-shiki": { + "version": "1.24.4", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.24.4.tgz", + "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", + "dev": true, + "dependencies": { + "@shikijs/engine-oniguruma": "^1.24.2", + "@shikijs/types": "^1.24.2", + "@shikijs/vscode-textmate": "^9.3.1" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -2280,11 +2291,12 @@ } }, "node_modules/@noble/curves": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", - "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.7.0.tgz", + "integrity": "sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==", + "license": "MIT", "dependencies": { - "@noble/hashes": "1.5.0" + "@noble/hashes": "1.6.0" }, "engines": { "node": "^14.21.3 || >=16" @@ -2293,10 +2305,22 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.0.tgz", + "integrity": "sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", - "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", "engines": { "node": "^14.21.3 || >=16" }, @@ -2606,98 +2630,63 @@ "dev": true }, "node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.1.tgz", + "integrity": "sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ==", + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip32": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.5.0.tgz", - "integrity": "sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.0.tgz", + "integrity": "sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA==", "dependencies": { - "@noble/curves": "~1.6.0", - "@noble/hashes": "~1.5.0", - "@scure/base": "~1.1.7" + "@noble/curves": "~1.7.0", + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@scure/bip39": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.4.0.tgz", - "integrity": "sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.0.tgz", + "integrity": "sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A==", "dependencies": { - "@noble/hashes": "~1.5.0", - "@scure/base": "~1.1.8" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "engines": { - "node": ">= 16" + "@noble/hashes": "~1.6.0", + "@scure/base": "~1.2.1" }, "funding": { "url": "https://paulmillr.com/funding/" } }, - "node_modules/@shikijs/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.19.0.tgz", - "integrity": "sha512-314J5MPdS1wzfjuD856MXvbAI2wN03ofMnUGkZ5ZDBOza/d38paLwd+YVyuKrrjxJ4hfPMjc4tRmPkXd6UDMPQ==", - "dev": true, - "dependencies": { - "@shikijs/engine-javascript": "1.19.0", - "@shikijs/engine-oniguruma": "1.19.0", - "@shikijs/types": "1.19.0", - "@shikijs/vscode-textmate": "^9.2.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.3" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.19.0.tgz", - "integrity": "sha512-D1sioU61n7fLWfDzTC9JNS19zEYZMr7qxkSVzv6ziEWDxnwzy2PvYoKPedJV4qUf+2VnrYPSaArDz2W0XgGB7A==", - "dev": true, - "dependencies": { - "@shikijs/types": "1.19.0", - "@shikijs/vscode-textmate": "^9.2.2", - "oniguruma-to-js": "0.4.3" - } - }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.19.0.tgz", - "integrity": "sha512-/JxwIefNVLGB4EmpB8i6P4JB/oVYRuzSixbqvx7m6iPW0lQ1T97c/0wmA+JlKbngEiExckSuPwa48fajlShB7A==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.25.1.tgz", + "integrity": "sha512-iKPMh3H+0USHtWfZ1irfMTH6tGmIUFSnqt3E2K8BgI1VEsqiPh0RYkG2WTwzNiM1/WHN4FzYx/nrKR7PDHiRyw==", "dev": true, "dependencies": { - "@shikijs/types": "1.19.0", - "@shikijs/vscode-textmate": "^9.2.2" + "@shikijs/types": "1.25.1", + "@shikijs/vscode-textmate": "^9.3.1" } }, "node_modules/@shikijs/types": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.19.0.tgz", - "integrity": "sha512-NZvVp3k1bP4MTRUbmnkGhYzPdoNMjNLSAwczMRUbtUl4oj2LlNRNbwERyeIyJt56Ac9fvPVZ2nn13OXk86E5UQ==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.25.1.tgz", + "integrity": "sha512-dceqFUoO95eY4tpOj3OGq8wE8EgJ4ey6Me1HQEu5UbwIYszFndEll/bjlB8Kp9wl4fx3uM7n4+y9XCYuDBmcXA==", "dev": true, "dependencies": { - "@shikijs/vscode-textmate": "^9.2.2", + "@shikijs/vscode-textmate": "^9.3.1", "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.2.tgz", - "integrity": "sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", + "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -2809,6 +2798,26 @@ "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -2854,9 +2863,10 @@ } }, "node_modules/@types/jest": { - "version": "29.5.12", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, - "license": "MIT", "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -2873,20 +2883,11 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", - "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==", + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz", + "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==", "dev": true }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/minimatch": { "version": "3.0.5", "dev": true, @@ -3139,170 +3140,188 @@ "license": "ISC" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "dev": true, - "license": "MIT" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "dev": true, - "license": "MIT" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/info": { - "version": "2.0.2", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/serve": { - "version": "2.0.5", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { @@ -3349,13 +3368,15 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "dev": true, - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", - "dev": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true }, "node_modules/abbrev": { "version": "1.1.1", @@ -3375,9 +3396,10 @@ } }, "node_modules/acorn": { - "version": "8.10.0", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -3385,15 +3407,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "dev": true, @@ -4098,7 +4111,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.3", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -4114,12 +4129,11 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001580", - "electron-to-chromium": "^1.4.648", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -4277,7 +4291,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001581", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "dev": true, "funding": [ { @@ -4292,24 +4308,13 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0" + ] }, "node_modules/caseless": { "version": "0.12.0", "dev": true, "license": "Apache-2.0" }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chai": { "version": "4.4.1", "dev": true, @@ -4350,26 +4355,6 @@ "node": ">=10" } }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/chardet": { "version": "0.7.0", "dev": true, @@ -4604,16 +4589,6 @@ "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/commander": { "version": "2.20.3", "dev": true, @@ -5248,6 +5223,7 @@ "version": "2.0.3", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -5277,19 +5253,6 @@ "node": ">=8" } }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/dezalgo": { "version": "1.0.4", "dev": true, @@ -5402,9 +5365,10 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.648", - "dev": true, - "license": "ISC" + "version": "1.5.62", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.62.tgz", + "integrity": "sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg==", + "dev": true }, "node_modules/emittery": { "version": "0.13.1", @@ -5539,9 +5503,10 @@ } }, "node_modules/envinfo": { - "version": "7.10.0", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", "dev": true, - "license": "MIT", "bin": { "envinfo": "dist/cli.js" }, @@ -5727,9 +5692,10 @@ } }, "node_modules/escalade": { - "version": "3.1.1", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } @@ -6663,6 +6629,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/flat-cache": { "version": "3.1.0", "dev": true, @@ -7336,42 +7311,6 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", - "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hosted-git-info": { "version": "4.1.0", "dev": true, @@ -7404,16 +7343,6 @@ "dev": true, "license": "MIT" }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/http-cache-semantics": { "version": "4.1.1", "dev": true, @@ -9552,6 +9481,7 @@ "version": "1.4.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -9676,27 +9606,6 @@ "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", - "dev": true, - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -9884,95 +9793,6 @@ "node": ">= 8" } }, - "node_modules/micromark-util-character": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", - "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", - "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", - "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-symbol": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", - "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, - "node_modules/micromark-util-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", - "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ] - }, "node_modules/micromatch": { "version": "4.0.5", "dev": true, @@ -10425,9 +10245,10 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.14", - "dev": true, - "license": "MIT" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true }, "node_modules/noms": { "version": "0.0.0", @@ -10984,18 +10805,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/oniguruma-to-js": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz", - "integrity": "sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==", - "dev": true, - "dependencies": { - "regex": "^4.3.2" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/opener": { "version": "1.5.2", "dev": true, @@ -11397,9 +11206,10 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -11586,16 +11396,6 @@ "license": "MIT", "peer": true }, - "node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/proto-list": { "version": "1.2.4", "dev": true, @@ -11749,12 +11549,11 @@ } }, "node_modules/react": { - "version": "18.3.1", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } @@ -12098,12 +11897,6 @@ "license": "MIT", "peer": true }, - "node_modules/regex": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/regex/-/regex-4.3.2.tgz", - "integrity": "sha512-kK/AA3A9K6q2js89+VMymcboLOlF5lZRCYJv3gzszXFHBr6kO6qLGzbm+UIugBEV8SMMKCTR59txoY6ctRHYVw==", - "dev": true - }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", @@ -12418,12 +12211,10 @@ } }, "node_modules/semver": { - "version": "7.5.4", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -12431,22 +12222,6 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "dev": true, @@ -12535,20 +12310,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/shiki": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.19.0.tgz", - "integrity": "sha512-Ng7Gd6XgWFLsv4Z3so65hOyXjV78qz1M117MuZHwdPQD6fgb5wR2IoLMvSlM/Ml14EXH7n+/YxIpTD74i7kDdw==", - "dev": true, - "dependencies": { - "@shikijs/core": "1.19.0", - "@shikijs/engine-javascript": "1.19.0", - "@shikijs/engine-oniguruma": "1.19.0", - "@shikijs/types": "1.19.0", - "@shikijs/vscode-textmate": "^9.2.2", - "@types/hast": "^3.0.4" - } - }, "node_modules/side-channel": { "version": "1.0.4", "dev": true, @@ -12770,16 +12531,6 @@ "source-map": "^0.6.0" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "dev": true, @@ -13074,20 +12825,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "dev": true, @@ -13469,16 +13206,6 @@ "node": ">=8" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "dev": true, @@ -13488,20 +13215,20 @@ } }, "node_modules/ts-jest": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz", - "integrity": "sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==", + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", "dev": true, "dependencies": { - "bs-logger": "0.x", - "ejs": "^3.0.0", - "fast-json-stable-stringify": "2.x", + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", "jest-util": "^29.0.0", "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "^7.5.3", - "yargs-parser": "^21.0.1" + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" }, "bin": { "ts-jest": "cli.js" @@ -13806,16 +13533,16 @@ } }, "node_modules/typedoc": { - "version": "0.26.10", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.10.tgz", - "integrity": "sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==", + "version": "0.27.6", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.6.tgz", + "integrity": "sha512-oBFRoh2Px6jFx366db0lLlihcalq/JzyCVp7Vaq1yphL/tbgx2e+bkpkCgJPunaPvPwoTOXSwasfklWHm7GfAw==", "dev": true, "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "shiki": "^1.16.2", - "yaml": "^2.5.1" + "yaml": "^2.6.1" }, "bin": { "typedoc": "bin/typedoc" @@ -13824,7 +13551,7 @@ "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" } }, "node_modules/typedoc/node_modules/brace-expansion": { @@ -13852,9 +13579,9 @@ } }, "node_modules/typedoc/node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "dev": true, "bin": { "yaml": "bin.mjs" @@ -13960,74 +13687,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/universal-user-agent": { "version": "6.0.0", "dev": true, @@ -14067,7 +13726,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -14083,10 +13744,9 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -14188,34 +13848,6 @@ "extsprintf": "^1.2.0" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", - "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", - "dev": true, - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/void-elements": { "version": "2.0.1", "dev": true, @@ -14261,18 +13893,18 @@ } }, "node_modules/webpack": { - "version": "5.95.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", - "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -14360,41 +13992,39 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, - "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", - "commander": "^10.0.1", + "commander": "^12.1.0", "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", + "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.82.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -14403,24 +14033,36 @@ } } }, + "node_modules/webpack-cli/node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/webpack-merge": { - "version": "5.9.0", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, - "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" + "flat": "^5.0.2", + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -14543,8 +14185,9 @@ }, "node_modules/wildcard": { "version": "2.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true }, "node_modules/wordwrap": { "version": "1.0.0", @@ -14834,16 +14477,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "packages/isomorphic": { "name": "@xrplf/isomorphic", "version": "1.0.1", @@ -14873,7 +14506,7 @@ } }, "packages/ripple-binary-codec": { - "version": "2.1.0", + "version": "2.2.0", "license": "ISC", "dependencies": { "@xrplf/isomorphic": "^1.0.1", @@ -14906,7 +14539,7 @@ } }, "packages/xrpl": { - "version": "4.0.0", + "version": "4.1.0", "license": "ISC", "dependencies": { "@scure/bip32": "^1.3.1", @@ -14916,7 +14549,7 @@ "bignumber.js": "^9.0.0", "eventemitter3": "^5.0.1", "ripple-address-codec": "^5.0.0", - "ripple-binary-codec": "^2.1.0", + "ripple-binary-codec": "^2.2.0", "ripple-keypairs": "^2.0.0" }, "devDependencies": { @@ -14928,9 +14561,9 @@ "karma-jasmine": "^5.1.0", "karma-webpack": "^5.0.0", "lodash": "^4.17.4", - "react": "^18.2.0", + "react": "^19.0.0", "run-s": "^0.0.0", - "typedoc": "0.26.10", + "typedoc": "0.27.6", "ws": "^8.14.2" }, "engines": { diff --git a/package.json b/package.json index 73c0b5ff35..5e38cf551f 100644 --- a/package.json +++ b/package.json @@ -58,13 +58,13 @@ "typescript": "^5.1.6", "webpack": "^5.81.0", "webpack-bundle-analyzer": "^4.1.0", - "webpack-cli": "^5.0.1" + "webpack-cli": "^6.0.1" }, "workspaces": [ "./packages/*" ], "engines": { "node": ">=18.0.0", - "npm": ">=7.10.0 < 10.0.0" + "npm": ">=7.10.0" } } diff --git a/packages/ripple-binary-codec/HISTORY.md b/packages/ripple-binary-codec/HISTORY.md index 2cf4b39c8a..eab2768410 100644 --- a/packages/ripple-binary-codec/HISTORY.md +++ b/packages/ripple-binary-codec/HISTORY.md @@ -2,6 +2,11 @@ ## Unreleased +## 2.2.0 (2024-12-23) + +### Added +* Support for the Multi-Purpose Token amendment (XLS-33) + ## 2.1.0 (2024-06-03) ### Added diff --git a/packages/ripple-binary-codec/package.json b/packages/ripple-binary-codec/package.json index 41aca52356..a1a26762ea 100644 --- a/packages/ripple-binary-codec/package.json +++ b/packages/ripple-binary-codec/package.json @@ -1,6 +1,6 @@ { "name": "ripple-binary-codec", - "version": "2.1.0", + "version": "2.2.0", "description": "XRP Ledger binary codec", "files": [ "dist/*", diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 77682ac876..3b9a3ae577 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -1,2449 +1,2319 @@ { - "TYPES": { - "Done": -1, - "Unknown": -2, - "NotPresent": 0, - "UInt16": 1, - "UInt32": 2, - "UInt64": 3, - "Hash128": 4, - "Hash256": 5, - "Amount": 6, - "Blob": 7, - "AccountID": 8, - "STObject": 14, - "STArray": 15, - "UInt8": 16, - "Hash160": 17, - "PathSet": 18, - "Vector256": 19, - "UInt96": 20, - "UInt192": 21, - "UInt384": 22, - "UInt512": 23, - "Issue": 24, - "XChainBridge": 25, - "Currency": 26, - "Transaction": 10001, - "LedgerEntry": 10002, - "Validation": 10003, - "Metadata": 10004 - }, - "LEDGER_ENTRY_TYPES": { - "Invalid": -1, - "AccountRoot": 97, - "DirectoryNode": 100, - "RippleState": 114, - "Ticket": 84, - "SignerList": 83, - "Offer": 111, - "Bridge": 105, - "LedgerHashes": 104, - "Amendments": 102, - "XChainOwnedClaimID": 113, - "XChainOwnedCreateAccountClaimID": 116, - "FeeSettings": 115, - "Escrow": 117, - "PayChannel": 120, - "Check": 67, - "DepositPreauth": 112, - "NegativeUNL": 78, - "NFTokenPage": 80, - "NFTokenOffer": 55, - "AMM": 121, - "DID": 73, - "Oracle": 128, - "Any": -3, - "Child": -2, - "Nickname": 110, - "Contract": 99, - "GeneratorMap": 103 - }, "FIELDS": [ [ "Generic", { - "nth": 0, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": 0, "type": "Unknown" } ], [ "Invalid", { - "nth": -1, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": -1, "type": "Unknown" } ], [ "ObjectEndMarker", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "STObject" } ], [ "ArrayEndMarker", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "STArray" } ], [ - "hash", + "taker_gets_funded", { - "nth": 257, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, - "type": "Hash256" - } - ], - [ - "index", - { - "nth": 258, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Hash256" - } - ], - [ - "taker_gets_funded", - { "nth": 258, - "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, "type": "Amount" } ], [ "taker_pays_funded", { - "nth": 259, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": 259, "type": "Amount" } ], [ - "LedgerEntry", + "LedgerEntryType", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "LedgerEntry" + "nth": 1, + "type": "UInt16" } ], [ - "Transaction", + "TransactionType", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Transaction" + "nth": 2, + "type": "UInt16" } ], [ - "Validation", + "SignerWeight", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Validation" + "nth": 3, + "type": "UInt16" } ], [ - "Metadata", + "TransferFee", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Metadata" + "nth": 4, + "type": "UInt16" } ], [ - "CloseResolution", + "TradingFee", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 5, + "type": "UInt16" } ], [ - "Method", + "DiscountedFee", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 6, + "type": "UInt16" } ], [ - "TransactionResult", + "Version", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 16, + "type": "UInt16" } ], [ - "Scale", + "HookStateChangeCount", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 17, + "type": "UInt16" } ], [ - "TickSize", + "HookEmitCount", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 18, + "type": "UInt16" } ], [ - "UNLModifyDisabling", + "HookExecutionIndex", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 19, + "type": "UInt16" } ], [ - "HookResult", + "HookApiVersion", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 20, + "type": "UInt16" } ], [ - "WasLockingChainSend", + "LedgerFixType", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 21, + "type": "UInt16" } ], [ - "LedgerEntryType", + "NetworkID", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 1, + "type": "UInt32" } ], [ - "TransactionType", + "Flags", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 2, + "type": "UInt32" } ], [ - "SignerWeight", + "SourceTag", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 3, + "type": "UInt32" } ], [ - "TransferFee", + "Sequence", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 4, + "type": "UInt32" } ], [ - "TradingFee", + "PreviousTxnLgrSeq", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 5, + "type": "UInt32" } ], [ - "DiscountedFee", + "LedgerSequence", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 6, + "type": "UInt32" } ], [ - "Version", + "CloseTime", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 7, + "type": "UInt32" } ], [ - "HookStateChangeCount", + "ParentCloseTime", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" - } - ], - [ - "HookEmitCount", - { - "nth": 18, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt16" - } - ], - [ - "HookExecutionIndex", - { - "nth": 19, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt16" - } - ], - [ - "HookApiVersion", - { - "nth": 20, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt16" - } - ], - [ - "NetworkID", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "Flags", - { - "nth": 2, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "SourceTag", - { - "nth": 3, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "Sequence", - { - "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "PreviousTxnLgrSeq", - { - "nth": 5, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "LedgerSequence", - { - "nth": 6, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "CloseTime", - { - "nth": 7, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "ParentCloseTime", - { "nth": 8, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, "type": "UInt32" } ], [ "SigningTime", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "UInt32" } ], [ "Expiration", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "UInt32" } ], [ "TransferRate", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "UInt32" } ], [ "WalletSize", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "UInt32" } ], [ "OwnerCount", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "UInt32" } ], [ "DestinationTag", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "UInt32" } ], [ "LastUpdateTime", { - "nth": 15, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 15, "type": "UInt32" } ], [ "HighQualityIn", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "UInt32" } ], [ "HighQualityOut", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "UInt32" } ], [ "LowQualityIn", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "UInt32" } ], [ "LowQualityOut", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "UInt32" } ], [ "QualityIn", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "UInt32" } ], [ "QualityOut", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "UInt32" } ], [ "StampEscrow", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "UInt32" } ], [ "BondAmount", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "UInt32" } ], [ "LoadFee", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "UInt32" } ], [ "OfferSequence", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "UInt32" } ], [ "FirstLedgerSequence", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "UInt32" } ], [ "LastLedgerSequence", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "UInt32" } ], [ "TransactionIndex", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "UInt32" } ], [ "OperationLimit", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "UInt32" } ], [ "ReferenceFeeUnits", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "UInt32" } ], [ "ReserveBase", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "UInt32" } ], [ "ReserveIncrement", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "UInt32" } ], [ "SetFlag", { - "nth": 33, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 33, "type": "UInt32" } ], [ "ClearFlag", { - "nth": 34, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 34, "type": "UInt32" } ], [ "SignerQuorum", { - "nth": 35, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 35, "type": "UInt32" } ], [ "CancelAfter", { - "nth": 36, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 36, "type": "UInt32" } ], [ "FinishAfter", { - "nth": 37, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 37, "type": "UInt32" } ], [ "SignerListID", { - "nth": 38, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 38, "type": "UInt32" } ], [ "SettleDelay", { - "nth": 39, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 39, "type": "UInt32" } ], [ "TicketCount", { - "nth": 40, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 40, "type": "UInt32" } ], [ "TicketSequence", { - "nth": 41, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 41, "type": "UInt32" } ], [ "NFTokenTaxon", { - "nth": 42, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 42, "type": "UInt32" } ], [ "MintedNFTokens", { - "nth": 43, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 43, "type": "UInt32" } ], [ "BurnedNFTokens", { - "nth": 44, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 44, "type": "UInt32" } ], [ "HookStateCount", { - "nth": 45, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 45, "type": "UInt32" } ], [ "EmitGeneration", { - "nth": 46, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 46, "type": "UInt32" } ], [ "VoteWeight", { - "nth": 48, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 48, "type": "UInt32" } ], [ "FirstNFTokenSequence", { - "nth": 50, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 50, "type": "UInt32" } ], [ "OracleDocumentID", { - "nth": 51, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 51, "type": "UInt32" } ], [ "IndexNext", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "UInt64" } ], [ "IndexPrevious", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 2, "type": "UInt64" } ], [ "BookNode", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 3, "type": "UInt64" } ], [ "OwnerNode", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "UInt64" } ], [ "BaseFee", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "UInt64" } ], [ "ExchangeRate", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "UInt64" } ], [ "LowNode", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "UInt64" } ], [ "HighNode", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "UInt64" } ], [ "DestinationNode", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "UInt64" } ], [ "Cookie", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "UInt64" } ], [ "ServerVersion", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "UInt64" } ], [ "NFTokenOfferNode", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "UInt64" } ], [ "EmitBurden", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "UInt64" } ], [ "HookOn", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "UInt64" } ], [ "HookInstructionCount", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "UInt64" } ], [ "HookReturnCode", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "UInt64" } ], [ "ReferenceCount", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "UInt64" } ], [ "XChainClaimID", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "UInt64" } ], [ "XChainAccountCreateCount", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "UInt64" } ], [ "XChainAccountClaimCount", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "UInt64" } ], [ "AssetPrice", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "UInt64" } ], [ - "EmailHash", + "MaximumAmount", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash128" + "isVLEncoded": false, + "nth": 24, + "type": "UInt64" } ], [ - "TakerPaysCurrency", + "OutstandingAmount", { - "nth": 1, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, + "nth": 25, + "type": "UInt64" + } + ], + [ + "MPTAmount", + { "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "isVLEncoded": false, + "nth": 26, + "type": "UInt64" } ], [ - "TakerPaysIssuer", + "IssuerNode", { - "nth": 2, + "nth": 27, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "UInt64" } ], [ - "TakerGetsCurrency", + "SubjectNode", { - "nth": 3, + "nth": 28, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "type": "UInt64" } ], [ - "TakerGetsIssuer", + "EmailHash", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "isVLEncoded": false, + "nth": 1, + "type": "Hash128" } ], [ "LedgerHash", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "Hash256" } ], [ "ParentHash", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 2, "type": "Hash256" } ], [ "TransactionHash", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 3, "type": "Hash256" } ], [ "AccountHash", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "Hash256" } ], [ "PreviousTxnID", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "Hash256" } ], [ "LedgerIndex", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "Hash256" } ], [ "WalletLocator", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "Hash256" } ], [ "RootIndex", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "Hash256" } ], [ "AccountTxnID", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "Hash256" } ], [ "NFTokenID", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "Hash256" } ], [ "EmitParentTxnID", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "Hash256" } ], [ "EmitNonce", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "Hash256" } ], [ "EmitHookHash", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "Hash256" } ], [ "AMMID", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "Hash256" } ], [ "BookDirectory", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "Hash256" } ], [ "InvoiceID", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "Hash256" } ], [ "Nickname", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "Hash256" } ], [ "Amendment", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "Hash256" } ], [ "Digest", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "Hash256" } ], [ "Channel", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "Hash256" } ], [ "ConsensusHash", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "Hash256" } ], [ "CheckID", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "Hash256" } ], [ "ValidatedHash", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "Hash256" } ], [ "PreviousPageMin", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "Hash256" } ], [ "NextPageMin", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "Hash256" } ], [ "NFTokenBuyOffer", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "Hash256" } ], [ "NFTokenSellOffer", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "Hash256" } ], [ "HookStateKey", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "Hash256" } ], [ "HookHash", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "Hash256" } ], [ "HookNamespace", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "Hash256" } ], [ "HookSetTxnID", { - "nth": 33, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 33, "type": "Hash256" } ], [ - "Amount", + "hash", { - "nth": 1, + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Hash256" + } + ], + [ + "index", + { + "isSerialized": false, + "isSigningField": false, "isVLEncoded": false, + "nth": 258, + "type": "Hash256" + } + ], + [ + "Amount", + { "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "Amount" } ], [ "Balance", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 2, "type": "Amount" } ], [ "LimitAmount", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 3, "type": "Amount" } ], [ "TakerPays", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "Amount" } ], [ "TakerGets", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "Amount" } ], [ "LowLimit", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "Amount" } ], [ "HighLimit", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "Amount" } ], [ "Fee", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "Amount" } ], [ "SendMax", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "Amount" } ], [ "DeliverMin", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "Amount" } ], [ "Amount2", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "Amount" } ], [ "BidMin", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "Amount" } ], [ "BidMax", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "Amount" } ], [ "MinimumOffer", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "Amount" } ], [ "RippleEscrow", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "Amount" } ], [ "DeliveredAmount", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "Amount" } ], [ "NFTokenBrokerFee", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "Amount" } ], [ "BaseFeeDrops", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "Amount" } ], [ "ReserveBaseDrops", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "Amount" } ], [ "ReserveIncrementDrops", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "Amount" } ], [ "LPTokenOut", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "Amount" } ], [ "LPTokenIn", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "Amount" } ], [ "EPrice", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "Amount" } ], [ "Price", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "Amount" } ], [ "SignatureReward", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "Amount" } ], [ "MinAccountCreateAmount", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "Amount" } ], [ "LPTokenBalance", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "Amount" } ], [ "PublicKey", { - "nth": 1, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 1, "type": "Blob" } ], [ "MessageKey", { - "nth": 2, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 2, "type": "Blob" } ], [ "SigningPubKey", { - "nth": 3, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 3, "type": "Blob" } ], [ "TxnSignature", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 4, "type": "Blob" } ], [ "URI", { - "nth": 5, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 5, "type": "Blob" } ], [ "Signature", { - "nth": 6, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 6, "type": "Blob" } ], [ "Domain", { - "nth": 7, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 7, "type": "Blob" } ], [ "FundCode", { - "nth": 8, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 8, "type": "Blob" } ], [ "RemoveCode", { - "nth": 9, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 9, "type": "Blob" } ], [ "ExpireCode", { - "nth": 10, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 10, "type": "Blob" } ], [ "CreateCode", { - "nth": 11, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 11, "type": "Blob" } ], [ "MemoType", { - "nth": 12, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 12, "type": "Blob" } ], [ "MemoData", { - "nth": 13, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 13, "type": "Blob" } ], [ "MemoFormat", { - "nth": 14, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 14, "type": "Blob" } ], [ "Fulfillment", { - "nth": 16, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 16, "type": "Blob" } ], [ "Condition", { - "nth": 17, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 17, "type": "Blob" } ], [ "MasterSignature", { - "nth": 18, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 18, "type": "Blob" } ], [ "UNLModifyValidator", { - "nth": 19, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 19, "type": "Blob" } ], [ "ValidatorToDisable", { - "nth": 20, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 20, "type": "Blob" } ], [ "ValidatorToReEnable", { - "nth": 21, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 21, "type": "Blob" } ], [ "HookStateData", { - "nth": 22, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 22, "type": "Blob" } ], [ "HookReturnString", { - "nth": 23, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 23, "type": "Blob" } ], [ "HookParameterName", { - "nth": 24, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 24, "type": "Blob" } ], [ "HookParameterValue", { - "nth": 25, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 25, "type": "Blob" } ], [ "DIDDocument", { - "nth": 26, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 26, "type": "Blob" } ], [ "Data", { - "nth": 27, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 27, "type": "Blob" } ], [ "AssetClass", { - "nth": 28, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 28, "type": "Blob" } ], [ "Provider", { - "nth": 29, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 29, "type": "Blob" } ], [ - "Account", + "MPTokenMetadata", { - "nth": 1, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "AccountID" + "isVLEncoded": true, + "nth": 30, + "type": "Blob" } ], [ - "Owner", + "CredentialType", { - "nth": 2, + "nth": 31, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "type": "Blob" + } + ], + [ + "Account", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 1, "type": "AccountID" } ], [ - "Destination", + "Owner", { - "nth": 3, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": true, + "nth": 2, + "type": "AccountID" + } + ], + [ + "Destination", + { "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 3, "type": "AccountID" } ], [ "Issuer", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 4, "type": "AccountID" } ], [ "Authorize", { - "nth": 5, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 5, "type": "AccountID" } ], [ "Unauthorize", { - "nth": 6, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 6, "type": "AccountID" } ], [ "RegularKey", { - "nth": 8, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 8, "type": "AccountID" } ], [ "NFTokenMinter", { - "nth": 9, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 9, "type": "AccountID" } ], [ "EmitCallback", { - "nth": 10, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": true, + "nth": 10, + "type": "AccountID" + } + ], + [ + "Holder", + { "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 11, "type": "AccountID" } ], [ "HookAccount", { - "nth": 16, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 16, "type": "AccountID" } ], [ "OtherChainSource", { - "nth": 18, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 18, "type": "AccountID" } ], [ "OtherChainDestination", { - "nth": 19, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 19, "type": "AccountID" } ], [ "AttestationSignerAccount", { - "nth": 20, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 20, "type": "AccountID" } ], [ "AttestationRewardAccount", { - "nth": 21, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 21, "type": "AccountID" } ], [ "LockingChainDoor", { - "nth": 22, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 22, "type": "AccountID" } ], [ "IssuingChainDoor", { - "nth": 23, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 23, "type": "AccountID" } ], [ - "Indexes", + "Subject", { - "nth": 1, + "nth": 24, "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "type": "AccountID" } ], [ - "Hashes", + "TransactionMetaData", { - "nth": 2, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 2, + "type": "STObject" } ], [ - "Amendments", + "CreatedNode", { - "nth": 3, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 3, + "type": "STObject" } ], [ - "NFTokenOffers", + "DeletedNode", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 4, + "type": "STObject" } ], [ - "Paths", + "ModifiedNode", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "PathSet" + "isVLEncoded": false, + "nth": 5, + "type": "STObject" } ], [ - "BaseAsset", + "PreviousFields", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Currency" + "isVLEncoded": false, + "nth": 6, + "type": "STObject" } ], [ - "QuoteAsset", + "FinalFields", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Currency" + "isVLEncoded": false, + "nth": 7, + "type": "STObject" } ], [ - "LockingChainIssue", + "NewFields", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Issue" + "isVLEncoded": false, + "nth": 8, + "type": "STObject" } ], [ - "IssuingChainIssue", + "TemplateEntry", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Issue" + "isVLEncoded": false, + "nth": 9, + "type": "STObject" } ], [ - "Asset", + "Memo", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Issue" + "isVLEncoded": false, + "nth": 10, + "type": "STObject" } ], [ - "Asset2", + "SignerEntry", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Issue" + "isVLEncoded": false, + "nth": 11, + "type": "STObject" } ], [ - "XChainBridge", + "NFToken", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "XChainBridge" + "isVLEncoded": false, + "nth": 12, + "type": "STObject" } ], [ - "TransactionMetaData", + "EmitDetails", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "STObject" } ], [ - "CreatedNode", + "Hook", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "STObject" } ], [ - "DeletedNode", + "Signer", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "STObject" } ], [ - "ModifiedNode", + "Majority", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "STObject" } ], [ - "PreviousFields", + "DisabledValidator", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "STObject" } ], [ - "FinalFields", + "EmittedTxn", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "STObject" } ], [ - "NewFields", + "HookExecution", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "STObject" } ], [ - "TemplateEntry", + "HookDefinition", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "STObject" } ], [ - "Memo", + "HookParameter", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "STObject" } ], [ - "SignerEntry", + "HookGrant", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "STObject" } ], [ - "NFToken", + "VoteEntry", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "STObject" } ], [ - "EmitDetails", + "AuctionSlot", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "STObject" } ], [ - "Hook", + "AuthAccount", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "STObject" } ], [ - "Signer", + "XChainClaimProofSig", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "STObject" } ], [ - "Majority", + "XChainCreateAccountProofSig", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "STObject" } ], [ - "DisabledValidator", + "XChainClaimAttestationCollectionElement", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "STObject" } ], [ - "EmittedTxn", + "XChainCreateAccountAttestationCollectionElement", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "STObject" } ], [ - "HookExecution", + "PriceData", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "STObject" } ], [ - "HookDefinition", + "Credential", { - "nth": 22, + "nth": 33, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2451,199 +2321,199 @@ } ], [ - "HookParameter", + "Signers", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, - "isSigningField": true, - "type": "STObject" + "isSigningField": false, + "isVLEncoded": false, + "nth": 3, + "type": "STArray" } ], [ - "HookGrant", + "SignerEntries", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 4, + "type": "STArray" } ], [ - "VoteEntry", + "Template", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 5, + "type": "STArray" } ], [ - "AuctionSlot", + "Necessary", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 6, + "type": "STArray" } ], [ - "AuthAccount", + "Sufficient", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 7, + "type": "STArray" } ], [ - "XChainClaimProofSig", + "AffectedNodes", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 8, + "type": "STArray" } ], [ - "XChainCreateAccountProofSig", + "Memos", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 9, + "type": "STArray" } ], [ - "XChainClaimAttestationCollectionElement", + "NFTokens", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 10, + "type": "STArray" } ], [ - "XChainCreateAccountAttestationCollectionElement", + "Hooks", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 11, + "type": "STArray" } ], [ - "PriceData", + "VoteSlots", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" + "isVLEncoded": false, + "nth": 12, + "type": "STArray" } ], [ - "Signers", + "Majorities", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, - "isSigningField": false, + "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "STArray" } ], [ - "SignerEntries", + "DisabledValidators", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "STArray" } ], [ - "Template", + "HookExecutions", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "STArray" } ], [ - "Necessary", + "HookParameters", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "STArray" } ], [ - "Sufficient", + "HookGrants", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "STArray" } ], [ - "AffectedNodes", + "XChainClaimAttestations", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "STArray" } ], [ - "Memos", + "XChainCreateAccountAttestations", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "STArray" } ], [ - "NFTokens", + "PriceDataSeries", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "STArray" } ], [ - "Hooks", + "AuthAccounts", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "STArray" } ], [ - "VoteSlots", + "AuthorizeCredentials", { - "nth": 12, + "nth": 26, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2651,9 +2521,9 @@ } ], [ - "Majorities", + "UnauthorizeCredentials", { - "nth": 16, + "nth": 27, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2661,115 +2531,476 @@ } ], [ - "DisabledValidators", + "CloseResolution", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 1, + "type": "UInt8" } ], [ - "HookExecutions", + "Method", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 2, + "type": "UInt8" } ], [ - "HookParameters", + "TransactionResult", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 3, + "type": "UInt8" } ], [ - "HookGrants", + "Scale", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 4, + "type": "UInt8" } ], [ - "XChainClaimAttestations", + "AssetScale", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 5, + "type": "UInt8" } ], [ - "XChainCreateAccountAttestations", + "TickSize", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 16, + "type": "UInt8" } ], [ - "PriceDataSeries", + "UNLModifyDisabling", { - "nth": 24, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, + "nth": 17, + "type": "UInt8" + } + ], + [ + "HookResult", + { "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 18, + "type": "UInt8" } ], [ - "AuthAccounts", + "WasLockingChainSend", { - "nth": 25, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, + "nth": 19, + "type": "UInt8" + } + ], + [ + "TakerPaysCurrency", + { "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 1, + "type": "Hash160" } - ] + ], + [ + "TakerPaysIssuer", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Hash160" + } + ], + [ + "TakerGetsCurrency", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 3, + "type": "Hash160" + } + ], + [ + "TakerGetsIssuer", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 4, + "type": "Hash160" + } + ], + [ + "Paths", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "PathSet" + } + ], + [ + "Indexes", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 1, + "type": "Vector256" + } + ], + [ + "Hashes", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 2, + "type": "Vector256" + } + ], + [ + "Amendments", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 3, + "type": "Vector256" + } + ], + [ + "NFTokenOffers", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 4, + "type": "Vector256" + } + ], + [ + "CredentialIDs", + { + "nth": 5, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Vector256" + } + ], + [ + "MPTokenIssuanceID", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Hash192" + } + ], + [ + "LockingChainIssue", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Issue" + } + ], + [ + "IssuingChainIssue", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Issue" + } + ], + [ + "Asset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 3, + "type": "Issue" + } + ], + [ + "Asset2", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 4, + "type": "Issue" + } + ], + [ + "XChainBridge", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "XChainBridge" + } + ], + [ + "BaseAsset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Currency" + } + ], + [ + "QuoteAsset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Currency" + } + ], + [ + "Transaction", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Transaction" + } + ], + [ + "LedgerEntry", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "LedgerEntry" + } + ], + [ + "Validation", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Validation" + } + ], + [ + "Metadata", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Metadata" + } + ] ], + "LEDGER_ENTRY_TYPES": { + "AMM": 121, + "AccountRoot": 97, + "Amendments": 102, + "Bridge": 105, + "Check": 67, + "DID": 73, + "DepositPreauth": 112, + "DirectoryNode": 100, + "Escrow": 117, + "FeeSettings": 115, + "Invalid": -1, + "LedgerHashes": 104, + "MPToken": 127, + "MPTokenIssuance": 126, + "NFTokenOffer": 55, + "NFTokenPage": 80, + "NegativeUNL": 78, + "Offer": 111, + "Oracle": 128, + "Credential": 129, + "PayChannel": 120, + "RippleState": 114, + "SignerList": 83, + "Ticket": 84, + "XChainOwnedClaimID": 113, + "XChainOwnedCreateAccountClaimID": 116 + }, "TRANSACTION_RESULTS": { - "telLOCAL_ERROR": -399, + "tecAMM_ACCOUNT": 168, + "tecAMM_BALANCE": 163, + "tecAMM_EMPTY": 166, + "tecAMM_FAILED": 164, + "tecAMM_INVALID_TOKENS": 165, + "tecAMM_NOT_EMPTY": 167, + "tecARRAY_EMPTY": 190, + "tecARRAY_TOO_LARGE": 191, + "tecBAD_CREDENTIALS": 193, + "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, + "tecCLAIM": 100, + "tecCRYPTOCONDITION_ERROR": 146, + "tecDIR_FULL": 121, + "tecDST_TAG_NEEDED": 143, + "tecDUPLICATE": 149, + "tecEMPTY_DID": 187, + "tecEXPIRED": 148, + "tecFAILED_PROCESSING": 105, + "tecFROZEN": 137, + "tecHAS_OBLIGATIONS": 151, + "tecINCOMPLETE": 169, + "tecINSUFFICIENT_FUNDS": 159, + "tecINSUFFICIENT_PAYMENT": 161, + "tecINSUFFICIENT_RESERVE": 141, + "tecINSUFF_FEE": 136, + "tecINSUF_RESERVE_LINE": 122, + "tecINSUF_RESERVE_OFFER": 123, + "tecINTERNAL": 144, + "tecINVALID_UPDATE_TIME": 188, + "tecINVARIANT_FAILED": 147, + "tecKILLED": 150, + "tecLOCKED": 192, + "tecMAX_SEQUENCE_REACHED": 154, + "tecNEED_MASTER_KEY": 142, + "tecNFTOKEN_BUY_SELL_MISMATCH": 156, + "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, + "tecNO_ALTERNATIVE_KEY": 130, + "tecNO_AUTH": 134, + "tecNO_DST": 124, + "tecNO_DST_INSUF_XRP": 125, + "tecNO_ENTRY": 140, + "tecNO_ISSUER": 133, + "tecNO_LINE": 135, + "tecNO_LINE_INSUF_RESERVE": 126, + "tecNO_LINE_REDUNDANT": 127, + "tecNO_PERMISSION": 139, + "tecNO_REGULAR_KEY": 131, + "tecNO_SUITABLE_NFTOKEN_PAGE": 155, + "tecNO_TARGET": 138, + "tecOBJECT_NOT_FOUND": 160, + "tecOVERSIZE": 145, + "tecOWNERS": 132, + "tecPATH_DRY": 128, + "tecPATH_PARTIAL": 101, + "tecTOKEN_PAIR_NOT_FOUND": 189, + "tecTOO_SOON": 152, + "tecUNFUNDED": 129, + "tecUNFUNDED_ADD": 102, + "tecUNFUNDED_AMM": 162, + "tecUNFUNDED_OFFER": 103, + "tecUNFUNDED_PAYMENT": 104, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, + "tecXCHAIN_BAD_CLAIM_ID": 172, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, + "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, + "tecXCHAIN_CLAIM_NO_QUORUM": 173, + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, + "tecXCHAIN_NO_CLAIM_ID": 171, + "tecXCHAIN_NO_SIGNERS_LIST": 178, + "tecXCHAIN_PAYMENT_FAILED": 183, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, + "tecXCHAIN_REWARD_MISMATCH": 177, + "tecXCHAIN_SELF_COMMIT": 184, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, + "tecXCHAIN_WRONG_CHAIN": 176, + "tefALREADY": -198, + "tefBAD_ADD_AUTH": -197, + "tefBAD_AUTH": -196, + "tefBAD_AUTH_MASTER": -183, + "tefBAD_LEDGER": -195, + "tefBAD_QUORUM": -185, + "tefBAD_SIGNATURE": -186, + "tefCREATED": -194, + "tefEXCEPTION": -193, + "tefFAILURE": -199, + "tefINTERNAL": -192, + "tefINVALID_LEDGER_FIX_TYPE": -178, + "tefINVARIANT_FAILED": -182, + "tefMASTER_DISABLED": -188, + "tefMAX_LEDGER": -187, + "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + "tefNOT_MULTI_SIGNING": -184, + "tefNO_AUTH_REQUIRED": -191, + "tefNO_TICKET": -180, + "tefPAST_SEQ": -190, + "tefTOO_BIG": -181, + "tefWRONG_PRIOR": -189, "telBAD_DOMAIN": -398, "telBAD_PATH_COUNT": -397, "telBAD_PUBLIC_KEY": -396, - "telFAILED_PROCESSING": -395, - "telINSUF_FEE_P": -394, - "telNO_DST_PARTIAL": -393, "telCAN_NOT_QUEUE": -392, "telCAN_NOT_QUEUE_BALANCE": -391, - "telCAN_NOT_QUEUE_BLOCKS": -390, "telCAN_NOT_QUEUE_BLOCKED": -389, + "telCAN_NOT_QUEUE_BLOCKS": -390, "telCAN_NOT_QUEUE_FEE": -388, "telCAN_NOT_QUEUE_FULL": -387, - "telWRONG_NETWORK": -386, - "telREQUIRES_NETWORK_ID": -385, - "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, "telENV_RPC_FAILED": -383, - - "temMALFORMED": -299, + "telFAILED_PROCESSING": -395, + "telINSUF_FEE_P": -394, + "telLOCAL_ERROR": -399, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, + "telNO_DST_PARTIAL": -393, + "telREQUIRES_NETWORK_ID": -385, + "telWRONG_NETWORK": -386, + "temARRAY_EMPTY": -253, + "temARRAY_TOO_LARGE": -252, + "temBAD_AMM_TOKENS": -261, "temBAD_AMOUNT": -298, "temBAD_CURRENCY": -297, "temBAD_EXPIRATION": -296, "temBAD_FEE": -295, "temBAD_ISSUER": -294, "temBAD_LIMIT": -293, + "temBAD_NFTOKEN_TRANSFER_FEE": -262, "temBAD_OFFER": -292, "temBAD_PATH": -291, "temBAD_PATH_LOOP": -290, + "temBAD_QUORUM": -271, "temBAD_REGKEY": -289, "temBAD_SEND_XRP_LIMIT": -288, "temBAD_SEND_XRP_MAX": -287, @@ -2778,205 +3009,134 @@ "temBAD_SEND_XRP_PATHS": -284, "temBAD_SEQUENCE": -283, "temBAD_SIGNATURE": -282, + "temBAD_SIGNER": -272, "temBAD_SRC_ACCOUNT": -281, + "temBAD_TICK_SIZE": -269, + "temBAD_TRANSFER_FEE": -251, "temBAD_TRANSFER_RATE": -280, + "temBAD_WEIGHT": -270, + "temCANNOT_PREAUTH_SELF": -267, + "temDISABLED": -273, "temDST_IS_SRC": -279, "temDST_NEEDED": -278, + "temEMPTY_DID": -254, "temINVALID": -277, + "temINVALID_ACCOUNT_ID": -268, + "temINVALID_COUNT": -266, "temINVALID_FLAG": -276, + "temMALFORMED": -299, "temREDUNDANT": -275, "temRIPPLE_EMPTY": -274, - "temDISABLED": -273, - "temBAD_SIGNER": -272, - "temBAD_QUORUM": -271, - "temBAD_WEIGHT": -270, - "temBAD_TICK_SIZE": -269, - "temINVALID_ACCOUNT_ID": -268, - "temCANNOT_PREAUTH_SELF": -267, - "temINVALID_COUNT": -266, + "temSEQ_AND_TICKET": -263, "temUNCERTAIN": -265, "temUNKNOWN": -264, - "temSEQ_AND_TICKET": -263, - "temBAD_NFTOKEN_TRANSFER_FEE": -262, - "temBAD_AMM_TOKENS": -261, - "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, "temXCHAIN_BAD_PROOF": -259, "temXCHAIN_BRIDGE_BAD_ISSUES": -258, - "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, - "temEMPTY_DID": -254, - "temARRAY_EMPTY": -253, - "temARRAY_TOO_LARGE": -252, - - "tefFAILURE": -199, - "tefALREADY": -198, - "tefBAD_ADD_AUTH": -197, - "tefBAD_AUTH": -196, - "tefBAD_LEDGER": -195, - "tefCREATED": -194, - "tefEXCEPTION": -193, - "tefINTERNAL": -192, - "tefNO_AUTH_REQUIRED": -191, - "tefPAST_SEQ": -190, - "tefWRONG_PRIOR": -189, - "tefMASTER_DISABLED": -188, - "tefMAX_LEDGER": -187, - "tefBAD_SIGNATURE": -186, - "tefBAD_QUORUM": -185, - "tefNOT_MULTI_SIGNING": -184, - "tefBAD_AUTH_MASTER": -183, - "tefINVARIANT_FAILED": -182, - "tefTOO_BIG": -181, - "tefNO_TICKET": -180, - "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, - - "terRETRY": -99, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, "terFUNDS_SPENT": -98, "terINSUF_FEE_B": -97, + "terLAST": -91, "terNO_ACCOUNT": -96, + "terNO_AMM": -87, "terNO_AUTH": -95, "terNO_LINE": -94, + "terNO_RIPPLE": -90, "terOWNERS": -93, "terPRE_SEQ": -92, - "terLAST": -91, - "terNO_RIPPLE": -90, - "terQUEUED": -89, "terPRE_TICKET": -88, - "terNO_AMM": -87, - - "tesSUCCESS": 0, - - "tecCLAIM": 100, - "tecPATH_PARTIAL": 101, - "tecUNFUNDED_ADD": 102, - "tecUNFUNDED_OFFER": 103, - "tecUNFUNDED_PAYMENT": 104, - "tecFAILED_PROCESSING": 105, - "tecDIR_FULL": 121, - "tecINSUF_RESERVE_LINE": 122, - "tecINSUF_RESERVE_OFFER": 123, - "tecNO_DST": 124, - "tecNO_DST_INSUF_XRP": 125, - "tecNO_LINE_INSUF_RESERVE": 126, - "tecNO_LINE_REDUNDANT": 127, - "tecPATH_DRY": 128, - "tecUNFUNDED": 129, - "tecNO_ALTERNATIVE_KEY": 130, - "tecNO_REGULAR_KEY": 131, - "tecOWNERS": 132, - "tecNO_ISSUER": 133, - "tecNO_AUTH": 134, - "tecNO_LINE": 135, - "tecINSUFF_FEE": 136, - "tecFROZEN": 137, - "tecNO_TARGET": 138, - "tecNO_PERMISSION": 139, - "tecNO_ENTRY": 140, - "tecINSUFFICIENT_RESERVE": 141, - "tecNEED_MASTER_KEY": 142, - "tecDST_TAG_NEEDED": 143, - "tecINTERNAL": 144, - "tecOVERSIZE": 145, - "tecCRYPTOCONDITION_ERROR": 146, - "tecINVARIANT_FAILED": 147, - "tecEXPIRED": 148, - "tecDUPLICATE": 149, - "tecKILLED": 150, - "tecHAS_OBLIGATIONS": 151, - "tecTOO_SOON": 152, - "tecHOOK_REJECTED": 153, - "tecMAX_SEQUENCE_REACHED": 154, - "tecNO_SUITABLE_NFTOKEN_PAGE": 155, - "tecNFTOKEN_BUY_SELL_MISMATCH": 156, - "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, - "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, - "tecINSUFFICIENT_FUNDS": 159, - "tecOBJECT_NOT_FOUND": 160, - "tecINSUFFICIENT_PAYMENT": 161, - "tecUNFUNDED_AMM": 162, - "tecAMM_BALANCE": 163, - "tecAMM_FAILED": 164, - "tecAMM_INVALID_TOKENS": 165, - "tecAMM_EMPTY": 166, - "tecAMM_NOT_EMPTY": 167, - "tecAMM_ACCOUNT": 168, - "tecINCOMPLETE": 169, - "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, - "tecXCHAIN_NO_CLAIM_ID": 171, - "tecXCHAIN_BAD_CLAIM_ID": 172, - "tecXCHAIN_CLAIM_NO_QUORUM": 173, - "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, - "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, - "tecXCHAIN_WRONG_CHAIN": 176, - "tecXCHAIN_REWARD_MISMATCH": 177, - "tecXCHAIN_NO_SIGNERS_LIST": 178, - "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, - "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, - "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, - "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, - "tecXCHAIN_PAYMENT_FAILED": 183, - "tecXCHAIN_SELF_COMMIT": 184, - "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, - "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, - "tecEMPTY_DID": 187, - "tecINVALID_UPDATE_TIME": 188, - "tecTOKEN_PAIR_NOT_FOUND": 189, - "tecARRAY_EMPTY": 190, - "tecARRAY_TOO_LARGE": 191 + "terQUEUED": -89, + "terRETRY": -99, + "tesSUCCESS": 0 }, "TRANSACTION_TYPES": { - "Invalid": -1, - "Payment": 0, - "EscrowCreate": 1, - "EscrowFinish": 2, + "AMMBid": 39, + "AMMCreate": 35, + "AMMDelete": 40, + "AMMDeposit": 36, + "AMMVote": 38, + "AMMWithdraw": 37, + "AccountDelete": 21, "AccountSet": 3, + "CheckCancel": 18, + "CheckCash": 17, + "CheckCreate": 16, + "Clawback": 30, + "CredentialCreate": 58, + "CredentialAccept": 59, + "CredentialDelete": 60, + "DIDDelete": 50, + "DIDSet": 49, + "DepositPreauth": 19, + "EnableAmendment": 100, "EscrowCancel": 4, - "SetRegularKey": 5, - "NickNameSet": 6, - "OfferCreate": 7, + "EscrowCreate": 1, + "EscrowFinish": 2, + "Invalid": -1, + "LedgerStateFix": 53, + "MPTokenAuthorize": 57, + "MPTokenIssuanceCreate": 54, + "MPTokenIssuanceDestroy": 55, + "MPTokenIssuanceSet": 56, + "NFTokenAcceptOffer": 29, + "NFTokenBurn": 26, + "NFTokenCancelOffer": 28, + "NFTokenCreateOffer": 27, + "NFTokenMint": 25, + "NFTokenModify": 61, "OfferCancel": 8, - "Contract": 9, - "TicketCreate": 10, - "TicketCancel": 11, - "SignerListSet": 12, + "OfferCreate": 7, + "OracleDelete": 52, + "OracleSet": 51, + "Payment": 0, + "PaymentChannelClaim": 15, "PaymentChannelCreate": 13, "PaymentChannelFund": 14, - "PaymentChannelClaim": 15, - "CheckCreate": 16, - "CheckCash": 17, - "CheckCancel": 18, - "DepositPreauth": 19, + "SetFee": 101, + "SetRegularKey": 5, + "SignerListSet": 12, + "TicketCreate": 10, "TrustSet": 20, - "AccountDelete": 21, - "SetHook": 22, - "NFTokenMint": 25, - "NFTokenBurn": 26, - "NFTokenCreateOffer": 27, - "NFTokenCancelOffer": 28, - "NFTokenAcceptOffer": 29, - "Clawback": 30, - "AMMCreate": 35, - "AMMDeposit": 36, - "AMMWithdraw": 37, - "AMMVote": 38, - "AMMBid": 39, - "AMMDelete": 40, - "XChainCreateClaimID": 41, - "XChainCommit": 42, - "XChainClaim": 43, + "UNLModify": 102, "XChainAccountCreateCommit": 44, - "XChainAddClaimAttestation": 45, "XChainAddAccountCreateAttestation": 46, - "XChainModifyBridge": 47, + "XChainAddClaimAttestation": 45, + "XChainClaim": 43, + "XChainCommit": 42, "XChainCreateBridge": 48, - "DIDSet": 49, - "DIDDelete": 50, - "OracleSet": 51, - "OracleDelete": 52, - "NFTokenModify": 61, - "EnableAmendment": 100, - "SetFee": 101, - "UNLModify": 102 + "XChainCreateClaimID": 41, + "XChainModifyBridge": 47 + }, + "TYPES": { + "AccountID": 8, + "Amount": 6, + "Blob": 7, + "Currency": 26, + "Done": -1, + "Hash128": 4, + "Hash160": 17, + "Hash192": 21, + "Hash256": 5, + "Issue": 24, + "LedgerEntry": 10002, + "Metadata": 10004, + "NotPresent": 0, + "PathSet": 18, + "STArray": 15, + "STObject": 14, + "Transaction": 10001, + "UInt16": 1, + "UInt32": 2, + "UInt384": 22, + "UInt512": 23, + "UInt64": 3, + "UInt8": 16, + "UInt96": 20, + "Unknown": -2, + "Validation": 10003, + "Vector256": 19, + "XChainBridge": 25 } } diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index b92ebf4fc2..44fb567441 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -6,6 +6,7 @@ import { JsonObject, SerializedType } from './serialized-type' import BigNumber from 'bignumber.js' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' +import { Hash192 } from './hash-192' /** * Constants for validating amounts @@ -16,6 +17,7 @@ const MAX_IOU_PRECISION = 16 const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) +const mptMask = BigInt(0x8000000000000000) /** * BigNumber configuration for Amount IOUs @@ -27,20 +29,28 @@ BigNumber.config({ ], }) -/** - * Interface for JSON objects that represent amounts - */ -interface AmountObject extends JsonObject { +interface AmountObjectIOU extends JsonObject { value: string currency: string issuer: string } +interface AmountObjectMPT extends JsonObject { + value: string + mpt_issuance_id: string +} + +/** + * Interface for JSON objects that represent amounts + */ +type AmountObject = AmountObjectIOU | AmountObjectMPT + /** - * Type guard for AmountObject + * Type guard for AmountObjectIOU */ -function isAmountObject(arg): arg is AmountObject { +function isAmountObjectIOU(arg): arg is AmountObjectIOU { const keys = Object.keys(arg).sort() + return ( keys.length === 3 && keys[0] === 'currency' && @@ -49,6 +59,17 @@ function isAmountObject(arg): arg is AmountObject { ) } +/** + * Type guard for AmountObjectMPT + */ +function isAmountObjectMPT(arg): arg is AmountObjectMPT { + const keys = Object.keys(arg).sort() + + return ( + keys.length === 2 && keys[0] === 'mpt_issuance_id' && keys[1] === 'value' + ) +} + /** * Class for serializing/Deserializing Amounts */ @@ -60,7 +81,7 @@ class Amount extends SerializedType { } /** - * Construct an amount from an IOU or string amount + * Construct an amount from an IOU, MPT or string amount * * @param value An Amount, object representing an IOU, or a string * representing an integer amount @@ -88,7 +109,7 @@ class Amount extends SerializedType { return new Amount(amount) } - if (isAmountObject(value)) { + if (isAmountObjectIOU(value)) { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) @@ -124,6 +145,24 @@ class Amount extends SerializedType { return new Amount(concat([amount, currency, issuer])) } + if (isAmountObjectMPT(value)) { + Amount.assertMptIsValid(value.value) + + let leadingByte = new Uint8Array(1) + leadingByte[0] |= 0x60 + + const num = BigInt(value.value) + + const intBuf = [new Uint8Array(4), new Uint8Array(4)] + writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) + writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) + + amount = concat(intBuf) + + const mptIssuanceID = Hash192.from(value.mpt_issuance_id).toBytes() + return new Amount(concat([leadingByte, amount, mptIssuanceID])) + } + throw new Error('Invalid type to construct an Amount') } @@ -134,8 +173,12 @@ class Amount extends SerializedType { * @returns An Amount object */ static fromParser(parser: BinaryParser): Amount { - const isXRP = parser.peek() & 0x80 - const numBytes = isXRP ? 48 : 8 + const isIOU = parser.peek() & 0x80 + if (isIOU) return new Amount(parser.read(48)) + + // the amount can be either MPT or XRP at this point + const isMPT = parser.peek() & 0x20 + const numBytes = isMPT ? 33 : 8 return new Amount(parser.read(numBytes)) } @@ -156,7 +199,9 @@ class Amount extends SerializedType { const num = (msb << BigInt(32)) | lsb return `${sign}${num.toString()}` - } else { + } + + if (this.isIOU()) { const parser = new BinaryParser(this.toString()) const mantissa = parser.read(8) const currency = Currency.fromParser(parser) as Currency @@ -182,6 +227,27 @@ class Amount extends SerializedType { issuer: issuer.toJSON(), } } + + if (this.isMPT()) { + const parser = new BinaryParser(this.toString()) + const leadingByte = parser.read(1) + const amount = parser.read(8) + const mptID = Hash192.fromParser(parser) as Hash192 + + const isPositive = leadingByte[0] & 0x40 + const sign = isPositive ? '' : '-' + + const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) + const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) + const num = (msb << BigInt(32)) | lsb + + return { + value: `${sign}${num.toString()}`, + mpt_issuance_id: mptID.toString(), + } + } + + throw new Error('Invalid amount to construct JSON') } /** @@ -224,6 +290,29 @@ class Amount extends SerializedType { } } + /** + * Validate MPT.value amount + * + * @param decimal BigNumber object representing MPT.value + * @returns void, but will throw if invalid amount + */ + private static assertMptIsValid(amount: string): void { + if (amount.indexOf('.') !== -1) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + + const decimal = new BigNumber(amount) + if (!decimal.isZero()) { + if (decimal < BigNumber(0)) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + + if (Number(BigInt(amount) & BigInt(mptMask)) != 0) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + } + } + /** * Ensure that the value after being multiplied by the exponent does not * contain a decimal. @@ -248,7 +337,25 @@ class Amount extends SerializedType { * @returns true if Native (XRP) */ private isNative(): boolean { - return (this.bytes[0] & 0x80) === 0 + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) === 0 + } + + /** + * Test if this amount is in units of MPT + * + * @returns true if MPT + */ + private isMPT(): boolean { + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 + } + + /** + * Test if this amount is in units of IOU + * + * @returns true if IOU + */ + private isIOU(): boolean { + return (this.bytes[0] & 0x80) !== 0 } } diff --git a/packages/ripple-binary-codec/src/types/hash-192.ts b/packages/ripple-binary-codec/src/types/hash-192.ts new file mode 100644 index 0000000000..6e05385a78 --- /dev/null +++ b/packages/ripple-binary-codec/src/types/hash-192.ts @@ -0,0 +1,19 @@ +import { Hash } from './hash' + +/** + * Hash with a width of 192 bits + */ +class Hash192 extends Hash { + static readonly width = 24 + static readonly ZERO_192: Hash192 = new Hash192(new Uint8Array(Hash192.width)) + + constructor(bytes?: Uint8Array) { + if (bytes && bytes.byteLength === 0) { + bytes = Hash192.ZERO_192.bytes + } + + super(bytes ?? Hash192.ZERO_192.bytes) + } +} + +export { Hash192 } diff --git a/packages/ripple-binary-codec/src/types/index.ts b/packages/ripple-binary-codec/src/types/index.ts index 2f2efef471..bc99a64711 100644 --- a/packages/ripple-binary-codec/src/types/index.ts +++ b/packages/ripple-binary-codec/src/types/index.ts @@ -4,6 +4,7 @@ import { Blob } from './blob' import { Currency } from './currency' import { Hash128 } from './hash-128' import { Hash160 } from './hash-160' +import { Hash192 } from './hash-192' import { Hash256 } from './hash-256' import { Issue } from './issue' import { PathSet } from './path-set' @@ -25,6 +26,7 @@ const coreTypes: Record = { Currency, Hash128, Hash160, + Hash192, Hash256, Issue, PathSet, @@ -51,6 +53,7 @@ export { Currency, Hash128, Hash160, + Hash192, Hash256, PathSet, STArray, diff --git a/packages/ripple-binary-codec/src/types/serialized-type.ts b/packages/ripple-binary-codec/src/types/serialized-type.ts index eb27f5d5a7..2f039f0cd1 100644 --- a/packages/ripple-binary-codec/src/types/serialized-type.ts +++ b/packages/ripple-binary-codec/src/types/serialized-type.ts @@ -67,7 +67,7 @@ class SerializedType { * Can be customized for sidechains and amendments. * @returns any type, if not overloaded returns hexString representation of bytes */ - toJSON(_definitions?: XrplDefinitionsBase): JSON { + toJSON(_definitions?: XrplDefinitionsBase, _fieldName?: string): JSON { return this.toHex() } diff --git a/packages/ripple-binary-codec/src/types/st-object.ts b/packages/ripple-binary-codec/src/types/st-object.ts index 75fec3a86c..2bc596c196 100644 --- a/packages/ripple-binary-codec/src/types/st-object.ts +++ b/packages/ripple-binary-codec/src/types/st-object.ts @@ -10,6 +10,7 @@ import { BinaryParser } from '../serdes/binary-parser' import { BinarySerializer, BytesList } from '../serdes/binary-serializer' import { STArray } from './st-array' +import { UInt64 } from './uint-64' const OBJECT_END_MARKER_BYTE = Uint8Array.from([0xe1]) const OBJECT_END_MARKER = 'ObjectEndMarker' @@ -137,6 +138,8 @@ class STObject extends SerializedType { ? this.from(xAddressDecoded[field.name], undefined, definitions) : field.type.name === 'STArray' ? STArray.from(xAddressDecoded[field.name], definitions) + : field.type.name === 'UInt64' + ? UInt64.from(xAddressDecoded[field.name], field.name) : field.associatedType.from(xAddressDecoded[field.name]) if (associatedValue == undefined) { @@ -182,7 +185,7 @@ class STObject extends SerializedType { accumulator[field.name] = objectParser .readFieldValue(field) - .toJSON(definitions) + .toJSON(definitions, field.name) } return accumulator diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index 584b468d65..f6c9e523f7 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -2,10 +2,20 @@ import { UInt } from './uint' import { BinaryParser } from '../serdes/binary-parser' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' +import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums' const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/ +const BASE10_REGEX = /^[0-9]{1,20}$/ const mask = BigInt(0x00000000ffffffff) +function useBase10(fieldName: string): boolean { + return ( + fieldName === 'MaximumAmount' || + fieldName === 'OutstandingAmount' || + fieldName === 'MPTAmount' + ) +} + /** * Derived UInt class for serializing/deserializing 64 bit UInt */ @@ -29,7 +39,10 @@ class UInt64 extends UInt { * @param val A UInt64, hex-string, bigInt, or number * @returns A UInt64 object */ - static from(val: T): UInt64 { + static from( + val: T, + fieldName = '', + ): UInt64 { if (val instanceof UInt64) { return val } @@ -51,11 +64,18 @@ class UInt64 extends UInt { } if (typeof val === 'string') { - if (!HEX_REGEX.test(val)) { + if (useBase10(fieldName)) { + if (!BASE10_REGEX.test(val)) { + throw new Error(`${fieldName} ${val} is not a valid base 10 string`) + } + val = BigInt(val).toString(16) as T + } + + if (typeof val === 'string' && !HEX_REGEX.test(val)) { throw new Error(`${val} is not a valid hex-string`) } - const strBuf = val.padStart(16, '0') + const strBuf = (val as string).padStart(16, '0') buf = hexToBytes(strBuf) return new UInt64(buf) } @@ -76,8 +96,16 @@ class UInt64 extends UInt { * * @returns a hex-string */ - toJSON(): string { - return bytesToHex(this.bytes) + toJSON( + _definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS, + fieldName = '', + ): string { + const hexString = bytesToHex(this.bytes) + if (useBase10(fieldName)) { + return BigInt('0x' + hexString).toString(10) + } + + return hexString } /** diff --git a/packages/ripple-binary-codec/test/amount.test.ts b/packages/ripple-binary-codec/test/amount.test.ts index 82b8c1c903..7a7518655f 100644 --- a/packages/ripple-binary-codec/test/amount.test.ts +++ b/packages/ripple-binary-codec/test/amount.test.ts @@ -1,5 +1,7 @@ import { coreTypes } from '../src/types' import fixtures from './fixtures/data-driven-tests.json' + +import { makeParser } from '../src/binary' const { Amount } = coreTypes function amountErrorTests() { @@ -25,6 +27,16 @@ describe('Amount', function () { it('can be parsed from', function () { expect(Amount.from('1000000') instanceof Amount).toBe(true) expect(Amount.from('1000000').toJSON()).toEqual('1000000') + + // it not valid to have negative XRP. But we test it anyways + // to ensure logic correctness for toJSON of the Amount class + { + const parser = makeParser('0000000000000001') + const value = parser.readType(Amount) + const json = value.toJSON() + expect(json).toEqual('-1') + } + const fixture = { value: '1', issuer: '0000000000000000000000000000000000000000', @@ -38,5 +50,35 @@ describe('Amount', function () { } expect(amt.toJSON()).toEqual(rewritten) }) + + it('can be parsed from MPT', function () { + let fixture = { + value: '100', + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + } + let amt = Amount.from(fixture) + expect(amt.toJSON()).toEqual(fixture) + + fixture = { + value: '9223372036854775807', + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + } + amt = Amount.from(fixture) + expect(amt.toJSON()).toEqual(fixture) + + // it not valid to have negative MPT. But we test it anyways + // to ensure logic correctness for toJSON of the Amount class + { + const parser = makeParser( + '20000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF', + ) + const value = parser.readType(Amount) + const json = value.toJSON() + expect(json).toEqual({ + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + value: '-100', + }) + } + }) amountErrorTests() }) diff --git a/packages/ripple-binary-codec/test/binary-parser.test.ts b/packages/ripple-binary-codec/test/binary-parser.test.ts index 60b3fa11a7..9efafdba62 100644 --- a/packages/ripple-binary-codec/test/binary-parser.test.ts +++ b/packages/ripple-binary-codec/test/binary-parser.test.ts @@ -22,6 +22,7 @@ function assertEqualAmountJSON(actual, expected) { } expect(actual.currency).toEqual(expected.currency) expect(actual.issuer).toEqual(expected.issuer) + expect(actual.mpt_issuance_id).toEqual(expected.mpt_issuance_id) expect( actual.value === expected.value || new BigNumber(actual.value).eq(new BigNumber(expected.value)), @@ -207,12 +208,12 @@ function amountParsingTests() { return } const parser = makeParser(f.expected_hex) - const testName = `values_tests[${i}] parses ${f.expected_hex.slice( + const hexToJsonTestName = `values_tests[${i}] parses ${f.expected_hex.slice( 0, 16, )}... as ${JSON.stringify(f.test_json)}` - it(testName, () => { + it(hexToJsonTestName, () => { const value = parser.readType(Amount) // May not actually be in canonical form. The fixtures are to be used // also for json -> binary; @@ -223,6 +224,15 @@ function amountParsingTests() { expect((exponent.e ?? 0) - 15).toEqual(f?.exponent) } }) + + const jsonToHexTestName = `values_tests[${i}] parses ${JSON.stringify( + f.test_json, + )}... + as ${f.expected_hex.slice(0, 16)}` + it(jsonToHexTestName, () => { + const amt = Amount.from(f.test_json) + expect(amt.toHex()).toEqual(f.expected_hex) + }) }) } diff --git a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json index 658c15660c..e7403768de 100644 --- a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json +++ b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json @@ -2499,7 +2499,7 @@ "type_id": 6, "is_native": true, "type": "Amount", - "expected_hex": "0000000000000001", + "error": "Value is negative", "is_negative": true }, { @@ -2914,6 +2914,170 @@ "type": "Amount", "error": "10000000000000000000 absolute XRP is bigger than max native value 100000000000.0", "is_negative": true + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "9223372036854775808" + }, + "type": "Amount", + "error": "Value is too large" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "18446744073709551615" + }, + "type": "Amount", + "error": "Value is too large" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "-1" + }, + "type": "Amount", + "error": "Value is negative" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10.1" + }, + "type": "Amount", + "error": "Value has decimal point" + }, + { + "test_json": { + "mpt_issuance_id": "10", + "value": "10" + }, + "type": "Amount", + "error": "mpt_issuance_id has invalid hash length" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji" + }, + "type": "Amount", + "error": "Issuer not valid for MPT" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10", + "currency": "USD" + }, + "type": "Amount", + "error": "Currency not valid for MPT" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "a" + }, + "type": "Amount", + "error": "Value has incorrect hex format" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xy" + }, + "type": "Amount", + "error": "Value has bad hex character" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "/" + }, + "type": "Amount", + "error": "Value has bad character" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0x8000000000000000" + }, + "type": "Amount", + "error": "Hex value out of range" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xFFFFFFFFFFFFFFFF" + }, + "type": "Amount", + "error": "Hex value out of range" + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "9223372036854775807" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "607FFFFFFFFFFFFFFF00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "-0" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "100" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xa" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000A00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0x7FFFFFFFFFFFFFFF" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "607FFFFFFFFFFFFFFF00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false } ] } diff --git a/packages/ripple-binary-codec/test/hash.test.ts b/packages/ripple-binary-codec/test/hash.test.ts index 246ac4c7c0..6b0447fe5b 100644 --- a/packages/ripple-binary-codec/test/hash.test.ts +++ b/packages/ripple-binary-codec/test/hash.test.ts @@ -1,4 +1,11 @@ -import { Hash128, Hash160, Hash256, AccountID, Currency } from '../src/types' +import { + Hash128, + Hash160, + Hash192, + Hash256, + AccountID, + Currency, +} from '../src/types' describe('Hash128', function () { it('has a static width member', function () { @@ -51,6 +58,33 @@ describe('Hash160', function () { }) }) +describe('Hash192', function () { + it('has a static width member', function () { + expect(Hash192.width).toBe(24) + }) + it('has a ZERO_192 member', function () { + expect(Hash192.ZERO_192.toJSON()).toBe( + '000000000000000000000000000000000000000000000000', + ) + }) + it('can be compared against another', function () { + const h1 = Hash192.from('100000000000000000000000000000000000000000000000') + const h2 = Hash192.from('200000000000000000000000000000000000000000000000') + const h3 = Hash192.from('000000000000000000000000000000000000000000000003') + expect(h1.lt(h2)).toBe(true) + expect(h3.lt(h2)).toBe(true) + }) + + it('throws when constructed from invalid hash length', () => { + expect(() => + Hash192.from('10000000000000000000000000000000000000000000000'), + ).toThrow(new Error('Invalid Hash length 23')) + expect(() => + Hash192.from('10000000000000000000000000000000000000000000000000'), + ).toThrow(new Error('Invalid Hash length 25')) + }) +}) + describe('Hash256', function () { it('has a static width member', function () { expect(Hash256.width).toBe(32) diff --git a/packages/ripple-binary-codec/test/signing-data-encoding.test.ts b/packages/ripple-binary-codec/test/signing-data-encoding.test.ts index 881f8132af..22bab766cb 100644 --- a/packages/ripple-binary-codec/test/signing-data-encoding.test.ts +++ b/packages/ripple-binary-codec/test/signing-data-encoding.test.ts @@ -73,7 +73,9 @@ describe('Signing data', function () { const customPaymentDefinitions = JSON.parse( JSON.stringify(normalDefinitions), ) - customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31 + + // custom number would need to updated in case it has been used by an existing transaction type + customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200 const newDefs = new XrplDefinitions(customPaymentDefinitions) const actual = encodeForSigning(tx_json, newDefs) @@ -82,7 +84,7 @@ describe('Signing data', function () { '53545800', // signingPrefix // TransactionType '12', - '001F', + '00C8', // Flags '22', '80000000', @@ -176,7 +178,9 @@ describe('Signing data', function () { const customPaymentDefinitions = JSON.parse( JSON.stringify(normalDefinitions), ) - customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31 + + // custom number would need to updated in case it has been used by an existing transaction type + customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200 const newDefs = new XrplDefinitions(customPaymentDefinitions) const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN' @@ -187,7 +191,7 @@ describe('Signing data', function () { '534D5400', // signingPrefix // TransactionType '12', - '001F', + '00C8', // Flags '22', '80000000', diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index debfad4a87..e3165fa1ed 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -1,5 +1,5 @@ import { UInt8, UInt64 } from '../src/types' -import { encode } from '../src' +import { encode, decode } from '../src' const binary = '11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F' @@ -96,6 +96,40 @@ const jsonEntry2 = { index: '0000041EFD027808D3F78C8352F97E324CB816318E00B977C74ECDDC7CD975B2', } +const mptIssuanceEntryBinary = + '11007E220000006224000002DF25000002E434000000000000000030187FFFFFFFFFFFFFFF30190000000000000064552E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE701EC1EC7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D8414A4D893CFBC4DC6AE877EB585F90A3B47528B958D051003' + +const mptIssuanceEntryJson = { + AssetScale: 3, + Flags: 98, + Issuer: 'rGpdGXDV2RFPeLEfWS9RFo5Nh9cpVDToZa', + LedgerEntryType: 'MPTokenIssuance', + MPTokenMetadata: + '7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D', + MaximumAmount: '9223372036854775807', + OutstandingAmount: '100', + OwnerNode: '0000000000000000', + PreviousTxnID: + '2E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE', + PreviousTxnLgrSeq: 740, + Sequence: 735, +} + +const mptokenEntryJson = { + Account: 'raDQsd1s8rqGjL476g59a9vVNi1rSwrC44', + Flags: 0, + LedgerEntryType: 'MPToken', + MPTAmount: '100', + MPTokenIssuanceID: '000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC', + OwnerNode: '0000000000000000', + PreviousTxnID: + '222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C8', + PreviousTxnLgrSeq: 741, +} + +const mptokenEntryBinary = + '11007F220000000025000002E5340000000000000000301A000000000000006455222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C881143930DB9A74C26D96CB58ADFFD7E8BB78BCFE62340115000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC' + it('compareToTests[0]', () => { expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0) }) @@ -144,3 +178,20 @@ it('valueOf tests', () => { expect(val.valueOf() | 0x2).toBe(3) }) + +it('UInt64 is parsed as base 10 for MPT amounts', () => { + expect(encode(mptIssuanceEntryJson)).toEqual(mptIssuanceEntryBinary) + expect(decode(mptIssuanceEntryBinary)).toEqual(mptIssuanceEntryJson) + + expect(encode(mptokenEntryJson)).toEqual(mptokenEntryBinary) + expect(decode(mptokenEntryBinary)).toEqual(mptokenEntryJson) + + const decodedIssuance = decode(mptIssuanceEntryBinary) + expect(typeof decodedIssuance.MaximumAmount).toBe('string') + expect(decodedIssuance.MaximumAmount).toBe('9223372036854775807') + expect(decodedIssuance.OutstandingAmount).toBe('100') + + const decodedToken = decode(mptokenEntryBinary) + expect(typeof decodedToken.MPTAmount).toBe('string') + expect(decodedToken.MPTAmount).toBe('100') +}) diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 136f85990d..d41e1c537b 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -2,10 +2,21 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release. -## Unreleased Changes +## Unreleased ### Added -* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion +* Adds utility function `convertTxFlagsToNumber` + +### Changed +* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead + +## 4.1.0 (2024-12-23) + +### Added +* Added new MPT transaction definitions (XLS-33) +* New `MPTAmount` type support for `Payment` and `Clawback` transactions +* `parseTransactionFlags` as a utility function in the xrpl package to streamline transactions flags-to-map conversion +* Support for XLS-70d (Credentials) * Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint` ### Fixed @@ -48,6 +59,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ## 3.0.0 (2024-02-01) ### BREAKING CHANGES +* The default signing algorithm in the `Wallet` was changed from secp256k1 to ed25519 * Bump typescript to 5.x * Remove Node 14 support * Remove `crypto` polyfills, `create-hash`, `elliptic`, `hash.js`, and their many dependencies in favor of `@noble/hashes` and `@nobel/curves` diff --git a/packages/xrpl/package.json b/packages/xrpl/package.json index 1d0ea17b63..beb5408ec8 100644 --- a/packages/xrpl/package.json +++ b/packages/xrpl/package.json @@ -1,6 +1,6 @@ { "name": "xrpl", - "version": "4.0.0", + "version": "4.1.0", "license": "ISC", "description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser", "files": [ @@ -29,7 +29,7 @@ "bignumber.js": "^9.0.0", "eventemitter3": "^5.0.1", "ripple-address-codec": "^5.0.0", - "ripple-binary-codec": "^2.1.0", + "ripple-binary-codec": "^2.2.0", "ripple-keypairs": "^2.0.0" }, "devDependencies": { @@ -41,9 +41,9 @@ "karma-jasmine": "^5.1.0", "karma-webpack": "^5.0.0", "lodash": "^4.17.4", - "react": "^18.2.0", + "react": "^19.0.0", "run-s": "^0.0.0", - "typedoc": "0.26.10", + "typedoc": "0.27.6", "ws": "^8.14.2" }, "resolutions": { diff --git a/packages/xrpl/src/client/index.ts b/packages/xrpl/src/client/index.ts index 722b169f2a..0afaa29b72 100644 --- a/packages/xrpl/src/client/index.ts +++ b/packages/xrpl/src/client/index.ts @@ -47,7 +47,7 @@ import type { OnEventToListenerMap, } from '../models/methods/subscribe' import type { SubmittableTransaction } from '../models/transactions' -import { setTransactionFlagsToNumber } from '../models/utils/flags' +import { convertTxFlagsToNumber } from '../models/utils/flags' import { ensureClassicAddress, submitRequest, @@ -665,7 +665,7 @@ class Client extends EventEmitter { const tx = { ...transaction } setValidAddresses(tx) - setTransactionFlagsToNumber(tx) + tx.Flags = convertTxFlagsToNumber(tx) const promises: Array> = [] if (tx.NetworkID == null) { diff --git a/packages/xrpl/src/client/partialPayment.ts b/packages/xrpl/src/client/partialPayment.ts index 4b5968c71e..8176fb7839 100644 --- a/packages/xrpl/src/client/partialPayment.ts +++ b/packages/xrpl/src/client/partialPayment.ts @@ -7,20 +7,32 @@ import type { TransactionV1Stream, TxResponse, } from '..' -import type { Amount, APIVersion, DEFAULT_API_VERSION } from '../models/common' +import type { + Amount, + IssuedCurrency, + APIVersion, + DEFAULT_API_VERSION, + MPTAmount, +} from '../models/common' import type { AccountTxTransaction, RequestResponseMap, } from '../models/methods' import { AccountTxVersionResponseMap } from '../models/methods/accountTx' import { BaseRequest, BaseResponse } from '../models/methods/baseMethod' -import { PaymentFlags, Transaction } from '../models/transactions' +import { PaymentFlags, Transaction, isMPTAmount } from '../models/transactions' import type { TransactionMetadata } from '../models/transactions/metadata' import { isFlagEnabled } from '../models/utils' const WARN_PARTIAL_PAYMENT_CODE = 2001 -function amountsEqual(amt1: Amount, amt2: Amount): boolean { +/* eslint-disable complexity -- check different token types */ +/* eslint-disable @typescript-eslint/consistent-type-assertions -- known currency type */ +function amountsEqual( + amt1: Amount | MPTAmount, + amt2: Amount | MPTAmount, +): boolean { + // Compare XRP if (typeof amt1 === 'string' && typeof amt2 === 'string') { return amt1 === amt2 } @@ -29,15 +41,32 @@ function amountsEqual(amt1: Amount, amt2: Amount): boolean { return false } + // Compare MPTs + if (isMPTAmount(amt1) && isMPTAmount(amt2)) { + const aValue = new BigNumber(amt1.value) + const bValue = new BigNumber(amt2.value) + + return ( + amt1.mpt_issuance_id === amt2.mpt_issuance_id && aValue.isEqualTo(bValue) + ) + } + + if (isMPTAmount(amt1) || isMPTAmount(amt2)) { + return false + } + + // Compare issued currency (IOU) const aValue = new BigNumber(amt1.value) const bValue = new BigNumber(amt2.value) return ( - amt1.currency === amt2.currency && - amt1.issuer === amt2.issuer && + (amt1 as IssuedCurrency).currency === (amt2 as IssuedCurrency).currency && + (amt1 as IssuedCurrency).issuer === (amt2 as IssuedCurrency).issuer && aValue.isEqualTo(bValue) ) } +/* eslint-enable complexity */ +/* eslint-enable @typescript-eslint/consistent-type-assertions */ function isPartialPayment( tx?: Transaction, diff --git a/packages/xrpl/src/models/common/index.ts b/packages/xrpl/src/models/common/index.ts index 98298d978f..cd16b08a49 100644 --- a/packages/xrpl/src/models/common/index.ts +++ b/packages/xrpl/src/models/common/index.ts @@ -20,6 +20,11 @@ export interface IssuedCurrencyAmount extends IssuedCurrency { value: string } +export interface MPTAmount { + mpt_issuance_id: string + value: string +} + export type Amount = IssuedCurrencyAmount | string export interface Balance { @@ -157,6 +162,16 @@ export interface AuthAccount { } } +export interface AuthorizeCredential { + Credential: { + /** The issuer of the credential. */ + Issuer: string + + /** A hex-encoded value to identify the type of credential from the issuer. */ + CredentialType: string + } +} + export interface XChainBridge { LockingChainDoor: string LockingChainIssue: Currency diff --git a/packages/xrpl/src/models/index.ts b/packages/xrpl/src/models/index.ts index 5c98faf82e..56212a9bc3 100644 --- a/packages/xrpl/src/models/index.ts +++ b/packages/xrpl/src/models/index.ts @@ -8,8 +8,9 @@ */ export * as LedgerEntry from './ledger' export { - setTransactionFlagsToNumber, parseAccountRootFlags, + setTransactionFlagsToNumber, + convertTxFlagsToNumber, parseTransactionFlags, } from './utils/flags' export * from './methods' diff --git a/packages/xrpl/src/models/ledger/Credential.ts b/packages/xrpl/src/models/ledger/Credential.ts new file mode 100644 index 0000000000..7716409ece --- /dev/null +++ b/packages/xrpl/src/models/ledger/Credential.ts @@ -0,0 +1,47 @@ +import { GlobalFlags } from '../transactions/common' + +import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' + +export interface CredentialFlags extends GlobalFlags { + lsfAccepted?: boolean +} + +/** + * + * A Credential object describes a credential, similar to a passport, which is an issuable identity verifier + * that can be used as a prerequisite for other transactions + * + * @category Ledger Entries + */ +export default interface Credential extends BaseLedgerEntry, HasPreviousTxnID { + LedgerEntryType: 'Credential' + /** + * A bit-map of boolean flags + */ + Flags: number | CredentialFlags + + /** The account that the credential is for. */ + Subject: string + + /** The issuer of the credential. */ + Issuer: string + + /** A hex-encoded value to identify the type of credential from the issuer. */ + CredentialType: string + + /** A hint indicating which page of the subject's owner directory links to this object, + * in case the directory consists of multiple pages. + */ + SubjectNode: string + + /** A hint indicating which page of the issuer's owner directory links to this object, + * in case the directory consists of multiple pages. + */ + IssuerNode: string + + /** Credential expiration. */ + Expiration?: number + + /** Additional data about the credential (such as a link to the VC document). */ + URI?: string +} diff --git a/packages/xrpl/src/models/ledger/DepositPreauth.ts b/packages/xrpl/src/models/ledger/DepositPreauth.ts index 70ba7d24a3..7d5d0804ae 100644 --- a/packages/xrpl/src/models/ledger/DepositPreauth.ts +++ b/packages/xrpl/src/models/ledger/DepositPreauth.ts @@ -1,3 +1,5 @@ +import { AuthorizeCredential } from '../common' + import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' /** @@ -12,8 +14,6 @@ export default interface DepositPreauth LedgerEntryType: 'DepositPreauth' /** The account that granted the preauthorization. */ Account: string - /** The account that received the preauthorization. */ - Authorize: string /** * A bit-map of boolean flags. No flags are defined for DepositPreauth * objects, so this value is always 0. @@ -24,4 +24,8 @@ export default interface DepositPreauth * object, in case the directory consists of multiple pages. */ OwnerNode: string + /** The account that received the preauthorization. */ + Authorize?: string + /** The credential(s) that received the preauthorization. */ + AuthorizeCredentials?: AuthorizeCredential[] } diff --git a/packages/xrpl/src/models/ledger/LedgerEntry.ts b/packages/xrpl/src/models/ledger/LedgerEntry.ts index 1f8f3ec32b..a9f7e4aa46 100644 --- a/packages/xrpl/src/models/ledger/LedgerEntry.ts +++ b/packages/xrpl/src/models/ledger/LedgerEntry.ts @@ -3,6 +3,7 @@ import Amendments from './Amendments' import AMM from './AMM' import Bridge from './Bridge' import Check from './Check' +import Credential from './Credential' import DepositPreauth from './DepositPreauth' import DirectoryNode from './DirectoryNode' import Escrow from './Escrow' @@ -24,6 +25,7 @@ type LedgerEntry = | AMM | Bridge | Check + | Credential | DepositPreauth | DirectoryNode | Escrow @@ -45,12 +47,15 @@ type LedgerEntryFilter = | 'amm' | 'bridge' | 'check' + | 'credential' | 'deposit_preauth' | 'did' | 'directory' | 'escrow' | 'fee' | 'hashes' + | 'mpt_issuance' + | 'mptoken' | 'nft_offer' | 'nft_page' | 'offer' diff --git a/packages/xrpl/src/models/ledger/MPToken.ts b/packages/xrpl/src/models/ledger/MPToken.ts new file mode 100644 index 0000000000..0de8d09a1f --- /dev/null +++ b/packages/xrpl/src/models/ledger/MPToken.ts @@ -0,0 +1,11 @@ +import { MPTAmount } from '../common' + +import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' + +export interface MPToken extends BaseLedgerEntry, HasPreviousTxnID { + LedgerEntryType: 'MPToken' + MPTokenIssuanceID: string + MPTAmount?: MPTAmount + Flags: number + OwnerNode?: string +} diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts new file mode 100644 index 0000000000..9bde70c7ab --- /dev/null +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -0,0 +1,13 @@ +import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' + +export interface MPTokenIssuance extends BaseLedgerEntry, HasPreviousTxnID { + LedgerEntryType: 'MPTokenIssuance' + Flags: number + Issuer: string + AssetScale?: number + MaximumAmount?: string + OutstandingAmount: string + TransferFee?: number + MPTokenMetadata?: string + OwnerNode?: string +} diff --git a/packages/xrpl/src/models/ledger/index.ts b/packages/xrpl/src/models/ledger/index.ts index 4433c2cc8a..4ac148f3d7 100644 --- a/packages/xrpl/src/models/ledger/index.ts +++ b/packages/xrpl/src/models/ledger/index.ts @@ -6,6 +6,7 @@ import Amendments, { Majority, AMENDMENTS_ID } from './Amendments' import AMM, { VoteSlot } from './AMM' import Bridge from './Bridge' import Check from './Check' +import Credential from './Credential' import DepositPreauth from './DepositPreauth' import DID from './DID' import DirectoryNode from './DirectoryNode' @@ -18,6 +19,8 @@ import FeeSettings, { import { Ledger, LedgerV1 } from './Ledger' import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry' import LedgerHashes from './LedgerHashes' +import { MPToken } from './MPToken' +import { MPTokenIssuance } from './MPTokenIssuance' import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL' import { NFTokenOffer } from './NFTokenOffer' import { NFToken, NFTokenPage } from './NFTokenPage' @@ -39,6 +42,7 @@ export { AMM, Bridge, Check, + Credential, DepositPreauth, DirectoryNode, DID, @@ -55,6 +59,8 @@ export { Majority, NEGATIVE_UNL_ID, NegativeUNL, + MPTokenIssuance, + MPToken, NFTokenOffer, NFTokenPage, NFToken, diff --git a/packages/xrpl/src/models/methods/depositAuthorized.ts b/packages/xrpl/src/models/methods/depositAuthorized.ts index 47952ad47a..c64b964e7f 100644 --- a/packages/xrpl/src/models/methods/depositAuthorized.ts +++ b/packages/xrpl/src/models/methods/depositAuthorized.ts @@ -15,6 +15,12 @@ export interface DepositAuthorizedRequest source_account: string /** The recipient of a possible payment. */ destination_account: string + /** + * The object IDs of Credential objects. If this field is included, then the + * credential will be taken into account when analyzing whether the sender can send + * funds to the destination. + */ + credentials?: string[] } /** @@ -52,5 +58,9 @@ export interface DepositAuthorizedResponse extends BaseResponse { source_account: string /** If true, the information comes from a validated ledger version. */ validated?: boolean + /** The object IDs of `Credential` objects. If this field is included, + * then the credential will be taken into account when analyzing whether + * the sender can send funds to the destination. */ + credentials?: string[] } } diff --git a/packages/xrpl/src/models/methods/ledgerEntry.ts b/packages/xrpl/src/models/methods/ledgerEntry.ts index d506525fe8..64fc64c493 100644 --- a/packages/xrpl/src/models/methods/ledgerEntry.ts +++ b/packages/xrpl/src/models/methods/ledgerEntry.ts @@ -21,6 +21,22 @@ import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod' */ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest { command: 'ledger_entry' + + /** + * Retrieve a MPTokenIssuance object from the ledger. + */ + mpt_issuance?: string + + /** + * Retrieve a MPToken object from the ledger. + */ + mptoken?: + | { + mpt_issuance_id: string + account: string + } + | string + /** * Retrieve an Automated Market Maker (AMM) object from the ledger. * This is similar to amm_info method, but the ledger_entry version returns only the ledger entry as stored. @@ -67,6 +83,23 @@ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest { /** The object ID of a Check object to retrieve. */ check?: string + /* Specify the Credential to retrieve. If a string, must be the ledger entry ID of + * the entry, as hexadecimal. If an object, requires subject, issuer, and + * credential_type sub-fields. + */ + credential?: + | { + /** The account that is the subject of the credential. */ + subject: string + + /** The account that issued the credential. */ + issuer: string + + /** The type of the credential, as issued. */ + credentialType: string + } + | string + /** * Specify a DepositPreauth object to retrieve. If a string, must be the * object ID of the DepositPreauth object, as hexadecimal. If an object, diff --git a/packages/xrpl/src/models/transactions/CredentialAccept.ts b/packages/xrpl/src/models/transactions/CredentialAccept.ts new file mode 100644 index 0000000000..cd0906fc71 --- /dev/null +++ b/packages/xrpl/src/models/transactions/CredentialAccept.ts @@ -0,0 +1,44 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateCredentialType, + validateRequiredField, +} from './common' + +/** + * Accepts a credential issued to the Account (i.e. the Account is the Subject of the Credential object). + * Credentials are represented in hex. Whilst they are allowed a maximum length of 64 + * bytes, every byte requires 2 hex characters for representation. + * The credential is not considered valid until it has been transferred/accepted. + * + * @category Transaction Models + * */ +export interface CredentialAccept extends BaseTransaction { + TransactionType: 'CredentialAccept' + + /** The subject of the credential. */ + Account: string + + /** The issuer of the credential. */ + Issuer: string + + /** A hex-encoded value to identify the type of credential from the issuer. */ + CredentialType: string +} + +/** + * Verify the form and type of a CredentialAccept at runtime. + * + * @param tx - A CredentialAccept Transaction. + * @throws When the CredentialAccept is Malformed. + */ +export function validateCredentialAccept(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'Account', isString) + + validateRequiredField(tx, 'Issuer', isString) + + validateCredentialType(tx) +} diff --git a/packages/xrpl/src/models/transactions/CredentialCreate.ts b/packages/xrpl/src/models/transactions/CredentialCreate.ts new file mode 100644 index 0000000000..8c82b7c74c --- /dev/null +++ b/packages/xrpl/src/models/transactions/CredentialCreate.ts @@ -0,0 +1,81 @@ +import { HEX_REGEX } from '@xrplf/isomorphic/utils' + +import { ValidationError } from '../../errors' + +import { + BaseTransaction, + isNumber, + isString, + validateBaseTransaction, + validateCredentialType, + validateOptionalField, + validateRequiredField, +} from './common' + +const MAX_URI_LENGTH = 256 + +/** + * Creates a Credential object. It must be sent by the issuer. + * + * @category Transaction Models + * */ +export interface CredentialCreate extends BaseTransaction { + TransactionType: 'CredentialCreate' + + /** The issuer of the credential. */ + Account: string + + /** The subject of the credential. */ + Subject: string + + /** A hex-encoded value to identify the type of credential from the issuer. */ + CredentialType: string + + /** Credential expiration. */ + Expiration?: number + + /** Additional data about the credential (such as a link to the VC document). */ + URI?: string +} + +/** + * Verify the form and type of a CredentialCreate at runtime. + * + * @param tx - A CredentialCreate Transaction. + * @throws When the CredentialCreate is Malformed. + */ +export function validateCredentialCreate(tx: Record): void { + validateBaseTransaction(tx) + + validateRequiredField(tx, 'Account', isString) + + validateRequiredField(tx, 'Subject', isString) + + validateCredentialType(tx) + + validateOptionalField(tx, 'Expiration', isNumber) + + validateURI(tx.URI) +} + +function validateURI(URI: unknown): void { + if (URI === undefined) { + return + } + + if (typeof URI !== 'string') { + throw new ValidationError('CredentialCreate: invalid field URI') + } + + if (URI.length === 0) { + throw new ValidationError('CredentialCreate: URI cannot be an empty string') + } else if (URI.length > MAX_URI_LENGTH) { + throw new ValidationError( + `CredentialCreate: URI length must be <= ${MAX_URI_LENGTH}`, + ) + } + + if (!HEX_REGEX.test(URI)) { + throw new ValidationError('CredentialCreate: URI must be encoded in hex') + } +} diff --git a/packages/xrpl/src/models/transactions/CredentialDelete.ts b/packages/xrpl/src/models/transactions/CredentialDelete.ts new file mode 100644 index 0000000000..d1b4ab0244 --- /dev/null +++ b/packages/xrpl/src/models/transactions/CredentialDelete.ts @@ -0,0 +1,55 @@ +import { ValidationError } from '../../errors' + +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateCredentialType, + validateOptionalField, + validateRequiredField, +} from './common' + +/** + * Deletes a Credential object. + * + * @category Transaction Models + * */ +export interface CredentialDelete extends BaseTransaction { + TransactionType: 'CredentialDelete' + + /** The transaction submitter. */ + Account: string + + /** A hex-encoded value to identify the type of credential from the issuer. */ + CredentialType: string + + /** The person that the credential is for. If omitted, Account is assumed to be the subject. */ + Subject?: string + + /** The issuer of the credential. If omitted, Account is assumed to be the issuer. */ + Issuer?: string +} + +/** + * Verify the form and type of a CredentialDelete at runtime. + * + * @param tx - A CredentialDelete Transaction. + * @throws When the CredentialDelete is Malformed. + */ +export function validateCredentialDelete(tx: Record): void { + validateBaseTransaction(tx) + + if (!tx.Subject && !tx.Issuer) { + throw new ValidationError( + 'CredentialDelete: either `Issuer` or `Subject` must be provided', + ) + } + + validateRequiredField(tx, 'Account', isString) + + validateCredentialType(tx) + + validateOptionalField(tx, 'Subject', isString) + + validateOptionalField(tx, 'Issuer', isString) +} diff --git a/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts new file mode 100644 index 0000000000..4453f571e7 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts @@ -0,0 +1,67 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, + Account, + validateOptionalField, + isAccount, + GlobalFlags, +} from './common' + +/** + * Transaction Flags for an MPTokenAuthorize Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenAuthorizeFlags { + /** + * If set and transaction is submitted by a holder, it indicates that the holder no + * longer wants to hold the MPToken, which will be deleted as a result. If the the holder's + * MPToken has non-zero balance while trying to set this flag, the transaction will fail. On + * the other hand, if set and transaction is submitted by an issuer, it would mean that the + * issuer wants to unauthorize the holder (only applicable for allow-listing), + * which would unset the lsfMPTAuthorized flag on the MPToken. + */ + tfMPTUnauthorize = 0x00000001, +} + +/** + * Map of flags to boolean values representing {@link MPTokenAuthorize} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenAuthorizeFlagsInterface extends GlobalFlags { + tfMPTUnauthorize?: boolean +} + +/** + * The MPTokenAuthorize transaction is used to globally lock/unlock a MPTokenIssuance, + * or lock/unlock an individual's MPToken. + */ +export interface MPTokenAuthorize extends BaseTransaction { + TransactionType: 'MPTokenAuthorize' + /** + * Identifies the MPTokenIssuance + */ + MPTokenIssuanceID: string + /** + * An optional XRPL Address of an individual token holder balance to lock/unlock. + * If omitted, this transaction will apply to all any accounts holding MPTs. + */ + Holder?: Account + Flags?: number | MPTokenAuthorizeFlagsInterface +} + +/** + * Verify the form and type of an MPTokenAuthorize at runtime. + * + * @param tx - An MPTokenAuthorize Transaction. + * @throws When the MPTokenAuthorize is Malformed. + */ +export function validateMPTokenAuthorize(tx: Record): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) + validateOptionalField(tx, 'Holder', isAccount) +} diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts new file mode 100644 index 0000000000..6566e4ce05 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -0,0 +1,179 @@ +import { ValidationError } from '../../errors' +import { isHex, INTEGER_SANITY_CHECK, isFlagEnabled } from '../utils' + +import { + BaseTransaction, + GlobalFlags, + validateBaseTransaction, + validateOptionalField, + isString, + isNumber, +} from './common' +import type { TransactionMetadataBase } from './metadata' + +// 2^63 - 1 +const MAX_AMT = '9223372036854775807' +const MAX_TRANSFER_FEE = 50000 + +/** + * Transaction Flags for an MPTokenIssuanceCreate Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenIssuanceCreateFlags { + /** + * If set, indicates that the MPT can be locked both individually and globally. + * If not set, the MPT cannot be locked in any way. + */ + tfMPTCanLock = 0x00000002, + /** + * If set, indicates that individual holders must be authorized. + * This enables issuers to limit who can hold their assets. + */ + tfMPTRequireAuth = 0x00000004, + /** + * If set, indicates that individual holders can place their balances into an escrow. + */ + tfMPTCanEscrow = 0x00000008, + /** + * If set, indicates that individual holders can trade their balances + * using the XRP Ledger DEX or AMM. + */ + tfMPTCanTrade = 0x00000010, + /** + * If set, indicates that tokens may be transferred to other accounts + * that are not the issuer. + */ + tfMPTCanTransfer = 0x00000020, + /** + * If set, indicates that the issuer may use the Clawback transaction + * to clawback value from individual holders. + */ + tfMPTCanClawback = 0x00000040, +} + +/** + * Map of flags to boolean values representing {@link MPTokenIssuanceCreate} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenIssuanceCreateFlagsInterface extends GlobalFlags { + tfMPTCanLock?: boolean + tfMPTRequireAuth?: boolean + tfMPTCanEscrow?: boolean + tfMPTCanTrade?: boolean + tfMPTCanTransfer?: boolean + tfMPTCanClawback?: boolean +} + +/** + * The MPTokenIssuanceCreate transaction creates a MPTokenIssuance object + * and adds it to the relevant directory node of the creator account. + * This transaction is the only opportunity an issuer has to specify any token fields + * that are defined as immutable (e.g., MPT Flags). If the transaction is successful, + * the newly created token will be owned by the account (the creator account) which + * executed the transaction. + */ +export interface MPTokenIssuanceCreate extends BaseTransaction { + TransactionType: 'MPTokenIssuanceCreate' + /** + * An asset scale is the difference, in orders of magnitude, between a standard unit and + * a corresponding fractional unit. More formally, the asset scale is a non-negative integer + * (0, 1, 2, â€Ļ) such that one standard unit equals 10^(-scale) of a corresponding + * fractional unit. If the fractional unit equals the standard unit, then the asset scale is 0. + * Note that this value is optional, and will default to 0 if not supplied. + */ + AssetScale?: number + /** + * Specifies the maximum asset amount of this token that should ever be issued. + * It is a non-negative integer string that can store a range of up to 63 bits. If not set, the max + * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF or 9223372036854775807) + * + * Example: + * ``` + * MaximumAmount: '9223372036854775807' + * ``` + */ + MaximumAmount?: string + /** + * Specifies the fee to charged by the issuer for secondary sales of the Token, + * if such sales are allowed. Valid values for this field are between 0 and 50,000 inclusive, + * allowing transfer rates of between 0.000% and 50.000% in increments of 0.001. + * The field must NOT be present if the `tfMPTCanTransfer` flag is not set. + */ + TransferFee?: number + /** + * Arbitrary metadata about this issuance, in hex format. + */ + MPTokenMetadata?: string | null + Flags?: number | MPTokenIssuanceCreateFlagsInterface +} + +export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase { + mpt_issuance_id?: string +} + +/* eslint-disable max-lines-per-function -- Not needed to reduce function */ +/** + * Verify the form and type of an MPTokenIssuanceCreate at runtime. + * + * @param tx - An MPTokenIssuanceCreate Transaction. + * @throws When the MPTokenIssuanceCreate is Malformed. + */ +export function validateMPTokenIssuanceCreate( + tx: Record, +): void { + validateBaseTransaction(tx) + validateOptionalField(tx, 'MaximumAmount', isString) + validateOptionalField(tx, 'MPTokenMetadata', isString) + validateOptionalField(tx, 'TransferFee', isNumber) + validateOptionalField(tx, 'AssetScale', isNumber) + + if (typeof tx.MPTokenMetadata === 'string' && tx.MPTokenMetadata === '') { + throw new ValidationError( + 'MPTokenIssuanceCreate: MPTokenMetadata must not be empty string', + ) + } + + if (typeof tx.MPTokenMetadata === 'string' && !isHex(tx.MPTokenMetadata)) { + throw new ValidationError( + 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', + ) + } + + if (typeof tx.MaximumAmount === 'string') { + if (!INTEGER_SANITY_CHECK.exec(tx.MaximumAmount)) { + throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount') + } else if ( + BigInt(tx.MaximumAmount) > BigInt(MAX_AMT) || + BigInt(tx.MaximumAmount) < BigInt(`0`) + ) { + throw new ValidationError( + 'MPTokenIssuanceCreate: MaximumAmount out of range', + ) + } + } + + if (typeof tx.TransferFee === 'number') { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary + const flags = tx.Flags as number | MPTokenIssuanceCreateFlagsInterface + const isTfMPTCanTransfer = + typeof flags === 'number' + ? isFlagEnabled(flags, MPTokenIssuanceCreateFlags.tfMPTCanTransfer) + : flags.tfMPTCanTransfer ?? false + + if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) { + throw new ValidationError( + `MPTokenIssuanceCreate: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`, + ) + } + + if (tx.TransferFee && !isTfMPTCanTransfer) { + throw new ValidationError( + 'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag', + ) + } + } +} +/* eslint-enable max-lines-per-function */ diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts new file mode 100644 index 0000000000..06cbfe9471 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts @@ -0,0 +1,34 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, +} from './common' + +/** + * The MPTokenIssuanceDestroy transaction is used to remove an MPTokenIssuance object + * from the directory node in which it is being held, effectively removing the token + * from the ledger. If this operation succeeds, the corresponding + * MPTokenIssuance is removed and the owner’s reserve requirement is reduced by one. + * This operation must fail if there are any holders who have non-zero balances. + */ +export interface MPTokenIssuanceDestroy extends BaseTransaction { + TransactionType: 'MPTokenIssuanceDestroy' + /** + * Identifies the MPTokenIssuance object to be removed by the transaction. + */ + MPTokenIssuanceID: string +} + +/** + * Verify the form and type of an MPTokenIssuanceDestroy at runtime. + * + * @param tx - An MPTokenIssuanceDestroy Transaction. + * @throws When the MPTokenIssuanceDestroy is Malformed. + */ +export function validateMPTokenIssuanceDestroy( + tx: Record, +): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) +} diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts new file mode 100644 index 0000000000..7e1c723382 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -0,0 +1,86 @@ +import { ValidationError } from '../../errors' +import { isFlagEnabled } from '../utils' + +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, + Account, + validateOptionalField, + isAccount, + GlobalFlags, +} from './common' + +/** + * Transaction Flags for an MPTokenIssuanceSet Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenIssuanceSetFlags { + /** + * If set, indicates that issuer locks the MPT + */ + tfMPTLock = 0x00000001, + /** + * If set, indicates that issuer unlocks the MPT + */ + tfMPTUnlock = 0x00000002, +} + +/** + * Map of flags to boolean values representing {@link MPTokenIssuanceSet} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlags { + tfMPTLock?: boolean + tfMPTUnlock?: boolean +} + +/** + * The MPTokenIssuanceSet transaction is used to globally lock/unlock a MPTokenIssuance, + * or lock/unlock an individual's MPToken. + */ +export interface MPTokenIssuanceSet extends BaseTransaction { + TransactionType: 'MPTokenIssuanceSet' + /** + * Identifies the MPTokenIssuance + */ + MPTokenIssuanceID: string + /** + * An optional XRPL Address of an individual token holder balance to lock/unlock. + * If omitted, this transaction will apply to all any accounts holding MPTs. + */ + Holder?: Account + Flags?: number | MPTokenIssuanceSetFlagsInterface +} + +/** + * Verify the form and type of an MPTokenIssuanceSet at runtime. + * + * @param tx - An MPTokenIssuanceSet Transaction. + * @throws When the MPTokenIssuanceSet is Malformed. + */ +export function validateMPTokenIssuanceSet(tx: Record): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) + validateOptionalField(tx, 'Holder', isAccount) + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary + const flags = tx.Flags as number | MPTokenIssuanceSetFlagsInterface + const isTfMPTLock = + typeof flags === 'number' + ? isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTLock) + : flags.tfMPTLock ?? false + + const isTfMPTUnlock = + typeof flags === 'number' + ? isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTUnlock) + : flags.tfMPTUnlock ?? false + + if (isTfMPTLock && isTfMPTUnlock) { + throw new ValidationError('MPTokenIssuanceSet: flag conflict') + } +} diff --git a/packages/xrpl/src/models/transactions/accountDelete.ts b/packages/xrpl/src/models/transactions/accountDelete.ts index bf9282ad5b..92344f2d18 100644 --- a/packages/xrpl/src/models/transactions/accountDelete.ts +++ b/packages/xrpl/src/models/transactions/accountDelete.ts @@ -4,6 +4,7 @@ import { isAccount, isNumber, validateBaseTransaction, + validateCredentialsList, validateOptionalField, validateRequiredField, } from './common' @@ -28,6 +29,12 @@ export interface AccountDelete extends BaseTransaction { * information for the recipient of the deleted account's leftover XRP. */ DestinationTag?: number + /** + * Credentials associated with sender of this transaction. The credentials included + * must not be expired. The list must not be empty when specified and cannot contain + * more than 8 credentials. + */ + CredentialIDs?: string[] } /** @@ -41,4 +48,11 @@ export function validateAccountDelete(tx: Record): void { validateRequiredField(tx, 'Destination', isAccount) validateOptionalField(tx, 'DestinationTag', isNumber) + + validateCredentialsList( + tx.CredentialIDs, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check + tx.TransactionType as string, + true, + ) } diff --git a/packages/xrpl/src/models/transactions/clawback.ts b/packages/xrpl/src/models/transactions/clawback.ts index fb51859cfc..759701118b 100644 --- a/packages/xrpl/src/models/transactions/clawback.ts +++ b/packages/xrpl/src/models/transactions/clawback.ts @@ -1,10 +1,13 @@ import { ValidationError } from '../../errors' -import { IssuedCurrencyAmount } from '../common' +import { IssuedCurrencyAmount, MPTAmount } from '../common' import { BaseTransaction, validateBaseTransaction, isIssuedCurrency, + isMPTAmount, + isAccount, + validateOptionalField, } from './common' /** @@ -15,15 +18,20 @@ export interface Clawback extends BaseTransaction { TransactionType: 'Clawback' /** * Indicates the AccountID that submitted this transaction. The account MUST - * be the issuer of the currency. + * be the issuer of the currency or MPT. */ Account: string /** - * The amount of currency to deliver, and it must be non-XRP. The nested field - * names MUST be lower-case. The `issuer` field MUST be the holder's address, + * The amount of currency or MPT to clawback, and it must be non-XRP. The nested field + * names MUST be lower-case. If the amount is IOU, the `issuer` field MUST be the holder's address, * whom to be clawed back. */ - Amount: IssuedCurrencyAmount + Amount: IssuedCurrencyAmount | MPTAmount + /** + * Indicates the AccountID that the issuer wants to clawback. This field is only valid for clawing back + * MPTs. + */ + Holder?: string } /** @@ -34,16 +42,29 @@ export interface Clawback extends BaseTransaction { */ export function validateClawback(tx: Record): void { validateBaseTransaction(tx) + validateOptionalField(tx, 'Holder', isAccount) if (tx.Amount == null) { throw new ValidationError('Clawback: missing field Amount') } - if (!isIssuedCurrency(tx.Amount)) { + if (!isIssuedCurrency(tx.Amount) && !isMPTAmount(tx.Amount)) { throw new ValidationError('Clawback: invalid Amount') } if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) { throw new ValidationError('Clawback: invalid holder Account') } + + if (isMPTAmount(tx.Amount) && tx.Account === tx.Holder) { + throw new ValidationError('Clawback: invalid holder Account') + } + + if (isIssuedCurrency(tx.Amount) && tx.Holder) { + throw new ValidationError('Clawback: cannot have Holder for currency') + } + + if (isMPTAmount(tx.Amount) && !tx.Holder) { + throw new ValidationError('Clawback: missing Holder') + } } diff --git a/packages/xrpl/src/models/transactions/common.ts b/packages/xrpl/src/models/transactions/common.ts index 5af72d038a..d82625355f 100644 --- a/packages/xrpl/src/models/transactions/common.ts +++ b/packages/xrpl/src/models/transactions/common.ts @@ -1,18 +1,25 @@ +/* eslint-disable max-lines -- common utility file */ +import { HEX_REGEX } from '@xrplf/isomorphic/utils' import { isValidClassicAddress, isValidXAddress } from 'ripple-address-codec' import { TRANSACTION_TYPES } from 'ripple-binary-codec' import { ValidationError } from '../../errors' import { Amount, + AuthorizeCredential, Currency, IssuedCurrencyAmount, Memo, Signer, XChainBridge, + MPTAmount, } from '../common' import { onlyHasFields } from '../utils' const MEMO_SIZE = 3 +const MAX_CREDENTIALS_LIST_LENGTH = 8 +const MAX_CREDENTIAL_BYTE_LENGTH = 64 +const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2 function isMemo(obj: { Memo?: unknown }): boolean { if (obj.Memo == null) { @@ -59,6 +66,8 @@ const XRP_CURRENCY_SIZE = 1 const ISSUE_SIZE = 2 const ISSUED_CURRENCY_SIZE = 3 const XCHAIN_BRIDGE_SIZE = 4 +const MPTOKEN_SIZE = 2 +const AUTHORIZE_CREDENTIAL_SIZE = 1 function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' @@ -119,6 +128,37 @@ export function isIssuedCurrency( ) } +/** + * Verify the form and type of an AuthorizeCredential at runtime + * + * @param input - The input to check the form and type of + * @returns Whether the AuthorizeCredential is properly formed + */ +function isAuthorizeCredential(input: unknown): input is AuthorizeCredential { + return ( + isRecord(input) && + isRecord(input.Credential) && + Object.keys(input).length === AUTHORIZE_CREDENTIAL_SIZE && + typeof input.Credential.CredentialType === 'string' && + typeof input.Credential.Issuer === 'string' + ) +} + +/** + * Verify the form and type of an MPT at runtime. + * + * @param input - The input to check the form and type of. + * @returns Whether the MPTAmount is properly formed. + */ +export function isMPTAmount(input: unknown): input is MPTAmount { + return ( + isRecord(input) && + Object.keys(input).length === MPTOKEN_SIZE && + typeof input.value === 'string' && + typeof input.mpt_issuance_id === 'string' + ) +} + /** * Must be a valid account address */ @@ -144,7 +184,11 @@ export function isAccount(account: unknown): account is Account { * @returns Whether the Amount is properly formed. */ export function isAmount(amount: unknown): amount is Amount { - return typeof amount === 'string' || isIssuedCurrency(amount) + return ( + typeof amount === 'string' || + isIssuedCurrency(amount) || + isMPTAmount(amount) + ) } /** @@ -366,3 +410,97 @@ export function parseAmountValue(amount: unknown): number { } return parseFloat(amount.value) } + +/** + * Verify the form and type of a CredentialType at runtime. + * + * @param tx A CredentialType Transaction. + * @throws when the CredentialType is malformed. + */ +export function validateCredentialType(tx: Record): void { + if (typeof tx.TransactionType !== 'string') { + throw new ValidationError('Invalid TransactionType') + } + if (tx.CredentialType === undefined) { + throw new ValidationError( + `${tx.TransactionType}: missing field CredentialType`, + ) + } + + if (!isString(tx.CredentialType)) { + throw new ValidationError( + `${tx.TransactionType}: CredentialType must be a string`, + ) + } + if (tx.CredentialType.length === 0) { + throw new ValidationError( + `${tx.TransactionType}: CredentialType cannot be an empty string`, + ) + } else if (tx.CredentialType.length > MAX_CREDENTIAL_TYPE_LENGTH) { + throw new ValidationError( + `${tx.TransactionType}: CredentialType length cannot be > ${MAX_CREDENTIAL_TYPE_LENGTH}`, + ) + } + + if (!HEX_REGEX.test(tx.CredentialType)) { + throw new ValidationError( + `${tx.TransactionType}: CredentialType must be encoded in hex`, + ) + } +} + +/** + * Check a CredentialAuthorize array for parameter errors + * + * @param credentials An array of credential IDs to check for errors + * @param transactionType The transaction type to include in error messages + * @param isStringID Toggle for if array contains IDs instead of AuthorizeCredential objects + * @throws Validation Error if the formatting is incorrect + */ +// eslint-disable-next-line max-lines-per-function -- separating logic further will add unnecessary complexity +export function validateCredentialsList( + credentials: unknown, + transactionType: string, + isStringID: boolean, +): void { + if (credentials == null) { + return + } + if (!Array.isArray(credentials)) { + throw new ValidationError( + `${transactionType}: Credentials must be an array`, + ) + } + if (credentials.length > MAX_CREDENTIALS_LIST_LENGTH) { + throw new ValidationError( + `${transactionType}: Credentials length cannot exceed ${MAX_CREDENTIALS_LIST_LENGTH} elements`, + ) + } else if (credentials.length === 0) { + throw new ValidationError( + `${transactionType}: Credentials cannot be an empty array`, + ) + } + credentials.forEach((credential) => { + if (isStringID) { + if (!isString(credential)) { + throw new ValidationError( + `${transactionType}: Invalid Credentials ID list format`, + ) + } + } else if (!isAuthorizeCredential(credential)) { + throw new ValidationError( + `${transactionType}: Invalid Credentials format`, + ) + } + }) + if (containsDuplicates(credentials)) { + throw new ValidationError( + `${transactionType}: Credentials cannot contain duplicate elements`, + ) + } +} + +function containsDuplicates(objectList: object[]): boolean { + const objSet = new Set(objectList.map((obj) => JSON.stringify(obj))) + return objSet.size !== objectList.length +} diff --git a/packages/xrpl/src/models/transactions/depositPreauth.ts b/packages/xrpl/src/models/transactions/depositPreauth.ts index eaf186e8b1..71476d42d4 100644 --- a/packages/xrpl/src/models/transactions/depositPreauth.ts +++ b/packages/xrpl/src/models/transactions/depositPreauth.ts @@ -1,6 +1,11 @@ import { ValidationError } from '../../errors' +import { AuthorizeCredential } from '../common' -import { BaseTransaction, validateBaseTransaction } from './common' +import { + BaseTransaction, + validateBaseTransaction, + validateCredentialsList, +} from './common' /** * A DepositPreauth transaction gives another account pre-approval to deliver @@ -18,6 +23,16 @@ export interface DepositPreauth extends BaseTransaction { * revoked. */ Unauthorize?: string + + /** + * The credential(s) to preauthorize. + */ + AuthorizeCredentials?: AuthorizeCredential[] + + /** + * The credential(s) whose preauthorization should be revoked. + */ + UnauthorizeCredentials?: AuthorizeCredential[] } /** @@ -29,17 +44,7 @@ export interface DepositPreauth extends BaseTransaction { export function validateDepositPreauth(tx: Record): void { validateBaseTransaction(tx) - if (tx.Authorize !== undefined && tx.Unauthorize !== undefined) { - throw new ValidationError( - "DepositPreauth: can't provide both Authorize and Unauthorize fields", - ) - } - - if (tx.Authorize === undefined && tx.Unauthorize === undefined) { - throw new ValidationError( - 'DepositPreauth: must provide either Authorize or Unauthorize field', - ) - } + validateSingleAuthorizationFieldProvided(tx) if (tx.Authorize !== undefined) { if (typeof tx.Authorize !== 'string') { @@ -51,9 +56,7 @@ export function validateDepositPreauth(tx: Record): void { "DepositPreauth: Account can't preauthorize its own address", ) } - } - - if (tx.Unauthorize !== undefined) { + } else if (tx.Unauthorize !== undefined) { if (typeof tx.Unauthorize !== 'string') { throw new ValidationError('DepositPreauth: Unauthorize must be a string') } @@ -63,5 +66,38 @@ export function validateDepositPreauth(tx: Record): void { "DepositPreauth: Account can't unauthorize its own address", ) } + } else if (tx.AuthorizeCredentials !== undefined) { + validateCredentialsList( + tx.AuthorizeCredentials, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check + tx.TransactionType as string, + false, + ) + } else if (tx.UnauthorizeCredentials !== undefined) { + validateCredentialsList( + tx.UnauthorizeCredentials, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check + tx.TransactionType as string, + false, + ) + } +} + +// Boolean logic to ensure exactly one of 4 inputs was provided +function validateSingleAuthorizationFieldProvided( + tx: Record, +): void { + const fields = [ + 'Authorize', + 'Unauthorize', + 'AuthorizeCredentials', + 'UnauthorizeCredentials', + ] + const countProvided = fields.filter((key) => tx[key] !== undefined).length + + if (countProvided !== 1) { + throw new ValidationError( + 'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.', + ) } } diff --git a/packages/xrpl/src/models/transactions/escrowFinish.ts b/packages/xrpl/src/models/transactions/escrowFinish.ts index e89da3c522..cba8fff202 100644 --- a/packages/xrpl/src/models/transactions/escrowFinish.ts +++ b/packages/xrpl/src/models/transactions/escrowFinish.ts @@ -5,6 +5,7 @@ import { BaseTransaction, isAccount, validateBaseTransaction, + validateCredentialsList, validateRequiredField, } from './common' @@ -32,6 +33,10 @@ export interface EscrowFinish extends BaseTransaction { * the held payment's Condition. */ Fulfillment?: string + /** Credentials associated with the sender of this transaction. + * The credentials included must not be expired. + */ + CredentialIDs?: string[] } /** @@ -45,6 +50,13 @@ export function validateEscrowFinish(tx: Record): void { validateRequiredField(tx, 'Owner', isAccount) + validateCredentialsList( + tx.CredentialIDs, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check + tx.TransactionType as string, + true, + ) + if (tx.OfferSequence == null) { throw new ValidationError('EscrowFinish: missing field OfferSequence') } diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 6ca9cc94f4..956f61a344 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -1,4 +1,4 @@ -export { BaseTransaction } from './common' +export { BaseTransaction, isMPTAmount } from './common' export { validate, PseudoTransaction, @@ -32,6 +32,9 @@ export { CheckCancel } from './checkCancel' export { CheckCash } from './checkCash' export { CheckCreate } from './checkCreate' export { Clawback } from './clawback' +export { CredentialAccept } from './CredentialAccept' +export { CredentialCreate } from './CredentialCreate' +export { CredentialDelete } from './CredentialDelete' export { DIDDelete } from './DIDDelete' export { DIDSet } from './DIDSet' export { DepositPreauth } from './depositPreauth' @@ -39,6 +42,22 @@ export { EscrowCancel } from './escrowCancel' export { EscrowCreate } from './escrowCreate' export { EscrowFinish } from './escrowFinish' export { EnableAmendment, EnableAmendmentFlags } from './enableAmendment' +export { + MPTokenAuthorize, + MPTokenAuthorizeFlags, + MPTokenAuthorizeFlagsInterface, +} from './MPTokenAuthorize' +export { + MPTokenIssuanceCreate, + MPTokenIssuanceCreateFlags, + MPTokenIssuanceCreateFlagsInterface, +} from './MPTokenIssuanceCreate' +export { MPTokenIssuanceDestroy } from './MPTokenIssuanceDestroy' +export { + MPTokenIssuanceSet, + MPTokenIssuanceSetFlags, + MPTokenIssuanceSetFlagsInterface, +} from './MPTokenIssuanceSet' export { NFTokenAcceptOffer } from './NFTokenAcceptOffer' export { NFTokenBurn } from './NFTokenBurn' export { NFTokenCancelOffer } from './NFTokenCancelOffer' diff --git a/packages/xrpl/src/models/transactions/metadata.ts b/packages/xrpl/src/models/transactions/metadata.ts index 3fcaeb54bf..75551d1a06 100644 --- a/packages/xrpl/src/models/transactions/metadata.ts +++ b/packages/xrpl/src/models/transactions/metadata.ts @@ -1,6 +1,10 @@ -import { Amount } from '../common' +import { Amount, MPTAmount } from '../common' import { BaseTransaction } from './common' +import { + MPTokenIssuanceCreate, + MPTokenIssuanceCreateMetadata, +} from './MPTokenIssuanceCreate' import { NFTokenAcceptOffer, NFTokenAcceptOfferMetadata, @@ -79,9 +83,9 @@ export function isDeletedNode(node: Node): node is DeletedNode { export interface TransactionMetadataBase { AffectedNodes: Node[] - DeliveredAmount?: Amount + DeliveredAmount?: Amount | MPTAmount // "unavailable" possible for transactions before 2014-01-20 - delivered_amount?: Amount | 'unavailable' + delivered_amount?: Amount | MPTAmount | 'unavailable' TransactionIndex: number TransactionResult: string } @@ -97,4 +101,6 @@ export type TransactionMetadata = ? NFTokenAcceptOfferMetadata : T extends NFTokenCancelOffer ? NFTokenCancelOfferMetadata + : T extends MPTokenIssuanceCreate + ? MPTokenIssuanceCreateMetadata : TransactionMetadataBase diff --git a/packages/xrpl/src/models/transactions/payment.ts b/packages/xrpl/src/models/transactions/payment.ts index 91d0b86c47..25f3dc8974 100644 --- a/packages/xrpl/src/models/transactions/payment.ts +++ b/packages/xrpl/src/models/transactions/payment.ts @@ -1,5 +1,5 @@ import { ValidationError } from '../../errors' -import { Amount, Path } from '../common' +import { Amount, Path, MPTAmount } from '../common' import { isFlagEnabled } from '../utils' import { @@ -12,6 +12,7 @@ import { validateOptionalField, isNumber, Account, + validateCredentialsList, } from './common' import type { TransactionMetadataBase } from './metadata' @@ -116,7 +117,7 @@ export interface Payment extends BaseTransaction { * names MUST be lower-case. If the tfPartialPayment flag is set, deliver up * to this amount instead. */ - Amount: Amount + Amount: Amount | MPTAmount /** The unique address of the account receiving the payment. */ Destination: Account /** @@ -142,19 +143,24 @@ export interface Payment extends BaseTransaction { * cross-currency/cross-issue payments. Must be omitted for XRP-to-XRP * Payments. */ - SendMax?: Amount + SendMax?: Amount | MPTAmount /** * Minimum amount of destination currency this transaction should deliver. * Only valid if this is a partial payment. For non-XRP amounts, the nested * field names are lower-case. */ - DeliverMin?: Amount + DeliverMin?: Amount | MPTAmount + /** + * Credentials associated with the sender of this transaction. + * The credentials included must not be expired. + */ + CredentialIDs?: string[] Flags?: number | PaymentFlagsInterface } export interface PaymentMetadata extends TransactionMetadataBase { - DeliveredAmount?: Amount - delivered_amount?: Amount | 'unavailable' + DeliveredAmount?: Amount | MPTAmount + delivered_amount?: Amount | MPTAmount | 'unavailable' } /** @@ -177,6 +183,13 @@ export function validatePayment(tx: Record): void { validateRequiredField(tx, 'Destination', isAccount) validateOptionalField(tx, 'DestinationTag', isNumber) + validateCredentialsList( + tx.CredentialIDs, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check + tx.TransactionType as string, + true, + ) + if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') { throw new ValidationError('PaymentTransaction: InvoiceID must be a string') } diff --git a/packages/xrpl/src/models/transactions/paymentChannelClaim.ts b/packages/xrpl/src/models/transactions/paymentChannelClaim.ts index c673f181bc..f99368b388 100644 --- a/packages/xrpl/src/models/transactions/paymentChannelClaim.ts +++ b/packages/xrpl/src/models/transactions/paymentChannelClaim.ts @@ -1,6 +1,11 @@ import { ValidationError } from '../../errors' -import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' +import { + BaseTransaction, + GlobalFlags, + validateBaseTransaction, + validateCredentialsList, +} from './common' /** * Enum representing values for PaymentChannelClaim transaction flags. @@ -127,6 +132,11 @@ export interface PaymentChannelClaim extends BaseTransaction { * field is omitted. */ PublicKey?: string + /** + * Credentials associated with the sender of this transaction. + * The credentials included must not be expired. + */ + CredentialIDs?: string[] } /** @@ -138,6 +148,13 @@ export interface PaymentChannelClaim extends BaseTransaction { export function validatePaymentChannelClaim(tx: Record): void { validateBaseTransaction(tx) + validateCredentialsList( + tx.CredentialIDs, + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check + tx.TransactionType as string, + true, + ) + if (tx.Channel === undefined) { throw new ValidationError('PaymentChannelClaim: missing Channel') } diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index cc8d09aab9..e774b2c41a 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -4,7 +4,7 @@ import { ValidationError } from '../../errors' import { IssuedCurrencyAmount, Memo } from '../common' import { isHex } from '../utils' -import { setTransactionFlagsToNumber } from '../utils/flags' +import { convertTxFlagsToNumber } from '../utils/flags' import { AccountDelete, validateAccountDelete } from './accountDelete' import { AccountSet, validateAccountSet } from './accountSet' @@ -19,6 +19,9 @@ import { CheckCash, validateCheckCash } from './checkCash' import { CheckCreate, validateCheckCreate } from './checkCreate' import { Clawback, validateClawback } from './clawback' import { BaseTransaction, isIssuedCurrency } from './common' +import { CredentialAccept, validateCredentialAccept } from './CredentialAccept' +import { CredentialCreate, validateCredentialCreate } from './CredentialCreate' +import { CredentialDelete, validateCredentialDelete } from './CredentialDelete' import { DepositPreauth, validateDepositPreauth } from './depositPreauth' import { DIDDelete, validateDIDDelete } from './DIDDelete' import { DIDSet, validateDIDSet } from './DIDSet' @@ -27,6 +30,19 @@ import { EscrowCancel, validateEscrowCancel } from './escrowCancel' import { EscrowCreate, validateEscrowCreate } from './escrowCreate' import { EscrowFinish, validateEscrowFinish } from './escrowFinish' import { TransactionMetadata } from './metadata' +import { MPTokenAuthorize, validateMPTokenAuthorize } from './MPTokenAuthorize' +import { + MPTokenIssuanceCreate, + validateMPTokenIssuanceCreate, +} from './MPTokenIssuanceCreate' +import { + MPTokenIssuanceDestroy, + validateMPTokenIssuanceDestroy, +} from './MPTokenIssuanceDestroy' +import { + MPTokenIssuanceSet, + validateMPTokenIssuanceSet, +} from './MPTokenIssuanceSet' import { NFTokenAcceptOffer, validateNFTokenAcceptOffer, @@ -110,12 +126,19 @@ export type SubmittableTransaction = | CheckCash | CheckCreate | Clawback + | CredentialAccept + | CredentialCreate + | CredentialDelete | DIDDelete | DIDSet | DepositPreauth | EscrowCancel | EscrowCreate | EscrowFinish + | MPTokenAuthorize + | MPTokenIssuanceCreate + | MPTokenIssuanceDestroy + | MPTokenIssuanceSet | NFTokenAcceptOffer | NFTokenBurn | NFTokenCancelOffer @@ -234,7 +257,7 @@ export function validate(transaction: Record): void { }) // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- okay here - setTransactionFlagsToNumber(tx as unknown as Transaction) + tx.Flags = convertTxFlagsToNumber(tx as unknown as Transaction) switch (tx.TransactionType) { case 'AMMBid': validateAMMBid(tx) @@ -284,6 +307,18 @@ export function validate(transaction: Record): void { validateClawback(tx) break + case 'CredentialAccept': + validateCredentialAccept(tx) + break + + case 'CredentialCreate': + validateCredentialCreate(tx) + break + + case 'CredentialDelete': + validateCredentialDelete(tx) + break + case 'DIDDelete': validateDIDDelete(tx) break @@ -308,6 +343,22 @@ export function validate(transaction: Record): void { validateEscrowFinish(tx) break + case 'MPTokenAuthorize': + validateMPTokenAuthorize(tx) + break + + case 'MPTokenIssuanceCreate': + validateMPTokenIssuanceCreate(tx) + break + + case 'MPTokenIssuanceDestroy': + validateMPTokenIssuanceDestroy(tx) + break + + case 'MPTokenIssuanceSet': + validateMPTokenIssuanceSet(tx) + break + case 'NFTokenAcceptOffer': validateNFTokenAcceptOffer(tx) break diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 81fba1aee7..c2a88662a7 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-param-reassign -- param reassign is safe */ /* eslint-disable no-bitwise -- flags require bitwise operations */ import { ValidationError } from '../../errors' import { @@ -8,7 +7,9 @@ import { import { AccountSetTfFlags } from '../transactions/accountSet' import { AMMDepositFlags } from '../transactions/AMMDeposit' import { AMMWithdrawFlags } from '../transactions/AMMWithdraw' -import { GlobalFlags } from '../transactions/common' +import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize' +import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' +import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' import { NFTokenCreateOfferFlags } from '../transactions/NFTokenCreateOffer' import { NFTokenMintFlags } from '../transactions/NFTokenMint' import { OfferCreateFlags } from '../transactions/offerCreate' @@ -48,6 +49,9 @@ const txToFlag = { AccountSet: AccountSetTfFlags, AMMDeposit: AMMDepositFlags, AMMWithdraw: AMMWithdrawFlags, + MPTokenAuthorize: MPTokenAuthorizeFlags, + MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags, + MPTokenIssuanceSet: MPTokenIssuanceSetFlags, NFTokenCreateOffer: NFTokenCreateOfferFlags, NFTokenMint: NFTokenMintFlags, OfferCreate: OfferCreateFlags, @@ -57,37 +61,61 @@ const txToFlag = { XChainModifyBridge: XChainModifyBridgeFlags, } +function isTxToFlagKey( + transactionType: string, +): transactionType is keyof typeof txToFlag { + return transactionType in txToFlag +} + /** * Sets a transaction's flags to its numeric representation. * + * @deprecated + * This utility function is deprecated. + * Use convertTxFlagsToNumber() instead and use the returned value to modify the Transaction.Flags from the caller. + * * @param tx - A transaction to set its flags to its numeric representation. */ export function setTransactionFlagsToNumber(tx: Transaction): void { - if (tx.Flags == null) { - tx.Flags = 0 - return + // eslint-disable-next-line no-console -- intended deprecation warning + console.warn( + 'This function is deprecated. Use convertTxFlagsToNumber() instead and use the returned value to modify the Transaction.Flags from the caller.', + ) + + if (tx.Flags) { + // eslint-disable-next-line no-param-reassign -- intended param reassign in setter, retain old functionality for compatibility + tx.Flags = convertTxFlagsToNumber(tx) + } +} + +/** + * Returns a Transaction's Flags as its numeric representation. + * + * @param tx - A Transaction to parse Flags for + * @returns A numerical representation of a Transaction's Flags + */ +export function convertTxFlagsToNumber(tx: Transaction): number { + if (!tx.Flags) { + return 0 } if (typeof tx.Flags === 'number') { - return + return tx.Flags } - tx.Flags = txToFlag[tx.TransactionType] - ? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType]) - : 0 -} + if (isTxToFlagKey(tx.TransactionType)) { + const flagEnum = txToFlag[tx.TransactionType] + return Object.keys(tx.Flags).reduce((resultFlags, flag) => { + if (flagEnum[flag] == null) { + throw new ValidationError( + `Invalid flag ${flag}. Valid flags are ${JSON.stringify(flagEnum)}`, + ) + } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum -function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number { - return Object.keys(flags).reduce((resultFlags, flag) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access - if (flagEnum[flag] == null) { - throw new ValidationError( - `flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`, - ) - } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access - return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags - }, 0) + return tx.Flags?.[flag] ? resultFlags | flagEnum[flag] : resultFlags + }, 0) + } + + return 0 } /** @@ -97,22 +125,24 @@ function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number { * @returns A map with all flags as booleans. */ export function parseTransactionFlags(tx: Transaction): object { - setTransactionFlagsToNumber(tx) - if (typeof tx.Flags !== 'number' || !tx.Flags || tx.Flags === 0) { + const flags = convertTxFlagsToNumber(tx) + if (flags === 0) { return {} } - const flags = tx.Flags - const flagsMap = {} + const booleanFlagMap = {} - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access - const flagEnum = txToFlag[tx.TransactionType] - Object.values(flagEnum).forEach((flag) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access - if (typeof flag === 'string' && isFlagEnabled(flags, flagEnum[flag])) { - flagsMap[flag] = true - } - }) + if (isTxToFlagKey(tx.TransactionType)) { + const transactionTypeFlags = txToFlag[tx.TransactionType] + Object.values(transactionTypeFlags).forEach((flag) => { + if ( + typeof flag === 'string' && + isFlagEnabled(flags, transactionTypeFlags[flag]) + ) { + booleanFlagMap[flag] = true + } + }) + } - return flagsMap + return booleanFlagMap } diff --git a/packages/xrpl/src/models/utils/index.ts b/packages/xrpl/src/models/utils/index.ts index 5a1d7520f0..54029bbd3c 100644 --- a/packages/xrpl/src/models/utils/index.ts +++ b/packages/xrpl/src/models/utils/index.ts @@ -1,4 +1,5 @@ const HEX_REGEX = /^[0-9A-Fa-f]+$/u +export const INTEGER_SANITY_CHECK = /^[0-9]+$/u /** * Verify that all fields of an object are in fields. diff --git a/packages/xrpl/test/integration/README.md b/packages/xrpl/test/integration/README.md index e4bdec3e33..cece8ff40c 100644 --- a/packages/xrpl/test/integration/README.md +++ b/packages/xrpl/test/integration/README.md @@ -1,7 +1,7 @@ To run integration tests: 1. Run rippled in standalone node, either in a docker container (preferred) or by installing rippled. * Go to the top-level of the `xrpl.js` repo, just above the `packages` folder. - * With docker, run `docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg` + * With docker, run `docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a'` * Or [download and build rippled](https://xrpl.org/install-rippled.html) and run `./rippled -a --start` * If you'd like to use the latest rippled amendments, you should modify your `rippled.cfg` file to enable amendments in the `[amendments]` section. You can view `.ci-config/rippled.cfg` in the top level folder as an example of this. 2. Run `npm run test:integration` or `npm run test:browser` diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index e0b3aedc43..58935a2782 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -6,6 +6,11 @@ import { TrustSet, Payment, Clawback, + MPTokenIssuanceCreate, + MPTokenIssuanceCreateFlags, + MPTokenAuthorize, + TransactionMetadata, + LedgerEntryResponse, } from '../../../src' import serverUrl from '../serverUrl' import { @@ -112,4 +117,92 @@ describe('Clawback', function () { }, TIMEOUT, ) + + it( + 'MPToken', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanClawback, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + const holderAuthTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, holderAuthTx, wallet2) + + const paymentTx: Payment = { + TransactionType: 'Payment', + Account: testContext.wallet.classicAddress, + Amount: { mpt_issuance_id: mptID!, value: '9223372036854775807' }, + Destination: wallet2.classicAddress, + } + + await testTransaction(testContext.client, paymentTx, testContext.wallet) + + let ledgerEntryResponse: LedgerEntryResponse = + await testContext.client.request({ + command: 'ledger_entry', + mptoken: { + mpt_issuance_id: mptID!, + account: wallet2.classicAddress, + }, + }) + + assert.equal( + // @ts-expect-error: Known issue with unknown object type + ledgerEntryResponse.result.node.MPTAmount, + '9223372036854775807', + ) + + // actual test - clawback + const clawTx: Clawback = { + TransactionType: 'Clawback', + Account: testContext.wallet.classicAddress, + Amount: { + mpt_issuance_id: mptID!, + value: '500', + }, + Holder: wallet2.classicAddress, + } + await testTransaction(testContext.client, clawTx, testContext.wallet) + + ledgerEntryResponse = await testContext.client.request({ + command: 'ledger_entry', + mptoken: { + mpt_issuance_id: mptID!, + account: wallet2.classicAddress, + }, + }) + + assert.equal( + // @ts-expect-error: Known issue with unknown object type + ledgerEntryResponse.result.node.MPTAmount, + '9223372036854775307', + ) + }, + TIMEOUT, + ) }) diff --git a/packages/xrpl/test/integration/transactions/credentialAccept.test.ts b/packages/xrpl/test/integration/transactions/credentialAccept.test.ts new file mode 100644 index 0000000000..7e8967c57d --- /dev/null +++ b/packages/xrpl/test/integration/transactions/credentialAccept.test.ts @@ -0,0 +1,62 @@ +import { stringToHex } from '@xrplf/isomorphic/utils' +import { assert } from 'chai' + +import { + AccountObjectsResponse, + CredentialAccept, + CredentialCreate, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { generateFundedWallet, testTransaction } from '../utils' + +describe('CredentialAccept', function () { + // testContext wallet acts as issuer in this test + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + }) + afterAll(async () => teardownClient(testContext)) + + it('base', async function () { + const subjectWallet = await generateFundedWallet(testContext.client) + + const credentialCreateTx: CredentialCreate = { + TransactionType: 'CredentialCreate', + Account: testContext.wallet.classicAddress, + Subject: subjectWallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialCreateTx, + testContext.wallet, + ) + + const credentialAcceptTx: CredentialAccept = { + TransactionType: 'CredentialAccept', + Account: subjectWallet.classicAddress, + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction(testContext.client, credentialAcceptTx, subjectWallet) + + // Credential is now an object in recipient's wallet after accept + const accountObjectsResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: subjectWallet.classicAddress, + type: 'credential', + }) + const { account_objects } = accountObjectsResponse.result + + assert.equal(account_objects.length, 1) + }) +}) diff --git a/packages/xrpl/test/integration/transactions/credentialCreate.test.ts b/packages/xrpl/test/integration/transactions/credentialCreate.test.ts new file mode 100644 index 0000000000..1d26595853 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/credentialCreate.test.ts @@ -0,0 +1,49 @@ +import { stringToHex } from '@xrplf/isomorphic/utils' +import { assert } from 'chai' + +import { AccountObjectsResponse, CredentialCreate } from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { generateFundedWallet, testTransaction } from '../utils' + +describe('CredentialCreate', function () { + // testContext wallet acts as issuer in this test + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + }) + afterAll(async () => teardownClient(testContext)) + + it('base', async function () { + const subjectWallet = await generateFundedWallet(testContext.client) + + const credentialCreateTx: CredentialCreate = { + TransactionType: 'CredentialCreate', + Account: testContext.wallet.classicAddress, + Subject: subjectWallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialCreateTx, + testContext.wallet, + ) + + // Unaccepted credential still belongs to issuer's account + const accountObjectsResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'credential', + }) + const { account_objects } = accountObjectsResponse.result + + assert.equal(account_objects.length, 1) + }) +}) diff --git a/packages/xrpl/test/integration/transactions/credentialDelete.test.ts b/packages/xrpl/test/integration/transactions/credentialDelete.test.ts new file mode 100644 index 0000000000..246397c66f --- /dev/null +++ b/packages/xrpl/test/integration/transactions/credentialDelete.test.ts @@ -0,0 +1,105 @@ +import { stringToHex } from '@xrplf/isomorphic/utils' +import { assert } from 'chai' + +import { + AccountObjectsResponse, + CredentialAccept, + CredentialCreate, +} from '../../../src' +import { CredentialDelete } from '../../../src/models/transactions/CredentialDelete' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { generateFundedWallet, testTransaction } from '../utils' + +describe('CredentialDelete', function () { + // testContext wallet acts as issuer in this test + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + }) + afterAll(async () => teardownClient(testContext)) + + it('base', async function () { + const subjectWallet = await generateFundedWallet(testContext.client) + + const credentialCreateTx: CredentialCreate = { + TransactionType: 'CredentialCreate', + Account: testContext.wallet.classicAddress, + Subject: subjectWallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialCreateTx, + testContext.wallet, + ) + + const createAccountObjectsResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'credential', + }) + + assert.equal(createAccountObjectsResponse.result.account_objects.length, 1) + + const credentialAcceptTx: CredentialAccept = { + TransactionType: 'CredentialAccept', + Account: subjectWallet.classicAddress, + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction(testContext.client, credentialAcceptTx, subjectWallet) + + // Credential is now an object in recipient's wallet after accept + const acceptAccountObjectsResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: subjectWallet.classicAddress, + type: 'credential', + }) + + assert.equal(acceptAccountObjectsResponse.result.account_objects.length, 1) + + const credentialDeleteTx: CredentialDelete = { + TransactionType: 'CredentialDelete', + Account: subjectWallet.classicAddress, + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction(testContext.client, credentialDeleteTx, subjectWallet) + + // Check both issuer and subject no longer have a credential tied to the account + const SubjectAccountObjectsDeleteResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: subjectWallet.classicAddress, + type: 'credential', + }) + + assert.equal( + SubjectAccountObjectsDeleteResponse.result.account_objects.length, + 0, + ) + + const IssuerAccountObjectsDeleteResponse: AccountObjectsResponse = + await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'credential', + }) + + assert.equal( + IssuerAccountObjectsDeleteResponse.result.account_objects.length, + 0, + ) + }) +}) diff --git a/packages/xrpl/test/integration/transactions/depositPreauth.test.ts b/packages/xrpl/test/integration/transactions/depositPreauth.test.ts index 4a8c8dcc3e..13960ffe01 100644 --- a/packages/xrpl/test/integration/transactions/depositPreauth.test.ts +++ b/packages/xrpl/test/integration/transactions/depositPreauth.test.ts @@ -1,11 +1,19 @@ -import { DepositPreauth, Wallet } from '../../../src' +import { stringToHex } from '@xrplf/isomorphic/utils' + +import { + AuthorizeCredential, + CredentialAccept, + CredentialCreate, + DepositPreauth, + Wallet, +} from '../../../src' import serverUrl from '../serverUrl' import { setupClient, teardownClient, type XrplIntegrationTestContext, } from '../setup' -import { fundAccount, testTransaction } from '../utils' +import { fundAccount, generateFundedWallet, testTransaction } from '../utils' // how long before each test case times out const TIMEOUT = 20000 @@ -32,4 +40,119 @@ describe('DepositPreauth', function () { }, TIMEOUT, ) + + it( + 'AuthorizeCredential base case', + async () => { + const subjectWallet = await generateFundedWallet(testContext.client) + + const credentialCreateTx: CredentialCreate = { + TransactionType: 'CredentialCreate', + Account: testContext.wallet.classicAddress, + Subject: subjectWallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialCreateTx, + testContext.wallet, + ) + + const credentialAcceptTx: CredentialAccept = { + TransactionType: 'CredentialAccept', + Account: subjectWallet.classicAddress, + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialAcceptTx, + subjectWallet, + ) + + const authorizeCredentialObj: AuthorizeCredential = { + Credential: { + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + }, + } + + const wallet2 = Wallet.generate() + await fundAccount(testContext.client, wallet2) + const tx: DepositPreauth = { + TransactionType: 'DepositPreauth', + Account: testContext.wallet.classicAddress, + AuthorizeCredentials: [authorizeCredentialObj], + } + await testTransaction(testContext.client, tx, testContext.wallet) + }, + TIMEOUT, + ) + + it( + 'UnauthorizeCredential base case', + async () => { + const subjectWallet = await generateFundedWallet(testContext.client) + + const credentialCreateTx: CredentialCreate = { + TransactionType: 'CredentialCreate', + Account: testContext.wallet.classicAddress, + Subject: subjectWallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialCreateTx, + testContext.wallet, + ) + + const credentialAcceptTx: CredentialAccept = { + TransactionType: 'CredentialAccept', + Account: subjectWallet.classicAddress, + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + } + + await testTransaction( + testContext.client, + credentialAcceptTx, + subjectWallet, + ) + + const authorizeCredentialObj: AuthorizeCredential = { + Credential: { + Issuer: testContext.wallet.classicAddress, + CredentialType: stringToHex('Test Credential Type'), + }, + } + + const wallet2 = Wallet.generate() + await fundAccount(testContext.client, wallet2) + const authCredDepositPreauthTx: DepositPreauth = { + TransactionType: 'DepositPreauth', + Account: testContext.wallet.classicAddress, + AuthorizeCredentials: [authorizeCredentialObj], + } + await testTransaction( + testContext.client, + authCredDepositPreauthTx, + testContext.wallet, + ) + + const UnauthCredDepositPreauthTx: DepositPreauth = { + TransactionType: 'DepositPreauth', + Account: testContext.wallet.classicAddress, + UnauthorizeCredentials: [authorizeCredentialObj], + } + await testTransaction( + testContext.client, + UnauthCredDepositPreauthTx, + testContext.wallet, + ) + }, + TIMEOUT, + ) }) diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts new file mode 100644 index 0000000000..57d310c556 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -0,0 +1,119 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenAuthorize, + MPTokenIssuanceCreateFlags, + MPTokenAuthorizeFlags, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction, generateFundedWallet } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenAuthorize', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTRequireAuth, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Should be exactly one issuance on the ledger', + ) + + let authTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + type: 'mptoken', + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Holder owns 1 MPToken on the ledger', + ) + + authTx = { + TransactionType: 'MPTokenAuthorize', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + Holder: wallet2.classicAddress, + } + + await testTransaction(testContext.client, authTx, testContext.wallet) + authTx = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 0, + 'Holder owns nothing on the ledger', + ) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts new file mode 100644 index 0000000000..59088d8dd9 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -0,0 +1,54 @@ +import { assert } from 'chai' + +import { MPTokenIssuanceCreate } from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceCreate', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const tx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', + AssetScale: 2, + } + + await testTransaction(testContext.client, tx, testContext.wallet) + + const accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Should be exactly one issuance on the ledger', + ) + assert.equal( + // @ts-expect-error: Known issue with unknown object type + accountObjectsResponse.result.account_objects[0].MaximumAmount, + `9223372036854775807`, + ) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts new file mode 100644 index 0000000000..03e5d30a78 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts @@ -0,0 +1,85 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenIssuanceDestroy, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceDestroy', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const destroyTx: MPTokenIssuanceDestroy = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, destroyTx, testContext.wallet) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 0, + 'Should be zero issuance on the ledger', + ) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts new file mode 100644 index 0000000000..195fbe5949 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -0,0 +1,78 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenIssuanceSet, + MPTokenIssuanceCreateFlags, + MPTokenIssuanceSetFlags, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceDestroy', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + const accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const setTx: MPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } + + await testTransaction(testContext.client, setTx, testContext.wallet) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/integration/transactions/payment.test.ts b/packages/xrpl/test/integration/transactions/payment.test.ts index 391ab317b2..2cccfc2c77 100644 --- a/packages/xrpl/test/integration/transactions/payment.test.ts +++ b/packages/xrpl/test/integration/transactions/payment.test.ts @@ -1,6 +1,12 @@ import { assert } from 'chai' -import { Payment, Wallet } from '../../../src' +import { + Payment, + Wallet, + MPTokenIssuanceCreate, + MPTokenAuthorize, + TransactionMetadata, +} from '../../../src' import serverUrl from '../serverUrl' import { setupClient, @@ -102,4 +108,89 @@ describe('Payment', function () { }, TIMEOUT, ) + + it( + 'Validate MPT Payment ', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id! + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const authTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + type: 'mptoken', + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects, + 1, + 'Holder owns 1 MPToken on the ledger', + ) + + const payTx: Payment = { + TransactionType: 'Payment', + Account: testContext.wallet.classicAddress, + Destination: wallet2.classicAddress, + Amount: { + mpt_issuance_id: mptID, + value: '100', + }, + } + + await testTransaction(testContext.client, payTx, testContext.wallet) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.equal( + // @ts-expect-error -- Object type not known + accountObjectsResponse.result.account_objects[0].OutstandingAmount, + `100`, + ) + }, + TIMEOUT, + ) }) diff --git a/packages/xrpl/test/models/CredentialAccept.test.ts b/packages/xrpl/test/models/CredentialAccept.test.ts new file mode 100644 index 0000000000..042101bd1f --- /dev/null +++ b/packages/xrpl/test/models/CredentialAccept.test.ts @@ -0,0 +1,153 @@ +import { stringToHex } from '@xrplf/isomorphic/dist/utils' +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateCredentialAccept } from '../../src/models/transactions/CredentialAccept' + +/** + * CredentialAccept Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('CredentialAccept', function () { + let credentialAccept + + beforeEach(function () { + credentialAccept = { + TransactionType: 'CredentialAccept', + Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ', + Account: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg', + CredentialType: stringToHex('Passport'), + Sequence: 1337, + Flags: 0, + } as any + }) + + it(`verifies valid CredentialAccept`, function () { + assert.doesNotThrow(() => validateCredentialAccept(credentialAccept)) + assert.doesNotThrow(() => validate(credentialAccept)) + }) + + it(`throws w/ missing field Account`, function () { + credentialAccept.Account = undefined + const errorMessage = 'CredentialAccept: missing field Account' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Account not a string`, function () { + credentialAccept.Account = 123 + const errorMessage = 'CredentialAccept: invalid field Account' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field Issuer`, function () { + credentialAccept.Issuer = undefined + const errorMessage = 'CredentialAccept: missing field Issuer' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Issuer not a string`, function () { + credentialAccept.Issuer = 123 + const errorMessage = 'CredentialAccept: invalid field Issuer' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field CredentialType`, function () { + credentialAccept.CredentialType = undefined + const errorMessage = 'CredentialAccept: missing field CredentialType' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field too long`, function () { + credentialAccept.CredentialType = stringToHex('A'.repeat(129)) + const errorMessage = + 'CredentialAccept: CredentialType length cannot be > 128' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field empty`, function () { + credentialAccept.CredentialType = '' + const errorMessage = + 'CredentialAccept: CredentialType cannot be an empty string' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field not hex`, function () { + credentialAccept.CredentialType = 'this is not hex' + const errorMessage = + 'CredentialAccept: CredentialType must be encoded in hex' + assert.throws( + () => validateCredentialAccept(credentialAccept), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialAccept), + ValidationError, + errorMessage, + ) + }) +}) diff --git a/packages/xrpl/test/models/CredentialCreate.test.ts b/packages/xrpl/test/models/CredentialCreate.test.ts new file mode 100644 index 0000000000..96a530c538 --- /dev/null +++ b/packages/xrpl/test/models/CredentialCreate.test.ts @@ -0,0 +1,230 @@ +import { stringToHex } from '@xrplf/isomorphic/dist/utils' +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateCredentialCreate } from '../../src/models/transactions/CredentialCreate' + +/** + * CredentialCreate Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('credentialCreate', function () { + let credentialCreate + + beforeEach(function () { + credentialCreate = { + TransactionType: 'CredentialCreate', + Account: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ', + Subject: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg', + CredentialType: stringToHex('Passport'), + Expiration: 1212025, + URI: stringToHex('TestURI'), + Sequence: 1337, + Flags: 0, + } as any + }) + + it(`verifies valid credentialCreate`, function () { + assert.doesNotThrow(() => validateCredentialCreate(credentialCreate)) + assert.doesNotThrow(() => validate(credentialCreate)) + }) + + it(`throws w/ missing field Account`, function () { + credentialCreate.Account = undefined + const errorMessage = 'CredentialCreate: missing field Account' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Account not string`, function () { + credentialCreate.Account = 123 + const errorMessage = 'CredentialCreate: invalid field Account' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field Subject`, function () { + credentialCreate.Subject = undefined + const errorMessage = 'CredentialCreate: missing field Subject' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Subject not string`, function () { + credentialCreate.Subject = 123 + const errorMessage = 'CredentialCreate: invalid field Subject' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field credentialType`, function () { + credentialCreate.CredentialType = undefined + const errorMessage = 'CredentialCreate: missing field CredentialType' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field too long`, function () { + credentialCreate.CredentialType = stringToHex('A'.repeat(129)) + const errorMessage = + 'CredentialCreate: CredentialType length cannot be > 128' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field empty`, function () { + credentialCreate.CredentialType = '' + const errorMessage = + 'CredentialCreate: CredentialType cannot be an empty string' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field not hex`, function () { + credentialCreate.CredentialType = 'this is not hex' + const errorMessage = + 'CredentialCreate: CredentialType must be encoded in hex' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Expiration field not number`, function () { + credentialCreate.Expiration = 'this is not a number' + const errorMessage = 'CredentialCreate: invalid field Expiration' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ URI field not a string`, function () { + credentialCreate.URI = 123 + const errorMessage = 'CredentialCreate: invalid field URI' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ URI field empty`, function () { + credentialCreate.URI = '' + const errorMessage = 'CredentialCreate: URI cannot be an empty string' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ URI field too long`, function () { + credentialCreate.URI = stringToHex('A'.repeat(129)) + const errorMessage = 'CredentialCreate: URI length must be <= 256' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ URI field not hex`, function () { + credentialCreate.URI = 'this is not hex' + const errorMessage = 'CredentialCreate: URI must be encoded in hex' + assert.throws( + () => validateCredentialCreate(credentialCreate), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialCreate), + ValidationError, + errorMessage, + ) + }) +}) diff --git a/packages/xrpl/test/models/CredentialDelete.test.ts b/packages/xrpl/test/models/CredentialDelete.test.ts new file mode 100644 index 0000000000..bb1ebc12c3 --- /dev/null +++ b/packages/xrpl/test/models/CredentialDelete.test.ts @@ -0,0 +1,171 @@ +import { stringToHex } from '@xrplf/isomorphic/dist/utils' +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' +import { validateCredentialDelete } from '../../src/models/transactions/CredentialDelete' + +/** + * CredentialDelete Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('CredentialDelete', function () { + let credentialDelete + + beforeEach(function () { + credentialDelete = { + TransactionType: 'CredentialDelete', + Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ', + Subject: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg', + Account: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg', + CredentialType: stringToHex('Passport'), + Sequence: 1337, + Flags: 0, + } as any + }) + + it(`verifies valid credentialDelete`, function () { + assert.doesNotThrow(() => validateCredentialDelete(credentialDelete)) + assert.doesNotThrow(() => validate(credentialDelete)) + }) + + it(`throws w/ missing field Account`, function () { + credentialDelete.Account = undefined + const errorMessage = 'CredentialDelete: missing field Account' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Account not string`, function () { + credentialDelete.Account = 123 + const errorMessage = 'CredentialDelete: invalid field Account' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Subject not string`, function () { + credentialDelete.Subject = 123 + const errorMessage = 'CredentialDelete: invalid field Subject' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ Issuer not string`, function () { + credentialDelete.Issuer = 123 + const errorMessage = 'CredentialDelete: invalid field Issuer' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field Subject and Issuer`, function () { + credentialDelete.Subject = undefined + credentialDelete.Issuer = undefined + const errorMessage = + 'CredentialDelete: either `Issuer` or `Subject` must be provided' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ missing field credentialType`, function () { + credentialDelete.CredentialType = undefined + const errorMessage = 'CredentialDelete: missing field CredentialType' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field too long`, function () { + credentialDelete.CredentialType = stringToHex('A'.repeat(129)) + const errorMessage = + 'CredentialDelete: CredentialType length cannot be > 128' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field empty`, function () { + credentialDelete.CredentialType = '' + const errorMessage = + 'CredentialDelete: CredentialType cannot be an empty string' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ credentialType field not hex`, function () { + credentialDelete.CredentialType = 'this is not hex' + const errorMessage = + 'CredentialDelete: CredentialType must be encoded in hex' + assert.throws( + () => validateCredentialDelete(credentialDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(credentialDelete), + ValidationError, + errorMessage, + ) + }) +}) diff --git a/packages/xrpl/test/models/MPTokenAuthorize.test.ts b/packages/xrpl/test/models/MPTokenAuthorize.test.ts new file mode 100644 index 0000000000..9e87dfa561 --- /dev/null +++ b/packages/xrpl/test/models/MPTokenAuthorize.test.ts @@ -0,0 +1,63 @@ +import { assert } from 'chai' + +import { validate, ValidationError, MPTokenAuthorizeFlags } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenAuthorize Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenAuthorize', function () { + it(`verifies valid MPTokenAuthorize`, function () { + let validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + }) + + it(`throws w/ missing MPTokenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenAuthorize: missing field MPTokenIssuanceID', + ) + }) +}) diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts new file mode 100644 index 0000000000..0752394382 --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -0,0 +1,149 @@ +import { assert } from 'chai' + +import { + convertStringToHex, + validate, + ValidationError, + MPTokenIssuanceCreateFlags, +} from '../../src' + +/** + * MPTokenIssuanceCreate Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceCreate', function () { + it(`verifies valid MPTokenIssuanceCreate`, function () { + const validMPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', + AssetScale: 2, + TransferFee: 1, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, + MPTokenMetadata: convertStringToHex('http://xrpl.org'), + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceCreate)) + }) + + it(`throws w/ MPTokenMetadata being an empty string`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + MPTokenMetadata: '', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MPTokenMetadata must not be empty string', + ) + }) + + it(`throws w/ MPTokenMetadata not in hex format`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + MPTokenMetadata: 'http://xrpl.org', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', + ) + }) + + it(`throws w/ Invalid MaximumAmount`, function () { + let invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '9223372036854775808', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MaximumAmount out of range', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '-1', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: Invalid MaximumAmount', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '0x12', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: Invalid MaximumAmount', + ) + }) + + it(`throws w/ Invalid TransferFee`, function () { + let invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: -1, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee must be between 0 and 50000', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: 50001, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee must be between 0 and 50000', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: 100, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: 100, + Flags: { tfMPTCanClawback: true }, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag', + ) + }) +}) diff --git a/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts new file mode 100644 index 0000000000..46bd53814c --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts @@ -0,0 +1,35 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenIssuanceDestroy Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceDestroy', function () { + it(`verifies valid MPTokenIssuanceDestroy`, function () { + const validMPTokenIssuanceDestroy = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceDestroy)) + }) + + it(`throws w/ missing MPTokenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceDestroy: missing field MPTokenIssuanceID', + ) + }) +}) diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts new file mode 100644 index 0000000000..0932727e54 --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -0,0 +1,82 @@ +import { assert } from 'chai' + +import { validate, ValidationError, MPTokenIssuanceSetFlags } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenIssuanceSet Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceSet', function () { + it(`verifies valid MPTokenIssuanceSet`, function () { + let validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + + validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + + // It's fine to not specify any flag, it means only tx fee is deducted + validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + }) + + it(`throws w/ missing MPTokenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceSet: missing field MPTokenIssuanceID', + ) + }) + + it(`throws w/ conflicting flags`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + } as any + + invalid.Flags = + // eslint-disable-next-line no-bitwise -- not needed + MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceSet: flag conflict', + ) + + invalid.Flags = { tfMPTLock: true, tfMPTUnlock: true } + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceSet: flag conflict', + ) + }) +}) diff --git a/packages/xrpl/test/models/accountDelete.test.ts b/packages/xrpl/test/models/accountDelete.test.ts index 4729bd7f03..34b0cfa0f0 100644 --- a/packages/xrpl/test/models/accountDelete.test.ts +++ b/packages/xrpl/test/models/accountDelete.test.ts @@ -9,8 +9,10 @@ import { validateAccountDelete } from '../../src/models/transactions/accountDele * Providing runtime verification testing for each specific transaction type. */ describe('AccountDelete', function () { - it(`verifies valid AccountDelete`, function () { - const validAccountDelete = { + let validAccountDelete + + beforeEach(() => { + validAccountDelete = { TransactionType: 'AccountDelete', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', @@ -18,76 +20,166 @@ describe('AccountDelete', function () { Fee: '5000000', Sequence: 2470665, Flags: 2147483648, + CredentialIDs: [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A', + ], } as any - + }) + it(`verifies valid AccountDelete`, function () { assert.doesNotThrow(() => validateAccountDelete(validAccountDelete)) }) it(`throws w/ missing Destination`, function () { - const invalidDestination = { - TransactionType: 'AccountDelete', - Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - Fee: '5000000', - Sequence: 2470665, - Flags: 2147483648, - } as any + validAccountDelete.Destination = undefined + const errorMessage = 'AccountDelete: missing field Destination' assert.throws( - () => validateAccountDelete(invalidDestination), + () => validateAccountDelete(validAccountDelete), ValidationError, - 'AccountDelete: missing field Destination', + errorMessage, ) assert.throws( - () => validate(invalidDestination), + () => validate(validAccountDelete), ValidationError, - 'AccountDelete: missing field Destination', + errorMessage, ) }) it(`throws w/ invalid Destination`, function () { - const invalidDestination = { - TransactionType: 'AccountDelete', - Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - Destination: 65478965, - Fee: '5000000', - Sequence: 2470665, - Flags: 2147483648, - } as any + validAccountDelete.Destination = 65478965 + const errorMessage = 'AccountDelete: invalid field Destination' assert.throws( - () => validateAccountDelete(invalidDestination), + () => validateAccountDelete(validAccountDelete), ValidationError, - 'AccountDelete: invalid field Destination', + errorMessage, ) assert.throws( - () => validate(invalidDestination), + () => validate(validAccountDelete), ValidationError, - 'AccountDelete: invalid field Destination', + errorMessage, ) }) it(`throws w/ invalid DestinationTag`, function () { - const invalidDestinationTag = { - TransactionType: 'AccountDelete', - Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe', - DestinationTag: 'gvftyujnbv', - Fee: '5000000', - Sequence: 2470665, - Flags: 2147483648, - } as any + validAccountDelete.DestinationTag = 'gvftyujnbv' + const errorMessage = 'AccountDelete: invalid field DestinationTag' + + assert.throws( + () => validateAccountDelete(validAccountDelete), + ValidationError, + errorMessage, + ) + + assert.throws( + () => validate(validAccountDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ non-array CredentialIDs`, function () { + validAccountDelete.CredentialIDs = + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A' + + const errorMessage = 'AccountDelete: Credentials must be an array' + + assert.throws( + () => validateAccountDelete(validAccountDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(validAccountDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws CredentialIDs length exceeds max length`, function () { + validAccountDelete.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = + 'AccountDelete: Credentials length cannot exceed 8 elements' assert.throws( - () => validateAccountDelete(invalidDestinationTag), + () => validateAccountDelete(validAccountDelete), ValidationError, - 'AccountDelete: invalid field DestinationTag', + errorMessage, ) + assert.throws( + () => validate(validAccountDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ empty CredentialIDs`, function () { + validAccountDelete.CredentialIDs = [] + const errorMessage = 'AccountDelete: Credentials cannot be an empty array' + + assert.throws( + () => validateAccountDelete(validAccountDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(validAccountDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ non-string CredentialIDs`, function () { + validAccountDelete.CredentialIDs = [ + 123123, + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = 'AccountDelete: Invalid Credentials ID list format' + + assert.throws( + () => validateAccountDelete(validAccountDelete), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(validAccountDelete), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ duplicate CredentialIDs`, function () { + validAccountDelete.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = + 'AccountDelete: Credentials cannot contain duplicate elements' + + assert.throws( + () => validateAccountDelete(validAccountDelete), + ValidationError, + errorMessage, + ) assert.throws( - () => validate(invalidDestinationTag), + () => validate(validAccountDelete), ValidationError, - 'AccountDelete: invalid field DestinationTag', + errorMessage, ) }) }) diff --git a/packages/xrpl/test/models/clawback.test.ts b/packages/xrpl/test/models/clawback.test.ts index b60f765311..30abcdfcb9 100644 --- a/packages/xrpl/test/models/clawback.test.ts +++ b/packages/xrpl/test/models/clawback.test.ts @@ -78,4 +78,72 @@ describe('Clawback', function () { 'Clawback: invalid holder Account', ) }) + + it(`verifies valid MPT Clawback`, function () { + const validClawback = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + + assert.doesNotThrow(() => validate(validClawback)) + }) + + it(`throws w/ invalid Holder Account`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Holder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: invalid holder Account', + ) + }) + + it(`throws w/ invalid Holder`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: missing Holder', + ) + }) + + it(`throws w/ invalid currency Holder`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + currency: 'DSH', + issuer: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + value: '43.11584856965009', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: cannot have Holder for currency', + ) + }) }) diff --git a/packages/xrpl/test/models/depositPreauth.test.ts b/packages/xrpl/test/models/depositPreauth.test.ts index 11e3305713..d2598ba452 100644 --- a/packages/xrpl/test/models/depositPreauth.test.ts +++ b/packages/xrpl/test/models/depositPreauth.test.ts @@ -1,6 +1,7 @@ +import { stringToHex } from '@xrplf/isomorphic/dist/utils' import { assert } from 'chai' -import { validate, ValidationError } from '../../src' +import { AuthorizeCredential, validate, ValidationError } from '../../src' import { validateDepositPreauth } from '../../src/models/transactions/depositPreauth' /** @@ -11,6 +12,13 @@ import { validateDepositPreauth } from '../../src/models/transactions/depositPre describe('DepositPreauth', function () { let depositPreauth + const validCredential = { + Credential: { + Issuer: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW', + CredentialType: stringToHex('Passport'), + }, + } + beforeEach(function () { depositPreauth = { TransactionType: 'DepositPreauth', @@ -30,32 +38,73 @@ describe('DepositPreauth', function () { assert.doesNotThrow(() => validate(depositPreauth)) }) - it('throws when both Authorize and Unauthorize are provided', function () { + it('verifies valid DepositPreauth when only AuthorizeCredentials is provided', function () { + depositPreauth.AuthorizeCredentials = [validCredential] + assert.doesNotThrow(() => validateDepositPreauth(depositPreauth)) + assert.doesNotThrow(() => validate(depositPreauth)) + }) + + it('verifies valid DepositPreauth when only UnauthorizeCredentials is provided', function () { + depositPreauth.UnauthorizeCredentials = [validCredential] + assert.doesNotThrow(() => validateDepositPreauth(depositPreauth)) + assert.doesNotThrow(() => validate(depositPreauth)) + }) + + it('throws when multiple of Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials are provided', function () { + const errorMessage = + 'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.' + depositPreauth.Authorize = 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW' + depositPreauth.UnauthorizeCredentials = [validCredential] + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + depositPreauth.Unauthorize = 'raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n' assert.throws( () => validateDepositPreauth(depositPreauth), ValidationError, - "DepositPreauth: can't provide both Authorize and Unauthorize fields", + errorMessage, ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + + depositPreauth.AuthorizeCredentials = [validCredential] assert.throws( - () => validate(depositPreauth), + () => validateDepositPreauth(depositPreauth), ValidationError, - "DepositPreauth: can't provide both Authorize and Unauthorize fields", + errorMessage, ) - }) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) - it('throws when neither Authorize nor Unauthorize are provided', function () { + depositPreauth.Authorize = undefined assert.throws( () => validateDepositPreauth(depositPreauth), ValidationError, - 'DepositPreauth: must provide either Authorize or Unauthorize field', + errorMessage, ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + + depositPreauth.UnauthorizeCredentials = undefined assert.throws( - () => validate(depositPreauth), + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when none of Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials are provided', function () { + const errorMessage = + 'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.' + assert.throws( + () => validateDepositPreauth(depositPreauth), ValidationError, - 'DepositPreauth: must provide either Authorize or Unauthorize field', + errorMessage, ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) }) it('throws when Authorize is not a string', function () { @@ -108,4 +157,154 @@ describe('DepositPreauth', function () { "DepositPreauth: Account can't unauthorize its own address", ) }) + + it('throws when AuthorizeCredentials is not an array', function () { + const errorMessage = 'DepositPreauth: Credentials must be an array' + depositPreauth.AuthorizeCredentials = validCredential + + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when UnauthorizeCredentials is not an array', function () { + const errorMessage = 'DepositPreauth: Credentials must be an array' + depositPreauth.UnauthorizeCredentials = validCredential + + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when AuthorizeCredentials is empty array', function () { + const errorMessage = 'DepositPreauth: Credentials cannot be an empty array' + depositPreauth.AuthorizeCredentials = [] + + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when UnauthorizeCredentials is empty array', function () { + const errorMessage = 'DepositPreauth: Credentials cannot be an empty array' + depositPreauth.UnauthorizeCredentials = [] + + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when AuthorizeCredentials is too long', function () { + const sampleCredentials: AuthorizeCredential[] = [] + const errorMessage = + 'DepositPreauth: Credentials length cannot exceed 8 elements' + for (let index = 0; index < 9; index++) { + sampleCredentials.push({ + Credential: { + Issuer: `SampleIssuer${index}`, + CredentialType: stringToHex('Passport'), + }, + }) + } + depositPreauth.AuthorizeCredentials = sampleCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when UnauthorizeCredentials is too long', function () { + const sampleCredentials: AuthorizeCredential[] = [] + const errorMessage = + 'DepositPreauth: Credentials length cannot exceed 8 elements' + for (let index = 0; index < 9; index++) { + sampleCredentials.push({ + Credential: { + Issuer: `SampleIssuer${index}`, + CredentialType: stringToHex('Passport'), + }, + }) + } + depositPreauth.UnauthorizeCredentials = sampleCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when AuthorizeCredentials is invalid shape', function () { + const invalidCredentials = [ + { Credential: 'Invalid Shape' }, + { Credential: 'Another Invalid Shape' }, + ] + const errorMessage = 'DepositPreauth: Invalid Credentials format' + + depositPreauth.AuthorizeCredentials = invalidCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when UnauthorizeCredentials is invalid shape', function () { + const invalidCredentials = [ + { Credential: 'Invalid Shape' }, + { Credential: 'Another Invalid Shape' }, + ] + const errorMessage = 'DepositPreauth: Invalid Credentials format' + + depositPreauth.UnauthorizeCredentials = invalidCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when AuthorizeCredentials has duplicates', function () { + const invalidCredentials = [validCredential, validCredential] + const errorMessage = + 'DepositPreauth: Credentials cannot contain duplicate elements' + + depositPreauth.AuthorizeCredentials = invalidCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) + + it('throws when UnauthorizeCredentials has duplicates', function () { + const invalidCredentials = [validCredential, validCredential] + const errorMessage = + 'DepositPreauth: Credentials cannot contain duplicate elements' + + depositPreauth.UnauthorizeCredentials = invalidCredentials + assert.throws( + () => validateDepositPreauth(depositPreauth), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(depositPreauth), ValidationError, errorMessage) + }) }) diff --git a/packages/xrpl/test/models/escrowFinish.test.ts b/packages/xrpl/test/models/escrowFinish.test.ts index e9d772a872..cac92d1e15 100644 --- a/packages/xrpl/test/models/escrowFinish.test.ts +++ b/packages/xrpl/test/models/escrowFinish.test.ts @@ -20,6 +20,9 @@ describe('EscrowFinish', function () { Condition: 'A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100', Fulfillment: 'A0028000', + CredentialIDs: [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A', + ], } }) it(`verifies valid EscrowFinish`, function () { @@ -28,8 +31,9 @@ describe('EscrowFinish', function () { }) it(`verifies valid EscrowFinish w/o optional`, function () { - delete escrow.Condition - delete escrow.Fulfillment + escrow.Condition = undefined + escrow.Fulfillment = undefined + escrow.CredentialIDs = undefined assert.doesNotThrow(() => validateEscrowFinish(escrow)) assert.doesNotThrow(() => validate(escrow)) @@ -101,4 +105,88 @@ describe('EscrowFinish', function () { 'EscrowFinish: Fulfillment must be a string', ) }) + + it(`throws w/ non-array CredentialIDs`, function () { + escrow.CredentialIDs = + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A' + + const errorMessage = 'EscrowFinish: Credentials must be an array' + + assert.throws( + () => validateEscrowFinish(escrow), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(escrow), ValidationError, errorMessage) + }) + + it(`throws CredentialIDs length exceeds max length`, function () { + escrow.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = + 'EscrowFinish: Credentials length cannot exceed 8 elements' + + assert.throws( + () => validateEscrowFinish(escrow), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(escrow), ValidationError, errorMessage) + }) + + it(`throws w/ empty CredentialIDs`, function () { + escrow.CredentialIDs = [] + + const errorMessage = 'EscrowFinish: Credentials cannot be an empty array' + + assert.throws( + () => validateEscrowFinish(escrow), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(escrow), ValidationError, errorMessage) + }) + + it(`throws w/ non-string CredentialIDs`, function () { + escrow.CredentialIDs = [ + 123123, + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = 'EscrowFinish: Invalid Credentials ID list format' + + assert.throws( + () => validateEscrowFinish(escrow), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(escrow), ValidationError, errorMessage) + }) + + it(`throws w/ duplicate CredentialIDs`, function () { + escrow.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = + 'EscrowFinish: Credentials cannot contain duplicate elements' + + assert.throws( + () => validateEscrowFinish(escrow), + ValidationError, + errorMessage, + ) + assert.throws(() => validate(escrow), ValidationError, errorMessage) + }) }) diff --git a/packages/xrpl/test/models/payment.test.ts b/packages/xrpl/test/models/payment.test.ts index a21eaddaf6..1af633e0a1 100644 --- a/packages/xrpl/test/models/payment.test.ts +++ b/packages/xrpl/test/models/payment.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-statements -- need additional tests for optional fields */ import { assert } from 'chai' import { validate, PaymentFlags, ValidationError } from '../../src' @@ -258,4 +259,121 @@ describe('Payment', function () { 'PaymentTransaction: tfPartialPayment flag required with DeliverMin', ) }) + + it(`verifies valid MPT PaymentTransaction`, function () { + const mptPaymentTransaction = { + TransactionType: 'Payment', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + assert.doesNotThrow(() => validatePayment(mptPaymentTransaction)) + assert.doesNotThrow(() => validate(mptPaymentTransaction)) + }) + + it(`throws w/ non-array CredentialIDs`, function () { + paymentTransaction.CredentialIDs = + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A' + + const errorMessage = 'Payment: Credentials must be an array' + + assert.throws( + () => validatePayment(paymentTransaction), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(paymentTransaction), + ValidationError, + errorMessage, + ) + }) + + it(`throws CredentialIDs length exceeds max length`, function () { + paymentTransaction.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = 'Payment: Credentials length cannot exceed 8 elements' + + assert.throws( + () => validatePayment(paymentTransaction), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(paymentTransaction), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ empty CredentialIDs`, function () { + paymentTransaction.CredentialIDs = [] + + const errorMessage = 'Payment: Credentials cannot be an empty array' + + assert.throws( + () => validatePayment(paymentTransaction), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(paymentTransaction), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ non-string CredentialIDs`, function () { + paymentTransaction.CredentialIDs = [ + 123123, + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = 'Payment: Invalid Credentials ID list format' + + assert.throws( + () => validatePayment(paymentTransaction), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(paymentTransaction), + ValidationError, + errorMessage, + ) + }) + + it(`throws w/ duplicate CredentialIDs`, function () { + paymentTransaction.CredentialIDs = [ + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + 'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662', + ] + + const errorMessage = + 'Payment: Credentials cannot contain duplicate elements' + + assert.throws( + () => validatePayment(paymentTransaction), + ValidationError, + errorMessage, + ) + assert.throws( + () => validate(paymentTransaction), + ValidationError, + errorMessage, + ) + }) }) diff --git a/packages/xrpl/test/models/utils.test.ts b/packages/xrpl/test/models/utils.test.ts index c4313610b7..88d9286a0a 100644 --- a/packages/xrpl/test/models/utils.test.ts +++ b/packages/xrpl/test/models/utils.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-deprecated -- using deprecated setTransactionFlagsToNumbers to ensure no breaking changes */ /* eslint-disable no-bitwise -- flags require bitwise operations */ import { assert } from 'chai' @@ -16,6 +17,7 @@ import { AccountRootFlags } from '../../src/models/ledger' import { isFlagEnabled } from '../../src/models/utils' import { setTransactionFlagsToNumber, + convertTxFlagsToNumber, parseAccountRootFlags, parseTransactionFlags, } from '../../src/models/utils/flags' @@ -46,6 +48,65 @@ describe('Models Utils', function () { }) }) + describe('parseAccountRootFlags', function () { + // eslint-disable-next-line complexity -- Simpler to list them all out at once. + it('all flags enabled', function () { + const accountRootFlags = + AccountRootFlags.lsfDefaultRipple | + AccountRootFlags.lsfDepositAuth | + AccountRootFlags.lsfDisableMaster | + AccountRootFlags.lsfDisallowXRP | + AccountRootFlags.lsfGlobalFreeze | + AccountRootFlags.lsfNoFreeze | + AccountRootFlags.lsfPasswordSpent | + AccountRootFlags.lsfRequireAuth | + AccountRootFlags.lsfRequireDestTag | + AccountRootFlags.lsfDisallowIncomingNFTokenOffer | + AccountRootFlags.lsfDisallowIncomingCheck | + AccountRootFlags.lsfDisallowIncomingPayChan | + AccountRootFlags.lsfDisallowIncomingTrustline | + AccountRootFlags.lsfAllowTrustLineClawback + + const parsed = parseAccountRootFlags(accountRootFlags) + + assert.isTrue( + parsed.lsfDefaultRipple && + parsed.lsfDepositAuth && + parsed.lsfDisableMaster && + parsed.lsfDisallowXRP && + parsed.lsfGlobalFreeze && + parsed.lsfNoFreeze && + parsed.lsfPasswordSpent && + parsed.lsfRequireAuth && + parsed.lsfRequireDestTag && + parsed.lsfDisallowIncomingNFTokenOffer && + parsed.lsfDisallowIncomingCheck && + parsed.lsfDisallowIncomingPayChan && + parsed.lsfDisallowIncomingTrustline && + parsed.lsfAllowTrustLineClawback, + ) + }) + + it('no flags set', function () { + const parsed = parseAccountRootFlags(0) + + assert.isUndefined(parsed.lsfDefaultRipple) + assert.isUndefined(parsed.lsfDepositAuth) + assert.isUndefined(parsed.lsfDisableMaster) + assert.isUndefined(parsed.lsfDisallowXRP) + assert.isUndefined(parsed.lsfGlobalFreeze) + assert.isUndefined(parsed.lsfNoFreeze) + assert.isUndefined(parsed.lsfPasswordSpent) + assert.isUndefined(parsed.lsfRequireAuth) + assert.isUndefined(parsed.lsfRequireDestTag) + assert.isUndefined(parsed.lsfDisallowIncomingNFTokenOffer) + assert.isUndefined(parsed.lsfDisallowIncomingCheck) + assert.isUndefined(parsed.lsfDisallowIncomingPayChan) + assert.isUndefined(parsed.lsfDisallowIncomingTrustline) + assert.isUndefined(parsed.lsfAllowTrustLineClawback) + }) + }) + describe('setTransactionFlagsToNumber', function () { it('sets OfferCreateFlags to its numeric value', function () { const tx: OfferCreate = { @@ -151,64 +212,9 @@ describe('Models Utils', function () { setTransactionFlagsToNumber(tx) assert.strictEqual(tx.Flags, 0) }) + }) - // eslint-disable-next-line complexity -- Simpler to list them all out at once. - it('parseAccountRootFlags all enabled', function () { - const accountRootFlags = - AccountRootFlags.lsfDefaultRipple | - AccountRootFlags.lsfDepositAuth | - AccountRootFlags.lsfDisableMaster | - AccountRootFlags.lsfDisallowXRP | - AccountRootFlags.lsfGlobalFreeze | - AccountRootFlags.lsfNoFreeze | - AccountRootFlags.lsfPasswordSpent | - AccountRootFlags.lsfRequireAuth | - AccountRootFlags.lsfRequireDestTag | - AccountRootFlags.lsfDisallowIncomingNFTokenOffer | - AccountRootFlags.lsfDisallowIncomingCheck | - AccountRootFlags.lsfDisallowIncomingPayChan | - AccountRootFlags.lsfDisallowIncomingTrustline | - AccountRootFlags.lsfAllowTrustLineClawback - - const parsed = parseAccountRootFlags(accountRootFlags) - - assert.isTrue( - parsed.lsfDefaultRipple && - parsed.lsfDepositAuth && - parsed.lsfDisableMaster && - parsed.lsfDisallowXRP && - parsed.lsfGlobalFreeze && - parsed.lsfNoFreeze && - parsed.lsfPasswordSpent && - parsed.lsfRequireAuth && - parsed.lsfRequireDestTag && - parsed.lsfDisallowIncomingNFTokenOffer && - parsed.lsfDisallowIncomingCheck && - parsed.lsfDisallowIncomingPayChan && - parsed.lsfDisallowIncomingTrustline && - parsed.lsfAllowTrustLineClawback, - ) - }) - - it('parseAccountFlags all false', function () { - const parsed = parseAccountRootFlags(0) - - assert.isUndefined(parsed.lsfDefaultRipple) - assert.isUndefined(parsed.lsfDepositAuth) - assert.isUndefined(parsed.lsfDisableMaster) - assert.isUndefined(parsed.lsfDisallowXRP) - assert.isUndefined(parsed.lsfGlobalFreeze) - assert.isUndefined(parsed.lsfNoFreeze) - assert.isUndefined(parsed.lsfPasswordSpent) - assert.isUndefined(parsed.lsfRequireAuth) - assert.isUndefined(parsed.lsfRequireDestTag) - assert.isUndefined(parsed.lsfDisallowIncomingNFTokenOffer) - assert.isUndefined(parsed.lsfDisallowIncomingCheck) - assert.isUndefined(parsed.lsfDisallowIncomingPayChan) - assert.isUndefined(parsed.lsfDisallowIncomingTrustline) - assert.isUndefined(parsed.lsfAllowTrustLineClawback) - }) - + describe('parseTransactionFlags', function () { it('parseTransactionFlags all enabled', function () { const tx: PaymentChannelClaim = { Account: 'r...', @@ -264,4 +270,111 @@ describe('Models Utils', function () { assert.notStrictEqual(flagsMap, expected) }) }) + + describe('convertTxFlagsToNumber', function () { + it('converts OfferCreateFlags to its numeric value', function () { + const tx: OfferCreate = { + Account: 'r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W', + Fee: '10', + TakerGets: { + currency: 'DSH', + issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX', + value: '43.11584856965009', + }, + TakerPays: '12928290425', + TransactionType: 'OfferCreate', + TxnSignature: + '3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91', + Flags: { + tfPassive: true, + tfImmediateOrCancel: false, + tfFillOrKill: true, + tfSell: false, + }, + } + + const { tfPassive, tfFillOrKill } = OfferCreateFlags + const expected: number = tfPassive | tfFillOrKill + + const result = convertTxFlagsToNumber(tx) + assert.strictEqual(result, expected) + }) + + it('converts PaymentChannelClaimFlags to its numeric value', function () { + const tx: PaymentChannelClaim = { + Account: 'r...', + TransactionType: 'PaymentChannelClaim', + Channel: + 'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198', + Flags: { + tfRenew: true, + tfClose: false, + }, + } + + const { tfRenew } = PaymentChannelClaimFlags + const expected: number = tfRenew + + const result = convertTxFlagsToNumber(tx) + assert.strictEqual(result, expected) + }) + + it('converts PaymentTransactionFlags to its numeric value', function () { + const tx: Payment = { + TransactionType: 'Payment', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + Amount: '1234', + Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + Flags: { + tfNoRippleDirect: false, + tfPartialPayment: true, + tfLimitQuality: true, + }, + } + + const { tfPartialPayment, tfLimitQuality } = PaymentFlags + const expected: number = tfPartialPayment | tfLimitQuality + + const result = convertTxFlagsToNumber(tx) + assert.strictEqual(result, expected) + }) + + it('converts TrustSetFlags to its numeric value', function () { + const tx: TrustSet = { + TransactionType: 'TrustSet', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + LimitAmount: { + currency: 'XRP', + issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX', + value: '4329.23', + }, + QualityIn: 1234, + QualityOut: 4321, + Flags: { + tfSetfAuth: true, + tfSetNoRipple: false, + tfClearNoRipple: true, + tfSetFreeze: false, + tfClearFreeze: true, + }, + } + + const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = TrustSetFlags + const expected: number = tfSetfAuth | tfClearNoRipple | tfClearFreeze + + const result = convertTxFlagsToNumber(tx) + assert.strictEqual(result, expected) + }) + + it('converts other transaction types flags to its numeric value', function () { + const tx: DepositPreauth = { + TransactionType: 'DepositPreauth', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + Flags: {}, + } + + const result = convertTxFlagsToNumber(tx) + assert.strictEqual(result, 0) + }) + }) })