mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-13 00:55:49 +00:00
Compare commits
33 Commits
xahau@4.0.
...
xrpl@4.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b16d0cfe3 | ||
|
|
35e40d9d71 | ||
|
|
ea9e3dcc98 | ||
|
|
61da4c567a | ||
|
|
189abc1a26 | ||
|
|
ce5ca316ca | ||
|
|
991a1d29a4 | ||
|
|
23d26c8c2e | ||
|
|
abdb192c69 | ||
|
|
84943ae0b6 | ||
|
|
d8126807a4 | ||
|
|
7a2fa3fcaa | ||
|
|
76c3355858 | ||
|
|
11e724253b | ||
|
|
f34d1a7a63 | ||
|
|
7bf6fecc71 | ||
|
|
303c2b983c | ||
|
|
448164da70 | ||
|
|
9f72c8d384 | ||
|
|
e42d418662 | ||
|
|
305f2c48bf | ||
|
|
b7dfcbf075 | ||
|
|
b04efe8c9e | ||
|
|
e3188b83ed | ||
|
|
f4011b58e7 | ||
|
|
00614753ff | ||
|
|
b3a76bd9c0 | ||
|
|
24e9ad7c12 | ||
|
|
c2dd2edbcc | ||
|
|
c9207337aa | ||
|
|
a6852dd588 | ||
|
|
3a604ce69a | ||
|
|
663b80f1d0 |
@@ -63,17 +63,10 @@ online_delete=256
|
|||||||
[debug_logfile]
|
[debug_logfile]
|
||||||
/var/log/rippled/debug.log
|
/var/log/rippled/debug.log
|
||||||
|
|
||||||
[sntp_servers]
|
|
||||||
time.windows.com
|
|
||||||
time.apple.com
|
|
||||||
time.nist.gov
|
|
||||||
pool.ntp.org
|
|
||||||
|
|
||||||
[ips]
|
[ips]
|
||||||
r.ripple.com 51235
|
r.ripple.com 51235
|
||||||
|
|
||||||
[validators_file]
|
|
||||||
validators.txt
|
|
||||||
|
|
||||||
[rpc_startup]
|
[rpc_startup]
|
||||||
{ "command": "log_level", "severity": "info" }
|
{ "command": "log_level", "severity": "info" }
|
||||||
@@ -178,3 +171,17 @@ PriceOracle
|
|||||||
fixEmptyDID
|
fixEmptyDID
|
||||||
fixXChainRewardRounding
|
fixXChainRewardRounding
|
||||||
fixPreviousTxnID
|
fixPreviousTxnID
|
||||||
|
fixAMMv1_1
|
||||||
|
# 2.3.0 Amendments
|
||||||
|
AMMClawback
|
||||||
|
fixAMMv1_2
|
||||||
|
Credentials
|
||||||
|
NFTokenMintOffer
|
||||||
|
MPTokensV1
|
||||||
|
fixNFTokenPageLinks
|
||||||
|
fixInnerObjTemplate2
|
||||||
|
fixEnforceNFTokenTrustline
|
||||||
|
fixReducedOffersV2
|
||||||
|
DeepFreeze
|
||||||
|
DynamicNFT
|
||||||
|
PermissionedDomains
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ reviews:
|
|||||||
review_status: true
|
review_status: true
|
||||||
# Generate walkthrough in a markdown collapsible section.
|
# Generate walkthrough in a markdown collapsible section.
|
||||||
collapse_walkthrough: false
|
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 the in-progress review if the pull request is closed or merged.
|
||||||
abort_on_close: true
|
abort_on_close: true
|
||||||
auto_review:
|
auto_review:
|
||||||
|
|||||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -33,11 +33,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v3
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# 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).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -62,4 +62,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|||||||
67
.github/workflows/nodejs.yml
vendored
67
.github/workflows/nodejs.yml
vendored
@@ -4,7 +4,7 @@
|
|||||||
name: Node.js CI
|
name: Node.js CI
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.2.0-b3
|
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -22,19 +22,19 @@ jobs:
|
|||||||
node-version: [18.x]
|
node-version: [18.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Setup npm version 9
|
- name: Setup npm version 10
|
||||||
run: |
|
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
|
- name: Cache node modules
|
||||||
id: cache-nodemodules
|
id: cache-nodemodules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-node-modules
|
cache-name: cache-node-modules
|
||||||
with:
|
with:
|
||||||
@@ -45,7 +45,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
||||||
${{ runner.os }}-deps-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
||||||
@@ -60,22 +59,22 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.x, 20.x]
|
node-version: [18.x, 20.x, 22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Setup npm version 9
|
- name: Setup npm version 10
|
||||||
run: |
|
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
|
- name: Cache node modules
|
||||||
id: cache-nodemodules
|
id: cache-nodemodules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-node-modules
|
cache-name: cache-node-modules
|
||||||
with:
|
with:
|
||||||
@@ -86,7 +85,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
||||||
${{ runner.os }}-deps-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
||||||
@@ -101,27 +99,27 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.x, 20.x]
|
node-version: [18.x, 20.x, 22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run docker in background
|
- name: Run docker in background
|
||||||
run: |
|
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 }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Setup npm version 9
|
- name: Setup npm version 10
|
||||||
run: |
|
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
|
- name: Cache node modules
|
||||||
id: cache-nodemodules
|
id: cache-nodemodules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-node-modules
|
cache-name: cache-node-modules
|
||||||
with:
|
with:
|
||||||
@@ -132,7 +130,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
||||||
${{ runner.os }}-deps-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
||||||
@@ -156,24 +153,24 @@ jobs:
|
|||||||
node-version: [18.x]
|
node-version: [18.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Run docker in background
|
- name: Run docker in background
|
||||||
run: |
|
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: |
|
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
|
- name: Cache node modules
|
||||||
id: cache-nodemodules
|
id: cache-nodemodules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-node-modules
|
cache-name: cache-node-modules
|
||||||
with:
|
with:
|
||||||
@@ -184,7 +181,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
||||||
${{ runner.os }}-deps-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
||||||
@@ -205,22 +201,22 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [18.x, 20.x]
|
node-version: [18.x, 20.x, 22.x]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Setup npm version 9
|
- name: Setup npm version 10
|
||||||
run: |
|
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
|
- name: Cache node modules
|
||||||
id: cache-nodemodules
|
id: cache-nodemodules
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-node-modules
|
cache-name: cache-node-modules
|
||||||
with:
|
with:
|
||||||
@@ -231,7 +227,6 @@ jobs:
|
|||||||
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
key: ${{ runner.os }}-deps-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
${{ runner.os }}-deps-${{ matrix.node-version }}-
|
||||||
${{ runner.os }}-deps-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
if: steps.cache-nodemodules.outputs.cache-hit != 'true'
|
||||||
|
|||||||
@@ -64,18 +64,20 @@ From the top-level xrpl.js folder (one level above `packages`), run the followin
|
|||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
# 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:develop -c 'rippled -a'
|
||||||
npm run build
|
npm run build
|
||||||
npm run test:integration
|
npm run test:integration
|
||||||
```
|
```
|
||||||
|
|
||||||
Breaking down the command:
|
Breaking down the command:
|
||||||
* `docker run -p 6006:6006` starts a Docker container with an open port for admin WebSocket requests.
|
* `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.
|
`--rm` tells docker to close the container after processes are done running.
|
||||||
* `-t` starts a terminal in the container for you to send commands to.
|
* `-it` allows you to interact with the container.
|
||||||
* `--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 `./`.
|
`--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
|
* `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:develop` manually overrides the entrypoint (for the latest version of rippled on the `develop` branch)
|
||||||
|
* `-c 'rippled -a'` provides the bash command to start `rippled` in standalone mode from the manual entrypoint
|
||||||
|
|
||||||
### Browser Tests
|
### Browser Tests
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ This should be run from the `xrpl.js` top level folder (one above the `packages`
|
|||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
# 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:develop -c 'rippled -a'
|
||||||
npm run test:browser
|
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.
|
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.
|
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.
|
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
|
## Generate reference docs
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ All of which works in Node.js (tested for v18+) & web browsers (tested for Chrom
|
|||||||
|
|
||||||
### Requirements
|
### 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
|
### Installing xrpl.js
|
||||||
|
|
||||||
|
|||||||
998
package-lock.json
generated
998
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -58,13 +58,13 @@
|
|||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"webpack": "^5.81.0",
|
"webpack": "^5.81.0",
|
||||||
"webpack-bundle-analyzer": "^4.1.0",
|
"webpack-bundle-analyzer": "^4.1.0",
|
||||||
"webpack-cli": "^5.0.1"
|
"webpack-cli": "^6.0.1"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"./packages/*"
|
"./packages/*"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0",
|
"node": ">=18.0.0",
|
||||||
"npm": ">=7.10.0 < 10.0.0"
|
"npm": ">=7.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,22 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## 2.3.0 (2025-2-13)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Support for the AMMClawback amendment (XLS-73)
|
||||||
|
* Support for the Permissioned Domains amendment (XLS-80).
|
||||||
|
|
||||||
|
## 2.2.0 (2024-12-23)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Support for the Multi-Purpose Token amendment (XLS-33)
|
||||||
|
|
||||||
## 2.1.0 (2024-06-03)
|
## 2.1.0 (2024-06-03)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Support for the Price Oracles amendment (XLS-47).
|
* Support for the Price Oracles amendment (XLS-47).
|
||||||
|
* Support for the `DynamicNFT` amendment (XLS-46)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Better error handling/error messages for serialization/deserialization errors.
|
* Better error handling/error messages for serialization/deserialization errors.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripple-binary-codec",
|
"name": "ripple-binary-codec",
|
||||||
"version": "2.1.0",
|
"version": "2.3.0",
|
||||||
"description": "XRP Ledger binary codec",
|
"description": "XRP Ledger binary codec",
|
||||||
"files": [
|
"files": [
|
||||||
"dist/*",
|
"dist/*",
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import { JsonObject, SerializedType } from './serialized-type'
|
|||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
|
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
|
||||||
import { readUInt32BE, writeUInt32BE } from '../utils'
|
import { readUInt32BE, writeUInt32BE } from '../utils'
|
||||||
|
import { Hash192 } from './hash-192'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants for validating amounts
|
* Constants for validating amounts
|
||||||
@@ -16,6 +17,7 @@ const MAX_IOU_PRECISION = 16
|
|||||||
const MAX_DROPS = new BigNumber('1e17')
|
const MAX_DROPS = new BigNumber('1e17')
|
||||||
const MIN_XRP = new BigNumber('1e-6')
|
const MIN_XRP = new BigNumber('1e-6')
|
||||||
const mask = BigInt(0x00000000ffffffff)
|
const mask = BigInt(0x00000000ffffffff)
|
||||||
|
const mptMask = BigInt(0x8000000000000000)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BigNumber configuration for Amount IOUs
|
* BigNumber configuration for Amount IOUs
|
||||||
@@ -27,20 +29,28 @@ BigNumber.config({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
interface AmountObjectIOU extends JsonObject {
|
||||||
* Interface for JSON objects that represent amounts
|
|
||||||
*/
|
|
||||||
interface AmountObject extends JsonObject {
|
|
||||||
value: string
|
value: string
|
||||||
currency: string
|
currency: string
|
||||||
issuer: string
|
issuer: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AmountObjectMPT extends JsonObject {
|
||||||
|
value: string
|
||||||
|
mpt_issuance_id: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type guard for AmountObject
|
* Interface for JSON objects that represent amounts
|
||||||
*/
|
*/
|
||||||
function isAmountObject(arg): arg is AmountObject {
|
type AmountObject = AmountObjectIOU | AmountObjectMPT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for AmountObjectIOU
|
||||||
|
*/
|
||||||
|
function isAmountObjectIOU(arg): arg is AmountObjectIOU {
|
||||||
const keys = Object.keys(arg).sort()
|
const keys = Object.keys(arg).sort()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
keys.length === 3 &&
|
keys.length === 3 &&
|
||||||
keys[0] === 'currency' &&
|
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
|
* 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
|
* @param value An Amount, object representing an IOU, or a string
|
||||||
* representing an integer amount
|
* representing an integer amount
|
||||||
@@ -88,7 +109,7 @@ class Amount extends SerializedType {
|
|||||||
return new Amount(amount)
|
return new Amount(amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAmountObject(value)) {
|
if (isAmountObjectIOU(value)) {
|
||||||
const number = new BigNumber(value.value)
|
const number = new BigNumber(value.value)
|
||||||
Amount.assertIouIsValid(number)
|
Amount.assertIouIsValid(number)
|
||||||
|
|
||||||
@@ -124,6 +145,24 @@ class Amount extends SerializedType {
|
|||||||
return new Amount(concat([amount, currency, issuer]))
|
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')
|
throw new Error('Invalid type to construct an Amount')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +173,12 @@ class Amount extends SerializedType {
|
|||||||
* @returns An Amount object
|
* @returns An Amount object
|
||||||
*/
|
*/
|
||||||
static fromParser(parser: BinaryParser): Amount {
|
static fromParser(parser: BinaryParser): Amount {
|
||||||
const isXRP = parser.peek() & 0x80
|
const isIOU = parser.peek() & 0x80
|
||||||
const numBytes = isXRP ? 48 : 8
|
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))
|
return new Amount(parser.read(numBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +199,9 @@ class Amount extends SerializedType {
|
|||||||
const num = (msb << BigInt(32)) | lsb
|
const num = (msb << BigInt(32)) | lsb
|
||||||
|
|
||||||
return `${sign}${num.toString()}`
|
return `${sign}${num.toString()}`
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (this.isIOU()) {
|
||||||
const parser = new BinaryParser(this.toString())
|
const parser = new BinaryParser(this.toString())
|
||||||
const mantissa = parser.read(8)
|
const mantissa = parser.read(8)
|
||||||
const currency = Currency.fromParser(parser) as Currency
|
const currency = Currency.fromParser(parser) as Currency
|
||||||
@@ -182,6 +227,27 @@ class Amount extends SerializedType {
|
|||||||
issuer: issuer.toJSON(),
|
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
|
* Ensure that the value after being multiplied by the exponent does not
|
||||||
* contain a decimal.
|
* contain a decimal.
|
||||||
@@ -248,7 +337,25 @@ class Amount extends SerializedType {
|
|||||||
* @returns true if Native (XRP)
|
* @returns true if Native (XRP)
|
||||||
*/
|
*/
|
||||||
private isNative(): boolean {
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
packages/ripple-binary-codec/src/types/hash-192.ts
Normal file
19
packages/ripple-binary-codec/src/types/hash-192.ts
Normal file
@@ -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 }
|
||||||
@@ -4,6 +4,7 @@ import { Blob } from './blob'
|
|||||||
import { Currency } from './currency'
|
import { Currency } from './currency'
|
||||||
import { Hash128 } from './hash-128'
|
import { Hash128 } from './hash-128'
|
||||||
import { Hash160 } from './hash-160'
|
import { Hash160 } from './hash-160'
|
||||||
|
import { Hash192 } from './hash-192'
|
||||||
import { Hash256 } from './hash-256'
|
import { Hash256 } from './hash-256'
|
||||||
import { Issue } from './issue'
|
import { Issue } from './issue'
|
||||||
import { PathSet } from './path-set'
|
import { PathSet } from './path-set'
|
||||||
@@ -25,6 +26,7 @@ const coreTypes: Record<string, typeof SerializedType> = {
|
|||||||
Currency,
|
Currency,
|
||||||
Hash128,
|
Hash128,
|
||||||
Hash160,
|
Hash160,
|
||||||
|
Hash192,
|
||||||
Hash256,
|
Hash256,
|
||||||
Issue,
|
Issue,
|
||||||
PathSet,
|
PathSet,
|
||||||
@@ -51,6 +53,7 @@ export {
|
|||||||
Currency,
|
Currency,
|
||||||
Hash128,
|
Hash128,
|
||||||
Hash160,
|
Hash160,
|
||||||
|
Hash192,
|
||||||
Hash256,
|
Hash256,
|
||||||
PathSet,
|
PathSet,
|
||||||
STArray,
|
STArray,
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ class SerializedType {
|
|||||||
* Can be customized for sidechains and amendments.
|
* Can be customized for sidechains and amendments.
|
||||||
* @returns any type, if not overloaded returns hexString representation of bytes
|
* @returns any type, if not overloaded returns hexString representation of bytes
|
||||||
*/
|
*/
|
||||||
toJSON(_definitions?: XrplDefinitionsBase): JSON {
|
toJSON(_definitions?: XrplDefinitionsBase, _fieldName?: string): JSON {
|
||||||
return this.toHex()
|
return this.toHex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { BinaryParser } from '../serdes/binary-parser'
|
|||||||
import { BinarySerializer, BytesList } from '../serdes/binary-serializer'
|
import { BinarySerializer, BytesList } from '../serdes/binary-serializer'
|
||||||
|
|
||||||
import { STArray } from './st-array'
|
import { STArray } from './st-array'
|
||||||
|
import { UInt64 } from './uint-64'
|
||||||
|
|
||||||
const OBJECT_END_MARKER_BYTE = Uint8Array.from([0xe1])
|
const OBJECT_END_MARKER_BYTE = Uint8Array.from([0xe1])
|
||||||
const OBJECT_END_MARKER = 'ObjectEndMarker'
|
const OBJECT_END_MARKER = 'ObjectEndMarker'
|
||||||
@@ -137,6 +138,8 @@ class STObject extends SerializedType {
|
|||||||
? this.from(xAddressDecoded[field.name], undefined, definitions)
|
? this.from(xAddressDecoded[field.name], undefined, definitions)
|
||||||
: field.type.name === 'STArray'
|
: field.type.name === 'STArray'
|
||||||
? STArray.from(xAddressDecoded[field.name], definitions)
|
? STArray.from(xAddressDecoded[field.name], definitions)
|
||||||
|
: field.type.name === 'UInt64'
|
||||||
|
? UInt64.from(xAddressDecoded[field.name], field.name)
|
||||||
: field.associatedType.from(xAddressDecoded[field.name])
|
: field.associatedType.from(xAddressDecoded[field.name])
|
||||||
|
|
||||||
if (associatedValue == undefined) {
|
if (associatedValue == undefined) {
|
||||||
@@ -182,7 +185,7 @@ class STObject extends SerializedType {
|
|||||||
|
|
||||||
accumulator[field.name] = objectParser
|
accumulator[field.name] = objectParser
|
||||||
.readFieldValue(field)
|
.readFieldValue(field)
|
||||||
.toJSON(definitions)
|
.toJSON(definitions, field.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return accumulator
|
return accumulator
|
||||||
|
|||||||
@@ -2,10 +2,20 @@ import { UInt } from './uint'
|
|||||||
import { BinaryParser } from '../serdes/binary-parser'
|
import { BinaryParser } from '../serdes/binary-parser'
|
||||||
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
|
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
|
||||||
import { readUInt32BE, writeUInt32BE } from '../utils'
|
import { readUInt32BE, writeUInt32BE } from '../utils'
|
||||||
|
import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums'
|
||||||
|
|
||||||
const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/
|
const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/
|
||||||
|
const BASE10_REGEX = /^[0-9]{1,20}$/
|
||||||
const mask = BigInt(0x00000000ffffffff)
|
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
|
* 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
|
* @param val A UInt64, hex-string, bigInt, or number
|
||||||
* @returns A UInt64 object
|
* @returns A UInt64 object
|
||||||
*/
|
*/
|
||||||
static from<T extends UInt64 | string | bigint | number>(val: T): UInt64 {
|
static from<T extends UInt64 | string | bigint | number>(
|
||||||
|
val: T,
|
||||||
|
fieldName = '',
|
||||||
|
): UInt64 {
|
||||||
if (val instanceof UInt64) {
|
if (val instanceof UInt64) {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
@@ -51,11 +64,18 @@ class UInt64 extends UInt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof val === 'string') {
|
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`)
|
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)
|
buf = hexToBytes(strBuf)
|
||||||
return new UInt64(buf)
|
return new UInt64(buf)
|
||||||
}
|
}
|
||||||
@@ -76,8 +96,16 @@ class UInt64 extends UInt {
|
|||||||
*
|
*
|
||||||
* @returns a hex-string
|
* @returns a hex-string
|
||||||
*/
|
*/
|
||||||
toJSON(): string {
|
toJSON(
|
||||||
return bytesToHex(this.bytes)
|
_definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS,
|
||||||
|
fieldName = '',
|
||||||
|
): string {
|
||||||
|
const hexString = bytesToHex(this.bytes)
|
||||||
|
if (useBase10(fieldName)) {
|
||||||
|
return BigInt('0x' + hexString).toString(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexString
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { coreTypes } from '../src/types'
|
import { coreTypes } from '../src/types'
|
||||||
import fixtures from './fixtures/data-driven-tests.json'
|
import fixtures from './fixtures/data-driven-tests.json'
|
||||||
|
|
||||||
|
import { makeParser } from '../src/binary'
|
||||||
const { Amount } = coreTypes
|
const { Amount } = coreTypes
|
||||||
|
|
||||||
function amountErrorTests() {
|
function amountErrorTests() {
|
||||||
@@ -25,6 +27,16 @@ describe('Amount', function () {
|
|||||||
it('can be parsed from', function () {
|
it('can be parsed from', function () {
|
||||||
expect(Amount.from('1000000') instanceof Amount).toBe(true)
|
expect(Amount.from('1000000') instanceof Amount).toBe(true)
|
||||||
expect(Amount.from('1000000').toJSON()).toEqual('1000000')
|
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 = {
|
const fixture = {
|
||||||
value: '1',
|
value: '1',
|
||||||
issuer: '0000000000000000000000000000000000000000',
|
issuer: '0000000000000000000000000000000000000000',
|
||||||
@@ -38,5 +50,35 @@ describe('Amount', function () {
|
|||||||
}
|
}
|
||||||
expect(amt.toJSON()).toEqual(rewritten)
|
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()
|
amountErrorTests()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ function assertEqualAmountJSON(actual, expected) {
|
|||||||
}
|
}
|
||||||
expect(actual.currency).toEqual(expected.currency)
|
expect(actual.currency).toEqual(expected.currency)
|
||||||
expect(actual.issuer).toEqual(expected.issuer)
|
expect(actual.issuer).toEqual(expected.issuer)
|
||||||
|
expect(actual.mpt_issuance_id).toEqual(expected.mpt_issuance_id)
|
||||||
expect(
|
expect(
|
||||||
actual.value === expected.value ||
|
actual.value === expected.value ||
|
||||||
new BigNumber(actual.value).eq(new BigNumber(expected.value)),
|
new BigNumber(actual.value).eq(new BigNumber(expected.value)),
|
||||||
@@ -207,12 +208,12 @@ function amountParsingTests() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const parser = makeParser(f.expected_hex)
|
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,
|
0,
|
||||||
16,
|
16,
|
||||||
)}...
|
)}...
|
||||||
as ${JSON.stringify(f.test_json)}`
|
as ${JSON.stringify(f.test_json)}`
|
||||||
it(testName, () => {
|
it(hexToJsonTestName, () => {
|
||||||
const value = parser.readType(Amount)
|
const value = parser.readType(Amount)
|
||||||
// May not actually be in canonical form. The fixtures are to be used
|
// May not actually be in canonical form. The fixtures are to be used
|
||||||
// also for json -> binary;
|
// also for json -> binary;
|
||||||
@@ -223,6 +224,15 @@ function amountParsingTests() {
|
|||||||
expect((exponent.e ?? 0) - 15).toEqual(f?.exponent)
|
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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2499,7 +2499,7 @@
|
|||||||
"type_id": 6,
|
"type_id": 6,
|
||||||
"is_native": true,
|
"is_native": true,
|
||||||
"type": "Amount",
|
"type": "Amount",
|
||||||
"expected_hex": "0000000000000001",
|
"error": "Value is negative",
|
||||||
"is_negative": true
|
"is_negative": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2914,6 +2914,170 @@
|
|||||||
"type": "Amount",
|
"type": "Amount",
|
||||||
"error": "10000000000000000000 absolute XRP is bigger than max native value 100000000000.0",
|
"error": "10000000000000000000 absolute XRP is bigger than max native value 100000000000.0",
|
||||||
"is_negative": true
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 () {
|
describe('Hash128', function () {
|
||||||
it('has a static width member', 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 () {
|
describe('Hash256', function () {
|
||||||
it('has a static width member', function () {
|
it('has a static width member', function () {
|
||||||
expect(Hash256.width).toBe(32)
|
expect(Hash256.width).toBe(32)
|
||||||
|
|||||||
@@ -73,7 +73,9 @@ describe('Signing data', function () {
|
|||||||
const customPaymentDefinitions = JSON.parse(
|
const customPaymentDefinitions = JSON.parse(
|
||||||
JSON.stringify(normalDefinitions),
|
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 newDefs = new XrplDefinitions(customPaymentDefinitions)
|
||||||
const actual = encodeForSigning(tx_json, newDefs)
|
const actual = encodeForSigning(tx_json, newDefs)
|
||||||
@@ -82,7 +84,7 @@ describe('Signing data', function () {
|
|||||||
'53545800', // signingPrefix
|
'53545800', // signingPrefix
|
||||||
// TransactionType
|
// TransactionType
|
||||||
'12',
|
'12',
|
||||||
'001F',
|
'00C8',
|
||||||
// Flags
|
// Flags
|
||||||
'22',
|
'22',
|
||||||
'80000000',
|
'80000000',
|
||||||
@@ -176,7 +178,9 @@ describe('Signing data', function () {
|
|||||||
const customPaymentDefinitions = JSON.parse(
|
const customPaymentDefinitions = JSON.parse(
|
||||||
JSON.stringify(normalDefinitions),
|
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 newDefs = new XrplDefinitions(customPaymentDefinitions)
|
||||||
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
|
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
|
||||||
@@ -187,7 +191,7 @@ describe('Signing data', function () {
|
|||||||
'534D5400', // signingPrefix
|
'534D5400', // signingPrefix
|
||||||
// TransactionType
|
// TransactionType
|
||||||
'12',
|
'12',
|
||||||
'001F',
|
'00C8',
|
||||||
// Flags
|
// Flags
|
||||||
'22',
|
'22',
|
||||||
'80000000',
|
'80000000',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UInt8, UInt64 } from '../src/types'
|
import { UInt8, UInt64 } from '../src/types'
|
||||||
import { encode } from '../src'
|
import { encode, decode } from '../src'
|
||||||
|
|
||||||
const binary =
|
const binary =
|
||||||
'11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F'
|
'11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F'
|
||||||
@@ -96,6 +96,40 @@ const jsonEntry2 = {
|
|||||||
index: '0000041EFD027808D3F78C8352F97E324CB816318E00B977C74ECDDC7CD975B2',
|
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]', () => {
|
it('compareToTests[0]', () => {
|
||||||
expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0)
|
expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0)
|
||||||
})
|
})
|
||||||
@@ -144,3 +178,20 @@ it('valueOf tests', () => {
|
|||||||
|
|
||||||
expect(val.valueOf() | 0x2).toBe(3)
|
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')
|
||||||
|
})
|
||||||
|
|||||||
@@ -2,10 +2,31 @@
|
|||||||
|
|
||||||
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.
|
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
|
||||||
|
|
||||||
|
## 4.2.0 (2025-2-13)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
* Support for the AMMClawback amendment (XLS-73)
|
||||||
|
* Adds utility function `convertTxFlagsToNumber`
|
||||||
|
* Support for the Permissioned Domains amendment (XLS-80).
|
||||||
|
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
|
||||||
|
* Support for XLS-77d Deep-Freeze amendment
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Include `network_id` field in the `server_state` response interface.
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
* Support for the `DynamicNFT` amendment (XLS-46)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* `TransactionStream` model supports APIv2
|
* `TransactionStream` model supports APIv2
|
||||||
@@ -47,6 +68,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
|||||||
## 3.0.0 (2024-02-01)
|
## 3.0.0 (2024-02-01)
|
||||||
|
|
||||||
### BREAKING CHANGES
|
### BREAKING CHANGES
|
||||||
|
* The default signing algorithm in the `Wallet` was changed from secp256k1 to ed25519
|
||||||
* Bump typescript to 5.x
|
* Bump typescript to 5.x
|
||||||
* Remove Node 14 support
|
* Remove Node 14 support
|
||||||
* Remove `crypto` polyfills, `create-hash`, `elliptic`, `hash.js`, and their many dependencies in favor of `@noble/hashes` and `@nobel/curves`
|
* Remove `crypto` polyfills, `create-hash`, `elliptic`, `hash.js`, and their many dependencies in favor of `@noble/hashes` and `@nobel/curves`
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "xrpl",
|
"name": "xrpl",
|
||||||
"version": "4.0.0",
|
"version": "4.2.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"bignumber.js": "^9.0.0",
|
"bignumber.js": "^9.0.0",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"ripple-address-codec": "^5.0.0",
|
"ripple-address-codec": "^5.0.0",
|
||||||
"ripple-binary-codec": "^2.1.0",
|
"ripple-binary-codec": "^2.3.0",
|
||||||
"ripple-keypairs": "^2.0.0"
|
"ripple-keypairs": "^2.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -41,9 +41,9 @@
|
|||||||
"karma-jasmine": "^5.1.0",
|
"karma-jasmine": "^5.1.0",
|
||||||
"karma-webpack": "^5.0.0",
|
"karma-webpack": "^5.0.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"react": "^18.2.0",
|
"react": "^19.0.0",
|
||||||
"run-s": "^0.0.0",
|
"run-s": "^0.0.0",
|
||||||
"typedoc": "0.26.10",
|
"typedoc": "0.27.6",
|
||||||
"ws": "^8.14.2"
|
"ws": "^8.14.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|||||||
@@ -40,14 +40,19 @@ import type {
|
|||||||
MarkerRequest,
|
MarkerRequest,
|
||||||
MarkerResponse,
|
MarkerResponse,
|
||||||
SubmitResponse,
|
SubmitResponse,
|
||||||
|
SimulateRequest,
|
||||||
} from '../models/methods'
|
} from '../models/methods'
|
||||||
import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers'
|
import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers'
|
||||||
|
import {
|
||||||
|
SimulateBinaryResponse,
|
||||||
|
SimulateJsonResponse,
|
||||||
|
} from '../models/methods/simulate'
|
||||||
import type {
|
import type {
|
||||||
EventTypes,
|
EventTypes,
|
||||||
OnEventToListenerMap,
|
OnEventToListenerMap,
|
||||||
} from '../models/methods/subscribe'
|
} from '../models/methods/subscribe'
|
||||||
import type { SubmittableTransaction } from '../models/transactions'
|
import type { SubmittableTransaction } from '../models/transactions'
|
||||||
import { setTransactionFlagsToNumber } from '../models/utils/flags'
|
import { convertTxFlagsToNumber } from '../models/utils/flags'
|
||||||
import {
|
import {
|
||||||
ensureClassicAddress,
|
ensureClassicAddress,
|
||||||
submitRequest,
|
submitRequest,
|
||||||
@@ -665,7 +670,7 @@ class Client extends EventEmitter<EventTypes> {
|
|||||||
const tx = { ...transaction }
|
const tx = { ...transaction }
|
||||||
|
|
||||||
setValidAddresses(tx)
|
setValidAddresses(tx)
|
||||||
setTransactionFlagsToNumber(tx)
|
tx.Flags = convertTxFlagsToNumber(tx)
|
||||||
|
|
||||||
const promises: Array<Promise<void>> = []
|
const promises: Array<Promise<void>> = []
|
||||||
if (tx.NetworkID == null) {
|
if (tx.NetworkID == null) {
|
||||||
@@ -764,6 +769,41 @@ class Client extends EventEmitter<EventTypes> {
|
|||||||
return submitRequest(this, signedTx, opts?.failHard)
|
return submitRequest(this, signedTx, opts?.failHard)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates an unsigned transaction.
|
||||||
|
* Steps performed on a transaction:
|
||||||
|
* 1. Autofill.
|
||||||
|
* 2. Sign & Encode.
|
||||||
|
* 3. Submit.
|
||||||
|
*
|
||||||
|
* @category Core
|
||||||
|
*
|
||||||
|
* @param transaction - A transaction to autofill, sign & encode, and submit.
|
||||||
|
* @param opts - (Optional) Options used to sign and submit a transaction.
|
||||||
|
* @param opts.binary - If true, return the metadata in a binary encoding.
|
||||||
|
*
|
||||||
|
* @returns A promise that contains SimulateResponse.
|
||||||
|
* @throws RippledError if the simulate request fails.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public async simulate<Binary extends boolean = false>(
|
||||||
|
transaction: SubmittableTransaction | string,
|
||||||
|
opts?: {
|
||||||
|
// If true, return the binary-encoded representation of the results.
|
||||||
|
binary?: Binary
|
||||||
|
},
|
||||||
|
): Promise<
|
||||||
|
Binary extends true ? SimulateBinaryResponse : SimulateJsonResponse
|
||||||
|
> {
|
||||||
|
// send request
|
||||||
|
const binary = opts?.binary ?? false
|
||||||
|
const request: SimulateRequest =
|
||||||
|
typeof transaction === 'string'
|
||||||
|
? { command: 'simulate', tx_blob: transaction, binary }
|
||||||
|
: { command: 'simulate', tx_json: transaction, binary }
|
||||||
|
return this.request(request)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously submits a transaction and verifies that it has been included in a
|
* Asynchronously submits a transaction and verifies that it has been included in a
|
||||||
* validated ledger (or has errored/will not be included for some reason).
|
* validated ledger (or has errored/will not be included for some reason).
|
||||||
|
|||||||
@@ -7,20 +7,32 @@ import type {
|
|||||||
TransactionV1Stream,
|
TransactionV1Stream,
|
||||||
TxResponse,
|
TxResponse,
|
||||||
} from '..'
|
} 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 {
|
import type {
|
||||||
AccountTxTransaction,
|
AccountTxTransaction,
|
||||||
RequestResponseMap,
|
RequestResponseMap,
|
||||||
} from '../models/methods'
|
} from '../models/methods'
|
||||||
import { AccountTxVersionResponseMap } from '../models/methods/accountTx'
|
import { AccountTxVersionResponseMap } from '../models/methods/accountTx'
|
||||||
import { BaseRequest, BaseResponse } from '../models/methods/baseMethod'
|
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 type { TransactionMetadata } from '../models/transactions/metadata'
|
||||||
import { isFlagEnabled } from '../models/utils'
|
import { isFlagEnabled } from '../models/utils'
|
||||||
|
|
||||||
const WARN_PARTIAL_PAYMENT_CODE = 2001
|
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') {
|
if (typeof amt1 === 'string' && typeof amt2 === 'string') {
|
||||||
return amt1 === amt2
|
return amt1 === amt2
|
||||||
}
|
}
|
||||||
@@ -29,15 +41,32 @@ function amountsEqual(amt1: Amount, amt2: Amount): boolean {
|
|||||||
return false
|
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 aValue = new BigNumber(amt1.value)
|
||||||
const bValue = new BigNumber(amt2.value)
|
const bValue = new BigNumber(amt2.value)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
amt1.currency === amt2.currency &&
|
(amt1 as IssuedCurrency).currency === (amt2 as IssuedCurrency).currency &&
|
||||||
amt1.issuer === amt2.issuer &&
|
(amt1 as IssuedCurrency).issuer === (amt2 as IssuedCurrency).issuer &&
|
||||||
aValue.isEqualTo(bValue)
|
aValue.isEqualTo(bValue)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
/* eslint-enable complexity */
|
||||||
|
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
||||||
|
|
||||||
function isPartialPayment(
|
function isPartialPayment(
|
||||||
tx?: Transaction,
|
tx?: Transaction,
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ export interface IssuedCurrencyAmount extends IssuedCurrency {
|
|||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MPTAmount {
|
||||||
|
mpt_issuance_id: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
export type Amount = IssuedCurrencyAmount | string
|
export type Amount = IssuedCurrencyAmount | string
|
||||||
|
|
||||||
export interface Balance {
|
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 {
|
export interface XChainBridge {
|
||||||
LockingChainDoor: string
|
LockingChainDoor: string
|
||||||
LockingChainIssue: Currency
|
LockingChainIssue: Currency
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
*/
|
*/
|
||||||
export * as LedgerEntry from './ledger'
|
export * as LedgerEntry from './ledger'
|
||||||
export {
|
export {
|
||||||
setTransactionFlagsToNumber,
|
|
||||||
parseAccountRootFlags,
|
parseAccountRootFlags,
|
||||||
|
setTransactionFlagsToNumber,
|
||||||
|
convertTxFlagsToNumber,
|
||||||
parseTransactionFlags,
|
parseTransactionFlags,
|
||||||
} from './utils/flags'
|
} from './utils/flags'
|
||||||
export * from './methods'
|
export * from './methods'
|
||||||
|
|||||||
47
packages/xrpl/src/models/ledger/Credential.ts
Normal file
47
packages/xrpl/src/models/ledger/Credential.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { AuthorizeCredential } from '../common'
|
||||||
|
|
||||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,8 +14,6 @@ export default interface DepositPreauth
|
|||||||
LedgerEntryType: 'DepositPreauth'
|
LedgerEntryType: 'DepositPreauth'
|
||||||
/** The account that granted the preauthorization. */
|
/** The account that granted the preauthorization. */
|
||||||
Account: string
|
Account: string
|
||||||
/** The account that received the preauthorization. */
|
|
||||||
Authorize: string
|
|
||||||
/**
|
/**
|
||||||
* A bit-map of boolean flags. No flags are defined for DepositPreauth
|
* A bit-map of boolean flags. No flags are defined for DepositPreauth
|
||||||
* objects, so this value is always 0.
|
* objects, so this value is always 0.
|
||||||
@@ -24,4 +24,8 @@ export default interface DepositPreauth
|
|||||||
* object, in case the directory consists of multiple pages.
|
* object, in case the directory consists of multiple pages.
|
||||||
*/
|
*/
|
||||||
OwnerNode: string
|
OwnerNode: string
|
||||||
|
/** The account that received the preauthorization. */
|
||||||
|
Authorize?: string
|
||||||
|
/** The credential(s) that received the preauthorization. */
|
||||||
|
AuthorizeCredentials?: AuthorizeCredential[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import Amendments from './Amendments'
|
|||||||
import AMM from './AMM'
|
import AMM from './AMM'
|
||||||
import Bridge from './Bridge'
|
import Bridge from './Bridge'
|
||||||
import Check from './Check'
|
import Check from './Check'
|
||||||
|
import Credential from './Credential'
|
||||||
import DepositPreauth from './DepositPreauth'
|
import DepositPreauth from './DepositPreauth'
|
||||||
import DirectoryNode from './DirectoryNode'
|
import DirectoryNode from './DirectoryNode'
|
||||||
import Escrow from './Escrow'
|
import Escrow from './Escrow'
|
||||||
@@ -12,6 +13,7 @@ import NegativeUNL from './NegativeUNL'
|
|||||||
import Offer from './Offer'
|
import Offer from './Offer'
|
||||||
import Oracle from './Oracle'
|
import Oracle from './Oracle'
|
||||||
import PayChannel from './PayChannel'
|
import PayChannel from './PayChannel'
|
||||||
|
import PermissionedDomain from './PermissionedDomain'
|
||||||
import RippleState from './RippleState'
|
import RippleState from './RippleState'
|
||||||
import SignerList from './SignerList'
|
import SignerList from './SignerList'
|
||||||
import Ticket from './Ticket'
|
import Ticket from './Ticket'
|
||||||
@@ -24,6 +26,7 @@ type LedgerEntry =
|
|||||||
| AMM
|
| AMM
|
||||||
| Bridge
|
| Bridge
|
||||||
| Check
|
| Check
|
||||||
|
| Credential
|
||||||
| DepositPreauth
|
| DepositPreauth
|
||||||
| DirectoryNode
|
| DirectoryNode
|
||||||
| Escrow
|
| Escrow
|
||||||
@@ -33,6 +36,7 @@ type LedgerEntry =
|
|||||||
| Offer
|
| Offer
|
||||||
| Oracle
|
| Oracle
|
||||||
| PayChannel
|
| PayChannel
|
||||||
|
| PermissionedDomain
|
||||||
| RippleState
|
| RippleState
|
||||||
| SignerList
|
| SignerList
|
||||||
| Ticket
|
| Ticket
|
||||||
@@ -45,17 +49,21 @@ type LedgerEntryFilter =
|
|||||||
| 'amm'
|
| 'amm'
|
||||||
| 'bridge'
|
| 'bridge'
|
||||||
| 'check'
|
| 'check'
|
||||||
|
| 'credential'
|
||||||
| 'deposit_preauth'
|
| 'deposit_preauth'
|
||||||
| 'did'
|
| 'did'
|
||||||
| 'directory'
|
| 'directory'
|
||||||
| 'escrow'
|
| 'escrow'
|
||||||
| 'fee'
|
| 'fee'
|
||||||
| 'hashes'
|
| 'hashes'
|
||||||
|
| 'mpt_issuance'
|
||||||
|
| 'mptoken'
|
||||||
| 'nft_offer'
|
| 'nft_offer'
|
||||||
| 'nft_page'
|
| 'nft_page'
|
||||||
| 'offer'
|
| 'offer'
|
||||||
| 'oracle'
|
| 'oracle'
|
||||||
| 'payment_channel'
|
| 'payment_channel'
|
||||||
|
| 'permissioned_domain'
|
||||||
| 'signer_list'
|
| 'signer_list'
|
||||||
| 'state'
|
| 'state'
|
||||||
| 'ticket'
|
| 'ticket'
|
||||||
|
|||||||
11
packages/xrpl/src/models/ledger/MPToken.ts
Normal file
11
packages/xrpl/src/models/ledger/MPToken.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
13
packages/xrpl/src/models/ledger/MPTokenIssuance.ts
Normal file
13
packages/xrpl/src/models/ledger/MPTokenIssuance.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
29
packages/xrpl/src/models/ledger/PermissionedDomain.ts
Normal file
29
packages/xrpl/src/models/ledger/PermissionedDomain.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { AuthorizeCredential } from '../common'
|
||||||
|
|
||||||
|
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||||
|
|
||||||
|
export default interface PermissionedDomain
|
||||||
|
extends BaseLedgerEntry,
|
||||||
|
HasPreviousTxnID {
|
||||||
|
/* The ledger object's type (PermissionedDomain). */
|
||||||
|
LedgerEntryType: 'PermissionedDomain'
|
||||||
|
|
||||||
|
/* The account that controls the settings of the domain. */
|
||||||
|
Owner: string
|
||||||
|
|
||||||
|
/* The credentials that are accepted by the domain.
|
||||||
|
Ownership of one of these credentials automatically
|
||||||
|
makes you a member of the domain. */
|
||||||
|
AcceptedCredentials: AuthorizeCredential[]
|
||||||
|
|
||||||
|
/* Flag values associated with this object. */
|
||||||
|
Flags: 0
|
||||||
|
|
||||||
|
/* Owner account's directory page containing the PermissionedDomain object. */
|
||||||
|
OwnerNode: string
|
||||||
|
|
||||||
|
/* The Sequence value of the PermissionedDomainSet
|
||||||
|
transaction that created this domain. Used in combination
|
||||||
|
with the Account to identify this domain. */
|
||||||
|
Sequence: number
|
||||||
|
}
|
||||||
@@ -77,4 +77,8 @@ export enum RippleStateFlags {
|
|||||||
lsfHighFreeze = 0x00800000,
|
lsfHighFreeze = 0x00800000,
|
||||||
// True, trust line to AMM. Used by client apps to identify payments via AMM.
|
// True, trust line to AMM. Used by client apps to identify payments via AMM.
|
||||||
lsfAMMNode = 0x01000000,
|
lsfAMMNode = 0x01000000,
|
||||||
|
// True, low side has set deep freeze flag
|
||||||
|
lsfLowDeepFreeze = 0x02000000,
|
||||||
|
// True, high side has set deep freeze flag
|
||||||
|
lsfHighDeepFreeze = 0x04000000,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import Amendments, { Majority, AMENDMENTS_ID } from './Amendments'
|
|||||||
import AMM, { VoteSlot } from './AMM'
|
import AMM, { VoteSlot } from './AMM'
|
||||||
import Bridge from './Bridge'
|
import Bridge from './Bridge'
|
||||||
import Check from './Check'
|
import Check from './Check'
|
||||||
|
import Credential from './Credential'
|
||||||
import DepositPreauth from './DepositPreauth'
|
import DepositPreauth from './DepositPreauth'
|
||||||
import DID from './DID'
|
import DID from './DID'
|
||||||
import DirectoryNode from './DirectoryNode'
|
import DirectoryNode from './DirectoryNode'
|
||||||
@@ -18,6 +19,8 @@ import FeeSettings, {
|
|||||||
import { Ledger, LedgerV1 } from './Ledger'
|
import { Ledger, LedgerV1 } from './Ledger'
|
||||||
import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
|
import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
|
||||||
import LedgerHashes from './LedgerHashes'
|
import LedgerHashes from './LedgerHashes'
|
||||||
|
import { MPToken } from './MPToken'
|
||||||
|
import { MPTokenIssuance } from './MPTokenIssuance'
|
||||||
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
|
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
|
||||||
import { NFTokenOffer } from './NFTokenOffer'
|
import { NFTokenOffer } from './NFTokenOffer'
|
||||||
import { NFToken, NFTokenPage } from './NFTokenPage'
|
import { NFToken, NFTokenPage } from './NFTokenPage'
|
||||||
@@ -39,6 +42,7 @@ export {
|
|||||||
AMM,
|
AMM,
|
||||||
Bridge,
|
Bridge,
|
||||||
Check,
|
Check,
|
||||||
|
Credential,
|
||||||
DepositPreauth,
|
DepositPreauth,
|
||||||
DirectoryNode,
|
DirectoryNode,
|
||||||
DID,
|
DID,
|
||||||
@@ -55,6 +59,8 @@ export {
|
|||||||
Majority,
|
Majority,
|
||||||
NEGATIVE_UNL_ID,
|
NEGATIVE_UNL_ID,
|
||||||
NegativeUNL,
|
NegativeUNL,
|
||||||
|
MPTokenIssuance,
|
||||||
|
MPToken,
|
||||||
NFTokenOffer,
|
NFTokenOffer,
|
||||||
NFTokenPage,
|
NFTokenPage,
|
||||||
NFToken,
|
NFToken,
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ export interface DepositAuthorizedRequest
|
|||||||
source_account: string
|
source_account: string
|
||||||
/** The recipient of a possible payment. */
|
/** The recipient of a possible payment. */
|
||||||
destination_account: string
|
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
|
source_account: string
|
||||||
/** If true, the information comes from a validated ledger version. */
|
/** If true, the information comes from a validated ledger version. */
|
||||||
validated?: boolean
|
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[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,14 @@ import {
|
|||||||
StateAccountingFinal,
|
StateAccountingFinal,
|
||||||
} from './serverInfo'
|
} from './serverInfo'
|
||||||
import { ServerStateRequest, ServerStateResponse } from './serverState'
|
import { ServerStateRequest, ServerStateResponse } from './serverState'
|
||||||
|
import {
|
||||||
|
SimulateBinaryRequest,
|
||||||
|
SimulateBinaryResponse,
|
||||||
|
SimulateJsonRequest,
|
||||||
|
SimulateJsonResponse,
|
||||||
|
SimulateRequest,
|
||||||
|
SimulateResponse,
|
||||||
|
} from './simulate'
|
||||||
import { SubmitRequest, SubmitResponse } from './submit'
|
import { SubmitRequest, SubmitResponse } from './submit'
|
||||||
import {
|
import {
|
||||||
SubmitMultisignedRequest,
|
SubmitMultisignedRequest,
|
||||||
@@ -203,6 +211,7 @@ type Request =
|
|||||||
| LedgerDataRequest
|
| LedgerDataRequest
|
||||||
| LedgerEntryRequest
|
| LedgerEntryRequest
|
||||||
// transaction methods
|
// transaction methods
|
||||||
|
| SimulateRequest
|
||||||
| SubmitRequest
|
| SubmitRequest
|
||||||
| SubmitMultisignedRequest
|
| SubmitMultisignedRequest
|
||||||
| TransactionEntryRequest
|
| TransactionEntryRequest
|
||||||
@@ -261,6 +270,7 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
|
|||||||
| LedgerDataResponse
|
| LedgerDataResponse
|
||||||
| LedgerEntryResponse
|
| LedgerEntryResponse
|
||||||
// transaction methods
|
// transaction methods
|
||||||
|
| SimulateResponse
|
||||||
| SubmitResponse
|
| SubmitResponse
|
||||||
| SubmitMultisignedVersionResponseMap<Version>
|
| SubmitMultisignedVersionResponseMap<Version>
|
||||||
| TransactionEntryResponse
|
| TransactionEntryResponse
|
||||||
@@ -398,6 +408,12 @@ export type RequestResponseMap<
|
|||||||
? LedgerDataResponse
|
? LedgerDataResponse
|
||||||
: T extends LedgerEntryRequest
|
: T extends LedgerEntryRequest
|
||||||
? LedgerEntryResponse
|
? LedgerEntryResponse
|
||||||
|
: T extends SimulateBinaryRequest
|
||||||
|
? SimulateBinaryResponse
|
||||||
|
: T extends SimulateJsonRequest
|
||||||
|
? SimulateJsonResponse
|
||||||
|
: T extends SimulateRequest
|
||||||
|
? SimulateJsonResponse
|
||||||
: T extends SubmitRequest
|
: T extends SubmitRequest
|
||||||
? SubmitResponse
|
? SubmitResponse
|
||||||
: T extends SubmitMultisignedRequest
|
: T extends SubmitMultisignedRequest
|
||||||
@@ -544,6 +560,8 @@ export {
|
|||||||
LedgerEntryRequest,
|
LedgerEntryRequest,
|
||||||
LedgerEntryResponse,
|
LedgerEntryResponse,
|
||||||
// transaction methods with types
|
// transaction methods with types
|
||||||
|
SimulateRequest,
|
||||||
|
SimulateResponse,
|
||||||
SubmitRequest,
|
SubmitRequest,
|
||||||
SubmitResponse,
|
SubmitResponse,
|
||||||
SubmitMultisignedRequest,
|
SubmitMultisignedRequest,
|
||||||
|
|||||||
@@ -203,13 +203,13 @@ export interface LedgerQueueData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LedgerBinary
|
export interface LedgerBinary
|
||||||
extends Omit<Omit<Ledger, 'transactions'>, 'accountState'> {
|
extends Omit<Ledger, 'transactions' | 'accountState'> {
|
||||||
accountState?: string[]
|
accountState?: string[]
|
||||||
transactions?: string[]
|
transactions?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LedgerBinaryV1
|
export interface LedgerBinaryV1
|
||||||
extends Omit<Omit<LedgerV1, 'transactions'>, 'accountState'> {
|
extends Omit<LedgerV1, 'transactions' | 'accountState'> {
|
||||||
accountState?: string[]
|
accountState?: string[]
|
||||||
transactions?: string[]
|
transactions?: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,22 @@ import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
|
|||||||
*/
|
*/
|
||||||
export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
|
export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
|
||||||
command: 'ledger_entry'
|
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.
|
* 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.
|
* 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. */
|
/** The object ID of a Check object to retrieve. */
|
||||||
check?: string
|
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
|
* Specify a DepositPreauth object to retrieve. If a string, must be the
|
||||||
* object ID of the DepositPreauth object, as hexadecimal. If an object,
|
* object ID of the DepositPreauth object, as hexadecimal. If an object,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export interface ServerStateResponse extends BaseResponse {
|
|||||||
load_factor_fee_queue?: number
|
load_factor_fee_queue?: number
|
||||||
load_factor_fee_reference?: number
|
load_factor_fee_reference?: number
|
||||||
load_factor_server?: number
|
load_factor_server?: number
|
||||||
|
network_id: number
|
||||||
peer_disconnects?: string
|
peer_disconnects?: string
|
||||||
peer_disconnects_resources?: string
|
peer_disconnects_resources?: string
|
||||||
peers: number
|
peers: number
|
||||||
|
|||||||
88
packages/xrpl/src/models/methods/simulate.ts
Normal file
88
packages/xrpl/src/models/methods/simulate.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
Transaction,
|
||||||
|
TransactionMetadata,
|
||||||
|
} from '../transactions'
|
||||||
|
|
||||||
|
import { BaseRequest, BaseResponse } from './baseMethod'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `simulate` method simulates a transaction without submitting it to the network.
|
||||||
|
* Returns a {@link SimulateResponse}.
|
||||||
|
*
|
||||||
|
* @category Requests
|
||||||
|
*/
|
||||||
|
export type SimulateRequest = BaseRequest & {
|
||||||
|
command: 'simulate'
|
||||||
|
|
||||||
|
binary?: boolean
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
tx_blob: string
|
||||||
|
tx_json?: never
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
tx_json: Transaction
|
||||||
|
tx_blob?: never
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export type SimulateBinaryRequest = SimulateRequest & {
|
||||||
|
binary: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SimulateJsonRequest = SimulateRequest & {
|
||||||
|
binary?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response expected from an {@link SimulateRequest}.
|
||||||
|
*
|
||||||
|
* @category Responses
|
||||||
|
*/
|
||||||
|
export type SimulateResponse = SimulateJsonResponse | SimulateBinaryResponse
|
||||||
|
|
||||||
|
export interface SimulateBinaryResponse extends BaseResponse {
|
||||||
|
result: {
|
||||||
|
applied: false
|
||||||
|
|
||||||
|
engine_result: string
|
||||||
|
|
||||||
|
engine_result_code: number
|
||||||
|
|
||||||
|
engine_result_message: string
|
||||||
|
|
||||||
|
tx_blob: string
|
||||||
|
|
||||||
|
meta_blob: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ledger index of the ledger version that was used to generate this
|
||||||
|
* response.
|
||||||
|
*/
|
||||||
|
ledger_index: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimulateJsonResponse<T extends BaseTransaction = Transaction>
|
||||||
|
extends BaseResponse {
|
||||||
|
result: {
|
||||||
|
applied: false
|
||||||
|
|
||||||
|
engine_result: string
|
||||||
|
|
||||||
|
engine_result_code: number
|
||||||
|
|
||||||
|
engine_result_message: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ledger index of the ledger version that was used to generate this
|
||||||
|
* response.
|
||||||
|
*/
|
||||||
|
ledger_index: number
|
||||||
|
|
||||||
|
tx_json: T
|
||||||
|
|
||||||
|
meta?: TransactionMetadata<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
120
packages/xrpl/src/models/transactions/AMMClawback.ts
Normal file
120
packages/xrpl/src/models/transactions/AMMClawback.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { ValidationError } from '../../errors'
|
||||||
|
import { Currency, IssuedCurrency, IssuedCurrencyAmount } from '../common'
|
||||||
|
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
BaseTransaction,
|
||||||
|
GlobalFlags,
|
||||||
|
isAccount,
|
||||||
|
isAmount,
|
||||||
|
isCurrency,
|
||||||
|
validateBaseTransaction,
|
||||||
|
validateOptionalField,
|
||||||
|
validateRequiredField,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing values for AMMClawback Transaction Flags.
|
||||||
|
*
|
||||||
|
* @category Transaction Flags
|
||||||
|
*/
|
||||||
|
export enum AMMClawbackFlags {
|
||||||
|
tfClawTwoAssets = 0x00000001,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of flags to boolean values representing {@link AMMClawback} transaction
|
||||||
|
* flags.
|
||||||
|
*
|
||||||
|
* @category Transaction Flags
|
||||||
|
*/
|
||||||
|
export interface AMMClawbackFlagsInterface extends GlobalFlags {
|
||||||
|
tfClawTwoAssets?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claw back tokens from a holder that has deposited your issued tokens into an AMM pool.
|
||||||
|
*
|
||||||
|
* Clawback is disabled by default. To use clawback, you must send an AccountSet transaction to enable the
|
||||||
|
* Allow Trust Line Clawback setting. An issuer with any existing tokens cannot enable clawback. You can
|
||||||
|
* only enable Allow Trust Line Clawback if you have a completely empty owner directory, meaning you must
|
||||||
|
* do so before you set up any trust lines, offers, escrows, payment channels, checks, or signer lists.
|
||||||
|
* After you enable clawback, it cannot reverted: the account permanently gains the ability to claw back
|
||||||
|
* issued assets on trust lines.
|
||||||
|
*/
|
||||||
|
export interface AMMClawback extends BaseTransaction {
|
||||||
|
TransactionType: 'AMMClawback'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The account holding the asset to be clawed back.
|
||||||
|
*/
|
||||||
|
Holder: Account
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the asset that the issuer wants to claw back from the AMM pool.
|
||||||
|
* In JSON, this is an object with currency and issuer fields. The issuer field must match with Account.
|
||||||
|
*/
|
||||||
|
Asset: IssuedCurrency
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the other asset in the AMM's pool. In JSON, this is an object with currency and
|
||||||
|
* issuer fields (omit issuer for XRP).
|
||||||
|
*/
|
||||||
|
Asset2: Currency
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount to claw back from the AMM account. The currency and issuer subfields should match
|
||||||
|
* the Asset subfields. If this field isn't specified, or the value subfield exceeds the holder's available
|
||||||
|
* tokens in the AMM, all of the holder's tokens will be clawed back.
|
||||||
|
*/
|
||||||
|
Amount?: IssuedCurrencyAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the form and type of an AMMClawback at runtime.
|
||||||
|
*
|
||||||
|
* @param tx - An AMMClawback Transaction.
|
||||||
|
* @throws {ValidationError} When the transaction is malformed.
|
||||||
|
*/
|
||||||
|
export function validateAMMClawback(tx: Record<string, unknown>): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'Holder', isAccount)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'Asset', isCurrency)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
|
||||||
|
const asset = tx.Asset as IssuedCurrency
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
|
||||||
|
const amount = tx.Amount as IssuedCurrencyAmount
|
||||||
|
|
||||||
|
if (tx.Holder === asset.issuer) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'AMMClawback: Holder and Asset.issuer must be distinct',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx.Account !== asset.issuer) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'AMMClawback: Account must be the same as Asset.issuer',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'Asset2', isCurrency)
|
||||||
|
|
||||||
|
validateOptionalField(tx, 'Amount', isAmount)
|
||||||
|
|
||||||
|
if (tx.Amount != null) {
|
||||||
|
if (amount.currency !== asset.currency) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'AMMClawback: Amount.currency must match Asset.currency',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount.issuer !== asset.issuer) {
|
||||||
|
throw new ValidationError(
|
||||||
|
'AMMClawback: Amount.issuer must match Amount.issuer',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
packages/xrpl/src/models/transactions/CredentialAccept.ts
Normal file
44
packages/xrpl/src/models/transactions/CredentialAccept.ts
Normal file
@@ -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<string, unknown>): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'Account', isString)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'Issuer', isString)
|
||||||
|
|
||||||
|
validateCredentialType(tx)
|
||||||
|
}
|
||||||
81
packages/xrpl/src/models/transactions/CredentialCreate.ts
Normal file
81
packages/xrpl/src/models/transactions/CredentialCreate.ts
Normal file
@@ -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<string, unknown>): 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
55
packages/xrpl/src/models/transactions/CredentialDelete.ts
Normal file
55
packages/xrpl/src/models/transactions/CredentialDelete.ts
Normal file
@@ -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<string, unknown>): 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)
|
||||||
|
}
|
||||||
67
packages/xrpl/src/models/transactions/MPTokenAuthorize.ts
Normal file
67
packages/xrpl/src/models/transactions/MPTokenAuthorize.ts
Normal file
@@ -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<string, unknown>): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
validateRequiredField(tx, 'MPTokenIssuanceID', isString)
|
||||||
|
validateOptionalField(tx, 'Holder', isAccount)
|
||||||
|
}
|
||||||
179
packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts
Normal file
179
packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts
Normal file
@@ -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<string, unknown>,
|
||||||
|
): 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 */
|
||||||
@@ -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<string, unknown>,
|
||||||
|
): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
validateRequiredField(tx, 'MPTokenIssuanceID', isString)
|
||||||
|
}
|
||||||
86
packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts
Normal file
86
packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts
Normal file
@@ -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<string, unknown>): 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,10 @@ export enum NFTokenMintFlags {
|
|||||||
* issuer.
|
* issuer.
|
||||||
*/
|
*/
|
||||||
tfTransferable = 0x00000008,
|
tfTransferable = 0x00000008,
|
||||||
|
/**
|
||||||
|
* If set, indicates that this NFT's URI can be modified.
|
||||||
|
*/
|
||||||
|
tfMutable = 0x00000010,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +55,7 @@ export interface NFTokenMintFlagsInterface extends GlobalFlags {
|
|||||||
tfOnlyXRP?: boolean
|
tfOnlyXRP?: boolean
|
||||||
tfTrustLine?: boolean
|
tfTrustLine?: boolean
|
||||||
tfTransferable?: boolean
|
tfTransferable?: boolean
|
||||||
|
tfMutable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { ValidationError } from '../../errors'
|
||||||
|
import { isHex } from '../utils'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
validateBaseTransaction,
|
||||||
|
isAccount,
|
||||||
|
isString,
|
||||||
|
validateOptionalField,
|
||||||
|
Account,
|
||||||
|
validateRequiredField,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NFTokenModify transaction modifies an NFToken's URI
|
||||||
|
* if its tfMutable is set to true.
|
||||||
|
*/
|
||||||
|
export interface NFTokenModify extends BaseTransaction {
|
||||||
|
TransactionType: 'NFTokenModify'
|
||||||
|
/**
|
||||||
|
* Identifies the NFTokenID of the NFToken object that the
|
||||||
|
* offer references.
|
||||||
|
*/
|
||||||
|
NFTokenID: string
|
||||||
|
/**
|
||||||
|
* Indicates the AccountID of the account that owns the corresponding NFToken.
|
||||||
|
* Can be omitted if the owner is the account submitting this transaction
|
||||||
|
*/
|
||||||
|
Owner?: Account
|
||||||
|
/**
|
||||||
|
* URI that points to the data and/or metadata associated with the NFT.
|
||||||
|
* This field need not be an HTTP or HTTPS URL; it could be an IPFS URI, a
|
||||||
|
* magnet link, immediate data encoded as an RFC2379 "data" URL, or even an
|
||||||
|
* opaque issuer-specific encoding. The URI is NOT checked for validity, but
|
||||||
|
* the field is limited to a maximum length of 256 bytes.
|
||||||
|
*
|
||||||
|
* This field must be hex-encoded. You can use `convertStringToHex` to
|
||||||
|
* convert this field to the proper encoding.
|
||||||
|
*
|
||||||
|
* This field must not be an empty string. Omit it from the transaction or
|
||||||
|
* set to `null` if you do not use it.
|
||||||
|
*/
|
||||||
|
URI?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the form and type of an NFTokenModify at runtime.
|
||||||
|
*
|
||||||
|
* @param tx - An NFTokenModify Transaction.
|
||||||
|
* @throws When the NFTokenModify is Malformed.
|
||||||
|
*/
|
||||||
|
export function validateNFTokenModify(tx: Record<string, unknown>): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'NFTokenID', isString)
|
||||||
|
validateOptionalField(tx, 'Owner', isAccount)
|
||||||
|
validateOptionalField(tx, 'URI', isString)
|
||||||
|
|
||||||
|
if (tx.URI !== undefined && typeof tx.URI === 'string') {
|
||||||
|
if (tx.URI === '') {
|
||||||
|
throw new ValidationError('NFTokenModify: URI must not be empty string')
|
||||||
|
}
|
||||||
|
if (!isHex(tx.URI)) {
|
||||||
|
throw new ValidationError('NFTokenModify: URI must be in hex format')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ import {
|
|||||||
isAccount,
|
isAccount,
|
||||||
isNumber,
|
isNumber,
|
||||||
validateBaseTransaction,
|
validateBaseTransaction,
|
||||||
|
validateCredentialsList,
|
||||||
validateOptionalField,
|
validateOptionalField,
|
||||||
validateRequiredField,
|
validateRequiredField,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
} from './common'
|
} from './common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +30,12 @@ export interface AccountDelete extends BaseTransaction {
|
|||||||
* information for the recipient of the deleted account's leftover XRP.
|
* information for the recipient of the deleted account's leftover XRP.
|
||||||
*/
|
*/
|
||||||
DestinationTag?: number
|
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 +49,12 @@ export function validateAccountDelete(tx: Record<string, unknown>): void {
|
|||||||
|
|
||||||
validateRequiredField(tx, 'Destination', isAccount)
|
validateRequiredField(tx, 'Destination', isAccount)
|
||||||
validateOptionalField(tx, 'DestinationTag', isNumber)
|
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,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
import { IssuedCurrencyAmount } from '../common'
|
import { IssuedCurrencyAmount, MPTAmount } from '../common'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseTransaction,
|
BaseTransaction,
|
||||||
validateBaseTransaction,
|
validateBaseTransaction,
|
||||||
isIssuedCurrency,
|
isIssuedCurrency,
|
||||||
|
isMPTAmount,
|
||||||
|
isAccount,
|
||||||
|
validateOptionalField,
|
||||||
} from './common'
|
} from './common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,15 +18,20 @@ export interface Clawback extends BaseTransaction {
|
|||||||
TransactionType: 'Clawback'
|
TransactionType: 'Clawback'
|
||||||
/**
|
/**
|
||||||
* Indicates the AccountID that submitted this transaction. The account MUST
|
* 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
|
Account: string
|
||||||
/**
|
/**
|
||||||
* The amount of currency to deliver, and it must be non-XRP. The nested field
|
* The amount of currency or MPT to clawback, and it must be non-XRP. The nested field
|
||||||
* names MUST be lower-case. The `issuer` field MUST be the holder's address,
|
* names MUST be lower-case. If the amount is IOU, the `issuer` field MUST be the holder's address,
|
||||||
* whom to be clawed back.
|
* 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<string, unknown>): void {
|
export function validateClawback(tx: Record<string, unknown>): void {
|
||||||
validateBaseTransaction(tx)
|
validateBaseTransaction(tx)
|
||||||
|
validateOptionalField(tx, 'Holder', isAccount)
|
||||||
|
|
||||||
if (tx.Amount == null) {
|
if (tx.Amount == null) {
|
||||||
throw new ValidationError('Clawback: missing field Amount')
|
throw new ValidationError('Clawback: missing field Amount')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isIssuedCurrency(tx.Amount)) {
|
if (!isIssuedCurrency(tx.Amount) && !isMPTAmount(tx.Amount)) {
|
||||||
throw new ValidationError('Clawback: invalid Amount')
|
throw new ValidationError('Clawback: invalid Amount')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) {
|
if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) {
|
||||||
throw new ValidationError('Clawback: invalid holder Account')
|
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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
/* eslint-disable max-lines -- common utility file */
|
||||||
|
import { HEX_REGEX } from '@xrplf/isomorphic/utils'
|
||||||
import { isValidClassicAddress, isValidXAddress } from 'ripple-address-codec'
|
import { isValidClassicAddress, isValidXAddress } from 'ripple-address-codec'
|
||||||
import { TRANSACTION_TYPES } from 'ripple-binary-codec'
|
import { TRANSACTION_TYPES } from 'ripple-binary-codec'
|
||||||
|
|
||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
import {
|
import {
|
||||||
Amount,
|
Amount,
|
||||||
|
AuthorizeCredential,
|
||||||
Currency,
|
Currency,
|
||||||
IssuedCurrencyAmount,
|
IssuedCurrencyAmount,
|
||||||
|
MPTAmount,
|
||||||
Memo,
|
Memo,
|
||||||
Signer,
|
Signer,
|
||||||
XChainBridge,
|
XChainBridge,
|
||||||
@@ -13,6 +17,9 @@ import {
|
|||||||
import { onlyHasFields } from '../utils'
|
import { onlyHasFields } from '../utils'
|
||||||
|
|
||||||
const MEMO_SIZE = 3
|
const MEMO_SIZE = 3
|
||||||
|
export const MAX_AUTHORIZED_CREDENTIALS = 8
|
||||||
|
const MAX_CREDENTIAL_BYTE_LENGTH = 64
|
||||||
|
const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2
|
||||||
|
|
||||||
function isMemo(obj: { Memo?: unknown }): boolean {
|
function isMemo(obj: { Memo?: unknown }): boolean {
|
||||||
if (obj.Memo == null) {
|
if (obj.Memo == null) {
|
||||||
@@ -59,6 +66,8 @@ const XRP_CURRENCY_SIZE = 1
|
|||||||
const ISSUE_SIZE = 2
|
const ISSUE_SIZE = 2
|
||||||
const ISSUED_CURRENCY_SIZE = 3
|
const ISSUED_CURRENCY_SIZE = 3
|
||||||
const XCHAIN_BRIDGE_SIZE = 4
|
const XCHAIN_BRIDGE_SIZE = 4
|
||||||
|
const MPTOKEN_SIZE = 2
|
||||||
|
const AUTHORIZE_CREDENTIAL_SIZE = 1
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return value !== null && typeof value === 'object'
|
return value !== null && typeof value === 'object'
|
||||||
@@ -119,6 +128,39 @@ 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
|
||||||
|
*/
|
||||||
|
export 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
|
* Must be a valid account address
|
||||||
*/
|
*/
|
||||||
@@ -144,7 +186,11 @@ export function isAccount(account: unknown): account is Account {
|
|||||||
* @returns Whether the Amount is properly formed.
|
* @returns Whether the Amount is properly formed.
|
||||||
*/
|
*/
|
||||||
export function isAmount(amount: unknown): amount is Amount {
|
export function isAmount(amount: unknown): amount is Amount {
|
||||||
return typeof amount === 'string' || isIssuedCurrency(amount)
|
return (
|
||||||
|
typeof amount === 'string' ||
|
||||||
|
isIssuedCurrency(amount) ||
|
||||||
|
isMPTAmount(amount)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -366,3 +412,135 @@ export function parseAmountValue(amount: unknown): number {
|
|||||||
}
|
}
|
||||||
return parseFloat(amount.value)
|
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<string, unknown>): 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
|
||||||
|
* @param maxCredentials The maximum length of the credentials array.
|
||||||
|
* PermissionedDomainSet transaction uses 10, other transactions use 8.
|
||||||
|
* @throws Validation Error if the formatting is incorrect
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line max-lines-per-function, max-params -- separating logic further will add unnecessary complexity
|
||||||
|
export function validateCredentialsList(
|
||||||
|
credentials: unknown,
|
||||||
|
transactionType: string,
|
||||||
|
isStringID: boolean,
|
||||||
|
maxCredentials: number,
|
||||||
|
): void {
|
||||||
|
if (credentials == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!Array.isArray(credentials)) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`${transactionType}: Credentials must be an array`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (credentials.length > maxCredentials) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`${transactionType}: Credentials length cannot exceed ${maxCredentials} 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`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type guard to ensure we're working with AuthorizeCredential[]
|
||||||
|
// Note: This is not a rigorous type-guard. A more thorough solution would be to iterate over the array and check each item.
|
||||||
|
function isAuthorizeCredentialArray(
|
||||||
|
list: AuthorizeCredential[] | string[],
|
||||||
|
): list is AuthorizeCredential[] {
|
||||||
|
return typeof list[0] !== 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an array of objects contains any duplicates.
|
||||||
|
*
|
||||||
|
* @param objectList - Array of objects to check for duplicates
|
||||||
|
* @returns True if duplicates exist, false otherwise
|
||||||
|
*/
|
||||||
|
export function containsDuplicates(
|
||||||
|
objectList: AuthorizeCredential[] | string[],
|
||||||
|
): boolean {
|
||||||
|
// Case-1: Process a list of string-IDs
|
||||||
|
if (typeof objectList[0] === 'string') {
|
||||||
|
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
|
||||||
|
return objSet.size !== objectList.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case-2: Process a list of nested objects
|
||||||
|
const seen = new Set<string>()
|
||||||
|
|
||||||
|
if (isAuthorizeCredentialArray(objectList)) {
|
||||||
|
for (const item of objectList) {
|
||||||
|
const key = `${item.Credential.Issuer}-${item.Credential.CredentialType}`
|
||||||
|
// eslint-disable-next-line max-depth -- necessary to check for type-guards
|
||||||
|
if (seen.has(key)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
seen.add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
|
import { AuthorizeCredential } from '../common'
|
||||||
|
|
||||||
import { BaseTransaction, validateBaseTransaction } from './common'
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
validateBaseTransaction,
|
||||||
|
validateCredentialsList,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A DepositPreauth transaction gives another account pre-approval to deliver
|
* A DepositPreauth transaction gives another account pre-approval to deliver
|
||||||
@@ -18,6 +24,16 @@ export interface DepositPreauth extends BaseTransaction {
|
|||||||
* revoked.
|
* revoked.
|
||||||
*/
|
*/
|
||||||
Unauthorize?: string
|
Unauthorize?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The credential(s) to preauthorize.
|
||||||
|
*/
|
||||||
|
AuthorizeCredentials?: AuthorizeCredential[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The credential(s) whose preauthorization should be revoked.
|
||||||
|
*/
|
||||||
|
UnauthorizeCredentials?: AuthorizeCredential[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,17 +45,7 @@ export interface DepositPreauth extends BaseTransaction {
|
|||||||
export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||||
validateBaseTransaction(tx)
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
if (tx.Authorize !== undefined && tx.Unauthorize !== undefined) {
|
validateSingleAuthorizationFieldProvided(tx)
|
||||||
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',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx.Authorize !== undefined) {
|
if (tx.Authorize !== undefined) {
|
||||||
if (typeof tx.Authorize !== 'string') {
|
if (typeof tx.Authorize !== 'string') {
|
||||||
@@ -51,9 +57,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
|||||||
"DepositPreauth: Account can't preauthorize its own address",
|
"DepositPreauth: Account can't preauthorize its own address",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} else if (tx.Unauthorize !== undefined) {
|
||||||
|
|
||||||
if (tx.Unauthorize !== undefined) {
|
|
||||||
if (typeof tx.Unauthorize !== 'string') {
|
if (typeof tx.Unauthorize !== 'string') {
|
||||||
throw new ValidationError('DepositPreauth: Unauthorize must be a string')
|
throw new ValidationError('DepositPreauth: Unauthorize must be a string')
|
||||||
}
|
}
|
||||||
@@ -63,5 +67,40 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
|||||||
"DepositPreauth: Account can't unauthorize its own address",
|
"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,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
} 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,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean logic to ensure exactly one of 4 inputs was provided
|
||||||
|
function validateSingleAuthorizationFieldProvided(
|
||||||
|
tx: Record<string, unknown>,
|
||||||
|
): 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.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import {
|
|||||||
BaseTransaction,
|
BaseTransaction,
|
||||||
isAccount,
|
isAccount,
|
||||||
validateBaseTransaction,
|
validateBaseTransaction,
|
||||||
|
validateCredentialsList,
|
||||||
validateRequiredField,
|
validateRequiredField,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
} from './common'
|
} from './common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,6 +34,10 @@ export interface EscrowFinish extends BaseTransaction {
|
|||||||
* the held payment's Condition.
|
* the held payment's Condition.
|
||||||
*/
|
*/
|
||||||
Fulfillment?: string
|
Fulfillment?: string
|
||||||
|
/** Credentials associated with the sender of this transaction.
|
||||||
|
* The credentials included must not be expired.
|
||||||
|
*/
|
||||||
|
CredentialIDs?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,6 +51,14 @@ export function validateEscrowFinish(tx: Record<string, unknown>): void {
|
|||||||
|
|
||||||
validateRequiredField(tx, 'Owner', isAccount)
|
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,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
|
||||||
if (tx.OfferSequence == null) {
|
if (tx.OfferSequence == null) {
|
||||||
throw new ValidationError('EscrowFinish: missing field OfferSequence')
|
throw new ValidationError('EscrowFinish: missing field OfferSequence')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export { BaseTransaction } from './common'
|
export { BaseTransaction, isMPTAmount } from './common'
|
||||||
export {
|
export {
|
||||||
validate,
|
validate,
|
||||||
PseudoTransaction,
|
PseudoTransaction,
|
||||||
@@ -15,13 +15,18 @@ export {
|
|||||||
} from './accountSet'
|
} from './accountSet'
|
||||||
export { AccountDelete } from './accountDelete'
|
export { AccountDelete } from './accountDelete'
|
||||||
export { AMMBid } from './AMMBid'
|
export { AMMBid } from './AMMBid'
|
||||||
|
export {
|
||||||
|
AMMClawbackFlags,
|
||||||
|
AMMClawbackFlagsInterface,
|
||||||
|
AMMClawback,
|
||||||
|
} from './AMMClawback'
|
||||||
|
export { AMMCreate } from './AMMCreate'
|
||||||
export { AMMDelete } from './AMMDelete'
|
export { AMMDelete } from './AMMDelete'
|
||||||
export {
|
export {
|
||||||
AMMDepositFlags,
|
AMMDepositFlags,
|
||||||
AMMDepositFlagsInterface,
|
AMMDepositFlagsInterface,
|
||||||
AMMDeposit,
|
AMMDeposit,
|
||||||
} from './AMMDeposit'
|
} from './AMMDeposit'
|
||||||
export { AMMCreate } from './AMMCreate'
|
|
||||||
export { AMMVote } from './AMMVote'
|
export { AMMVote } from './AMMVote'
|
||||||
export {
|
export {
|
||||||
AMMWithdrawFlags,
|
AMMWithdrawFlags,
|
||||||
@@ -32,6 +37,9 @@ export { CheckCancel } from './checkCancel'
|
|||||||
export { CheckCash } from './checkCash'
|
export { CheckCash } from './checkCash'
|
||||||
export { CheckCreate } from './checkCreate'
|
export { CheckCreate } from './checkCreate'
|
||||||
export { Clawback } from './clawback'
|
export { Clawback } from './clawback'
|
||||||
|
export { CredentialAccept } from './CredentialAccept'
|
||||||
|
export { CredentialCreate } from './CredentialCreate'
|
||||||
|
export { CredentialDelete } from './CredentialDelete'
|
||||||
export { DIDDelete } from './DIDDelete'
|
export { DIDDelete } from './DIDDelete'
|
||||||
export { DIDSet } from './DIDSet'
|
export { DIDSet } from './DIDSet'
|
||||||
export { DepositPreauth } from './depositPreauth'
|
export { DepositPreauth } from './depositPreauth'
|
||||||
@@ -39,6 +47,22 @@ export { EscrowCancel } from './escrowCancel'
|
|||||||
export { EscrowCreate } from './escrowCreate'
|
export { EscrowCreate } from './escrowCreate'
|
||||||
export { EscrowFinish } from './escrowFinish'
|
export { EscrowFinish } from './escrowFinish'
|
||||||
export { EnableAmendment, EnableAmendmentFlags } from './enableAmendment'
|
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 { NFTokenAcceptOffer } from './NFTokenAcceptOffer'
|
||||||
export { NFTokenBurn } from './NFTokenBurn'
|
export { NFTokenBurn } from './NFTokenBurn'
|
||||||
export { NFTokenCancelOffer } from './NFTokenCancelOffer'
|
export { NFTokenCancelOffer } from './NFTokenCancelOffer'
|
||||||
@@ -52,6 +76,7 @@ export {
|
|||||||
NFTokenMintFlags,
|
NFTokenMintFlags,
|
||||||
NFTokenMintFlagsInterface,
|
NFTokenMintFlagsInterface,
|
||||||
} from './NFTokenMint'
|
} from './NFTokenMint'
|
||||||
|
export { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||||
export { OfferCancel } from './offerCancel'
|
export { OfferCancel } from './offerCancel'
|
||||||
export {
|
export {
|
||||||
OfferCreateFlags,
|
OfferCreateFlags,
|
||||||
@@ -86,3 +111,6 @@ export {
|
|||||||
XChainModifyBridgeFlags,
|
XChainModifyBridgeFlags,
|
||||||
XChainModifyBridgeFlagsInterface,
|
XChainModifyBridgeFlagsInterface,
|
||||||
} from './XChainModifyBridge'
|
} from './XChainModifyBridge'
|
||||||
|
|
||||||
|
export { PermissionedDomainSet } from './permissionedDomainSet'
|
||||||
|
export { PermissionedDomainDelete } from './permissionedDomainDelete'
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Amount } from '../common'
|
import { Amount, MPTAmount } from '../common'
|
||||||
|
|
||||||
import { BaseTransaction } from './common'
|
import { BaseTransaction } from './common'
|
||||||
|
import {
|
||||||
|
MPTokenIssuanceCreate,
|
||||||
|
MPTokenIssuanceCreateMetadata,
|
||||||
|
} from './MPTokenIssuanceCreate'
|
||||||
import {
|
import {
|
||||||
NFTokenAcceptOffer,
|
NFTokenAcceptOffer,
|
||||||
NFTokenAcceptOfferMetadata,
|
NFTokenAcceptOfferMetadata,
|
||||||
@@ -79,9 +83,9 @@ export function isDeletedNode(node: Node): node is DeletedNode {
|
|||||||
|
|
||||||
export interface TransactionMetadataBase {
|
export interface TransactionMetadataBase {
|
||||||
AffectedNodes: Node[]
|
AffectedNodes: Node[]
|
||||||
DeliveredAmount?: Amount
|
DeliveredAmount?: Amount | MPTAmount
|
||||||
// "unavailable" possible for transactions before 2014-01-20
|
// "unavailable" possible for transactions before 2014-01-20
|
||||||
delivered_amount?: Amount | 'unavailable'
|
delivered_amount?: Amount | MPTAmount | 'unavailable'
|
||||||
TransactionIndex: number
|
TransactionIndex: number
|
||||||
TransactionResult: string
|
TransactionResult: string
|
||||||
}
|
}
|
||||||
@@ -97,4 +101,6 @@ export type TransactionMetadata<T extends BaseTransaction = Transaction> =
|
|||||||
? NFTokenAcceptOfferMetadata
|
? NFTokenAcceptOfferMetadata
|
||||||
: T extends NFTokenCancelOffer
|
: T extends NFTokenCancelOffer
|
||||||
? NFTokenCancelOfferMetadata
|
? NFTokenCancelOfferMetadata
|
||||||
|
: T extends MPTokenIssuanceCreate
|
||||||
|
? MPTokenIssuanceCreateMetadata
|
||||||
: TransactionMetadataBase
|
: TransactionMetadataBase
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
import { Amount, Path } from '../common'
|
import { Amount, Path, MPTAmount } from '../common'
|
||||||
import { isFlagEnabled } from '../utils'
|
import { isFlagEnabled } from '../utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
validateOptionalField,
|
validateOptionalField,
|
||||||
isNumber,
|
isNumber,
|
||||||
Account,
|
Account,
|
||||||
|
validateCredentialsList,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
} from './common'
|
} from './common'
|
||||||
import type { TransactionMetadataBase } from './metadata'
|
import type { TransactionMetadataBase } from './metadata'
|
||||||
|
|
||||||
@@ -116,7 +118,7 @@ export interface Payment extends BaseTransaction {
|
|||||||
* names MUST be lower-case. If the tfPartialPayment flag is set, deliver up
|
* names MUST be lower-case. If the tfPartialPayment flag is set, deliver up
|
||||||
* to this amount instead.
|
* to this amount instead.
|
||||||
*/
|
*/
|
||||||
Amount: Amount
|
Amount: Amount | MPTAmount
|
||||||
/** The unique address of the account receiving the payment. */
|
/** The unique address of the account receiving the payment. */
|
||||||
Destination: Account
|
Destination: Account
|
||||||
/**
|
/**
|
||||||
@@ -142,19 +144,24 @@ export interface Payment extends BaseTransaction {
|
|||||||
* cross-currency/cross-issue payments. Must be omitted for XRP-to-XRP
|
* cross-currency/cross-issue payments. Must be omitted for XRP-to-XRP
|
||||||
* Payments.
|
* Payments.
|
||||||
*/
|
*/
|
||||||
SendMax?: Amount
|
SendMax?: Amount | MPTAmount
|
||||||
/**
|
/**
|
||||||
* Minimum amount of destination currency this transaction should deliver.
|
* Minimum amount of destination currency this transaction should deliver.
|
||||||
* Only valid if this is a partial payment. For non-XRP amounts, the nested
|
* Only valid if this is a partial payment. For non-XRP amounts, the nested
|
||||||
* field names are lower-case.
|
* 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
|
Flags?: number | PaymentFlagsInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaymentMetadata extends TransactionMetadataBase {
|
export interface PaymentMetadata extends TransactionMetadataBase {
|
||||||
DeliveredAmount?: Amount
|
DeliveredAmount?: Amount | MPTAmount
|
||||||
delivered_amount?: Amount | 'unavailable'
|
delivered_amount?: Amount | MPTAmount | 'unavailable'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,6 +184,14 @@ export function validatePayment(tx: Record<string, unknown>): void {
|
|||||||
validateRequiredField(tx, 'Destination', isAccount)
|
validateRequiredField(tx, 'Destination', isAccount)
|
||||||
validateOptionalField(tx, 'DestinationTag', isNumber)
|
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,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
|
||||||
if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') {
|
if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') {
|
||||||
throw new ValidationError('PaymentTransaction: InvoiceID must be a string')
|
throw new ValidationError('PaymentTransaction: InvoiceID must be a string')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
|
|
||||||
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
GlobalFlags,
|
||||||
|
validateBaseTransaction,
|
||||||
|
validateCredentialsList,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum representing values for PaymentChannelClaim transaction flags.
|
* Enum representing values for PaymentChannelClaim transaction flags.
|
||||||
@@ -127,6 +133,11 @@ export interface PaymentChannelClaim extends BaseTransaction {
|
|||||||
* field is omitted.
|
* field is omitted.
|
||||||
*/
|
*/
|
||||||
PublicKey?: string
|
PublicKey?: string
|
||||||
|
/**
|
||||||
|
* Credentials associated with the sender of this transaction.
|
||||||
|
* The credentials included must not be expired.
|
||||||
|
*/
|
||||||
|
CredentialIDs?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,6 +149,14 @@ export interface PaymentChannelClaim extends BaseTransaction {
|
|||||||
export function validatePaymentChannelClaim(tx: Record<string, unknown>): void {
|
export function validatePaymentChannelClaim(tx: Record<string, unknown>): void {
|
||||||
validateBaseTransaction(tx)
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateCredentialsList(
|
||||||
|
tx.CredentialIDs,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||||
|
tx.TransactionType as string,
|
||||||
|
true,
|
||||||
|
MAX_AUTHORIZED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
|
||||||
if (tx.Channel === undefined) {
|
if (tx.Channel === undefined) {
|
||||||
throw new ValidationError('PaymentChannelClaim: missing Channel')
|
throw new ValidationError('PaymentChannelClaim: missing Channel')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
isString,
|
||||||
|
validateBaseTransaction,
|
||||||
|
validateRequiredField,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
|
export interface PermissionedDomainDelete extends BaseTransaction {
|
||||||
|
/* The transaction type (PermissionedDomainDelete). */
|
||||||
|
TransactionType: 'PermissionedDomainDelete'
|
||||||
|
|
||||||
|
/* The domain to delete. */
|
||||||
|
DomainID: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the form and type of a PermissionedDomainDelete transaction.
|
||||||
|
*
|
||||||
|
* @param tx - The transaction to verify.
|
||||||
|
* @throws When the transaction is malformed.
|
||||||
|
*/
|
||||||
|
export function validatePermissionedDomainDelete(
|
||||||
|
tx: Record<string, unknown>,
|
||||||
|
): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'DomainID', isString)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { AuthorizeCredential } from '../common'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
isString,
|
||||||
|
validateBaseTransaction,
|
||||||
|
validateOptionalField,
|
||||||
|
validateRequiredField,
|
||||||
|
validateCredentialsList,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
|
const MAX_ACCEPTED_CREDENTIALS = 10
|
||||||
|
|
||||||
|
export interface PermissionedDomainSet extends BaseTransaction {
|
||||||
|
/* The transaction type (PermissionedDomainSet). */
|
||||||
|
TransactionType: 'PermissionedDomainSet'
|
||||||
|
|
||||||
|
/* The domain to modify. Must be included if modifying an existing domain. */
|
||||||
|
DomainID?: string
|
||||||
|
|
||||||
|
/* The credentials that are accepted by the domain. Ownership of one
|
||||||
|
of these credentials automatically makes you a member of the domain.
|
||||||
|
An empty array means deleting the field. */
|
||||||
|
AcceptedCredentials: AuthorizeCredential[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a PermissionedDomainSet transaction.
|
||||||
|
*
|
||||||
|
* @param tx - The transaction to validate.
|
||||||
|
* @throws {ValidationError} When the transaction is invalid.
|
||||||
|
*/
|
||||||
|
export function validatePermissionedDomainSet(
|
||||||
|
tx: Record<string, unknown>,
|
||||||
|
): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateOptionalField(tx, 'DomainID', isString)
|
||||||
|
validateRequiredField(
|
||||||
|
tx,
|
||||||
|
'AcceptedCredentials',
|
||||||
|
() => tx.AcceptedCredentials instanceof Array,
|
||||||
|
)
|
||||||
|
|
||||||
|
validateCredentialsList(
|
||||||
|
tx.AcceptedCredentials,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||||
|
tx.TransactionType as string,
|
||||||
|
// PermissionedDomainSet uses AuthorizeCredential nested objects only, strings are not allowed
|
||||||
|
false,
|
||||||
|
// PermissionedDomainSet uses at most 10 accepted credentials. This is different from Credential-feature transactions.
|
||||||
|
MAX_ACCEPTED_CREDENTIALS,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,11 +4,12 @@
|
|||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
import { IssuedCurrencyAmount, Memo } from '../common'
|
import { IssuedCurrencyAmount, Memo } from '../common'
|
||||||
import { isHex } from '../utils'
|
import { isHex } from '../utils'
|
||||||
import { setTransactionFlagsToNumber } from '../utils/flags'
|
import { convertTxFlagsToNumber } from '../utils/flags'
|
||||||
|
|
||||||
import { AccountDelete, validateAccountDelete } from './accountDelete'
|
import { AccountDelete, validateAccountDelete } from './accountDelete'
|
||||||
import { AccountSet, validateAccountSet } from './accountSet'
|
import { AccountSet, validateAccountSet } from './accountSet'
|
||||||
import { AMMBid, validateAMMBid } from './AMMBid'
|
import { AMMBid, validateAMMBid } from './AMMBid'
|
||||||
|
import { AMMClawback, validateAMMClawback } from './AMMClawback'
|
||||||
import { AMMCreate, validateAMMCreate } from './AMMCreate'
|
import { AMMCreate, validateAMMCreate } from './AMMCreate'
|
||||||
import { AMMDelete, validateAMMDelete } from './AMMDelete'
|
import { AMMDelete, validateAMMDelete } from './AMMDelete'
|
||||||
import { AMMDeposit, validateAMMDeposit } from './AMMDeposit'
|
import { AMMDeposit, validateAMMDeposit } from './AMMDeposit'
|
||||||
@@ -19,6 +20,9 @@ import { CheckCash, validateCheckCash } from './checkCash'
|
|||||||
import { CheckCreate, validateCheckCreate } from './checkCreate'
|
import { CheckCreate, validateCheckCreate } from './checkCreate'
|
||||||
import { Clawback, validateClawback } from './clawback'
|
import { Clawback, validateClawback } from './clawback'
|
||||||
import { BaseTransaction, isIssuedCurrency } from './common'
|
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 { DepositPreauth, validateDepositPreauth } from './depositPreauth'
|
||||||
import { DIDDelete, validateDIDDelete } from './DIDDelete'
|
import { DIDDelete, validateDIDDelete } from './DIDDelete'
|
||||||
import { DIDSet, validateDIDSet } from './DIDSet'
|
import { DIDSet, validateDIDSet } from './DIDSet'
|
||||||
@@ -27,6 +31,19 @@ import { EscrowCancel, validateEscrowCancel } from './escrowCancel'
|
|||||||
import { EscrowCreate, validateEscrowCreate } from './escrowCreate'
|
import { EscrowCreate, validateEscrowCreate } from './escrowCreate'
|
||||||
import { EscrowFinish, validateEscrowFinish } from './escrowFinish'
|
import { EscrowFinish, validateEscrowFinish } from './escrowFinish'
|
||||||
import { TransactionMetadata } from './metadata'
|
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 {
|
import {
|
||||||
NFTokenAcceptOffer,
|
NFTokenAcceptOffer,
|
||||||
validateNFTokenAcceptOffer,
|
validateNFTokenAcceptOffer,
|
||||||
@@ -41,6 +58,7 @@ import {
|
|||||||
validateNFTokenCreateOffer,
|
validateNFTokenCreateOffer,
|
||||||
} from './NFTokenCreateOffer'
|
} from './NFTokenCreateOffer'
|
||||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||||
|
import { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||||
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
||||||
@@ -58,6 +76,14 @@ import {
|
|||||||
PaymentChannelFund,
|
PaymentChannelFund,
|
||||||
validatePaymentChannelFund,
|
validatePaymentChannelFund,
|
||||||
} from './paymentChannelFund'
|
} from './paymentChannelFund'
|
||||||
|
import {
|
||||||
|
PermissionedDomainDelete,
|
||||||
|
validatePermissionedDomainDelete,
|
||||||
|
} from './permissionedDomainDelete'
|
||||||
|
import {
|
||||||
|
PermissionedDomainSet,
|
||||||
|
validatePermissionedDomainSet,
|
||||||
|
} from './permissionedDomainSet'
|
||||||
import { SetFee } from './setFee'
|
import { SetFee } from './setFee'
|
||||||
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
|
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
|
||||||
import { SignerListSet, validateSignerListSet } from './signerListSet'
|
import { SignerListSet, validateSignerListSet } from './signerListSet'
|
||||||
@@ -98,6 +124,7 @@ import {
|
|||||||
*/
|
*/
|
||||||
export type SubmittableTransaction =
|
export type SubmittableTransaction =
|
||||||
| AMMBid
|
| AMMBid
|
||||||
|
| AMMClawback
|
||||||
| AMMCreate
|
| AMMCreate
|
||||||
| AMMDelete
|
| AMMDelete
|
||||||
| AMMDeposit
|
| AMMDeposit
|
||||||
@@ -109,17 +136,25 @@ export type SubmittableTransaction =
|
|||||||
| CheckCash
|
| CheckCash
|
||||||
| CheckCreate
|
| CheckCreate
|
||||||
| Clawback
|
| Clawback
|
||||||
|
| CredentialAccept
|
||||||
|
| CredentialCreate
|
||||||
|
| CredentialDelete
|
||||||
| DIDDelete
|
| DIDDelete
|
||||||
| DIDSet
|
| DIDSet
|
||||||
| DepositPreauth
|
| DepositPreauth
|
||||||
| EscrowCancel
|
| EscrowCancel
|
||||||
| EscrowCreate
|
| EscrowCreate
|
||||||
| EscrowFinish
|
| EscrowFinish
|
||||||
|
| MPTokenAuthorize
|
||||||
|
| MPTokenIssuanceCreate
|
||||||
|
| MPTokenIssuanceDestroy
|
||||||
|
| MPTokenIssuanceSet
|
||||||
| NFTokenAcceptOffer
|
| NFTokenAcceptOffer
|
||||||
| NFTokenBurn
|
| NFTokenBurn
|
||||||
| NFTokenCancelOffer
|
| NFTokenCancelOffer
|
||||||
| NFTokenCreateOffer
|
| NFTokenCreateOffer
|
||||||
| NFTokenMint
|
| NFTokenMint
|
||||||
|
| NFTokenModify
|
||||||
| OfferCancel
|
| OfferCancel
|
||||||
| OfferCreate
|
| OfferCreate
|
||||||
| OracleDelete
|
| OracleDelete
|
||||||
@@ -128,6 +163,8 @@ export type SubmittableTransaction =
|
|||||||
| PaymentChannelClaim
|
| PaymentChannelClaim
|
||||||
| PaymentChannelCreate
|
| PaymentChannelCreate
|
||||||
| PaymentChannelFund
|
| PaymentChannelFund
|
||||||
|
| PermissionedDomainSet
|
||||||
|
| PermissionedDomainDelete
|
||||||
| SetRegularKey
|
| SetRegularKey
|
||||||
| SignerListSet
|
| SignerListSet
|
||||||
| TicketCreate
|
| TicketCreate
|
||||||
@@ -232,12 +269,16 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- okay here
|
// 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) {
|
switch (tx.TransactionType) {
|
||||||
case 'AMMBid':
|
case 'AMMBid':
|
||||||
validateAMMBid(tx)
|
validateAMMBid(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'AMMClawback':
|
||||||
|
validateAMMClawback(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'AMMCreate':
|
case 'AMMCreate':
|
||||||
validateAMMCreate(tx)
|
validateAMMCreate(tx)
|
||||||
break
|
break
|
||||||
@@ -282,6 +323,18 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
validateClawback(tx)
|
validateClawback(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'CredentialAccept':
|
||||||
|
validateCredentialAccept(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'CredentialCreate':
|
||||||
|
validateCredentialCreate(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'CredentialDelete':
|
||||||
|
validateCredentialDelete(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'DIDDelete':
|
case 'DIDDelete':
|
||||||
validateDIDDelete(tx)
|
validateDIDDelete(tx)
|
||||||
break
|
break
|
||||||
@@ -306,6 +359,22 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
validateEscrowFinish(tx)
|
validateEscrowFinish(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'MPTokenAuthorize':
|
||||||
|
validateMPTokenAuthorize(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'MPTokenIssuanceCreate':
|
||||||
|
validateMPTokenIssuanceCreate(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'MPTokenIssuanceDestroy':
|
||||||
|
validateMPTokenIssuanceDestroy(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'MPTokenIssuanceSet':
|
||||||
|
validateMPTokenIssuanceSet(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'NFTokenAcceptOffer':
|
case 'NFTokenAcceptOffer':
|
||||||
validateNFTokenAcceptOffer(tx)
|
validateNFTokenAcceptOffer(tx)
|
||||||
break
|
break
|
||||||
@@ -326,6 +395,10 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
validateNFTokenMint(tx)
|
validateNFTokenMint(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'NFTokenModify':
|
||||||
|
validateNFTokenModify(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'OfferCancel':
|
case 'OfferCancel':
|
||||||
validateOfferCancel(tx)
|
validateOfferCancel(tx)
|
||||||
break
|
break
|
||||||
@@ -358,6 +431,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
validatePaymentChannelFund(tx)
|
validatePaymentChannelFund(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'PermissionedDomainSet':
|
||||||
|
validatePermissionedDomainSet(tx)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'PermissionedDomainDelete':
|
||||||
|
validatePermissionedDomainDelete(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'SetRegularKey':
|
case 'SetRegularKey':
|
||||||
validateSetRegularKey(tx)
|
validateSetRegularKey(tx)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ export enum TrustSetFlags {
|
|||||||
tfSetFreeze = 0x00100000,
|
tfSetFreeze = 0x00100000,
|
||||||
/** Unfreeze the trust line. */
|
/** Unfreeze the trust line. */
|
||||||
tfClearFreeze = 0x00200000,
|
tfClearFreeze = 0x00200000,
|
||||||
|
/** Deep-Freeze the trustline -- disallow sending and receiving the said IssuedCurrency */
|
||||||
|
/** Allowed only if the trustline is already regularly frozen, or if tfSetFreeze is set in the same transaction. */
|
||||||
|
tfSetDeepFreeze = 0x00400000,
|
||||||
|
/** Clear a Deep-Frozen trustline */
|
||||||
|
tfClearDeepFreeze = 0x00800000,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,6 +94,11 @@ export interface TrustSetFlagsInterface extends GlobalFlags {
|
|||||||
tfSetFreeze?: boolean
|
tfSetFreeze?: boolean
|
||||||
/** Unfreeze the trust line. */
|
/** Unfreeze the trust line. */
|
||||||
tfClearFreeze?: boolean
|
tfClearFreeze?: boolean
|
||||||
|
/** Deep-Freeze the trustline -- disallow sending and receiving the said IssuedCurrency */
|
||||||
|
/** Allowed only if the trustline is already regularly frozen, or if tfSetFreeze is set in the same transaction. */
|
||||||
|
tfSetDeepFreeze?: boolean
|
||||||
|
/** Clear a Deep-Frozen trust line */
|
||||||
|
tfClearDeepFreeze?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-param-reassign -- param reassign is safe */
|
|
||||||
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
||||||
import { ValidationError } from '../../errors'
|
import { ValidationError } from '../../errors'
|
||||||
import {
|
import {
|
||||||
@@ -6,9 +5,12 @@ import {
|
|||||||
AccountRootFlags,
|
AccountRootFlags,
|
||||||
} from '../ledger/AccountRoot'
|
} from '../ledger/AccountRoot'
|
||||||
import { AccountSetTfFlags } from '../transactions/accountSet'
|
import { AccountSetTfFlags } from '../transactions/accountSet'
|
||||||
|
import { AMMClawbackFlags } from '../transactions/AMMClawback'
|
||||||
import { AMMDepositFlags } from '../transactions/AMMDeposit'
|
import { AMMDepositFlags } from '../transactions/AMMDeposit'
|
||||||
import { AMMWithdrawFlags } from '../transactions/AMMWithdraw'
|
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 { NFTokenCreateOfferFlags } from '../transactions/NFTokenCreateOffer'
|
||||||
import { NFTokenMintFlags } from '../transactions/NFTokenMint'
|
import { NFTokenMintFlags } from '../transactions/NFTokenMint'
|
||||||
import { OfferCreateFlags } from '../transactions/offerCreate'
|
import { OfferCreateFlags } from '../transactions/offerCreate'
|
||||||
@@ -46,8 +48,12 @@ export function parseAccountRootFlags(
|
|||||||
|
|
||||||
const txToFlag = {
|
const txToFlag = {
|
||||||
AccountSet: AccountSetTfFlags,
|
AccountSet: AccountSetTfFlags,
|
||||||
|
AMMClawback: AMMClawbackFlags,
|
||||||
AMMDeposit: AMMDepositFlags,
|
AMMDeposit: AMMDepositFlags,
|
||||||
AMMWithdraw: AMMWithdrawFlags,
|
AMMWithdraw: AMMWithdrawFlags,
|
||||||
|
MPTokenAuthorize: MPTokenAuthorizeFlags,
|
||||||
|
MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags,
|
||||||
|
MPTokenIssuanceSet: MPTokenIssuanceSetFlags,
|
||||||
NFTokenCreateOffer: NFTokenCreateOfferFlags,
|
NFTokenCreateOffer: NFTokenCreateOfferFlags,
|
||||||
NFTokenMint: NFTokenMintFlags,
|
NFTokenMint: NFTokenMintFlags,
|
||||||
OfferCreate: OfferCreateFlags,
|
OfferCreate: OfferCreateFlags,
|
||||||
@@ -57,37 +63,61 @@ const txToFlag = {
|
|||||||
XChainModifyBridge: XChainModifyBridgeFlags,
|
XChainModifyBridge: XChainModifyBridgeFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTxToFlagKey(
|
||||||
|
transactionType: string,
|
||||||
|
): transactionType is keyof typeof txToFlag {
|
||||||
|
return transactionType in txToFlag
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a transaction's flags to its numeric representation.
|
* 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.
|
* @param tx - A transaction to set its flags to its numeric representation.
|
||||||
*/
|
*/
|
||||||
export function setTransactionFlagsToNumber(tx: Transaction): void {
|
export function setTransactionFlagsToNumber(tx: Transaction): void {
|
||||||
if (tx.Flags == null) {
|
// eslint-disable-next-line no-console -- intended deprecation warning
|
||||||
tx.Flags = 0
|
console.warn(
|
||||||
return
|
'This function is deprecated. Use convertTxFlagsToNumber() instead and use the returned value to modify the Transaction.Flags from the caller.',
|
||||||
}
|
)
|
||||||
if (typeof tx.Flags === 'number') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.Flags = txToFlag[tx.TransactionType]
|
if (tx.Flags) {
|
||||||
? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType])
|
// eslint-disable-next-line no-param-reassign -- intended param reassign in setter, retain old functionality for compatibility
|
||||||
: 0
|
tx.Flags = convertTxFlagsToNumber(tx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
|
/**
|
||||||
function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
|
* Returns a Transaction's Flags as its numeric representation.
|
||||||
return Object.keys(flags).reduce((resultFlags, flag) => {
|
*
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
* @param tx - A Transaction to parse Flags for
|
||||||
if (flagEnum[flag] == null) {
|
* @returns A numerical representation of a Transaction's Flags
|
||||||
throw new ValidationError(
|
*/
|
||||||
`flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
|
export function convertTxFlagsToNumber(tx: Transaction): number {
|
||||||
)
|
if (!tx.Flags) {
|
||||||
}
|
return 0
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
}
|
||||||
return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags
|
if (typeof tx.Flags === 'number') {
|
||||||
}, 0)
|
return tx.Flags
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Flags?.[flag] ? resultFlags | flagEnum[flag] : resultFlags
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,22 +127,24 @@ function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
|
|||||||
* @returns A map with all flags as booleans.
|
* @returns A map with all flags as booleans.
|
||||||
*/
|
*/
|
||||||
export function parseTransactionFlags(tx: Transaction): object {
|
export function parseTransactionFlags(tx: Transaction): object {
|
||||||
setTransactionFlagsToNumber(tx)
|
const flags = convertTxFlagsToNumber(tx)
|
||||||
if (typeof tx.Flags !== 'number' || !tx.Flags || tx.Flags === 0) {
|
if (flags === 0) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const flags = tx.Flags
|
const booleanFlagMap = {}
|
||||||
const flagsMap = {}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access
|
if (isTxToFlagKey(tx.TransactionType)) {
|
||||||
const flagEnum = txToFlag[tx.TransactionType]
|
const transactionTypeFlags = txToFlag[tx.TransactionType]
|
||||||
Object.values(flagEnum).forEach((flag) => {
|
Object.values(transactionTypeFlags).forEach((flag) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
if (
|
||||||
if (typeof flag === 'string' && isFlagEnabled(flags, flagEnum[flag])) {
|
typeof flag === 'string' &&
|
||||||
flagsMap[flag] = true
|
isFlagEnabled(flags, transactionTypeFlags[flag])
|
||||||
}
|
) {
|
||||||
})
|
booleanFlagMap[flag] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return flagsMap
|
return booleanFlagMap
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const HEX_REGEX = /^[0-9A-Fa-f]+$/u
|
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.
|
* Verify that all fields of an object are in fields.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { decode, encode } from 'ripple-binary-codec'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Client,
|
Client,
|
||||||
SubmitRequest,
|
SubmitRequest,
|
||||||
@@ -12,6 +10,7 @@ import { ValidationError, XrplError } from '../errors'
|
|||||||
import { Signer } from '../models/common'
|
import { Signer } from '../models/common'
|
||||||
import { TxResponse } from '../models/methods'
|
import { TxResponse } from '../models/methods'
|
||||||
import { BaseTransaction } from '../models/transactions/common'
|
import { BaseTransaction } from '../models/transactions/common'
|
||||||
|
import { decode, encode } from '../utils'
|
||||||
|
|
||||||
/** Approximate time for a ledger to close, in milliseconds */
|
/** Approximate time for a ledger to close, in milliseconds */
|
||||||
const LEDGER_CLOSE_TIME = 1000
|
const LEDGER_CLOSE_TIME = 1000
|
||||||
@@ -52,7 +51,7 @@ export async function submitRequest(
|
|||||||
failHard = false,
|
failHard = false,
|
||||||
): Promise<SubmitResponse> {
|
): Promise<SubmitResponse> {
|
||||||
if (!isSigned(signedTransaction)) {
|
if (!isSigned(signedTransaction)) {
|
||||||
throw new ValidationError('Transaction must be signed')
|
throw new ValidationError('Transaction must be signed.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const signedTxEncoded =
|
const signedTxEncoded =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
To run integration tests:
|
To run integration tests:
|
||||||
1. Run rippled in standalone node, either in a docker container (preferred) or by installing rippled.
|
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.
|
* 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`
|
* 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.
|
* 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`
|
2. Run `npm run test:integration` or `npm run test:browser`
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ describe('server_info (rippled)', function () {
|
|||||||
'build_version',
|
'build_version',
|
||||||
'node_size',
|
'node_size',
|
||||||
'initial_sync_duration_us',
|
'initial_sync_duration_us',
|
||||||
|
'network_id',
|
||||||
|
'git',
|
||||||
]
|
]
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
omit(response.result.info, removeKeys),
|
omit(response.result.info, removeKeys),
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ describe('server_state', function () {
|
|||||||
load_factor_fee_queue: 256,
|
load_factor_fee_queue: 256,
|
||||||
load_factor_fee_reference: 256,
|
load_factor_fee_reference: 256,
|
||||||
load_factor_server: 256,
|
load_factor_server: 256,
|
||||||
|
network_id: 63456,
|
||||||
peer_disconnects: '0',
|
peer_disconnects: '0',
|
||||||
peer_disconnects_resources: '0',
|
peer_disconnects_resources: '0',
|
||||||
peers: 0,
|
peers: 0,
|
||||||
@@ -116,6 +117,8 @@ describe('server_state', function () {
|
|||||||
'node_size',
|
'node_size',
|
||||||
'initial_sync_duration_us',
|
'initial_sync_duration_us',
|
||||||
'ports',
|
'ports',
|
||||||
|
'git',
|
||||||
|
'network_id',
|
||||||
]
|
]
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
omit(response.result.state, removeKeys),
|
omit(response.result.state, removeKeys),
|
||||||
|
|||||||
85
packages/xrpl/test/integration/requests/simulate.test.ts
Normal file
85
packages/xrpl/test/integration/requests/simulate.test.ts
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { AccountSet, SimulateRequest } from '../../../src'
|
||||||
|
import { SimulateBinaryRequest } from '../../../src/models/methods/simulate'
|
||||||
|
import serverUrl from '../serverUrl'
|
||||||
|
import {
|
||||||
|
setupClient,
|
||||||
|
teardownClient,
|
||||||
|
type XrplIntegrationTestContext,
|
||||||
|
} from '../setup'
|
||||||
|
|
||||||
|
// how long before each test case times out
|
||||||
|
const TIMEOUT = 20000
|
||||||
|
|
||||||
|
describe('simulate', function () {
|
||||||
|
let testContext: XrplIntegrationTestContext
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
testContext = await setupClient(serverUrl)
|
||||||
|
})
|
||||||
|
afterEach(async () => teardownClient(testContext))
|
||||||
|
|
||||||
|
it(
|
||||||
|
'json',
|
||||||
|
async () => {
|
||||||
|
const simulateRequest: SimulateRequest = {
|
||||||
|
command: 'simulate',
|
||||||
|
tx_json: {
|
||||||
|
TransactionType: 'AccountSet',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
NFTokenMinter: testContext.wallet.address,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const simulateResponse = await testContext.client.request(simulateRequest)
|
||||||
|
|
||||||
|
assert.equal(simulateResponse.type, 'response')
|
||||||
|
assert.typeOf(simulateResponse.result.meta, 'object')
|
||||||
|
assert.typeOf(simulateResponse.result.tx_json, 'object')
|
||||||
|
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||||
|
assert.isFalse(simulateResponse.result.applied)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'binary',
|
||||||
|
async () => {
|
||||||
|
const simulateRequest: SimulateBinaryRequest = {
|
||||||
|
command: 'simulate',
|
||||||
|
tx_json: {
|
||||||
|
TransactionType: 'AccountSet',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
},
|
||||||
|
binary: true,
|
||||||
|
}
|
||||||
|
const simulateResponse = await testContext.client.request(simulateRequest)
|
||||||
|
|
||||||
|
assert.equal(simulateResponse.type, 'response')
|
||||||
|
assert.typeOf(simulateResponse.result.meta_blob, 'string')
|
||||||
|
assert.typeOf(simulateResponse.result.tx_blob, 'string')
|
||||||
|
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||||
|
assert.isFalse(simulateResponse.result.applied)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'sugar',
|
||||||
|
async () => {
|
||||||
|
const tx: AccountSet = {
|
||||||
|
TransactionType: 'AccountSet',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
NFTokenMinter: testContext.wallet.address,
|
||||||
|
}
|
||||||
|
const simulateResponse = await testContext.client.simulate(tx)
|
||||||
|
|
||||||
|
assert.equal(simulateResponse.type, 'response')
|
||||||
|
assert.typeOf(simulateResponse.result.meta, 'object')
|
||||||
|
assert.typeOf(simulateResponse.result.tx_json, 'object')
|
||||||
|
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||||
|
assert.isFalse(simulateResponse.result.applied)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { AMMClawback, AMMDeposit, AMMDepositFlags, XRP } from 'xrpl'
|
||||||
|
|
||||||
|
import serverUrl from '../serverUrl'
|
||||||
|
import {
|
||||||
|
setupClient,
|
||||||
|
teardownClient,
|
||||||
|
type XrplIntegrationTestContext,
|
||||||
|
} from '../setup'
|
||||||
|
import { createAMMPool, testTransaction } from '../utils'
|
||||||
|
|
||||||
|
describe('AMMClawback', function () {
|
||||||
|
let testContext: XrplIntegrationTestContext
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testContext = await setupClient(serverUrl)
|
||||||
|
})
|
||||||
|
afterAll(async () => teardownClient(testContext))
|
||||||
|
|
||||||
|
it('base', async function () {
|
||||||
|
const ammPool = await createAMMPool(testContext.client, true)
|
||||||
|
const { issuerWallet } = ammPool
|
||||||
|
const holderWallet = ammPool.lpWallet
|
||||||
|
|
||||||
|
const asset = {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: issuerWallet.classicAddress,
|
||||||
|
}
|
||||||
|
const asset2 = {
|
||||||
|
currency: 'XRP',
|
||||||
|
} as XRP
|
||||||
|
|
||||||
|
const ammDepositTx: AMMDeposit = {
|
||||||
|
TransactionType: 'AMMDeposit',
|
||||||
|
Account: holderWallet.classicAddress,
|
||||||
|
Asset: asset,
|
||||||
|
Asset2: asset2,
|
||||||
|
Amount: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: issuerWallet.address,
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
Flags: AMMDepositFlags.tfSingleAsset,
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(testContext.client, ammDepositTx, holderWallet)
|
||||||
|
|
||||||
|
const ammClawback: AMMClawback = {
|
||||||
|
TransactionType: 'AMMClawback',
|
||||||
|
Account: issuerWallet.address,
|
||||||
|
Holder: holderWallet.address,
|
||||||
|
Asset: asset,
|
||||||
|
Asset2: asset2,
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(testContext.client, ammClawback, issuerWallet)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -6,6 +6,11 @@ import {
|
|||||||
TrustSet,
|
TrustSet,
|
||||||
Payment,
|
Payment,
|
||||||
Clawback,
|
Clawback,
|
||||||
|
MPTokenIssuanceCreate,
|
||||||
|
MPTokenIssuanceCreateFlags,
|
||||||
|
MPTokenAuthorize,
|
||||||
|
TransactionMetadata,
|
||||||
|
LedgerEntryResponse,
|
||||||
} from '../../../src'
|
} from '../../../src'
|
||||||
import serverUrl from '../serverUrl'
|
import serverUrl from '../serverUrl'
|
||||||
import {
|
import {
|
||||||
@@ -112,4 +117,92 @@ describe('Clawback', function () {
|
|||||||
},
|
},
|
||||||
TIMEOUT,
|
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<MPTokenIssuanceCreate>
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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 serverUrl from '../serverUrl'
|
||||||
import {
|
import {
|
||||||
setupClient,
|
setupClient,
|
||||||
teardownClient,
|
teardownClient,
|
||||||
type XrplIntegrationTestContext,
|
type XrplIntegrationTestContext,
|
||||||
} from '../setup'
|
} from '../setup'
|
||||||
import { fundAccount, testTransaction } from '../utils'
|
import { fundAccount, generateFundedWallet, testTransaction } from '../utils'
|
||||||
|
|
||||||
// how long before each test case times out
|
// how long before each test case times out
|
||||||
const TIMEOUT = 20000
|
const TIMEOUT = 20000
|
||||||
@@ -32,4 +40,119 @@ describe('DepositPreauth', function () {
|
|||||||
},
|
},
|
||||||
TIMEOUT,
|
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,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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<MPTokenIssuanceCreate>
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -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<MPTokenIssuanceCreate>
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -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<MPTokenIssuanceCreate>
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { NFTokenModify } from '../../../dist/npm'
|
||||||
|
import { NFTokenMintFlags } from '../../../dist/npm/src'
|
||||||
|
import {
|
||||||
|
convertStringToHex,
|
||||||
|
getNFTokenID,
|
||||||
|
NFTokenMint,
|
||||||
|
TransactionMetadata,
|
||||||
|
TxRequest,
|
||||||
|
} from '../../../src'
|
||||||
|
import { hashSignedTx } from '../../../src/utils/hashes'
|
||||||
|
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('NFTokenModify', function () {
|
||||||
|
let testContext: XrplIntegrationTestContext
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
testContext = await setupClient(serverUrl)
|
||||||
|
})
|
||||||
|
afterEach(async () => teardownClient(testContext))
|
||||||
|
|
||||||
|
// Mint an NFToken with tfMutable flag and modify URI later
|
||||||
|
it(
|
||||||
|
'modify NFToken URI',
|
||||||
|
async function () {
|
||||||
|
const oldUri = convertStringToHex('https://www.google.com')
|
||||||
|
const newUri = convertStringToHex('https://www.youtube.com')
|
||||||
|
|
||||||
|
const mutableMint: NFTokenMint = {
|
||||||
|
TransactionType: 'NFTokenMint',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
Flags: NFTokenMintFlags.tfMutable,
|
||||||
|
URI: oldUri,
|
||||||
|
NFTokenTaxon: 0,
|
||||||
|
}
|
||||||
|
const response = await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
mutableMint,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
assert.equal(response.type, 'response')
|
||||||
|
|
||||||
|
const mutableTx: TxRequest = {
|
||||||
|
command: 'tx',
|
||||||
|
transaction: hashSignedTx(response.result.tx_blob),
|
||||||
|
}
|
||||||
|
const mutableTxResponse = await testContext.client.request(mutableTx)
|
||||||
|
|
||||||
|
const mutableNFTokenID =
|
||||||
|
getNFTokenID(
|
||||||
|
mutableTxResponse.result.meta as TransactionMetadata<NFTokenMint>,
|
||||||
|
) ?? 'undefined'
|
||||||
|
|
||||||
|
const accountNFTs = await testContext.client.request({
|
||||||
|
command: 'account_nfts',
|
||||||
|
account: testContext.wallet.address,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
accountNFTs.result.account_nfts.find(
|
||||||
|
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||||
|
)?.URI,
|
||||||
|
oldUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
const modifyTx: NFTokenModify = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
NFTokenID: mutableNFTokenID,
|
||||||
|
URI: newUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifyResponse = await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
modifyTx,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
assert.equal(modifyResponse.type, 'response')
|
||||||
|
|
||||||
|
const nfts = await testContext.client.request({
|
||||||
|
command: 'account_nfts',
|
||||||
|
account: testContext.wallet.address,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
nfts.result.account_nfts.find(
|
||||||
|
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||||
|
)?.URI,
|
||||||
|
newUri,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -1,24 +1,36 @@
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
|
|
||||||
import { OfferCreate } from '../../../src'
|
import { OfferCreate, TrustSet, Wallet } from '../../../src'
|
||||||
import serverUrl from '../serverUrl'
|
import serverUrl from '../serverUrl'
|
||||||
import {
|
import {
|
||||||
setupClient,
|
setupClient,
|
||||||
teardownClient,
|
teardownClient,
|
||||||
type XrplIntegrationTestContext,
|
type XrplIntegrationTestContext,
|
||||||
} from '../setup'
|
} from '../setup'
|
||||||
import { testTransaction } from '../utils'
|
import {
|
||||||
|
testTransaction,
|
||||||
|
generateFundedWallet,
|
||||||
|
submitTransaction,
|
||||||
|
} from '../utils'
|
||||||
|
|
||||||
// how long before each test case times out
|
// how long before each test case times out
|
||||||
const TIMEOUT = 20000
|
const TIMEOUT = 20000
|
||||||
|
|
||||||
describe('OfferCreate', function () {
|
describe('OfferCreate', function () {
|
||||||
let testContext: XrplIntegrationTestContext
|
let testContext: XrplIntegrationTestContext
|
||||||
|
let wallet_deep_freeze_trustline: Wallet | undefined
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeAll(async () => {
|
||||||
testContext = await setupClient(serverUrl)
|
testContext = await setupClient(serverUrl)
|
||||||
|
if (!wallet_deep_freeze_trustline) {
|
||||||
|
// eslint-disable-next-line require-atomic-updates -- race condition doesn't really matter
|
||||||
|
wallet_deep_freeze_trustline = await generateFundedWallet(
|
||||||
|
testContext.client,
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
afterEach(async () => teardownClient(testContext))
|
|
||||||
|
afterAll(async () => teardownClient(testContext))
|
||||||
|
|
||||||
it(
|
it(
|
||||||
'base',
|
'base',
|
||||||
@@ -49,4 +61,52 @@ describe('OfferCreate', function () {
|
|||||||
},
|
},
|
||||||
TIMEOUT,
|
TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'OfferCreate with Deep-Frozen trustline must fail',
|
||||||
|
async () => {
|
||||||
|
assert(wallet_deep_freeze_trustline != null)
|
||||||
|
|
||||||
|
// deep-freeze the trust line
|
||||||
|
const trust_set_tx: TrustSet = {
|
||||||
|
TransactionType: 'TrustSet',
|
||||||
|
Account: testContext.wallet.classicAddress,
|
||||||
|
LimitAmount: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: wallet_deep_freeze_trustline.classicAddress,
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
Flags: {
|
||||||
|
tfSetFreeze: true,
|
||||||
|
tfSetDeepFreeze: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
trust_set_tx,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
|
||||||
|
const offer_create_tx: OfferCreate = {
|
||||||
|
TransactionType: 'OfferCreate',
|
||||||
|
Account: testContext.wallet.classicAddress,
|
||||||
|
TakerGets: '13100000',
|
||||||
|
TakerPays: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: wallet_deep_freeze_trustline.classicAddress,
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await submitTransaction({
|
||||||
|
client: testContext.client,
|
||||||
|
transaction: offer_create_tx,
|
||||||
|
wallet: testContext.wallet,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(response.result.engine_result, 'tecFROZEN')
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
|
|
||||||
import { Payment, Wallet } from '../../../src'
|
import {
|
||||||
|
Payment,
|
||||||
|
Wallet,
|
||||||
|
MPTokenIssuanceCreate,
|
||||||
|
MPTokenAuthorize,
|
||||||
|
TransactionMetadata,
|
||||||
|
} from '../../../src'
|
||||||
import serverUrl from '../serverUrl'
|
import serverUrl from '../serverUrl'
|
||||||
import {
|
import {
|
||||||
setupClient,
|
setupClient,
|
||||||
@@ -102,4 +108,89 @@ describe('Payment', function () {
|
|||||||
},
|
},
|
||||||
TIMEOUT,
|
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<MPTokenIssuanceCreate>
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import {
|
||||||
|
LedgerEntryRequest,
|
||||||
|
PermissionedDomainDelete,
|
||||||
|
PermissionedDomainSet,
|
||||||
|
AuthorizeCredential,
|
||||||
|
} from '../../../src'
|
||||||
|
import PermissionedDomain from '../../../src/models/ledger/PermissionedDomain'
|
||||||
|
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('PermissionedDomainSet', function () {
|
||||||
|
let testContext: XrplIntegrationTestContext
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
testContext = await setupClient(serverUrl)
|
||||||
|
})
|
||||||
|
afterEach(async () => teardownClient(testContext))
|
||||||
|
|
||||||
|
it(
|
||||||
|
'Lifecycle of PermissionedDomain ledger object',
|
||||||
|
async () => {
|
||||||
|
const sampleCredential: AuthorizeCredential = {
|
||||||
|
Credential: {
|
||||||
|
CredentialType: stringToHex('Passport'),
|
||||||
|
Issuer: testContext.wallet.classicAddress,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step-1: Test the PermissionedDomainSet transaction
|
||||||
|
const pdSet: PermissionedDomainSet = {
|
||||||
|
TransactionType: 'PermissionedDomainSet',
|
||||||
|
Account: testContext.wallet.classicAddress,
|
||||||
|
AcceptedCredentials: [sampleCredential],
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(testContext.client, pdSet, testContext.wallet)
|
||||||
|
|
||||||
|
// Step-2: Validate the ledger_entry, account_objects RPC methods
|
||||||
|
// validate the account_objects RPC
|
||||||
|
const result = await testContext.client.request({
|
||||||
|
command: 'account_objects',
|
||||||
|
account: testContext.wallet.classicAddress,
|
||||||
|
type: 'permissioned_domain',
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(result.result.account_objects.length, 1)
|
||||||
|
const pd = result.result.account_objects[0] as PermissionedDomain
|
||||||
|
|
||||||
|
assert.equal(pd.Flags, 0)
|
||||||
|
expect(pd.AcceptedCredentials).toEqual([sampleCredential])
|
||||||
|
|
||||||
|
// validate the ledger_entry RPC
|
||||||
|
const ledgerEntryRequest: LedgerEntryRequest = {
|
||||||
|
command: 'ledger_entry',
|
||||||
|
// fetch the PD `index` from the previous account_objects RPC response
|
||||||
|
index: pd.index,
|
||||||
|
}
|
||||||
|
const ledgerEntryResult = await testContext.client.request(
|
||||||
|
ledgerEntryRequest,
|
||||||
|
)
|
||||||
|
assert.deepEqual(pd, ledgerEntryResult.result.node)
|
||||||
|
|
||||||
|
// Step-3: Test the PDDelete transaction
|
||||||
|
const pdDelete: PermissionedDomainDelete = {
|
||||||
|
TransactionType: 'PermissionedDomainDelete',
|
||||||
|
Account: testContext.wallet.classicAddress,
|
||||||
|
// fetch the PD `index` from the previous account_objects RPC response
|
||||||
|
DomainID: pd.index,
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(testContext.client, pdDelete, testContext.wallet)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
|
|
||||||
import { TrustSet, percentToQuality, Wallet } from '../../../src'
|
import { TrustSet, percentToQuality, Wallet } from '../../../src'
|
||||||
|
import { RippleState } from '../../../src/models/ledger/index'
|
||||||
|
import { RippleStateFlags } from '../../../src/models/ledger/RippleState'
|
||||||
import serverUrl from '../serverUrl'
|
import serverUrl from '../serverUrl'
|
||||||
import {
|
import {
|
||||||
setupClient,
|
setupClient,
|
||||||
@@ -85,4 +87,60 @@ describe('TrustSet', function () {
|
|||||||
},
|
},
|
||||||
TIMEOUT,
|
TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'Create a Deep-Frozen trustline',
|
||||||
|
async () => {
|
||||||
|
assert(wallet2 != null)
|
||||||
|
// deep-freeze a trustline with the specified counter-party/currency-code
|
||||||
|
const tx: TrustSet = {
|
||||||
|
TransactionType: 'TrustSet',
|
||||||
|
Account: testContext.wallet.classicAddress,
|
||||||
|
LimitAmount: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: wallet2.classicAddress,
|
||||||
|
value: '10',
|
||||||
|
},
|
||||||
|
Flags: {
|
||||||
|
tfSetFreeze: true,
|
||||||
|
tfSetDeepFreeze: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
tx,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
assert.equal(response.result.engine_result, 'tesSUCCESS')
|
||||||
|
|
||||||
|
// assert that the trustline is frozen
|
||||||
|
const trustLine = await testContext.client.request({
|
||||||
|
command: 'account_lines',
|
||||||
|
account: testContext.wallet.classicAddress,
|
||||||
|
})
|
||||||
|
assert.equal(trustLine.result.lines[0].freeze, true)
|
||||||
|
|
||||||
|
// verify that the trust-line is deep-frozen
|
||||||
|
// this operation cannot be done with the account_lines RPC
|
||||||
|
const account_objects = await testContext.client.request({
|
||||||
|
command: 'account_objects',
|
||||||
|
account: testContext.wallet.classicAddress,
|
||||||
|
})
|
||||||
|
|
||||||
|
const rippleState = account_objects.result
|
||||||
|
.account_objects[0] as RippleState
|
||||||
|
|
||||||
|
// Depending on the pseudo-random generation of accounts,
|
||||||
|
// either of the below leger-object flags must be set
|
||||||
|
|
||||||
|
const hasDeepFreeze =
|
||||||
|
// eslint-disable-next-line no-bitwise -- required to validate flag
|
||||||
|
(rippleState.Flags & RippleStateFlags.lsfHighDeepFreeze) |
|
||||||
|
// eslint-disable-next-line no-bitwise -- required to validate flag
|
||||||
|
(rippleState.Flags & RippleStateFlags.lsfLowDeepFreeze)
|
||||||
|
assert.isTrue(hasDeepFreeze !== 0)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -373,7 +373,10 @@ export async function getIOUBalance(
|
|||||||
return (await client.request(request)).result.lines[0].balance
|
return (await client.request(request)).result.lines[0].balance
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAMMPool(client: Client): Promise<{
|
export async function createAMMPool(
|
||||||
|
client: Client,
|
||||||
|
enableAMMClawback = false,
|
||||||
|
): Promise<{
|
||||||
issuerWallet: Wallet
|
issuerWallet: Wallet
|
||||||
lpWallet: Wallet
|
lpWallet: Wallet
|
||||||
asset: Currency
|
asset: Currency
|
||||||
@@ -391,6 +394,16 @@ export async function createAMMPool(client: Client): Promise<{
|
|||||||
|
|
||||||
await testTransaction(client, accountSetTx, issuerWallet)
|
await testTransaction(client, accountSetTx, issuerWallet)
|
||||||
|
|
||||||
|
if (enableAMMClawback) {
|
||||||
|
const accountSetTx2: AccountSet = {
|
||||||
|
TransactionType: 'AccountSet',
|
||||||
|
Account: issuerWallet.classicAddress,
|
||||||
|
SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
|
||||||
|
}
|
||||||
|
|
||||||
|
await testTransaction(client, accountSetTx2, issuerWallet)
|
||||||
|
}
|
||||||
|
|
||||||
const trustSetTx: TrustSet = {
|
const trustSetTx: TrustSet = {
|
||||||
TransactionType: 'TrustSet',
|
TransactionType: 'TrustSet',
|
||||||
Flags: TrustSetFlags.tfClearNoRipple,
|
Flags: TrustSetFlags.tfClearNoRipple,
|
||||||
|
|||||||
176
packages/xrpl/test/models/AMMClawback.test.ts
Normal file
176
packages/xrpl/test/models/AMMClawback.test.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { validate, ValidationError } from '../../src'
|
||||||
|
import {
|
||||||
|
AMMClawbackFlags,
|
||||||
|
validateAMMClawback,
|
||||||
|
} from '../../src/models/transactions/AMMClawback'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMMClawback Transaction Verification Testing.
|
||||||
|
*
|
||||||
|
* Providing runtime verification testing for each specific transaction type.
|
||||||
|
*/
|
||||||
|
describe('AMMClawback', function () {
|
||||||
|
let ammClawback
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
ammClawback = {
|
||||||
|
TransactionType: 'AMMClawback',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
Holder: 'rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9',
|
||||||
|
Asset: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
},
|
||||||
|
Asset2: {
|
||||||
|
currency: 'XRP',
|
||||||
|
},
|
||||||
|
Amount: {
|
||||||
|
currency: 'USD',
|
||||||
|
issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
value: '1000',
|
||||||
|
},
|
||||||
|
Sequence: 1337,
|
||||||
|
} as any
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`verifies valid AMMClawback`, function () {
|
||||||
|
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||||
|
assert.doesNotThrow(() => validate(ammClawback))
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`verifies valid AMMClawback without Amount`, function () {
|
||||||
|
delete ammClawback.Amount
|
||||||
|
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||||
|
assert.doesNotThrow(() => validate(ammClawback))
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`verifies valid AMMClawback with tfClawTwoAssets`, function () {
|
||||||
|
ammClawback.flags = AMMClawbackFlags.tfClawTwoAssets
|
||||||
|
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||||
|
assert.doesNotThrow(() => validate(ammClawback))
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ missing Holder`, function () {
|
||||||
|
delete ammClawback.Holder
|
||||||
|
const errorMessage = 'AMMClawback: missing field Holder'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ invalid field Holder`, function () {
|
||||||
|
ammClawback.Holder = 1234
|
||||||
|
const errorMessage = 'AMMClawback: invalid field Holder'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ Holder and Asset.issuer must be distinct`, function () {
|
||||||
|
ammClawback.Holder = ammClawback.Asset.issuer
|
||||||
|
const errorMessage = 'AMMClawback: Holder and Asset.issuer must be distinct'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ missing Asset`, function () {
|
||||||
|
delete ammClawback.Asset
|
||||||
|
const errorMessage = 'AMMClawback: missing field Asset'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ invalid field Asset`, function () {
|
||||||
|
ammClawback.Asset = '1000'
|
||||||
|
const errorMessage = 'AMMClawback: invalid field Asset'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ Account must be the same as Asset.issuer`, function () {
|
||||||
|
ammClawback.Account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'
|
||||||
|
const errorMessage = 'AMMClawback: Account must be the same as Asset.issuer'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ missing Asset2`, function () {
|
||||||
|
delete ammClawback.Asset2
|
||||||
|
const errorMessage = 'AMMClawback: missing field Asset2'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ invalid field Asset2`, function () {
|
||||||
|
ammClawback.Asset2 = '1000'
|
||||||
|
const errorMessage = 'AMMClawback: invalid field Asset2'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ invalid field Amount`, function () {
|
||||||
|
ammClawback.Amount = 1000
|
||||||
|
const errorMessage = 'AMMClawback: invalid field Amount'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ Amount.currency must match Asset.currency`, function () {
|
||||||
|
ammClawback.Amount.currency = 'ETH'
|
||||||
|
const errorMessage =
|
||||||
|
'AMMClawback: Amount.currency must match Asset.currency'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ Amount.issuer must match Amount.issuer`, function () {
|
||||||
|
ammClawback.Amount.issuer = 'rnYgaEtpqpNRt3wxE39demVpDAA817rQEY'
|
||||||
|
const errorMessage = 'AMMClawback: Amount.issuer must match Amount.issuer'
|
||||||
|
assert.throws(
|
||||||
|
() => validateAMMClawback(ammClawback),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
|
)
|
||||||
|
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||||
|
})
|
||||||
|
})
|
||||||
153
packages/xrpl/test/models/CredentialAccept.test.ts
Normal file
153
packages/xrpl/test/models/CredentialAccept.test.ts
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
230
packages/xrpl/test/models/CredentialCreate.test.ts
Normal file
230
packages/xrpl/test/models/CredentialCreate.test.ts
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
171
packages/xrpl/test/models/CredentialDelete.test.ts
Normal file
171
packages/xrpl/test/models/CredentialDelete.test.ts
Normal file
@@ -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,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
63
packages/xrpl/test/models/MPTokenAuthorize.test.ts
Normal file
63
packages/xrpl/test/models/MPTokenAuthorize.test.ts
Normal file
@@ -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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
149
packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts
Normal file
149
packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts
Normal file
@@ -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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
35
packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts
Normal file
35
packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts
Normal file
@@ -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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
82
packages/xrpl/test/models/MPTokenIssuanceSet.test.ts
Normal file
82
packages/xrpl/test/models/MPTokenIssuanceSet.test.ts
Normal file
@@ -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',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
75
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
75
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { convertStringToHex, validate, ValidationError } from '../../src'
|
||||||
|
|
||||||
|
const TOKEN_ID =
|
||||||
|
'00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFTokenModify Transaction Verification Testing.
|
||||||
|
*
|
||||||
|
* Providing runtime verification testing for each specific transaction type.
|
||||||
|
*/
|
||||||
|
describe('NFTokenModify', function () {
|
||||||
|
it(`verifies valid NFTokenModify`, function () {
|
||||||
|
const validNFTokenModify = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
NFTokenID: TOKEN_ID,
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
URI: convertStringToHex('http://xrpl.org'),
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => validate(validNFTokenModify))
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ missing NFTokenID`, function () {
|
||||||
|
const invalid = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => validate(invalid),
|
||||||
|
ValidationError,
|
||||||
|
'NFTokenModify: missing field NFTokenID',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ URI being an empty string`, function () {
|
||||||
|
const invalid = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
NFTokenID: TOKEN_ID,
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
URI: '',
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => validate(invalid),
|
||||||
|
ValidationError,
|
||||||
|
'NFTokenModify: URI must not be empty string',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ URI not in hex format`, function () {
|
||||||
|
const invalid = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
NFTokenID: TOKEN_ID,
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
URI: '--',
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => validate(invalid),
|
||||||
|
ValidationError,
|
||||||
|
'NFTokenModify: URI must be in hex format',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -9,8 +9,10 @@ import { validateAccountDelete } from '../../src/models/transactions/accountDele
|
|||||||
* Providing runtime verification testing for each specific transaction type.
|
* Providing runtime verification testing for each specific transaction type.
|
||||||
*/
|
*/
|
||||||
describe('AccountDelete', function () {
|
describe('AccountDelete', function () {
|
||||||
it(`verifies valid AccountDelete`, function () {
|
let validAccountDelete
|
||||||
const validAccountDelete = {
|
|
||||||
|
beforeEach(() => {
|
||||||
|
validAccountDelete = {
|
||||||
TransactionType: 'AccountDelete',
|
TransactionType: 'AccountDelete',
|
||||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe',
|
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe',
|
||||||
@@ -18,76 +20,166 @@ describe('AccountDelete', function () {
|
|||||||
Fee: '5000000',
|
Fee: '5000000',
|
||||||
Sequence: 2470665,
|
Sequence: 2470665,
|
||||||
Flags: 2147483648,
|
Flags: 2147483648,
|
||||||
|
CredentialIDs: [
|
||||||
|
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||||
|
],
|
||||||
} as any
|
} as any
|
||||||
|
})
|
||||||
|
it(`verifies valid AccountDelete`, function () {
|
||||||
assert.doesNotThrow(() => validateAccountDelete(validAccountDelete))
|
assert.doesNotThrow(() => validateAccountDelete(validAccountDelete))
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`throws w/ missing Destination`, function () {
|
it(`throws w/ missing Destination`, function () {
|
||||||
const invalidDestination = {
|
validAccountDelete.Destination = undefined
|
||||||
TransactionType: 'AccountDelete',
|
const errorMessage = 'AccountDelete: missing field Destination'
|
||||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
|
||||||
Fee: '5000000',
|
|
||||||
Sequence: 2470665,
|
|
||||||
Flags: 2147483648,
|
|
||||||
} as any
|
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validateAccountDelete(invalidDestination),
|
() => validateAccountDelete(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: missing field Destination',
|
errorMessage,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validate(invalidDestination),
|
() => validate(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: missing field Destination',
|
errorMessage,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`throws w/ invalid Destination`, function () {
|
it(`throws w/ invalid Destination`, function () {
|
||||||
const invalidDestination = {
|
validAccountDelete.Destination = 65478965
|
||||||
TransactionType: 'AccountDelete',
|
const errorMessage = 'AccountDelete: invalid field Destination'
|
||||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
|
||||||
Destination: 65478965,
|
|
||||||
Fee: '5000000',
|
|
||||||
Sequence: 2470665,
|
|
||||||
Flags: 2147483648,
|
|
||||||
} as any
|
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validateAccountDelete(invalidDestination),
|
() => validateAccountDelete(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: invalid field Destination',
|
errorMessage,
|
||||||
)
|
)
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validate(invalidDestination),
|
() => validate(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: invalid field Destination',
|
errorMessage,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`throws w/ invalid DestinationTag`, function () {
|
it(`throws w/ invalid DestinationTag`, function () {
|
||||||
const invalidDestinationTag = {
|
validAccountDelete.DestinationTag = 'gvftyujnbv'
|
||||||
TransactionType: 'AccountDelete',
|
const errorMessage = 'AccountDelete: invalid field DestinationTag'
|
||||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
|
||||||
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe',
|
|
||||||
DestinationTag: 'gvftyujnbv',
|
|
||||||
Fee: '5000000',
|
|
||||||
Sequence: 2470665,
|
|
||||||
Flags: 2147483648,
|
|
||||||
} as any
|
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validateAccountDelete(invalidDestinationTag),
|
() => validateAccountDelete(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: invalid field DestinationTag',
|
errorMessage,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => validate(invalidDestinationTag),
|
() => validate(validAccountDelete),
|
||||||
ValidationError,
|
ValidationError,
|
||||||
'AccountDelete: invalid field DestinationTag',
|
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(validAccountDelete),
|
||||||
|
ValidationError,
|
||||||
|
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(validAccountDelete),
|
||||||
|
ValidationError,
|
||||||
|
errorMessage,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,4 +78,72 @@ describe('Clawback', function () {
|
|||||||
'Clawback: invalid holder Account',
|
'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',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user