Compare commits

..

41 Commits

Author SHA1 Message Date
tequ
9887516ea7 4.1.0-alpha.0 2026-06-19 22:05:57 +09:00
tequ
e351c57e88 Aliter: Implement DeliverMax alias in Payment transactions, through autofill method (#2689) (#63) 2026-06-19 21:32:17 +09:00
tequ
8f1c5db1f3 feat: add Price Oracles support (#2688) (#68) 2026-06-19 19:30:30 +09:00
tequ
18d343fcb5 HookOnV2 Amendment (#51) 2026-06-18 12:11:57 +00:00
tequ
0c6578156f update XAHAUD_VERSION (#67) 2026-06-18 21:08:34 +09:00
tequ
4cd8736eb4 NamedHook Amendment (#50) 2026-05-29 22:25:12 +09:00
tequ
5e789b1d10 IOUClaimReward Amendment (#45) 2026-05-29 22:05:06 +09:00
tequ
8dbe640aa9 feat: add support for the simulate RPC (XLS-69d) (#2867) (#64) 2026-05-29 09:16:34 +00:00
tequ
4fc68a4bbf Add fields test for SetHook (#66) 2026-05-29 18:08:16 +09:00
tequ
deaf6af88b Add integration test for SetHook transaction (#65)
* Add integration test for SetHook transaction
* Update xahaud.cfg feature stanza
2026-05-27 20:43:13 +09:00
tequ
64608cb9a6 Xahaud 2026.5.26-dev+3273 (#60) 2026-05-27 20:01:37 +09:00
tequ
cc5cf1f2ea Update custom Payment to a higher number in binary codec test (#2824) (#62) 2026-05-27 12:14:29 +09:00
tequ
b417d67b28 4.0.4 (#61) 2026-05-27 11:54:01 +09:00
tequ
bf788fe7b9 4.0.4-alpha.3 (#59) 2026-05-26 03:25:35 +00:00
tequ
3c7609429f Fix publish to use release_tag (#58) 2026-05-26 03:17:31 +00:00
tequ
b85f34d16f 4.0.4-alpha.2 (#57) 2026-05-26 02:50:40 +00:00
tequ
c70e4c1eee fix publish workflow to use node 24 (Support OIDC publish) (#56) 2026-05-26 02:42:36 +00:00
tequ
4918e914c5 4.0.4-alpha.1 (#55) 2026-05-26 02:24:59 +00:00
tequ
7552ae8635 fix publish workflow (#54) 2026-05-26 02:18:25 +00:00
tequ
fd100f6e92 4.0.4-alpha.0 (#53) 2026-05-26 02:04:14 +00:00
tequ
587a75403a Add npm trusted publishing workflow (#48)
Add GitHub Actions workflow for npm trusted publishing via OIDC.
2026-05-26 10:47:27 +09:00
tequ
9444693967 Use xahaud binary directly in workflow instead xahauci image (#52) 2026-05-24 11:59:28 +09:00
tequ
1de9e4e35b Update Node.js versions in GitHub Actions workflow to support 20,22,24 and 26 (#40) 2026-05-24 11:07:44 +09:00
tequ
fd43b82827 comment outed mailing list (#49) 2026-05-20 13:23:55 +09:00
tequ
b310c6c25a Fix setTransactionFlagsToNumber for Xahau transactions (#26)
* Fix SetHook txflags
* fix setTransactionFlagsToNumber
* additional checks for setTransactionFlagsToNumber
* fix setTransactionFlagsToNumber for SetHook
* Add URITokenMint, ClaimReward to txToFlag
2026-05-20 11:00:42 +09:00
zgrguric
a8939c883b Refactor amount assignment in partialPayment.ts (#43)
DeliverMax is expected from xahau.js but it should fallback to Amount.

DeliverMax is returned in xrpl api v2, but Amount is used when querying via API v1 which is xahau at at the moment.
2026-05-13 03:11:40 +00:00
tequ
05e8c2dbda XAH Ledger -> Xahau Network (#47) 2026-05-13 12:08:30 +09:00
zgrguric
e838caaffc Improve HookStateScale validation (#38)
* Fix HookStateScale validation
* Fix new line in accountSet test
2026-05-12 15:04:26 +00:00
zgrguric
b9fb8a1924 Add lsfTshCollect flag in AccountRoot (#41) 2026-05-12 14:47:02 +00:00
tequ
de97ef13e9 Update History.md (#46) 2026-05-12 23:42:55 +09:00
tequ
c1aed4ef48 Fix: workflow browser tests work correctly (#44) 2026-05-12 23:09:41 +09:00
Denis Angell
35c294218d cut v4.0.3 2025-11-18 12:29:03 +01:00
tequ
58150f156e generate correct xahau-latest-min.js file name (#3034) (#39) 2025-11-18 12:15:20 +01:00
Denis Angell
d7b3a02ad2 cut v4.0.2 2025-11-12 12:31:40 +01:00
tequ
ac33a1584d Support ExtendedHookState (#34) 2025-11-12 12:01:45 +01:00
tequ
8bbc84057c Support Cron StartTime, improvements (#33) 2025-11-12 12:01:33 +01:00
tequ
0ca36e1314 Update xahauci 2025.7.9 (#36) 2025-11-12 12:01:16 +01:00
tequ
e454c61994 Support Cron Amendment (#32) 2025-10-17 18:44:07 +10:00
Denis Angell
b5f15ac075 cut v4.0.1 2025-10-02 19:09:24 +02:00
tequ
5ec5ad8e1e Deep Freeze XLS-77d (#2873) (#29)
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com>
Co-authored-by: Denis Angell <dangell@transia.co>
2025-10-02 18:51:18 +02:00
tequ
043620b637 feat: add Clawback amendment support (#2353) (#28)
* Add Clawback transaction
* Account flag lsfAllowTrustLineClawback
* Support bitwise flag checking of 64 bit flags

Co-authored-by: Shawn Xie <35279399+shawnxie999@users.noreply.github.com>
2025-10-02 18:50:42 +02:00
94 changed files with 9715 additions and 4444 deletions

View File

@@ -0,0 +1,8 @@
[validator_list_sites]
http://vl/vl.json
[validator_list_keys]
ED87E0EA91AAFFA130B78B75D2CC3E53202AA1BD8AB3D5E7BAC530C8440E328501
[import_vl_keys]
ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1

View File

@@ -63,11 +63,6 @@ online_delete=256
[debug_logfile]
/var/log/rippled/debug.log
[sntp_servers]
time.windows.com
time.apple.com
time.nist.gov
pool.ntp.org
[ips]
r.ripple.com 51235
@@ -106,67 +101,105 @@ r.ripple.com 51235
# If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled
[features]
# Amendments
NegativeUNL
fixGuardDepth32
NamedHooks
IOURewardClaim
fixIOULockedBalanceInvariant
fixImportIssuer
HookAPISerializedType240
# PermissionedDomains Supported::no
# DynamicNFT Supported::no
# Credentials Supported::no
AMMClawback
# MPTokensV1 Supported::no
# InvariantsV1_1 Supported::no
fixNFTokenPageLinks
fixEnforceNFTokenTrustline
fixReducedOffersV2
# NFTokenMintOffer Supported::no
fixPreviousTxnID
PriceOracle
fixInnerObjTemplate
fixNFTokenReserve
fixFillOrKill
# DID Supported::no
fixDisallowIncomingV1
# XChainBridge Supported::no
AMM
fixReducedOffersV1
HooksUpdate2
HookOnV2
fixHookAPI20251128
fixCronStacking
ExtendedHookState
fixInvalidTxFlags
Cron
IOUIssuerWeakTSH
DeepFreeze
fixProvisionalDoubleThreading
Clawback
fixRewardClaimFlags
HookCanEmit
fix20250131
fixXahauV3
fixReduceImport
Touch
Remarks
fixFloatDivide
fix240911
fixPageCap
fix240819
fixNSDelete
ZeroB2M
Remit
fixXahauV2
fixXahauV1
HooksUpdate1
XahauGenesis
Import
URIToken
PaychanAndEscrowForTokens
BalanceRewards
Hooks
fixNFTokenRemint
fixNonFungibleTokensV1_2
fixUniversalNumber
XRPFees
DisallowIncoming
ImmediateOfferKilled
fixRemoveNFTokenAutoTrustLine
NonFungibleTokensV1
fixTrustLinesToSelf
NonFungibleTokensV1_1
ExpandedSignerList
CheckCashMakesTrustLine
fixRmSmallIncreasedQOffers
fixSTAmountCanonicalize
FlowSortStrands
TicketBatch
fixQualityUpperBound
FlowCross
HardenedValidations
DepositPreauth
MultiSignReserve
fix1623
fix1513
RequireFullyCanonicalSig
fix1543
fix1781
fixCheckThreading
fix1515
CryptoConditionsSuite
fixPayChanRecipientOwnerDir
fix1578
fix1571
NegativeUNL
fixAmendmentMajorityCalc
fixTakerDryOfferRemoval
fixMasterKeyAsRegularKey
Flow
HardenedValidations
fix1781
RequireFullyCanonicalSig
fixQualityUpperBound
DeletableAccounts
DepositAuth
fixPayChanRecipientOwnerDir
fixCheckThreading
fixMasterKeyAsRegularKey
fixTakerDryOfferRemoval
MultiSignReserve
fix1578
fix1515
DepositPreauth
fix1623
fix1543
fix1571
Checks
NonFungibleTokensV1_1
DisallowIncoming
fixNonFungibleTokensV1_2
fixUniversalNumber
ImmediateOfferKilled
XRPFees
ExpandedSignerList
fixNFTokenRemint
# Additional Amendments
BalanceRewards
Hooks
HooksUpdate1
Import
Remit
URIToken
XahauGenesis
ZeroB2M
fix240819
fix240911
fixFloatDivide
fixNFTokenDirV1
fixNFTokenNegOffer
fixNSDelete
fixPageCap
fixReduceImport
fixXahauV1
fixXahauV2
fixXahauV3
PaychanAndEscrowForTokens
DepositAuth
fix1513
FlowCross
Flow
# OwnerPaysFee Supported::no
[network_id]
21337

View File

@@ -4,7 +4,7 @@
name: Node.js CI
env:
XAHAUD_DOCKER_IMAGE: xahauci/xahaud:2025.2.6
XAHAUD_VERSION: 2026.6.16-dev+3330
on:
push:
@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
node-version: [18.x]
node-version: [20.x, 22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v3
@@ -60,7 +60,7 @@ jobs:
strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v3
@@ -95,20 +95,55 @@ jobs:
- run: npm run build
- run: npm test
download-binary:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: cache binary
id: cache-binary
uses: actions/cache@v5
with:
path: xahaud
key: ${{ runner.os }}-xahaud-${{ env.XAHAUD_VERSION }}
- name: Download binary
if: steps.cache-binary.outputs.cache-hit != 'true'
run: |
wget https://build.xahau.tech/${{ env.XAHAUD_VERSION }} -q -O xahaud
chmod +x xahaud
integration:
runs-on: ubuntu-latest
needs: download-binary
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v3
- name: Restore binary
uses: actions/cache/restore@v5
with:
path: xahaud
key: ${{ runner.os }}-xahaud-${{ env.XAHAUD_VERSION }}
- name: Run docker in background
run: |
docker run --detach --rm --name xahaud-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/xahau/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.XAHAUD_DOCKER_IMAGE }} /opt/xahau/bin/xahaud -a --conf /opt/xahau/etc/xahaud.cfg
docker run --detach --rm \
--name xahaud-service \
-p 6006:6006 \
--volume "${{ github.workspace }}/.ci-config/":"/opt/xahau/etc/" \
--volume "${{ github.workspace }}/xahaud":"/opt/xahau/bin/xahaud" \
--health-cmd="wget localhost:6006 || exit 1" \
--health-interval=5s \
--health-retries=10 \
--health-timeout=2s \
--env GITHUB_ACTIONS=true \
--env CI=true \
ubuntu:latest /opt/xahau/bin/xahaud -a --conf /opt/xahau/etc/xahaud.cfg
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
@@ -149,11 +184,12 @@ jobs:
browser:
runs-on: ubuntu-latest
needs: download-binary
timeout-minutes: 10
strategy:
matrix:
node-version: [18.x]
node-version: [20.x, 22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v3
@@ -163,9 +199,26 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- name: Restore binary
uses: actions/cache/restore@v5
with:
path: xahaud
key: ${{ runner.os }}-xahaud-${{ env.XAHAUD_VERSION }}
- name: Run docker in background
run: |
docker run --detach --rm --name xahaud-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/xahau/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.XAHAUD_DOCKER_IMAGE }} /opt/xahau/bin/xahaud -a --conf /opt/xahau/etc/xahaud.cfg
docker run --detach --rm \
--name xahaud-service \
-p 6006:6006 \
--volume "${{ github.workspace }}/.ci-config/":"/opt/xahau/etc/" \
--volume "${{ github.workspace }}/xahaud":"/opt/xahau/bin/xahaud" \
--health-cmd="wget localhost:6006 || exit 1" \
--health-interval=5s \
--health-retries=10 \
--health-timeout=2s \
--env GITHUB_ACTIONS=true \
--env CI=true \
ubuntu:latest /opt/xahau/bin/xahaud -a --conf /opt/xahau/etc/xahaud.cfg
- name: Setup npm version 9
run: |

109
.github/workflows/npm-publish.yml vendored Normal file
View File

@@ -0,0 +1,109 @@
name: Publish npm packages
on:
release:
types: [published]
concurrency:
group: npm-publish-${{ github.event.release.tag_name }}
cancel-in-progress: false
jobs:
publish:
name: Publish ${{ github.event.release.tag_name }}
runs-on: ubuntu-latest
environment: npm
permissions:
contents: read
id-token: write
steps:
- id: release
name: Resolve package from tag
shell: bash
run: |
case "$RELEASE_TAG" in
xahau@*) package_path="packages/xahau" ;;
xahau-address-codec@*) package_path="packages/xahau-address-codec" ;;
xahau-binary-codec@*) package_path="packages/xahau-binary-codec" ;;
xahau-keypairs@*) package_path="packages/xahau-keypairs" ;;
*)
echo "Unsupported release tag: $RELEASE_TAG" >&2
echo "Expected xahau@<version>, xahau-address-codec@<version>, xahau-binary-codec@<version>, or xahau-keypairs@<version>." >&2
exit 1
;;
esac
echo "package-path=$package_path" >> "$GITHUB_OUTPUT"
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
- uses: actions/checkout@v6
with:
ref: ${{ github.event.release.tag_name }}
- uses: actions/setup-node@v6
with:
node-version: "24"
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Install dependencies
run: npm ci
- name: Build package
run: npm run build
- id: package
name: Read package metadata
shell: bash
run: |
package_json="${{ steps.release.outputs.package-path }}/package.json"
name="$(jq -r .name "$package_json")"
version="$(jq -r .version "$package_json")"
tag="$name@$version"
if [[ "$version" == *"-"* ]]; then
release_tag="${version#*-}"
release_tag="${release_tag%%.*}"
else
release_tag="latest"
fi
{
echo "name=$name"
echo "version=$version"
echo "tag=$tag"
echo "release_tag=$release_tag"
} >> "$GITHUB_OUTPUT"
- name: Check release tag matches package version
shell: bash
run: |
if [[ "$RELEASE_TAG" != "$PACKAGE_TAG" ]]; then
echo "Release tag $RELEASE_TAG does not match package tag $PACKAGE_TAG." >&2
exit 1
fi
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
PACKAGE_TAG: ${{ steps.package.outputs.tag }}
- name: Check package version is unpublished
shell: bash
run: |
package_spec="${{ steps.package.outputs.tag }}"
stderr_file="$(mktemp)"
if npm view "$package_spec" version --registry https://registry.npmjs.org 2>"$stderr_file"; then
echo "$package_spec is already published." >&2
exit 1
fi
if grep -Eq "E404|404 Not Found|is not in this registry" "$stderr_file"; then
echo "$package_spec is not published yet."
exit 0
fi
cat "$stderr_file" >&2
exit 1
- name: Publish to npm
run: npm publish --workspace "${{ steps.release.outputs.package-path }}" --registry https://registry.npmjs.org --tag "${{ steps.package.outputs.release_tag }}"

View File

@@ -226,49 +226,51 @@ This should almost always be done using the [`xrpl-codec-gen`](https://github.co
## Release
1. Checkout `main` (or your beta branch) and `git pull`.
1. Create a new branch (`git checkout -b <BRANCH_NAME>`) to capture updates that take place during this process.
1. Update `HISTORY.md` to reflect release changes.
2. Create a new branch (`git checkout -b <BRANCH_NAME>`) to capture updates that take place during this process.
3. Update `HISTORY.md` to reflect release changes.
- [ ] Update the version number and release date, and ensure it lists the changes since the previous release.
1. Run `npm run docgen` if the docs were modified in this release to update them (skip this step for a beta).
1. Run `npm run build` to triple check the build still works
1. Run `npx lerna version --no-git-tag-version` - This bumps the package versions.
4. Run `npm run docgen` if the docs were modified in this release to update them (skip this step for a beta).
5. Run `npm run clean` to delete previously generated artifacts.
6. Run `npm run build` to triple check the build still works
7. Run `npx lerna version --no-git-tag-version` - This bumps the package versions.
- For each changed package, pick what the new version should be. Lerna will bump the versions, commit version bumps to `main`, and create a new git tag for each published package.
- If you do NOT want to update the package number, choose "Custom Version" and set the version to be the same as the existing version. Lerna will not publish any changes in this case.
- If publishing a beta, make sure that the versions are all of the form `a.b.c-beta.d`, where `a`, `b`, and `c` are identical to the last normal release except for one, which has been incremented by 1.
1. Run `npm i` to update the package-lock with the updated versions.
1. Create a new PR from this branch into `main` and merge it (you can directly merge into the beta branch for a beta).
1. Checkout `main` and `git pull` (you can skip this step for a beta since you already have the latest version of the beta branch).
1. Actually publish the packages with one of the following:
8. Run `npm i` to update the package-lock with the updated versions.
9. Create a new PR from this branch into `main` and merge it (you can directly merge into the beta branch for a beta).
10. Checkout `main` and `git pull` (you can skip this step for a beta since you already have the latest version of the beta branch).
11. Actually publish the packages with one of the following:
- Stable release: Run `npx lerna publish from-package --yes`
- Beta release: Run `npx lerna publish from-package --dist-tag beta --yes`
Notice this allows developers to install the package with `npm add xahau@beta`
- Stable release: Run `npx lerna publish from-package --yes`
- Beta release: Run `npx lerna publish from-package --dist-tag beta --yes`
Notice this allows developers to install the package with `npm add xahau@beta`
1. If requested, enter your [npmjs.com](https://npmjs.com) OTP (one-time password) to complete publication.
12. If requested, enter your [npmjs.com](https://npmjs.com) OTP (one-time password) to complete publication.
NOW YOU HAVE PUBLISHED! But you're not done; we have to notify people!
NOW YOU HAVE PUBLISHED! But you're not done; we have to notify people!
1. Run `git tag <tagname> -m <tagname>`, where `<tagname>` is the new package and version (e.g. `xahau@2.1.1`), for each version released.
1. Run `git push --follow-tags`, to push the tags to Github.
1. On GitHub, click the "Releases" link on the right-hand side of the page.
13. Run `git tag <tagname> -m <tagname>`, where `<tagname>` is the new package and version (e.g. `xahau@2.1.1`), for each version released.
14. Run `git push --follow-tags`, to push the tags to Github.
15. On GitHub, click the "Releases" link on the right-hand side of the page.
1. Repeat for each release:
16. Repeat for each release:
1. Click "Draft a new release"
1. Click "Choose a tag", and choose a tag that you just created.
1. Edit the name of the release to match the tag (IE \<package\>@\<version\>) and edit the description as you see fit.
1. Click "Draft a new release"
2. Click "Choose a tag", and choose a tag that you just created.
3. Edit the name of the release to match the tag (IE \<package\>@\<version\>) and edit the description as you see fit.
1. Send an email to [xahau-announce](https://groups.google.com/g/xahau-announce).
1. Lastly, send a similar message to the Xahau Discord in the [`javascript` channel](https://discord.com/channels/1085202760548499486/1085203623111295068). The message should include:
17. Send an email to [xahau-announce](https://groups.google.com/g/xahau-announce).
18. Lastly, send a similar message to the Xahau Discord in the [`javascript` channel](https://discord.com/channels/1085202760548499486/1085203623111295068). The message should include:
1. The version changes for xahau libraries
1. A link to the more detailed changes
1. Highlights of important changes
2. A link to the more detailed changes
3. Highlights of important changes
<!--
## Mailing Lists
We have a low-traffic mailing list for announcements of new `xahau.js` releases. (About 1 email every couple of weeks)
@@ -278,3 +280,4 @@ We have a low-traffic mailing list for announcements of new `xahau.js` releases.
If you're using the Xahau Ledger in production, you should run a [xahaud server](https://github.com/xahau/xahaud) and subscribe to the xahau-server mailing list as well.
- [Subscribe to xahau-server](https://groups.google.com/g/xahau-server)
-->

View File

@@ -86,6 +86,7 @@ As you develop with xahau.js, there's two sites you'll use extensively:
- What kinds of transactions there are ([Transaction Types](https://docs.xahau.network/technical/protocol-reference/transactions/transaction-types))
- Requests you can send ([Public API Methods](https://docs.xahau.network/features/http-websocket-apis))
<!--
### Mailing Lists
If you want to hear when we release new versions of xahau.js, you can join our low-traffic mailing list (About 1 email per week):
@@ -97,3 +98,4 @@ If you're using the Xahau Ledger in production, you should run a [xahaud server]
- [Subscribe to xahau-server](https://groups.google.com/g/xahau-server)
You are also welcome to create an [issue](https://github.com/Xahau/xahau.js/issues) here and we'll do our best to respond within 3 days.
-->

8
command.sh Executable file
View File

@@ -0,0 +1,8 @@
for file in $(git log --diff-filter=D --name-only --format="" | grep -E "oracle.*\.ts$"); do
commit=$(git rev-list -n 1 HEAD -- "$file")
if [ ! -z "$commit" ]; then
git checkout "$commit~1" -- "$file"
echo "restore: $file"
fi
done
rsync -av packages/xrpl/ packages/xahau/ && rm -rf packages/xrpl/

View File

@@ -1,5 +1,4 @@
{
"version": "independent",
"useWorkspaces": true,
"npmClient": "npm"
}

7772
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,10 +16,10 @@
"dependencies": {
"@xrplf/isomorphic": "file:packages/isomorphic",
"@xrplf/secret-numbers": "file:packages/secret-numbers",
"xahau": "file:packages/xahau",
"xahau-address-codec": "file:packages/xahau-address-codec",
"xahau-binary-codec": "file:packages/xahau-binary-codec",
"xahau-keypairs": "file:packages/xahau-keypairs",
"xahau": "file:packages/xahau"
"xahau-keypairs": "file:packages/xahau-keypairs"
},
"devDependencies": {
"@types/chai": "^4.2.21",
@@ -45,7 +45,7 @@
"expect": "^29.3.1",
"jest": "^29.3.1",
"jest-mock": "^29.3.1",
"lerna": "^4.0.0",
"lerna": "^9.0.7",
"lodash": "^4.17.21",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.2",

View File

@@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased
### BREAKING CHANGES:
* Renamed `build/xrplf-secret-numbers-latest.min.js` to `build/xrplf-secret-numbers-latest-min.js`.
## 1.0.0 (2024-02-01)
### BREAKING CHANGES:

View File

@@ -8,7 +8,7 @@
"types": "dist/index.d.ts",
"scripts": {
"prepublish": "npm run clean && npm run lint && npm run test && npm run test:browser && npm run build",
"clean": "rm -rf ./dist ./coverage ./test/testCompiledForWeb tsconfig.build.tsbuildinfo",
"clean": "rm -rf ./build ./dist ./coverage ./test/testCompiledForWeb tsconfig.build.tsbuildinfo",
"test": "jest --verbose",
"test:browser": "npm run build && npm run build:browserTests && karma start ./karma.config.js",
"build": "run-s build:lib build:web",

View File

@@ -1,7 +1,7 @@
{
"name": "xahau-address-codec",
"version": "5.0.0",
"description": "encodes/decodes base58 encoded XAH Ledger identifiers",
"description": "encodes/decodes base58 encoded Xahau Network identifiers",
"files": [
"dist/*",
"src/*"

View File

@@ -162,9 +162,9 @@ export {
encodeAccountID,
// Decode a classic address to its raw bytes
decodeAccountID,
// Encode bytes to XAH Ledger node public key format
// Encode bytes to Xahau Network node public key format
encodeNodePublic,
// Decode an XAH Ledger node public key into its raw bytes
// Decode an Xahau Network node public key into its raw bytes
decodeNodePublic,
// Encode a public key, as for payment channels
encodeAccountPublic,

View File

@@ -2,8 +2,6 @@
## Unreleased
## 2.1.0 (2024-06-03)
### Added
* Support for the Price Oracles amendment (XLS-47).

View File

@@ -1,7 +1,7 @@
{
"name": "xahau-binary-codec",
"version": "2.1.0",
"description": "XAH Ledger binary codec",
"version": "2.2.0-alpha.0",
"description": "Xahau Network binary codec",
"files": [
"dist/*",
"src/*"

File diff suppressed because it is too large Load Diff

View File

@@ -74,7 +74,13 @@ class XrplDefinitionsBase {
.filter(([_key, value]) => value >= 0)
.map(([key, _value]) => key)
const ignoreList = ['EnableAmendment', 'SetFee', 'UNLModify', 'EmitFailure']
const ignoreList = [
'EnableAmendment',
'SetFee',
'UNLModify',
'EmitFailure',
'Cron',
]
this.transactionMap = Object.assign(
{},
...Object.entries(enums.TRANSACTION_TYPES)

View File

@@ -4449,6 +4449,36 @@
"Flags": 0,
"Sequence": 62
}
},
{
"binary": "12003C2FFFFFFFFF2033000004D2750B6469645F6578616D706C65701D0863757272656E6379701E0870726F7669646572811401476926B590BA3245F63C829116A0A3AF7F382DF018E020301700000000000001E2041003011A0000000000000000000000000000000000000000021A0000000000000000000000005553440000000000E1F1",
"json": {
"TransactionType": "OracleSet",
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
"OracleDocumentID": 1234,
"LastUpdateTime": 4294967295,
"PriceDataSeries": [
{
"PriceData": {
"BaseAsset": "XAH",
"QuoteAsset": "USD",
"AssetPrice": "00000000000001E2",
"Scale": 3
}
}
],
"Provider": "70726F7669646572",
"URI": "6469645F6578616D706C65",
"AssetClass": "63757272656E6379"
}
},
{
"binary": "12003D2033000004D2811401476926B590BA3245F63C829116A0A3AF7F382D",
"json": {
"TransactionType": "OracleDelete",
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
"OracleDocumentID": 1234
}
}
],
"ledgerData": [{

View File

@@ -73,7 +73,9 @@ describe('Signing data', function () {
const customPaymentDefinitions = JSON.parse(
JSON.stringify(normalDefinitions),
)
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31
// custom number would need to updated in case it has been used by an existing transaction type
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200
const newDefs = new XrplDefinitions(customPaymentDefinitions)
const actual = encodeForSigning(tx_json, newDefs)
@@ -82,7 +84,7 @@ describe('Signing data', function () {
'53545800', // signingPrefix
// TransactionType
'12',
'001F',
'00C8',
// Flags
'22',
'80000000',
@@ -176,7 +178,9 @@ describe('Signing data', function () {
const customPaymentDefinitions = JSON.parse(
JSON.stringify(normalDefinitions),
)
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31
// custom number would need to updated in case it has been used by an existing transaction type
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200
const newDefs = new XrplDefinitions(customPaymentDefinitions)
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
@@ -187,7 +191,7 @@ describe('Signing data', function () {
'534D5400', // signingPrefix
// TransactionType
'12',
'001F',
'00C8',
// Flags
'22',
'80000000',

View File

@@ -1,7 +1,7 @@
{
"name": "xahau-keypairs",
"version": "2.0.0",
"description": "Cryptographic key pairs for the XAH Ledger",
"description": "Cryptographic key pairs for the Xahau Network",
"scripts": {
"build": "tsc --build tsconfig.build.json",
"test": "jest --verbose false --silent=false ./test/*.test.ts",

View File

@@ -47,7 +47,7 @@ const ed25519: SigningScheme = {
// ZIP 215 is a stricter Ed25519 signature verification scheme.
// However, setting it to false adheres to the more commonly used
// RFC8032 / NIST186-5 standards, making it compatible with systems
// like the XAH Ledger.
// like the Xahau Network.
{ zip215: false },
)
},

View File

@@ -4,8 +4,39 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased Changes
### Added
* Support for HookOnV2 Amendment
* Support for IOUClaimReward Amendment
* Support for NamedHooks
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
* Support for the Price Oracles amendment (XLS-47).
## 4.0.4 (2026-05-27)
### Added
* Improve HookStateScale validation
### Fixed
* Add lsfTshCollect flag in AccountRoot
* Refactor amount assignment in partialPayment.ts
* Fix setTransactionFlagsToNumber for Xahau transactions
## 4.0.3 (2025-11-18)
### Added
* Fixed minified `build/xahau-latest-min.js` to have all the latest xahau package changes.
## 4.0.2 (2025-11-12)
### Added
* Support for Cron Amendment
* Support for ExtendedHookState Amendment
## 4.0.1 (2025-10-03)
### Added
* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion
* Support for XLS-77d Deep-Freeze amendment
### Fixed
* `TransactionStream` model supports APIv2
@@ -37,6 +68,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### Added
* Support for the Price Oracles amendment (XLS-47).
### Added
* Support for the Price Oracles amendment (XLS-47).
### Fixed
* Typo in `Channel` type `source_tab` -> `source_tag`
* Fix `client.requestAll` to handle filters better

View File

@@ -1,8 +1,8 @@
{
"name": "xahau",
"version": "4.0.0",
"version": "4.1.0-alpha.0",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the XAH Ledger in Node.js and the browser",
"description": "A TypeScript/JavaScript API for interacting with the Xahau Network in Node.js and the browser",
"files": [
"build/xahau-latest-min.js",
"build/xahau-latest-min.js.map",
@@ -29,7 +29,7 @@
"bignumber.js": "^9.0.0",
"eventemitter3": "^5.0.1",
"xahau-address-codec": "^5.0.0",
"xahau-binary-codec": "^2.1.0",
"xahau-binary-codec": "^2.2.0-alpha.0",
"xahau-keypairs": "^2.0.0"
},
"devDependencies": {
@@ -56,7 +56,7 @@
"build:browserTests": "webpack --config ./test/webpack.config.js",
"analyze": "webpack --analyze",
"watch": "run-s build:lib --watch",
"clean": "rm -rf ./dist ./coverage ./test/testCompiledForWeb tsconfig.build.tsbuildinfo",
"clean": "rm -rf ./build ./dist ./coverage ./test/testCompiledForWeb tsconfig.build.tsbuildinfo",
"docgen": "tsc --build tsconfig.docs.json && typedoc && echo js.xahau.org >> ../../docs/CNAME",
"prepare": "copyfiles ../../README.md xahau/README.md",
"prepublish": "run-s clean build",

View File

@@ -355,13 +355,13 @@ export class Wallet {
* Output of `sign` includes a `tx_blob` and a `hash`, both of which are needed to submit & verify the results.
* Note: If you pass a `Wallet` to `client.submit` or `client.submitAndWait` it will do signing like this under the hood.
*
* `tx_blob` is a binary representation of a transaction on the XAH Ledger. It's essentially a byte array
* `tx_blob` is a binary representation of a transaction on the Xahau Network. It's essentially a byte array
* that encodes all of the data necessary to execute the transaction, including the source address, the destination
* address, the amount, and any additional fields required for the specific transaction type.
*
* `hash` is a unique identifier that's generated from the signed transaction data on the XAH Ledger. It's essentially
* `hash` is a unique identifier that's generated from the signed transaction data on the Xahau Network. It's essentially
* A cryptographic digest of the signed transaction blob, created using a hash function. The signed transaction hash is
* Useful for identifying and tracking specific transactions on the XAH Ledger. It can be used to query transaction
* Useful for identifying and tracking specific transactions on the Xahau Network. It can be used to query transaction
* Information, verify the authenticity of a transaction, and detect any tampering with the transaction data.
*
* @param this - Wallet instance.

View File

@@ -40,8 +40,13 @@ import type {
MarkerRequest,
MarkerResponse,
SubmitResponse,
SimulateRequest,
} from '../models/methods'
import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers'
import {
SimulateBinaryResponse,
SimulateJsonResponse,
} from '../models/methods/simulate'
import type {
EventTypes,
OnEventToListenerMap,
@@ -655,7 +660,7 @@ class Client extends EventEmitter<EventTypes> {
* @returns The autofilled transaction.
* @throws ValidationError If Amount and DeliverMax fields are not identical in a Payment Transaction
*/
// eslint-disable-next-line complexity -- handling Payment transaction API v2 requires more logic
public async autofill<T extends SubmittableTransaction>(
transaction: T,
signersCount?: number,
@@ -676,6 +681,34 @@ class Client extends EventEmitter<EventTypes> {
promises.push(setLatestValidatedLedgerSequence(this, tx))
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
if (tx.TransactionType === 'Payment' && tx.DeliverMax != null) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount == null) {
// If only DeliverMax is provided, use it to populate the Amount field
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a known RPC-level property
tx.Amount = tx.DeliverMax
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- This is a valid null check for Amount
if (tx.Amount != null && tx.Amount !== tx.DeliverMax) {
return Promise.reject(
new ValidationError(
'PaymentTransaction: Amount and DeliverMax fields must be identical when both are provided',
),
)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- ignore type-assertions on the DeliverMax property
// @ts-expect-error -- DeliverMax property exists only at the RPC level, not at the protocol level
delete tx.DeliverMax
}
await Promise.all(promises).then(() => tx)
if (tx.Fee == null) {
@@ -734,6 +767,41 @@ class Client extends EventEmitter<EventTypes> {
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
* validated ledger (or has errored/will not be included for some reason).
@@ -1063,7 +1131,7 @@ class Client extends EventEmitter<EventTypes> {
/**
* The fundWallet() method is used to send an amount of XAH (usually 1000) to a new (randomly generated)
* or existing XAH Ledger wallet.
* or existing Xahau Network wallet.
*
* @category Faucet
*

View File

@@ -64,10 +64,11 @@ function isPartialPayment(
}
const delivered = meta.delivered_amount
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- DeliverMax is a valid field on Payment response
// @ts-expect-error -- DeliverMax is a valid field on Payment response
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- DeliverMax is a valid field on Payment response
const amount = tx.DeliverMax
const amount = tx.DeliverMax ?? tx.Amount
if (delivered === undefined) {
return false

View File

@@ -71,7 +71,7 @@ export interface SignerEntry {
*/
SignerEntry: {
/**
* An XAH Ledger address whose signature contributes to the multi-signature.
* An Xahau Network address whose signature contributes to the multi-signature.
* It does not need to be a funded address in the ledger.
*/
Account: string

View File

@@ -1,5 +1,28 @@
import { Amount } from '.'
/**
* Enum representing values for Hook Flags for SetHook Transaction.
*
* @category Transaction Flags
*/
export enum HookFlags {
/**
*/
hsfOverride = 0x00000001,
/**
*/
hsfNSDelete = 0x0000002,
/**
*/
hsfCollect = 0x00000004,
}
export interface HookFlagsInterface {
hsfOverride?: boolean
hsfNSDelete?: boolean
hsfCollect?: boolean
}
export interface AmountEntry {
AmountEntry: { Amount: Amount }
}
@@ -58,11 +81,19 @@ export interface Hook {
/**
* The flags that are set on the hook.
*/
Flags?: number
Flags?: number | HookFlagsInterface
/**
* The transactions that triggers the hook. Represented as a 256Hash
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The transactions that can emit from the hook. Represented as a 256Hash
*/
@@ -83,6 +114,10 @@ export interface Hook {
* The grants of the hook.
*/
HookGrants?: HookGrant[]
/**
* The name of the hook.
*/
HookName?: string
}
}
@@ -98,6 +133,14 @@ export interface EmitDetails {
sfEmitCallback?: string
}
export enum MintURITokenFlags {
tfBurnable = 0x00000001,
}
export interface MintURITokenFlagsInterface {
tfBurnable?: boolean
}
/**
* The object that describes the uritoken in MintURIToken.
*/
@@ -113,5 +156,5 @@ export interface MintURIToken {
/**
* The flags that are set on the uritoken.
*/
Flags?: number
Flags?: number | MintURITokenFlagsInterface
}

View File

@@ -84,6 +84,9 @@ export default interface AccountRoot extends BaseLedgerEntry, HasPreviousTxnID {
GovernanceMarks?: string
AccountIndex?: number
TouchCount?: number
HookStateScale?: number
/* The cron job that is associated with this account. */
Cron?: string
}
/**
@@ -152,6 +155,11 @@ export interface AccountRootFlagsInterface {
* Disallow incoming Remit from other accounts.
*/
lsfDisallowIncomingRemit?: boolean
/**
* This address can claw back issued IOUs. Once enabled, cannot be disabled.
*/
lsfAllowTrustLineClawback?: boolean
}
export enum AccountRootFlags {
@@ -192,6 +200,10 @@ export enum AccountRootFlags {
* (It has DepositAuth enabled.)
*/
lsfDepositAuth = 0x01000000,
/**
* The TSH pays for the execution of their own Hook Chain. Added by the Hooks amendment.
*/
lsfTshCollect = 0x02000000,
/**
* Disallow incoming NFTOffers from other accounts.
*/
@@ -216,4 +228,8 @@ export enum AccountRootFlags {
* Disallow incoming Remits from other accounts.
*/
lsfDisallowIncomingRemit = 0x80000000,
/**
* This address can claw back issued IOUs. Once enabled, cannot be disabled.
*/
lsfAllowTrustLineClawback = 0x00001000,
}

View File

@@ -0,0 +1,25 @@
import { Account } from '../transactions/common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
/**
* The EmittedTxn object type contains the
*
* @category Ledger Entries
*/
export default interface Cron extends BaseLedgerEntry, HasPreviousTxnID {
LedgerEntryType: 'Cron'
/** The owner of the cron job. */
Owner: Account
/** The start time of the cron job. */
StartTime: number
/** The delay seconds of the cron job. */
DelaySeconds: number
/** The repeat count of the cron job. */
RepeatCount: number
/**
* A hint indicating which page of the sender's owner directory links to this
* object, in case the directory consists of multiple pages.
*/
OwnerNode: string
}

View File

@@ -11,7 +11,7 @@ export interface FeeSettingsPreAmendmentFields {
BaseFee: string
/** The BaseFee translated into "fee units". */
ReferenceFeeUnits: number
/** The base reserve for an account in the XAH Ledger, as drops of XAH. */
/** The base reserve for an account in the Xahau Network, as drops of XAH. */
ReserveBase: number
/** The incremental owner reserve for owning objects, as drops of XAH. */
ReserveIncrement: number
@@ -20,7 +20,7 @@ export interface FeeSettingsPreAmendmentFields {
export interface FeeSettingsPostAmendmentFields {
/** The transaction cost of the "reference transaction" in drops of XAH as hexadecimal. */
BaseFeeDrops: string
/** The base reserve for an account in the XAH Ledger, as drops of XAH. */
/** The base reserve for an account in the Xahau Network, as drops of XAH. */
ReserveBaseDrops: string
/** The incremental owner reserve for owning objects, as drops of XAH. */
ReserveIncrementDrops: string

View File

@@ -27,6 +27,16 @@ export default interface HookDefintion
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The namespace of the hook.
*/

View File

@@ -1,6 +1,7 @@
import AccountRoot from './AccountRoot'
import Amendments from './Amendments'
import Check from './Check'
import Cron from './Cron'
import DepositPreauth from './DepositPreauth'
import DirectoryNode from './DirectoryNode'
import EmittedTxn from './EmittedTxn'
@@ -13,6 +14,7 @@ import ImportVLSequence from './ImportVLSequence'
import LedgerHashes from './LedgerHashes'
import NegativeUNL from './NegativeUNL'
import Offer from './Offer'
import Oracle from './Oracle'
import PayChannel from './PayChannel'
import RippleState from './RippleState'
import SignerList from './SignerList'
@@ -23,6 +25,7 @@ import URIToken from './URIToken'
type LedgerEntry =
| AccountRoot
| Amendments
| Cron
| Check
| DepositPreauth
| DirectoryNode
@@ -36,6 +39,7 @@ type LedgerEntry =
| LedgerHashes
| NegativeUNL
| Offer
| Oracle
| PayChannel
| RippleState
| SignerList
@@ -46,6 +50,7 @@ type LedgerEntry =
type LedgerEntryFilter =
| 'account'
| 'amendments'
| 'cron'
| 'check'
| 'deposit_preauth'
| 'directory'
@@ -57,6 +62,7 @@ type LedgerEntryFilter =
| 'import_vl_sequence'
| 'hashes'
| 'offer'
| 'oracle'
| 'payment_channel'
| 'signer_list'
| 'state'

View File

@@ -0,0 +1,43 @@
import { PriceData } from '../common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
/**
* The Oracle object type describes a single Price Oracle instance.
*
* @category Ledger Entries
*/
export default interface Oracle extends BaseLedgerEntry, HasPreviousTxnID {
LedgerEntryType: 'Oracle'
/**
* The time the data was last updated, represented as a unix timestamp in seconds.
*/
LastUpdateTime: number
/**
* The XRPL account with update and delete privileges for the oracle.
*/
Owner: string
/**
* Describes the type of asset, such as "currency", "commodity", or "index".
*/
AssetClass: string
/**
* The oracle provider, such as Chainlink, Band, or DIA.
*/
Provider: string
/**
* An array of up to 10 PriceData objects.
*/
PriceDataSeries: PriceData[]
/**
* A bit-map of boolean flags. No flags are defined for the Oracle object
* type, so this value is always 0.
*/
Flags: 0
}

View File

@@ -2,6 +2,13 @@ import { IssuedCurrencyAmount } from '../common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
export interface RippleStateReward {
RewardLgrFirst: number
RewardLgrLast: number
RewardTime: number
TrustLineRewardAccumulator: IssuedCurrencyAmount
}
/**
* The RippleState object type connects two accounts in a single currency.
*
@@ -61,6 +68,8 @@ export default interface RippleState extends BaseLedgerEntry, HasPreviousTxnID {
* equivalent to 1 billion, or face value.
*/
HighQualityOut?: number
HighReward?: RippleStateReward
LowReward?: RippleStateReward
}
export enum RippleStateFlags {
@@ -77,4 +86,8 @@ export enum RippleStateFlags {
lsfHighFreeze = 0x00800000,
// True, trust line to AMM. Used by client apps to identify payments via AMM.
lsfAMMNode = 0x01000000,
// True, low side has set deep freeze flag
lsfLowDeepFreeze = 0x02000000,
// True, high side has set deep freeze flag
lsfHighDeepFreeze = 0x04000000,
}

View File

@@ -4,6 +4,7 @@ import AccountRoot, {
} from './AccountRoot'
import Amendments, { Majority, AMENDMENTS_ID } from './Amendments'
import Check from './Check'
import Cron from './Cron'
import DepositPreauth from './DepositPreauth'
import DirectoryNode from './DirectoryNode'
import EmittedTxn from './EmittedTxn'
@@ -22,6 +23,7 @@ import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
import LedgerHashes from './LedgerHashes'
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
import Offer, { OfferFlags } from './Offer'
import Oracle from './Oracle'
import PayChannel from './PayChannel'
import RippleState, { RippleStateFlags } from './RippleState'
import SignerList, { SignerListFlags } from './SignerList'
@@ -36,6 +38,7 @@ export {
AMENDMENTS_ID,
Amendments,
Check,
Cron,
DepositPreauth,
DirectoryNode,
EmittedTxn,
@@ -58,6 +61,7 @@ export {
NegativeUNL,
Offer,
OfferFlags,
Oracle,
PayChannel,
RippleState,
RippleStateFlags,

View File

@@ -3,7 +3,7 @@ import { Amount } from '../common'
import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
/**
* Represents a payment channel in the XAH Ledger.
* Represents a payment channel in the Xahau Network.
*/
export interface Channel {
/** The owner of the channel, as an Address. */
@@ -38,7 +38,7 @@ export interface Channel {
settle_delay: number
/**
* The public key for the payment channel in the XAH Ledger's base58 format.
* The public key for the payment channel in the Xahau Network's base58 format.
* Signed claims against this channel must be redeemed with the matching key pair.
*/
public_key?: string

View File

@@ -14,7 +14,7 @@ export interface AccountCurrenciesRequest
/** A unique identifier for the account, most commonly the account's address. */
account: string
/**
* If true, then the account field only accepts a public key or XAH Ledger
* If true, then the account field only accepts a public key or Xahau Network
* address. Otherwise, account can be a secret or passphrase (not
* recommended). The default is false.
*/

View File

@@ -25,7 +25,7 @@ export interface AccountInfoRequest extends BaseRequest, LookupByLedgerRequest {
*/
signer_lists?: boolean
/**
* If true, then the account field only accepts a public key or XAH Ledger
* If true, then the account field only accepts a public key or Xahau Network
* address. Otherwise, account can be a secret or passphrase (not
* recommended). The default is false.
*/
@@ -159,7 +159,7 @@ interface BaseAccountInfoResponse extends BaseResponse {
/**
* Information about queued transactions sent by this account. This
* information describes the state of the local xahaud server, which may be
* different from other servers in the peer-to-peer XAH Ledger network. Some
* different from other servers in the peer-to-peer Xahau Network network. Some
* fields may be omitted because the values are calculated "lazily" by the
* queuing mechanism.
*/

View File

@@ -26,7 +26,7 @@ export interface AccountOffersRequest
*/
marker?: unknown
/**
* If true, then the account field only accepts a public key or XAH Ledger
* If true, then the account field only accepts a public key or Xahau Network
* address. Otherwise, account can be a secret or passphrase (not
* recommended). The default is false.
*/

View File

@@ -18,7 +18,7 @@ export interface ChannelVerifyRequest extends BaseRequest {
channel_id: string
/**
* The public key of the channel and the key pair that was used to create the
* signature, in hexadecimal or the XAH Ledger's base58 format.
* signature, in hexadecimal or the Xahau Network's base58 format.
*/
public_key: string
/** The signature to verify, in hexadecimal. */

View File

@@ -0,0 +1,119 @@
import { BaseRequest, BaseResponse } from './baseMethod'
/**
* The `get_aggregate_price` method retrieves the aggregate price of specified Oracle objects,
* returning three price statistics: mean, median, and trimmed mean.
* Returns an {@link GetAggregatePriceResponse}.
*
* @category Requests
*/
export interface GetAggregatePriceRequest extends BaseRequest {
command: 'get_aggregate_price'
/**
* The currency code of the asset to be priced.
*/
base_asset: string
/**
* The currency code of the asset to quote the price of the base asset.
*/
quote_asset: string
/**
* The oracle identifier.
*/
oracles: Array<{
/**
* The XRPL account that controls the Oracle object.
*/
account: string
/**
* A unique identifier of the price oracle for the Account
*/
oracle_document_id: string | number
}>
/**
* The percentage of outliers to trim. Valid trim range is 1-25. If included, the API returns statistics for the trimmed mean.
*/
trim?: number
/**
* Defines a time range in seconds for filtering out older price data. Default value is 0, which doesn't filter any data.
*/
trim_threshold?: number
}
/**
* Response expected from an {@link GetAggregatePriceRequest}.
*
* @category Responses
*/
export interface GetAggregatePriceResponse extends BaseResponse {
result: {
/**
* The statistics from the collected oracle prices.
*/
entire_set: {
/**
* The simple mean.
*/
mean: string
/**
* The size of the data set to calculate the mean.
*/
size: number
/**
* The standard deviation.
*/
standard_deviation: string
}
/**
* The trimmed statistics from the collected oracle prices. Only appears if the trim field was specified in the request.
*/
trimmed_set?: {
/**
* The simple mean of the trimmed data.
*/
mean: string
/**
* The size of the data to calculate the trimmed mean.
*/
size: number
/**
* The standard deviation of the trimmed data.
*/
standard_deviation: string
}
/**
* The median of the collected oracle prices.
*/
median: string
/**
* The most recent timestamp out of all LastUpdateTime values.
*/
time: number
/**
* The ledger index of the ledger version that was used to generate this
* response.
*/
ledger_current_index: number
/**
* If included and set to true, the information in this response comes from
* a validated ledger version. Otherwise, the information is subject to
* change.
*/
validated: boolean
}
}

View File

@@ -78,6 +78,10 @@ import {
GatewayBalancesRequest,
GatewayBalancesResponse,
} from './gatewayBalances'
import {
GetAggregatePriceRequest,
GetAggregatePriceResponse,
} from './getAggregatePrice'
import {
LedgerBinary,
LedgerModifiedOfferCreateTransaction,
@@ -133,6 +137,14 @@ import {
StateAccountingFinal,
} from './serverInfo'
import { ServerStateRequest, ServerStateResponse } from './serverState'
import {
SimulateBinaryRequest,
SimulateBinaryResponse,
SimulateJsonRequest,
SimulateJsonResponse,
SimulateRequest,
SimulateResponse,
} from './simulate'
import { SubmitRequest, SubmitResponse } from './submit'
import {
SubmitMultisignedRequest,
@@ -188,6 +200,7 @@ type Request =
| LedgerDataRequest
| LedgerEntryRequest
// transaction methods
| SimulateRequest
| SubmitRequest
| SubmitMultisignedRequest
| TransactionEntryRequest
@@ -212,6 +225,8 @@ type Request =
// utility methods
| PingRequest
| RandomRequest
// Price Oracle methods
| GetAggregatePriceRequest
/**
* @category Responses
@@ -235,6 +250,7 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
| LedgerDataResponse
| LedgerEntryResponse
// transaction methods
| SimulateResponse
| SubmitResponse
| SubmitMultisignedVersionResponseMap<Version>
| TransactionEntryResponse
@@ -259,6 +275,8 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
// utility methods
| PingResponse
| RandomResponse
// Price Oracle methods
| GetAggregatePriceResponse
export type RequestResponseMap<
T,
@@ -279,6 +297,8 @@ export type RequestResponseMap<
? AccountTxVersionResponseMap<Version>
: T extends GatewayBalancesRequest
? GatewayBalancesResponse
: T extends GetAggregatePriceRequest
? GetAggregatePriceResponse
: T extends NoRippleCheckRequest
? NoRippleCheckResponse
: // NOTE: The order of these LedgerRequest types is important
@@ -355,6 +375,12 @@ export type RequestResponseMap<
? LedgerDataResponse
: T extends LedgerEntryRequest
? LedgerEntryResponse
: T extends SimulateBinaryRequest
? SimulateBinaryResponse
: T extends SimulateJsonRequest
? SimulateJsonResponse
: T extends SimulateRequest
? SimulateJsonResponse
: T extends SubmitRequest
? SubmitResponse
: T extends SubmitMultisignedRequest
@@ -465,6 +491,8 @@ export {
GatewayBalance,
GatewayBalancesRequest,
GatewayBalancesResponse,
GetAggregatePriceRequest,
GetAggregatePriceResponse,
NoRippleCheckRequest,
NoRippleCheckResponse,
// ledger methods
@@ -486,6 +514,8 @@ export {
LedgerEntryRequest,
LedgerEntryResponse,
// transaction methods with types
SimulateRequest,
SimulateResponse,
SubmitRequest,
SubmitResponse,
SubmitMultisignedRequest,

View File

@@ -203,13 +203,13 @@ export interface LedgerQueueData {
}
export interface LedgerBinary
extends Omit<Omit<Ledger, 'transactions'>, 'accountState'> {
extends Omit<Ledger, 'transactions' | 'accountState'> {
accountState?: string[]
transactions?: string[]
}
export interface LedgerBinaryV1
extends Omit<Omit<LedgerV1, 'transactions'>, 'accountState'> {
extends Omit<LedgerV1, 'transactions' | 'accountState'> {
accountState?: string[]
transactions?: string[]
}

View File

@@ -3,7 +3,7 @@ import { LedgerEntry } from '../ledger'
import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod'
/**
* The `ledger_entry` method returns a single ledger object from the XAH Ledger
* The `ledger_entry` method returns a single ledger object from the Xahau Network
* in its raw format. Expects a response in the form of a {@link
* LedgerEntryResponse}.
*
@@ -30,7 +30,7 @@ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
include_deleted?: boolean
/**
* If true, return the requested ledger object's contents as a hex string in
* the XAH Ledger's binary format. Otherwise, return data in JSON format. The
* the Xahau Network's binary format. Otherwise, return data in JSON format. The
* default is false.
*/
binary?: boolean
@@ -200,6 +200,20 @@ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
uri: string
}
| string
/**
* The Cron object to retrieve. If a string, must be the object ID of the
* Cron, as hexadecimal. If an object, the `owner` and `time`
* sub-fields are required to uniquely specify the Cron entry.
*/
cron?:
| {
/** The owner of the Cron object. */
owner: string
/** The start time of the Cron object. */
time: number
}
| string
}
/**

View File

@@ -108,7 +108,7 @@ export interface ServerInfoResponse extends BaseResponse {
* The number of times (since starting up) that this server has had over
* 250 transactions waiting to be processed at once. A large number here
* may mean that your server is unable to handle the transaction load of
* the XAH Ledger network.
* the Xahau Network network.
*/
jq_trans_overflow: string
/**

View 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>
}
}

View File

@@ -65,7 +65,7 @@ export interface SubmitResponse extends BaseResponse {
applied: boolean
/**
* The value true indicates this transaction was broadcast to peer servers
* in the peer-to-peer XAH Ledger network.
* in the peer-to-peer Xahau Network network.
*/
broadcast: boolean
/**

View File

@@ -29,7 +29,7 @@ export interface SubscribeBook {
taker_pays: Currency
/**
* Unique account address to use as a perspective for viewing offers, in the.
* XAH Ledger's base58 format.
* Xahau Network's base58 format.
*/
taker: string
/**
@@ -54,7 +54,7 @@ export interface SubscribeRequest extends BaseRequest {
streams?: StreamType[]
/**
* Array with the unique addresses of accounts to monitor for validated
* transactions. The addresses must be in the XAH Ledger's base58 format.
* transactions. The addresses must be in the Xahau Network's base58 format.
* The server sends a notification for any transaction that affects at least
* one of these accounts.
*/
@@ -235,7 +235,7 @@ export interface ValidationStream extends BaseStream {
load_fee?: number
/**
* The validator's master public key, if the validator is using a validator
* token, in the XAH Ledger's base58 format.
* token, in the Xahau Network's base58 format.
*/
master_key?: string
/**
@@ -254,7 +254,7 @@ export interface ValidationStream extends BaseStream {
signing_time: number
/**
* The public key from the key-pair that the validator used to sign the
* message, in the XAH Ledger's base58 format. This identifies the validator
* message, in the Xahau Network's base58 format. This identifies the validator
* sending the message and can also be used to verify the signature. If the
* validator is using a token, this is an ephemeral public key.
*/

View File

@@ -24,7 +24,7 @@ export interface UnsubscribeRequest extends BaseRequest {
streams?: StreamType[]
/**
* Array of unique account addresses to stop receiving updates for, in the.
* XAH Ledger's base58 format.
* Xahau Network's base58 format.
*/
accounts?: string[]
/**

View File

@@ -4,6 +4,7 @@ import {
Account,
BaseTransaction,
isAccount,
isNumber,
validateBaseTransaction,
validateOptionalField,
} from './common'
@@ -61,6 +62,8 @@ export enum AccountSetAsfFlags {
asfDisallowIncomingTrustline = 15,
/** Disallow other accounts from sending incoming Remits */
asfDisallowIncomingRemit = 16,
/** Permanently gain the ability to claw back issued IOUs */
asfAllowTrustLineClawback = 17,
}
/**
@@ -161,10 +164,16 @@ export interface AccountSet extends BaseTransaction {
* account's behalf using NFTokenMint's `Issuer` field.
*/
NFTokenMinter?: Account
/**
* The allowed scale of the hook state.
*/
HookStateScale?: number
}
const MIN_TICK_SIZE = 3
const MAX_TICK_SIZE = 15
const MAX_HOOK_STATE_SCALE = 16
const MIN_HOOK_STATE_SCALE = 1
/**
* Verify the form and type of an AccountSet at runtime.
@@ -172,7 +181,7 @@ const MAX_TICK_SIZE = 15
* @param tx - An AccountSet Transaction.
* @throws When the AccountSet is Malformed.
*/
// eslint-disable-next-line max-lines-per-function -- okay for this method, only a little over
// eslint-disable-next-line max-lines-per-function, max-statements -- okay for this method, only a little over
export function validateAccountSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
@@ -223,4 +232,24 @@ export function validateAccountSet(tx: Record<string, unknown>): void {
throw new ValidationError('AccountSet: invalid TickSize')
}
}
validateOptionalField(tx, 'HookStateScale', isNumber)
if (
typeof tx.HookStateScale === 'number' &&
tx.HookStateScale > MAX_HOOK_STATE_SCALE
) {
throw new ValidationError(
`AccountSet: HookStateScale must be less than or equal to ${MAX_HOOK_STATE_SCALE}`,
)
}
if (
typeof tx.HookStateScale === 'number' &&
tx.HookStateScale < MIN_HOOK_STATE_SCALE
) {
throw new ValidationError(
`AccountSet: HookStateScale must be greater than or equal to ${MIN_HOOK_STATE_SCALE}`,
)
}
}

View File

@@ -1,6 +1,7 @@
import { ValidationError } from '../../errors'
import { Currency } from '../common'
import { BaseTransaction, validateBaseTransaction } from './common'
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
/**
* Transaction Flags for an ClaimReward Transaction.
*
@@ -13,6 +14,39 @@ export enum ClaimRewardFlags {
tfOptOut = 0x00000001,
}
/**
* Map of flags to boolean values representing {@link ClaimReward} transaction
* flags.
*
* @category Transaction Flags
*
* @example
* ```typescript
* const tx: ClaimReward = {
* Account: 'rhFcpWDHLqpBmX4ezWiA5VLSS4e1BHqhHd',
* TransactionType: 'ClaimReward',
* Flags: {
* tfOptOut: true,
* },
* }
*
* // Autofill the tx to see how flags actually look compared to the interface usage.
* const autofilledTx = await client.autofill(tx)
* console.log(autofilledTx)
* // {
* // Account: 'rhFcpWDHLqpBmX4ezWiA5VLSS4e1BHqhHd',
* // TransactionType: 'ClaimReward',
* // Flags: 0,
* // Sequence: 21970384,
* // Fee: '12',
* // LastLedgerSequence: 21970404
* // }
* ```
*/
export interface ClaimRewardFlagsInterface extends GlobalFlags {
tfOptOut?: boolean
}
/**
* ClaimReward is a transaction model that allows an account to claim rewards.
*
@@ -20,9 +54,10 @@ export enum ClaimRewardFlags {
*/
export interface ClaimReward extends BaseTransaction {
TransactionType: 'ClaimReward'
Flags?: number | ClaimRewardFlags
Flags?: number | ClaimRewardFlagsInterface
/** The unique address of the issuer where the reward.c hook is installed. */
Issuer?: string
ClaimCurrency?: Currency
}
/**

View File

@@ -0,0 +1,49 @@
import { ValidationError } from '../../errors'
import { IssuedCurrencyAmount } from '../common'
import {
BaseTransaction,
validateBaseTransaction,
isIssuedCurrency,
} from './common'
/**
* The Clawback transaction is used by the token issuer to claw back
* issued tokens from a holder.
*/
export interface Clawback extends BaseTransaction {
TransactionType: 'Clawback'
/**
* Indicates the AccountID that submitted this transaction. The account MUST
* be the issuer of the currency.
*/
Account: string
/**
* The amount of currency to deliver, and it must be non-XRP. The nested field
* names MUST be lower-case. The `issuer` field MUST be the holder's address,
* whom to be clawed back.
*/
Amount: IssuedCurrencyAmount
}
/**
* Verify the form and type of an Clawback at runtime.
*
* @param tx - An Clawback Transaction.
* @throws When the Clawback is Malformed.
*/
export function validateClawback(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (tx.Amount == null) {
throw new ValidationError('Clawback: missing field Amount')
}
if (!isIssuedCurrency(tx.Amount)) {
throw new ValidationError('Clawback: invalid Amount')
}
if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) {
throw new ValidationError('Clawback: invalid holder Account')
}
}

View File

@@ -275,6 +275,10 @@ export interface BaseTransaction {
* The hook parameters of the transaction.
*/
HookParameters?: HookParameter[]
/**
* The name of the hooks triggered by the transaction.
*/
HookName?: string
/**
* The hook parameters of the transaction.
*/

View File

@@ -0,0 +1,17 @@
import { BaseTransaction } from './common'
/**
* Cron job to be executed.
*
* @category Pseudo Transaction Models
*/
export interface Cron extends BaseTransaction {
TransactionType: 'Cron'
/**
* The ledger index where this pseudo-transaction appears.
* This distinguishes the pseudo-transaction from other occurrences of the same change.
*/
LedgerSequence: number
/** The owner of the cron job. */
Owner: string
}

View File

@@ -0,0 +1,123 @@
import { ValidationError } from '../../errors'
import {
BaseTransaction,
GlobalFlags,
isNumber,
validateBaseTransaction,
validateOptionalField,
validateRequiredField,
} from './common'
/**
* Transaction Flags for an CronSet Transaction.
*
* @category Transaction Flags
*/
export enum CronSetFlags {
/**
* If set, indicates that the user would like to unset the cron job.
*/
tfCronUnset = 0x00000001,
}
/**
* Map of flags to boolean values representing {@link CronSet} transaction
* flags.
*
* @category Transaction Flags
*
* @example
* ```typescript
* const tx: CronSet = {
* Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
* TransactionType: 'CronSet',
* Flags: {
* tfCronUnset: true,
* },
* }
*
* // Autofill the tx to see how flags actually look compared to the interface usage.
* const autofilledTx = await client.autofill(tx)
* console.log(autofilledTx)
* // {
* // Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
* // TransactionType: 'CronSet',
* // Flags: 0,
* // Sequence: 21970384,
* // Fee: '12',
* // LastLedgerSequence: 21970404
* // }
* ```
*/
export interface CronSetFlagsInterface extends GlobalFlags {
tfCronUnset?: boolean
}
/**
* CronSet is a transaction model that allows an account to set a cron job.
*
* @category Transaction Models
*/
export interface CronSet extends BaseTransaction {
TransactionType: 'CronSet'
Flags?: number | CronSetFlagsInterface
RepeatCount?: number
DelaySeconds?: number
StartTime?: number
}
const MAX_REPEAT_COUNT = 256
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- seconds in a year
const MIN_DELAY_SECONDS = 365 * 24 * 60 * 60
/**
* Verify the form and type of an CronSet at runtime.
*
* @param tx - An CronSet Transaction.
* @throws When the CronSet is Malformed.
*/
export function validateCronSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
if (
typeof tx.Flags === 'number' &&
// eslint-disable-next-line no-bitwise -- bitwise operation to check if the flag is set
tx.Flags & CronSetFlags.tfCronUnset
) {
if (
tx.RepeatCount !== undefined ||
tx.DelaySeconds !== undefined ||
tx.StartTime !== undefined
) {
throw new ValidationError(
'CronSet: RepeatCount, DelaySeconds, and StartTime must not be set when Flags is set to tfCronUnset',
)
}
return
}
validateRequiredField(tx, 'StartTime', isNumber)
validateOptionalField(tx, 'RepeatCount', isNumber)
validateOptionalField(tx, 'DelaySeconds', isNumber)
if ((tx.RepeatCount === undefined) !== (tx.DelaySeconds === undefined)) {
throw new ValidationError(
'CronSet: Both RepeatCount and DelaySeconds must be set, or neither should be set',
)
}
if (typeof tx.RepeatCount === 'number' && tx.RepeatCount > MAX_REPEAT_COUNT) {
throw new ValidationError(
`CronSet: RepeatCount must be less than ${MAX_REPEAT_COUNT}`,
)
}
if (
typeof tx.DelaySeconds === 'number' &&
tx.DelaySeconds > MIN_DELAY_SECONDS
) {
throw new ValidationError(
`CronSet: DelaySeconds must be less than ${MIN_DELAY_SECONDS}`,
)
}
}

View File

@@ -11,10 +11,10 @@ import { BaseTransaction, validateBaseTransaction } from './common'
*/
export interface DepositPreauth extends BaseTransaction {
TransactionType: 'DepositPreauth'
/** The XAH Ledger address of the sender to preauthorize. */
/** The Xahau Network address of the sender to preauthorize. */
Authorize?: string
/**
* The XAH Ledger address of a sender whose preauthorization should be.
* The Xahau Network address of a sender whose preauthorization should be.
* revoked.
*/
Unauthorize?: string

View File

@@ -17,6 +17,8 @@ export { CheckCancel } from './checkCancel'
export { CheckCash } from './checkCash'
export { CheckCreate } from './checkCreate'
export { ClaimReward, ClaimRewardFlags } from './claimReward'
export { Cron } from './cron'
export { CronSet, CronSetFlags } from './cronSet'
export { DepositPreauth } from './depositPreauth'
export { EscrowCancel } from './escrowCancel'
export { EscrowCreate } from './escrowCreate'
@@ -30,6 +32,8 @@ export {
OfferCreateFlagsInterface,
OfferCreate,
} from './offerCreate'
export { OracleDelete } from './oracleDelete'
export { OracleSet } from './oracleSet'
export { PaymentFlags, PaymentFlagsInterface, Payment } from './payment'
export {
PaymentChannelClaimFlags,
@@ -39,7 +43,7 @@ export {
export { PaymentChannelCreate } from './paymentChannelCreate'
export { PaymentChannelFund } from './paymentChannelFund'
export { Remit } from './remit'
export { SetHookFlagsInterface, SetHookFlags, SetHook } from './setHook'
export { SetHook } from './setHook'
export { SetFee, SetFeePreAmendment, SetFeePostAmendment } from './setFee'
export { SetRegularKey } from './setRegularKey'
export {
@@ -61,3 +65,4 @@ export { URITokenCreateSellOffer } from './uriTokenCreateSellOffer'
export { URITokenBuy } from './uriTokenBuy'
export { URITokenCancelSellOffer } from './uriTokenCancelSellOffer'
export { UNLModify } from './UNLModify'
export { Clawback } from './clawback'

View File

@@ -3,7 +3,7 @@ import { ValidationError } from '../../errors'
import { BaseTransaction, validateBaseTransaction } from './common'
/**
* An OfferCancel transaction removes an Offer object from the XAH Ledger.
* An OfferCancel transaction removes an Offer object from the Xahau Network.
*
* @category Transaction Models
*/

View File

@@ -0,0 +1,32 @@
import {
BaseTransaction,
isNumber,
validateBaseTransaction,
validateRequiredField,
} from './common'
/**
* Delete an Oracle ledger entry.
*
* @category Transaction Models
*/
export interface OracleDelete extends BaseTransaction {
TransactionType: 'OracleDelete'
/**
* A unique identifier of the price oracle for the Account.
*/
OracleDocumentID: number
}
/**
* Verify the form and type of a OracleDelete at runtime.
*
* @param tx - A OracleDelete Transaction.
* @throws When the OracleDelete is malformed.
*/
export function validateOracleDelete(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
validateRequiredField(tx, 'OracleDocumentID', isNumber)
}

View File

@@ -0,0 +1,198 @@
import { ValidationError } from '../../errors'
import { PriceData } from '../common'
import { isHex } from '../utils'
import {
BaseTransaction,
isNumber,
isString,
validateBaseTransaction,
validateOptionalField,
validateRequiredField,
} from './common'
const PRICE_DATA_SERIES_MAX_LENGTH = 10
const SCALE_MAX = 10
const MINIMUM_ASSET_PRICE_LENGTH = 1
const MAXIMUM_ASSET_PRICE_LENGTH = 16
/**
* Creates a new Oracle ledger entry or updates the fields of an existing one, using the Oracle ID.
*
* The oracle provider must complete these steps before submitting this transaction:
* 1. Create or own the XRPL account in the Owner field and have enough XRP to meet the reserve and transaction fee requirements.
* 2. Publish the XRPL account public key, so it can be used for verification by dApps.
* 3. Publish a registry of available price oracles with their unique OracleDocumentID.
*
* @category Transaction Models
*/
export interface OracleSet extends BaseTransaction {
TransactionType: 'OracleSet'
/**
* A unique identifier of the price oracle for the Account.
*/
OracleDocumentID: number
/**
* The time the data was last updated, represented as a unix timestamp in seconds.
*/
LastUpdateTime: number
/**
* An array of up to 10 PriceData objects, each representing the price information
* for a token pair. More than five PriceData objects require two owner reserves.
*/
PriceDataSeries: PriceData[]
/**
* An arbitrary value that identifies an oracle provider, such as Chainlink, Band,
* or DIA. This field is a string, up to 256 ASCII hex encoded characters (0x20-0x7E).
* This field is required when creating a new Oracle ledger entry, but is optional for updates.
*/
Provider?: string
/**
* An optional Universal Resource Identifier to reference price data off-chain. This field is limited to 256 bytes.
*/
URI?: string
/**
* Describes the type of asset, such as "currency", "commodity", or "index". This field is a string, up to 16 ASCII
* hex encoded characters (0x20-0x7E). This field is required when creating a new Oracle ledger entry, but is optional
* for updates.
*/
AssetClass?: string
}
/**
* Verify the form and type of a OracleSet at runtime.
*
* @param tx - A OracleSet Transaction.
* @throws When the OracleSet is malformed.
*/
// eslint-disable-next-line max-lines-per-function -- necessary to validate many fields
export function validateOracleSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
validateRequiredField(tx, 'OracleDocumentID', isNumber)
validateRequiredField(tx, 'LastUpdateTime', isNumber)
validateOptionalField(tx, 'Provider', isString)
validateOptionalField(tx, 'URI', isString)
validateOptionalField(tx, 'AssetClass', isString)
/* eslint-disable max-statements, max-lines-per-function -- necessary to validate many fields */
validateRequiredField(tx, 'PriceDataSeries', (value) => {
if (!Array.isArray(value)) {
throw new ValidationError('OracleSet: PriceDataSeries must be an array')
}
if (value.length > PRICE_DATA_SERIES_MAX_LENGTH) {
throw new ValidationError(
`OracleSet: PriceDataSeries must have at most ${PRICE_DATA_SERIES_MAX_LENGTH} PriceData objects`,
)
}
// TODO: add support for handling inner objects easier (similar to validateRequiredField/validateOptionalField)
for (const priceData of value) {
if (typeof priceData !== 'object') {
throw new ValidationError(
'OracleSet: PriceDataSeries must be an array of objects',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (priceData.PriceData == null) {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `PriceData` object',
)
}
// check if priceData only has PriceData
if (Object.keys(priceData).length !== 1) {
throw new ValidationError(
'OracleSet: PriceDataSeries must only have a single PriceData object',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (typeof priceData.PriceData.BaseAsset !== 'string') {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `BaseAsset` string',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (typeof priceData.PriceData.QuoteAsset !== 'string') {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `QuoteAsset` string',
)
}
// Either AssetPrice and Scale are both present or both excluded
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
(priceData.PriceData.AssetPrice == null) !==
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
(priceData.PriceData.Scale == null)
) {
throw new ValidationError(
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present',
)
}
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-depth --
we need to validate priceData.PriceData.AssetPrice value */
if ('AssetPrice' in priceData.PriceData) {
if (!isNumber(priceData.PriceData.AssetPrice)) {
if (typeof priceData.PriceData.AssetPrice !== 'string') {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a string or a number',
)
}
if (!isHex(priceData.PriceData.AssetPrice)) {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a valid hex string',
)
}
if (
priceData.PriceData.AssetPrice.length <
MINIMUM_ASSET_PRICE_LENGTH ||
priceData.PriceData.AssetPrice.length > MAXIMUM_ASSET_PRICE_LENGTH
) {
throw new ValidationError(
`OracleSet: Length of AssetPrice field must be between ${MINIMUM_ASSET_PRICE_LENGTH} and ${MAXIMUM_ASSET_PRICE_LENGTH} characters long`,
)
}
}
}
/* eslint-enable @typescript-eslint/no-unsafe-member-access, max-depth */
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
'Scale' in priceData.PriceData &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
!isNumber(priceData.PriceData.Scale)
) {
throw new ValidationError('OracleSet: invalid field Scale')
}
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
priceData.PriceData.Scale < 0 ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
priceData.PriceData.Scale > SCALE_MAX
) {
throw new ValidationError(
`OracleSet: Scale must be in range 0-${SCALE_MAX}`,
)
}
}
return true
})
/* eslint-enable max-statements, max-lines-per-function */
}

View File

@@ -1,30 +1,7 @@
import { ValidationError } from '../../errors'
import { Hook } from '../common/xahau'
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
/**
* Enum representing values for Set Hook Transaction Flags.
*
* @category Transaction Flags
*/
export enum SetHookFlags {
/**
*/
hsfOverride = 0x00000001,
/**
*/
hsfNSDelete = 0x0000002,
/**
*/
hsfCollect = 0x00000004,
}
export interface SetHookFlagsInterface extends GlobalFlags {
hsfOverride?: boolean
hsfNSDelete?: boolean
hsfCollect?: boolean
}
import { BaseTransaction, validateBaseTransaction } from './common'
/**
*
@@ -37,12 +14,14 @@ export interface SetHook extends BaseTransaction {
*
*/
Hooks: Hook[]
Flags?: number | SetHookFlagsInterface
}
const MAX_HOOKS = 10
const HEX_REGEX = /^[0-9A-Fa-f]{64}$/u
/**
* 4-16 bytes in hex
*/
const HOOKNAME_REGEX = /^[0-9A-Fa-f]{8,32}$/u
/**
* Verify the form and type of an SetHook at runtime.
@@ -50,6 +29,7 @@ const HEX_REGEX = /^[0-9A-Fa-f]{64}$/u
* @param tx - An SetHook Transaction.
* @throws When the SetHook is Malformed.
*/
// eslint-disable-next-line max-lines-per-function -- okay for this method
export function validateSetHook(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
@@ -66,12 +46,29 @@ export function validateSetHook(tx: Record<string, unknown>): void {
for (const hook of tx.Hooks) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be a Hook
const hookObject = hook as Hook
const { HookOn, HookCanEmit, HookNamespace } = hookObject.Hook
const {
HookOn,
HookOnIncoming,
HookOnOutgoing,
HookCanEmit,
HookNamespace,
HookName,
} = hookObject.Hook
if (HookOn !== undefined && !HEX_REGEX.test(HookOn)) {
throw new ValidationError(
`SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnIncoming !== undefined && !HEX_REGEX.test(HookOnIncoming)) {
throw new ValidationError(
`SetHook: HookOnIncoming in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnOutgoing !== undefined && !HEX_REGEX.test(HookOnOutgoing)) {
throw new ValidationError(
`SetHook: HookOnOutgoing in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookCanEmit !== undefined && !HEX_REGEX.test(HookCanEmit)) {
throw new ValidationError(
`SetHook: HookCanEmit in Hook must be a 256-bit (32-byte) hexadecimal value`,
@@ -82,5 +79,10 @@ export function validateSetHook(tx: Record<string, unknown>): void {
`SetHook: HookNamespace in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookName !== undefined && !HOOKNAME_REGEX.test(HookName)) {
throw new ValidationError(
`SetHook: HookName in Hook must be a hex string of 8-32 hex characters`,
)
}
}
}

View File

@@ -10,7 +10,10 @@ import { CheckCancel, validateCheckCancel } from './checkCancel'
import { CheckCash, validateCheckCash } from './checkCash'
import { CheckCreate, validateCheckCreate } from './checkCreate'
import { ClaimReward, validateClaimReward } from './claimReward'
import { Clawback, validateClawback } from './clawback'
import { BaseTransaction, isIssuedCurrency } from './common'
import { Cron } from './cron'
import { CronSet, validateCronSet } from './cronSet'
import { DepositPreauth, validateDepositPreauth } from './depositPreauth'
import { EnableAmendment } from './enableAmendment'
import { EscrowCancel, validateEscrowCancel } from './escrowCancel'
@@ -21,6 +24,8 @@ import { Invoke, validateInvoke } from './invoke'
import { TransactionMetadata } from './metadata'
import { OfferCancel, validateOfferCancel } from './offerCancel'
import { OfferCreate, validateOfferCreate } from './offerCreate'
import { OracleDelete, validateOracleDelete } from './oracleDelete'
import { OracleSet, validateOracleSet } from './oracleSet'
import { Payment, validatePayment } from './payment'
import {
PaymentChannelClaim,
@@ -66,6 +71,8 @@ export type SubmittableTransaction =
| CheckCash
| CheckCreate
| ClaimReward
| Clawback
| CronSet
| DepositPreauth
| EscrowCancel
| EscrowCreate
@@ -74,6 +81,8 @@ export type SubmittableTransaction =
| Invoke
| OfferCancel
| OfferCreate
| OracleDelete
| OracleSet
| Payment
| PaymentChannelClaim
| PaymentChannelCreate
@@ -96,7 +105,7 @@ export type SubmittableTransaction =
*
* @category Transaction Models
*/
export type PseudoTransaction = EnableAmendment | SetFee | UNLModify
export type PseudoTransaction = Cron | EnableAmendment | SetFee | UNLModify
/**
* All transactions that can live on the XAHL
@@ -204,6 +213,14 @@ export function validate(transaction: Record<string, unknown>): void {
validateClaimReward(tx)
break
case 'Clawback':
validateClawback(tx)
break
case 'CronSet':
validateCronSet(tx)
break
case 'DepositPreauth':
validateDepositPreauth(tx)
break
@@ -236,6 +253,14 @@ export function validate(transaction: Record<string, unknown>): void {
validateOfferCreate(tx)
break
case 'OracleDelete':
validateOracleDelete(tx)
break
case 'OracleSet':
validateOracleSet(tx)
break
case 'Payment':
validatePayment(tx)
break
@@ -302,6 +327,7 @@ export function validate(transaction: Record<string, unknown>): void {
default:
throw new ValidationError(
// eslint-disable-next-line max-lines -- allowed here
`Invalid field TransactionType: ${tx.TransactionType}`,
)
}

View File

@@ -30,6 +30,11 @@ export enum TrustSetFlags {
tfSetFreeze = 0x00100000,
/** Unfreeze the trust line. */
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
/** Unfreeze the trust line. */
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
}
/**

View File

@@ -1,24 +1,22 @@
/* eslint-disable no-param-reassign -- param reassign is safe */
/* eslint-disable no-bitwise -- flags require bitwise operations */
import { ValidationError } from '../../errors'
import { Hook } from '../common/xahau'
import { Hook, HookFlags, MintURITokenFlags } from '../common/xahau'
import {
AccountRootFlagsInterface,
AccountRootFlags,
} from '../ledger/AccountRoot'
import { AccountSetTfFlags } from '../transactions/accountSet'
import { ClaimRewardFlags } from '../transactions/claimReward'
import { GlobalFlags } from '../transactions/common'
import { CronSetFlags } from '../transactions/cronSet'
import { OfferCreateFlags } from '../transactions/offerCreate'
import { PaymentFlags } from '../transactions/payment'
import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim'
import { SetHookFlagsInterface, SetHookFlags } from '../transactions/setHook'
import {
RemarkFlagsInterface,
RemarkFlags,
Remark,
} from '../transactions/setRemarks'
import { RemarkFlags, Remark } from '../transactions/setRemarks'
import type { Transaction } from '../transactions/transaction'
import { TrustSetFlags } from '../transactions/trustSet'
import { URITokenMintFlags } from '../transactions/uriTokenMint'
import { isFlagEnabled } from '.'
@@ -52,6 +50,9 @@ const txToFlag = {
PaymentChannelClaim: PaymentChannelClaimFlags,
Payment: PaymentFlags,
TrustSet: TrustSetFlags,
URITokenMint: URITokenMintFlags,
ClaimReward: ClaimRewardFlags,
CronSet: CronSetFlags,
}
/**
@@ -60,6 +61,36 @@ const txToFlag = {
* @param tx - A transaction to set its flags to its numeric representation.
*/
export function setTransactionFlagsToNumber(tx: Transaction): void {
if (tx.TransactionType === 'SetHook' && Array.isArray(tx.Hooks)) {
tx.Hooks.forEach((hook: Hook) => {
if (typeof hook.Hook.Flags === 'object') {
hook.Hook.Flags = convertFlagsToNumber(hook.Hook.Flags, HookFlags)
}
})
}
if (tx.TransactionType === 'Remit') {
if (tx.MintURIToken != null && typeof tx.MintURIToken.Flags === 'object') {
tx.MintURIToken.Flags = convertFlagsToNumber(
tx.MintURIToken.Flags,
MintURITokenFlags,
)
}
}
if (tx.TransactionType === 'SetRemarks') {
if (Array.isArray(tx.Remarks)) {
tx.Remarks.forEach((remark: Remark) => {
if (typeof remark.Remark.Flags === 'object') {
remark.Remark.Flags = convertFlagsToNumber(
remark.Remark.Flags,
RemarkFlags,
)
}
})
}
}
if (tx.Flags == null) {
tx.Flags = 0
return
@@ -68,25 +99,6 @@ export function setTransactionFlagsToNumber(tx: Transaction): void {
return
}
if (tx.TransactionType === 'SetHook') {
tx.Flags = convertFlagsToNumber(tx.Flags, SetHookFlags)
tx.Hooks.forEach((hook: Hook) => {
hook.Hook.Flags = convertFlagsToNumber(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- idk
hook.Hook.Flags as SetHookFlagsInterface,
SetHookFlags,
)
})
} else if (tx.TransactionType === 'SetRemarks') {
tx.Remarks.forEach((remark: Remark) => {
remark.Remark.Flags = convertFlagsToNumber(
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- idk
remark.Remark.Flags as RemarkFlagsInterface,
RemarkFlags,
)
})
}
tx.Flags = txToFlag[tx.TransactionType]
? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType])
: 0

View File

@@ -1,5 +1,3 @@
import { decode, encode } from 'xahau-binary-codec'
import type {
Client,
SubmitRequest,
@@ -12,6 +10,7 @@ import { ValidationError, XahlError } from '../errors'
import { Signer } from '../models/common'
import { TxResponse } from '../models/methods'
import { BaseTransaction } from '../models/transactions/common'
import { decode, encode } from '../utils'
/** Approximate time for a ledger to close, in milliseconds */
const LEDGER_CLOSE_TIME = 1000
@@ -52,7 +51,7 @@ export async function submitRequest(
failHard = false,
): Promise<SubmitResponse> {
if (!isSigned(signedTransaction)) {
throw new ValidationError('Transaction must be signed')
throw new ValidationError('Transaction must be signed.')
}
const signedTxEncoded =

View File

@@ -199,4 +199,28 @@ export function hashURIToken(issuer: string, uri: string): string {
)
}
/**
* Compute the Hash of a Cron LedgerEntry.
*
* @param owner - Account of the Cron.
* @param time - Time of the Cron.
* @returns Hash of the Cron.
* @category Utilities
*/
export function hashCron(owner: string, time: number): string {
const timeString = bytesToHex([
(time >> 24) & 0xff,
(time >> 16) & 0xff,
(time >> 8) & 0xff,
(time >> 0) & 0xff,
])
const nsHash = sha512Half(ledgerSpaceHex('cron')).slice(0, 16)
const accHash = sha512Half(
ledgerSpaceHex('cron') + timeString + addressToHex(owner),
).slice(0, 40)
return nsHash + timeString + accHash
}
export { hashLedgerHeader, hashSignedTx, hashLedger, hashStateTree, hashTxTree }

View File

@@ -1,7 +1,7 @@
/**
* XAH Ledger namespace prefixes.
* Xahau Network namespace prefixes.
*
* The XAH Ledger is a key-value store. In order to avoid name collisions,
* The Xahau Network is a key-value store. In order to avoid name collisions,
* names are partitioned into namespaces.
*
* Each namespace is just a single character prefix.
@@ -30,6 +30,7 @@ const ledgerSpaces = {
check: 'C',
depositPreauth: 'p',
uriToken: 'U',
cron: 'L',
}
export default ledgerSpaces

View File

@@ -1,12 +1,14 @@
import { assert } from 'chai'
import { EscrowFinish, Payment, Transaction } from '../../src'
import { ValidationError } from '../../src/errors'
import xahaud from '../fixtures/xahaud'
import {
setupClient,
teardownClient,
type XrplTestContext,
} from '../setupClient'
import { assertRejects } from '../testUtils'
const NetworkID = 1025
const Fee = '10'
@@ -15,6 +17,8 @@ const LastLedgerSequence = 2908734
describe('client.autofill', function () {
let testContext: XrplTestContext
const AMOUNT = '1234'
let paymentTx: Payment
async function setupMockRippledVersionAndID(
buildVersion: string,
@@ -38,6 +42,64 @@ describe('client.autofill', function () {
})
afterAll(async () => teardownClient(testContext))
beforeEach(async () => {
paymentTx = {
TransactionType: 'Payment',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
DestinationTag: 1,
Fee: '12',
Flags: 2147483648,
LastLedgerSequence: 65953073,
Sequence: 65923914,
SigningPubKey:
'02F9E33F16DF9507705EC954E3F94EB5F10D1FC4A354606DBE6297DBB1096FE654',
TxnSignature:
'3045022100E3FAE0EDEC3D6A8FF6D81BC9CF8288A61B7EEDE8071E90FF9314CB4621058D10022043545CF631706D700CEE65A1DB83EFDD185413808292D9D90F14D87D3DC2D8CB',
InvoiceID:
'6F1DFD1D0FE8A32E40E1F2C05CF1C15545BAB56B617F9C6C2D63A6B704BEF59B',
Paths: [
[{ currency: 'BTC', issuer: 'r9vbV3EHvXWjSkeQ6CAcYVPGeq7TuiXY2X' }],
],
SendMax: '100000000',
}
})
it('Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field', async function () {
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
})
it('Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
})
it('Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
const txResult = await testContext.client.autofill(paymentTx)
assert.strictEqual(txResult.Amount, AMOUNT)
assert.strictEqual('DeliverMax' in txResult, false)
})
it('Validate Payment transaction API v2: Payment Transaction: differing DeliverMax and Amount fields', async function () {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = '6789'
paymentTx.Amount = '1234'
await assertRejects(testContext.client.autofill(paymentTx), ValidationError)
})
it('should not autofill if fields are present', async function () {
const tx: Transaction = {
TransactionType: 'DepositPreauth',

View File

@@ -0,0 +1,78 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet } 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('get_aggregate_price', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const tx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, tx, testContext.wallet)
// confirm that the Oracle was actually created
const getAggregatePriceResponse = await testContext.client.request({
command: 'get_aggregate_price',
account: testContext.wallet.classicAddress,
base_asset: 'XRP',
quote_asset: 'USD',
trim: 20,
oracles: [
{
account: testContext.wallet.classicAddress,
oracle_document_id: 1234,
},
],
})
assert.deepEqual(getAggregatePriceResponse.result.entire_set, {
mean: '0.74',
size: 1,
standard_deviation: '0',
})
assert.deepEqual(getAggregatePriceResponse.result.trimmed_set, {
mean: '0.74',
size: 1,
standard_deviation: '0',
})
assert.equal(getAggregatePriceResponse.result.median, '0.74')
assert.equal(getAggregatePriceResponse.result.time, tx.LastUpdateTime)
},
TIMEOUT,
)
})

View File

@@ -117,6 +117,7 @@ describe('server_info (xahaud)', function () {
'time',
'uptime',
'complete_ledgers',
'complete_ledgers_pinned',
'hostid',
'load',
'state_accounting',
@@ -127,6 +128,7 @@ describe('server_info (xahaud)', function () {
'node_size',
'initial_sync_duration_us',
'ports',
'git',
]
assert.deepEqual(
omit(response.result.info, removeKeys),

View File

@@ -105,6 +105,7 @@ describe('server_state', function () {
const removeKeys = [
'complete_ledgers',
'complete_ledgers_pinned',
'load',
'state_accounting',
'pubkey_node',
@@ -117,6 +118,7 @@ describe('server_state', function () {
'node_size',
'initial_sync_duration_us',
'ports',
'git',
]
assert.deepEqual(
omit(response.result.state, removeKeys),

View 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,
)
})

View File

@@ -1,24 +1,66 @@
import { assert } from 'chai'
import { ClaimReward, ClaimRewardFlags } from '../../../src'
import {
ClaimReward,
ClaimRewardFlags,
SetHook,
TrustSet,
Wallet,
} from '../../../src'
import { RippleState } from '../../../src/models/ledger'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('ClaimReward', function () {
let testContext: XrplIntegrationTestContext
const acceptHook =
'0061736D0100000001130360027F7F017F60037F7F7E017E60017F017E02170203656E76025F67000003656E760661636365707400010302010205030100020621057F01418088040B7F004180080B7F004180080B7F00418088040B7F004180080B07080104686F6F6B00020A9D80000199800000410141011080808080001A4100410042001081808080000B'
beforeEach(async () => {
describe('XAH ClaimReward', function () {
let testContext: XrplIntegrationTestContext
let genesisWallet: Wallet
beforeAll(async () => {
genesisWallet = new Wallet(
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020',
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
)
testContext = await setupClient(serverUrl)
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: genesisWallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, genesisWallet)
})
afterAll(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: genesisWallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, genesisWallet)
await teardownClient(testContext)
})
afterEach(async () => teardownClient(testContext))
it(
'opt in',
@@ -26,7 +68,7 @@ describe('ClaimReward', function () {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Issuer: genesisWallet.address,
}
await testTransaction(testContext.client, tx, testContext.wallet)
@@ -42,6 +84,7 @@ describe('ClaimReward', function () {
},
TIMEOUT,
)
it(
'opt out',
async () => {
@@ -67,3 +110,135 @@ describe('ClaimReward', function () {
TIMEOUT,
)
})
describe('IOU ClaimReward', function () {
let testContext: XrplIntegrationTestContext
let issuerWallet: Wallet
let hookWallet: Wallet
beforeAll(async () => {
testContext = await setupClient(serverUrl)
issuerWallet = await generateFundedWallet(testContext.client)
hookWallet = await generateFundedWallet(testContext.client)
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: hookWallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, hookWallet)
const trustSetTx: TrustSet = {
TransactionType: 'TrustSet',
Account: testContext.wallet.classicAddress,
LimitAmount: {
currency: 'USD',
issuer: issuerWallet.address,
value: '10000000',
},
}
await testTransaction(testContext.client, trustSetTx, testContext.wallet)
})
afterAll(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: hookWallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, hookWallet)
await teardownClient(testContext)
})
it(
'opt in',
async () => {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Issuer: hookWallet.classicAddress,
ClaimCurrency: {
currency: 'USD',
issuer: issuerWallet.address,
},
}
await testTransaction(testContext.client, tx, testContext.wallet)
const rippleStateResponse = await testContext.client.request({
command: 'ledger_entry',
ripple_state: {
currency: 'USD',
accounts: [
testContext.wallet.classicAddress,
issuerWallet.classicAddress,
],
},
})
const node = rippleStateResponse.result.node as RippleState
assert.exists(node)
// Either LowReward or HighReward must exist
expect(Boolean(node.LowReward) || Boolean(node.HighReward))
if (node.LowReward) {
assert.exists(node.LowReward.TrustLineRewardAccumulator)
assert.exists(node.LowReward.RewardLgrFirst)
assert.exists(node.LowReward.RewardLgrLast)
assert.exists(node.LowReward.RewardTime)
}
if (node.HighReward) {
assert.exists(node.HighReward.TrustLineRewardAccumulator)
assert.exists(node.HighReward.RewardLgrFirst)
assert.exists(node.HighReward.RewardLgrLast)
assert.exists(node.HighReward.RewardTime)
}
},
TIMEOUT,
)
it(
'opt out',
async () => {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Flags: ClaimRewardFlags.tfOptOut,
ClaimCurrency: {
currency: 'USD',
issuer: issuerWallet.address,
},
}
await testTransaction(testContext.client, tx, testContext.wallet)
const rippleStateResponse = await testContext.client.request({
command: 'ledger_entry',
ripple_state: {
currency: 'USD',
accounts: [
testContext.wallet.classicAddress,
issuerWallet.classicAddress,
],
},
})
const node = rippleStateResponse.result.node as RippleState
assert.exists(node)
assert.notExists(node.LowReward)
assert.notExists(node.HighReward)
},
TIMEOUT,
)
})

View File

@@ -0,0 +1,115 @@
import { assert } from 'chai'
import {
AccountSet,
AccountSetAsfFlags,
TrustSet,
Payment,
Clawback,
} from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('Clawback', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const wallet2 = await generateFundedWallet(testContext.client)
const setupAccountSetTx: AccountSet = {
TransactionType: 'AccountSet',
Account: testContext.wallet.classicAddress,
SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
}
await testTransaction(
testContext.client,
setupAccountSetTx,
testContext.wallet,
)
const setupTrustSetTx: TrustSet = {
TransactionType: 'TrustSet',
Account: wallet2.classicAddress,
LimitAmount: {
currency: 'USD',
issuer: testContext.wallet.classicAddress,
value: '1000',
},
}
await testTransaction(testContext.client, setupTrustSetTx, wallet2)
const setupPaymentTx: Payment = {
TransactionType: 'Payment',
Account: testContext.wallet.classicAddress,
Destination: wallet2.classicAddress,
Amount: {
currency: 'USD',
issuer: testContext.wallet.classicAddress,
value: '1000',
},
}
await testTransaction(
testContext.client,
setupPaymentTx,
testContext.wallet,
)
// verify that line is created
const objectsResponse = await testContext.client.request({
command: 'account_objects',
account: wallet2.classicAddress,
type: 'state',
})
assert.lengthOf(
objectsResponse.result.account_objects,
1,
'Should be exactly one line on the ledger',
)
// actual test - clawback
const tx: Clawback = {
TransactionType: 'Clawback',
Account: testContext.wallet.classicAddress,
Amount: {
currency: 'USD',
issuer: wallet2.classicAddress,
value: '500',
},
}
await testTransaction(testContext.client, tx, testContext.wallet)
// verify amount clawed back
const linesResponse = await testContext.client.request({
command: 'account_lines',
account: wallet2.classicAddress,
})
assert.lengthOf(
linesResponse.result.lines,
1,
'Should be exactly one line on the ledger',
)
assert.equal(
'500',
linesResponse.result.lines[0].balance,
`Holder balance incorrect after Clawback`,
)
},
TIMEOUT,
)
})

View File

@@ -1,24 +1,36 @@
import { assert } from 'chai'
import { OfferCreate } from '../../../src'
import { OfferCreate, TrustSet, Wallet } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
import {
testTransaction,
generateFundedWallet,
submitTransaction,
} from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('OfferCreate', function () {
let testContext: XrplIntegrationTestContext
let wallet_deep_freeze_trustline: Wallet | undefined
beforeEach(async () => {
beforeAll(async () => {
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(
'base',
@@ -49,4 +61,52 @@ describe('OfferCreate', function () {
},
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,
)
})

View File

@@ -0,0 +1,76 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet, OracleDelete } 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('OracleDelete', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const setTx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, setTx, testContext.wallet)
const aoResult = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
type: 'oracle',
})
// confirm that the Oracle was created
assert.equal(aoResult.result.account_objects.length, 1)
const deleteTx: OracleDelete = {
TransactionType: 'OracleDelete',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
}
await testTransaction(testContext.client, deleteTx, testContext.wallet)
const aoResult2 = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
})
// confirm that the Oracle was actually deleted
assert.equal(aoResult2.result.account_objects.length, 0)
},
TIMEOUT,
)
})

View File

@@ -0,0 +1,91 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet } from '../../../src'
import { Oracle } from '../../../src/models/ledger'
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('OracleSet', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const tx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'INR',
// Upper bound admissible value for AssetPrice field
// large numeric values necessarily have to use str type in Javascript
// number type uses double-precision floating point representation, hence represents a smaller range of values
AssetPrice: 'ffffffffffffffff',
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, tx, testContext.wallet)
const result = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
type: 'oracle',
})
// confirm that the Oracle was actually created
assert.equal(result.result.account_objects.length, 1)
// confirm details of Oracle ledger entry object
const oracle = result.result.account_objects[0] as Oracle
assert.equal(oracle.LastUpdateTime, tx.LastUpdateTime)
assert.equal(oracle.Owner, testContext.wallet.classicAddress)
assert.equal(oracle.AssetClass, tx.AssetClass)
assert.equal(oracle.Provider, tx.Provider)
assert.equal(oracle.PriceDataSeries.length, 2)
assert.equal(oracle.PriceDataSeries[1].PriceData.BaseAsset, 'XRP')
assert.equal(oracle.PriceDataSeries[1].PriceData.QuoteAsset, 'USD')
assert.equal(oracle.PriceDataSeries[1].PriceData.AssetPrice, '2e4')
assert.equal(oracle.PriceDataSeries[1].PriceData.Scale, 3)
assert.equal(oracle.Flags, 0)
// validate the serialization of large AssetPrice values
assert.equal(
oracle.PriceDataSeries[0].PriceData.AssetPrice,
'ffffffffffffffff',
)
},
TIMEOUT,
)
})

View File

@@ -1,3 +1,5 @@
import { assert } from 'chai'
import { Payment, Wallet } from '../../../src'
import serverUrl from '../serverUrl'
import {
@@ -12,9 +14,22 @@ const TIMEOUT = 20000
describe('Payment', function () {
let testContext: XrplIntegrationTestContext
let paymentTx: Payment
const AMOUNT = '10000000'
// This wallet is used for DeliverMax related tests
let senderWallet: Wallet
beforeEach(async () => {
// this payment transaction JSON needs to be refreshed before every test.
// Because, we tinker with Amount and DeliverMax fields in the API v2 tests
paymentTx = {
TransactionType: 'Payment',
Account: senderWallet.classicAddress,
Amount: AMOUNT,
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
}
})
beforeAll(async () => {
testContext = await setupClient(serverUrl)
senderWallet = await generateFundedWallet(testContext.client)
@@ -34,4 +49,57 @@ describe('Payment', function () {
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only Amount field',
async () => {
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: Specify Only DeliverMax field',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
delete paymentTx.Amount
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
it(
'Validate Payment transaction API v2: Payment Transaction: identical DeliverMax and Amount fields',
async () => {
// @ts-expect-error -- DeliverMax is a non-protocol, RPC level field in Payment transactions
paymentTx.DeliverMax = paymentTx.Amount
const result = await testTransaction(
testContext.client,
paymentTx,
senderWallet,
)
assert.equal(result.result.engine_result_code, 0)
assert.equal((result.result.tx_json as Payment).Amount, AMOUNT)
},
TIMEOUT,
)
})

View File

@@ -1,4 +1,4 @@
import { stringToHex } from '@xrplf/isomorphic/src/utils'
import { stringToHex } from '@xrplf/isomorphic/utils'
import { Remit } from '../../../src'
import serverUrl from '../serverUrl'
@@ -65,6 +65,9 @@ describe('Remit', function () {
Destination: wallet2.classicAddress,
MintURIToken: {
URI: stringToHex('https://example.com'),
Flags: {
tfBurnable: true,
},
},
}

View File

@@ -0,0 +1,138 @@
import { SetHook, Wallet } from '../../../src'
import { Hook, HookDefinition } from '../../../src/models/ledger'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
const acceptHook =
'0061736D0100000001130360027F7F017F60037F7F7E017E60017F017E02170203656E76025F67000003656E760661636365707400010302010205030100020621057F01418088040B7F004180080B7F004180080B7F00418088040B7F004180080B07080104686F6F6B00020A9D80000199800000410141011080808080001A4100410042001081808080000B'
describe('SetHook', function () {
let testContext: XrplIntegrationTestContext
let wallet: Wallet
beforeEach(async () => {
testContext = await setupClient(serverUrl)
wallet = await generateFundedWallet(testContext.client)
})
afterEach(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, wallet)
await teardownClient(testContext)
})
it(
'base',
async () => {
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookCanEmit: '00'.repeat(32),
HookName: '484F4F4B',
HookParameters: [
{
HookParameter: {
HookParameterName: 'DEADBEEF',
HookParameterValue: 'DEADBEEF',
},
},
],
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, wallet)
const ledgerEntryResponse = await testContext.client.request({
command: 'ledger_entry',
hook: { account: wallet.classicAddress },
})
const node = ledgerEntryResponse.result.node as Hook
expect(node.Hooks.length).toEqual(1)
const hook = node.Hooks[0].Hook
expect(Object.keys(hook).length).toEqual(2)
expect(hook.HookHash).toBeDefined()
expect(hook.HookName).toBeDefined()
const hookHash = hook.HookHash!
const hookDefinitionResponse = await testContext.client.request({
command: 'ledger_entry',
hook_definition: hookHash,
})
const hookDefinitionNode = hookDefinitionResponse.result
.node as HookDefinition
expect(hookDefinitionNode.HookHash).toEqual(hookHash)
expect(hookDefinitionNode.CreateCode).toEqual(acceptHook)
expect(hookDefinitionNode.HookApiVersion).toEqual(0)
expect(hookDefinitionNode.HookOn).toEqual('00'.repeat(32))
expect(hookDefinitionNode.HookNamespace).toEqual('00'.repeat(32))
// @ts-expect-error - HookName is not defined in HookDefinition
expect(hookDefinitionNode.HookName).toBeUndefined()
expect(hookDefinitionNode.HookParameters?.length).toEqual(1)
const parameter = hookDefinitionNode.HookParameters![0].HookParameter
expect(parameter.HookParameterName).toEqual('DEADBEEF')
expect(parameter.HookParameterValue).toEqual('DEADBEEF')
},
TIMEOUT,
)
it(
'hook on incoming/outgoing',
async () => {
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOnIncoming: '00'.repeat(32),
// eslint-disable-next-line no-inline-comments -- for readability
HookOnOutgoing: `01${'00'.repeat(31)}`, // should be different from HookOnIncoming
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, wallet)
const ledgerEntryResponse = await testContext.client.request({
command: 'ledger_entry',
hook: { account: wallet.classicAddress },
})
const node = ledgerEntryResponse.result.node as Hook
const hook = node.Hooks[0].Hook
const hookHash = hook.HookHash!
const hookDefinitionResponse = await testContext.client.request({
command: 'ledger_entry',
hook_definition: hookHash,
})
const hookDefinitionNode = hookDefinitionResponse.result
.node as HookDefinition
expect(hookDefinitionNode.HookOn).toBeUndefined()
expect(hookDefinitionNode.HookOnIncoming).toBeDefined()
expect(hookDefinitionNode.HookOnOutgoing).toBeDefined()
},
TIMEOUT,
)
})

View File

@@ -1,6 +1,8 @@
import { assert } from 'chai'
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 {
setupClient,
@@ -85,4 +87,60 @@ describe('TrustSet', function () {
},
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,
)
})

View File

@@ -13,6 +13,8 @@ import {
ECDSA,
AccountLinesRequest,
IssuedCurrency,
XAHAUD_API_V2,
TxResponse,
} from '../../src'
import {
Payment,
@@ -184,20 +186,22 @@ export async function verifySubmittedTransaction(
hashTx?: string,
): Promise<void> {
const hash = hashTx ?? hashSignedTx(tx)
const data = await client.request({
const data: TxResponse = await client.request({
command: 'tx',
transaction: hash,
// The current default version is v1, but we'll be using v2 for this test.
api_version: XAHAUD_API_V2,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: handle this API change for 2.0.0
const decodedTx: any = typeof tx === 'string' ? decode(tx) : tx
if (decodedTx.TransactionType === 'Payment' && client.apiVersion !== 1) {
if (decodedTx.TransactionType === 'Payment') {
decodedTx.DeliverMax = decodedTx.Amount
delete decodedTx.Amount
}
assert(data.result)
assert.deepEqual(
omit(data.result, [
omit(data.result.tx_json, [
'ctid',
'date',
'hash',

View File

@@ -163,4 +163,43 @@ describe('AccountSet', function () {
'AccountSet: invalid field NFTokenMinter',
)
})
it(`throws w/ invalid HookStateScale`, function () {
account.HookStateScale = ''
assert.throws(
() => validateAccountSet(account),
ValidationError,
'AccountSet: invalid field HookStateScale',
)
assert.throws(
() => validate(account),
ValidationError,
'AccountSet: invalid field HookStateScale',
)
account.HookStateScale = 17
assert.throws(
() => validateAccountSet(account),
ValidationError,
'AccountSet: HookStateScale must be less than or equal to 16',
)
assert.throws(
() => validate(account),
ValidationError,
'AccountSet: HookStateScale must be less than or equal to 16',
)
account.HookStateScale = 0
assert.throws(
() => validateAccountSet(account),
ValidationError,
'AccountSet: HookStateScale must be greater than or equal to 1',
)
assert.throws(
() => validate(account),
ValidationError,
'AccountSet: HookStateScale must be greater than or equal to 1',
)
})
})

View File

@@ -0,0 +1,81 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
/**
* Clawback Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('Clawback', function () {
it(`verifies valid Clawback`, function () {
const validClawback = {
TransactionType: 'Clawback',
Amount: {
currency: 'DSH',
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
value: '43.11584856965009',
},
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
} as any
assert.doesNotThrow(() => validate(validClawback))
})
it(`throws w/ missing Amount`, function () {
const missingAmount = {
TransactionType: 'Clawback',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
} as any
assert.throws(
() => validate(missingAmount),
ValidationError,
'Clawback: missing field Amount',
)
})
it(`throws w/ invalid Amount`, function () {
const invalidAmount = {
TransactionType: 'Clawback',
Amount: 100000000,
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
} as any
assert.throws(
() => validate(invalidAmount),
ValidationError,
'Clawback: invalid Amount',
)
const invalidStrAmount = {
TransactionType: 'Clawback',
Amount: '1234',
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
} as any
assert.throws(
() => validate(invalidStrAmount),
ValidationError,
'Clawback: invalid Amount',
)
})
it(`throws w/ invalid holder Account`, function () {
const invalidAccount = {
TransactionType: 'Clawback',
Amount: {
currency: 'DSH',
issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
value: '43.11584856965009',
},
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
} as any
assert.throws(
() => validate(invalidAccount),
ValidationError,
'Clawback: invalid holder Account',
)
})
})

View File

@@ -0,0 +1,121 @@
import { assert } from 'chai'
import {
setTransactionFlagsToNumber,
validate,
ValidationError,
} from '../../src'
import {
CronSetFlags,
validateCronSet,
} from '../../src/models/transactions/cronSet'
/**
* CronSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('CronSet', function () {
it(`verifies valid CronSet`, function () {
let validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
RepeatCount: 256,
DelaySeconds: 365 * 24 * 60 * 60,
StartTime: 0,
} as any
assert.doesNotThrow(() => validateCronSet(validCronSet))
assert.doesNotThrow(() => validate(validCronSet))
validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
Flags: CronSetFlags.tfCronUnset,
} as any
assert.doesNotThrow(() => validateCronSet(validCronSet))
assert.doesNotThrow(() => validate(validCronSet))
validCronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Fee: '100',
Flags: { tfCronUnset: true },
} as any
assert.doesNotThrow(() => {
setTransactionFlagsToNumber(validCronSet)
validateCronSet(validCronSet)
})
assert.doesNotThrow(() => validate(validCronSet))
})
it(`throws w/ invalid Delete Operation`, function () {
const invalidDeleteOperation = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Flags: CronSetFlags.tfCronUnset,
RepeatCount: 1,
DelaySeconds: 1,
StartTime: 1,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidDeleteOperation),
ValidationError,
'CronSet: RepeatCount, DelaySeconds, and StartTime must not be set when Flags is set to tfCronUnset',
)
assert.throws(
() => validate(invalidDeleteOperation),
ValidationError,
'CronSet: RepeatCount, DelaySeconds, and StartTime must not be set when Flags is set to tfCronUnset',
)
})
it(`throws w/ invalid RepeatCount`, function () {
const invalidRepeatCount = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
RepeatCount: 257,
StartTime: 1,
DelaySeconds: 1,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidRepeatCount),
ValidationError,
'CronSet: RepeatCount must be less than 256',
)
assert.throws(
() => validate(invalidRepeatCount),
ValidationError,
'CronSet: RepeatCount must be less than 256',
)
})
it(`throws w/ invalid DelaySeconds`, function () {
const invalidDelaySeconds = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
DelaySeconds: 365 * 24 * 60 * 60 + 1,
StartTime: 1,
RepeatCount: 1,
Fee: '100',
} as any
assert.throws(
() => validateCronSet(invalidDelaySeconds),
ValidationError,
`CronSet: DelaySeconds must be less than ${365 * 24 * 60 * 60}`,
)
assert.throws(
() => validate(invalidDelaySeconds),
ValidationError,
`CronSet: DelaySeconds must be less than ${365 * 24 * 60 * 60}`,
)
})
})

View File

@@ -0,0 +1,40 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateOracleDelete } from '../../src/models/transactions/oracleDelete'
/**
* OracleDelete Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('OracleDelete', function () {
let tx
beforeEach(function () {
tx = {
TransactionType: 'OracleDelete',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
OracleDocumentID: 1234,
} as any
})
it('verifies valid OracleDelete', function () {
assert.doesNotThrow(() => validateOracleDelete(tx))
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ missing field OracleDocumentID`, function () {
delete tx.OracleDocumentID
const errorMessage = 'OracleDelete: missing field OracleDocumentID'
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid OracleDocumentID`, function () {
tx.OracleDocumentID = '1234'
const errorMessage = 'OracleDelete: invalid field OracleDocumentID'
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,212 @@
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateOracleSet } from '../../src/models/transactions/oracleSet'
/**
* OracleSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('OracleSet', function () {
let tx
beforeEach(function () {
tx = {
TransactionType: 'OracleSet',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
OracleDocumentID: 1234,
LastUpdateTime: 768062172,
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
} as any
})
it('verifies valid OracleSet', function () {
assert.doesNotThrow(() => validateOracleSet(tx))
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ missing field OracleDocumentID`, function () {
delete tx.OracleDocumentID
const errorMessage = 'OracleSet: missing field OracleDocumentID'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid OracleDocumentID`, function () {
tx.OracleDocumentID = '1234'
const errorMessage = 'OracleSet: invalid field OracleDocumentID'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing field LastUpdateTime`, function () {
delete tx.LastUpdateTime
const errorMessage = 'OracleSet: missing field LastUpdateTime'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid LastUpdateTime`, function () {
tx.LastUpdateTime = '768062172'
const errorMessage = 'OracleSet: invalid field LastUpdateTime'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid Provider`, function () {
tx.Provider = 1234
const errorMessage = 'OracleSet: invalid field Provider'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid URI`, function () {
tx.URI = 1234
const errorMessage = 'OracleSet: invalid field URI'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid AssetClass`, function () {
tx.AssetClass = 1234
const errorMessage = 'OracleSet: invalid field AssetClass'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid PriceDataSeries must be an array`, function () {
tx.PriceDataSeries = 1234
const errorMessage = 'OracleSet: PriceDataSeries must be an array'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid PriceDataSeries must be an array of objects`, function () {
tx.PriceDataSeries = [1234]
const errorMessage =
'OracleSet: PriceDataSeries must be an array of objects'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must have at most 10 PriceData objects`, function () {
tx.PriceDataSeries = new Array(11).fill({
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
})
const errorMessage =
'OracleSet: PriceDataSeries must have at most 10 PriceData objects'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must have a PriceData object`, function () {
delete tx.PriceDataSeries[0].PriceData
const errorMessage =
'OracleSet: PriceDataSeries must have a `PriceData` object'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must only have a single PriceData object`, function () {
tx.PriceDataSeries[0].ExtraProp = 'extraprop'
const errorMessage =
'OracleSet: PriceDataSeries must only have a single PriceData object'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing BaseAsset of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.BaseAsset
const errorMessage =
'OracleSet: PriceDataSeries must have a `BaseAsset` string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing QuoteAsset of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.QuoteAsset
const errorMessage =
'OracleSet: PriceDataSeries must have a `QuoteAsset` string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing AssetPrice with Scale present of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.AssetPrice
const errorMessage =
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing Scale with AssetPrice present of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.Scale
const errorMessage =
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid AssetPrice of PriceDataSeries`, function () {
// value cannot be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ghij'
const errorMessage =
'OracleSet: Field AssetPrice must be a valid hex string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`verifies valid AssetPrice of PriceDataSeries`, function () {
// valid string which can be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ab15'
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ invalid AssetPrice type in PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.AssetPrice = ['sample', 'invalid', 'type']
const errorMessage =
'OracleSet: Field AssetPrice must be a string or a number'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid Scale of PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.Scale = '1234'
const errorMessage = 'OracleSet: invalid field Scale'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ Scale must be in range 0-10 when above max`, function () {
tx.PriceDataSeries[0].PriceData.Scale = 11
const errorMessage = 'OracleSet: Scale must be in range 0-10'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ Scale must be in range 0-10 when below min`, function () {
tx.PriceDataSeries[0].PriceData.Scale = -1
const errorMessage = 'OracleSet: Scale must be in range 0-10'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
})

View File

@@ -24,12 +24,15 @@ describe('SetHook', function () {
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
Flags: {
hsfOverride: true,
},
HookCanEmit:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
HookName: 'DEADBEEF',
},
},
],
@@ -64,6 +67,10 @@ describe('SetHook', function () {
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnIncoming:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnOutgoing:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
@@ -91,30 +98,31 @@ describe('SetHook', function () {
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it(`throws w/ invalid HookOn in Hooks`, function () {
setHookTx.SignerQuorum = 2
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
it.each(['HookOn', 'HookOnIncoming', 'HookOnOutgoing'])(
`throws w/ invalid %s in Hooks`,
function (field: string) {
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
[field]: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
},
},
},
]
const errorMessage =
'SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value'
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
]
const errorMessage = `SetHook: ${field} in Hook must be a 256-bit (32-byte) hexadecimal value`
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
},
)
it(`throws w/ invalid HookCanEmit in Hooks`, function () {
setHookTx.Hooks = [
@@ -166,4 +174,25 @@ describe('SetHook', function () {
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it.each(['', '0'.repeat(7), '0'.repeat(33), 'ZZZZZZZZ'])(
`throws w/ invalid HookName in Hooks: %s`,
function (value: string) {
setHookTx.Hooks = [
{
Hook: {
HookName: value,
},
},
]
const errorMessage =
'SetHook: HookName in Hook must be a hex string of 8-32 hex characters'
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
},
)
})

View File

@@ -22,6 +22,11 @@ describe('TrustSet', function () {
},
QualityIn: 1234,
QualityOut: 4321,
// an example of deep-frozen trustline
Flags: {
tfSetFreeze: true,
tfSetDeepFreeze: true,
},
} as any
})

View File

@@ -11,7 +11,17 @@ import {
PaymentFlags,
TrustSet,
TrustSetFlags,
Remit,
convertStringToHex,
SetHook,
URITokenMint,
URITokenMintFlags,
ClaimReward,
ClaimRewardFlags,
CronSet,
CronSetFlags,
} from '../../src'
import { HookFlags, MintURITokenFlags } from '../../src/models/common/xahau'
import { AccountRootFlags } from '../../src/models/ledger'
import { isFlagEnabled } from '../../src/models/utils'
import {
@@ -141,6 +151,94 @@ describe('Models Utils', function () {
assert.strictEqual(tx.Flags, expected)
})
it('sets URITokenMintFlags to its numeric value', function () {
const tx: URITokenMint = {
TransactionType: 'URITokenMint',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
URI: convertStringToHex('https://xahau.network'),
Flags: {
tfBurnable: true,
},
}
const { tfBurnable } = URITokenMintFlags
const expected: number = tfBurnable
setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
})
it('sets ClaimRewardFlags to its numeric value', function () {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Flags: {
tfOptOut: true,
},
}
const { tfOptOut } = ClaimRewardFlags
const expected: number = tfOptOut
setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
})
it('sets CronSetFlags to its numeric value', function () {
const tx: CronSet = {
TransactionType: 'CronSet',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Flags: {
tfCronUnset: true,
},
}
const { tfCronUnset } = CronSetFlags
const expected: number = tfCronUnset
setTransactionFlagsToNumber(tx)
assert.strictEqual(tx.Flags, expected)
})
it('sets MintURITokenFlags in Remit Transaction to its numeric value', function () {
const tx: Remit = {
TransactionType: 'Remit',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Destination: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
MintURIToken: {
URI: convertStringToHex('https://xahau.network'),
Flags: {
tfBurnable: true,
},
},
}
setTransactionFlagsToNumber(tx)
const expected = MintURITokenFlags.tfBurnable
assert.strictEqual(tx.MintURIToken?.Flags, expected)
})
it('sets HookFlags in SetHook Transaction to its numeric value', function () {
const tx: SetHook = {
TransactionType: 'SetHook',
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
Hooks: [
{
Hook: {
// invalid flags but for testing purposes
Flags: {
hsfCollect: true,
hsfNSDelete: true,
hsfOverride: true,
},
},
},
],
}
setTransactionFlagsToNumber(tx)
const expected =
HookFlags.hsfCollect | HookFlags.hsfNSDelete | HookFlags.hsfOverride
assert.strictEqual(tx.Hooks[0].Hook.Flags, expected)
})
it('sets other transaction types flags to its numeric value', function () {
const tx: DepositPreauth = {
TransactionType: 'DepositPreauth',
@@ -168,7 +266,8 @@ describe('Models Utils', function () {
AccountRootFlags.lsfDisallowIncomingCheck |
AccountRootFlags.lsfDisallowIncomingPayChan |
AccountRootFlags.lsfDisallowIncomingTrustline |
AccountRootFlags.lsfDisallowIncomingRemit
AccountRootFlags.lsfDisallowIncomingRemit |
AccountRootFlags.lsfAllowTrustLineClawback
const parsed = parseAccountRootFlags(accountRootFlags)
@@ -186,7 +285,8 @@ describe('Models Utils', function () {
parsed.lsfDisallowIncomingCheck &&
parsed.lsfDisallowIncomingPayChan &&
parsed.lsfDisallowIncomingTrustline &&
parsed.lsfDisallowIncomingRemit,
parsed.lsfDisallowIncomingRemit &&
parsed.lsfAllowTrustLineClawback,
)
})
@@ -207,6 +307,7 @@ describe('Models Utils', function () {
assert.isUndefined(parsed.lsfDisallowIncomingPayChan)
assert.isUndefined(parsed.lsfDisallowIncomingTrustline)
assert.isUndefined(parsed.lsfDisallowIncomingRemit)
assert.isUndefined(parsed.lsfAllowTrustLineClawback)
})
it('parseTransactionFlags all enabled', function () {

View File

@@ -20,6 +20,7 @@ import {
hashAccountRoot,
hashOfferId,
hashSignerListId,
hashCron,
} from '../../src/utils/hashes'
import fixtures from '../fixtures/xahaud'
import { assertResultMatch } from '../testUtils'
@@ -148,6 +149,15 @@ describe('Hashes', function () {
assert.equal(actualEntryHash, expectedEntryHash)
})
it('calcCronEntryHash', function () {
const owner = 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn'
const time = 30758410
const expectedEntryHash =
'F7B645436187CC6101D5560AF1127C15262825333ADC45B3155918D98149BD3F'
const actualEntryHash = hashCron(owner, time)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('Hash a signed transaction correctly', function () {
const expected_hash =
'458101D51051230B1D56E9ACAFAA34451BF65FA000F95DF6F0FF5B3A62D83FC2'

View File

@@ -48,7 +48,7 @@ module.exports = {
const localConfig = merge(config, {
mode: "production",
output: {
filename: `${filename}-latest.min.js`,
filename: `${filename}-latest-min.js`,
},
});