Compare commits

..

75 Commits

Author SHA1 Message Date
Oliver Eggert
508a39908c add manage credentials tutorial with java sample 2026-04-24 19:19:12 -07:00
Oliver Eggert
8c68d4a7ad update readme file 2026-04-23 18:10:53 -07:00
Oliver Eggert
4e7d0aadc9 add requireSuccess helper and finalize script steps 2026-04-23 01:08:59 -07:00
Oliver Eggert
bbe80b34c9 update helpers for async and sequential error handling/calls 2026-04-22 18:43:44 -07:00
Oliver Eggert
3152430e47 improve error handling and output 2026-04-22 17:23:29 -07:00
Oliver Eggert
81279b4761 more helper cleanup 2026-04-22 16:27:07 -07:00
Oliver Eggert
aaa4668392 update helpers 2026-04-21 21:59:45 -07:00
Oliver Eggert
583b169680 update code sample to use up-to-date functions and code conventions 2026-04-18 13:53:00 -07:00
Oliver Eggert
1241a33a5d restructure for java conventions 2026-04-18 09:56:08 -07:00
Oliver Eggert
d0f6d04715 add logback.xml 2026-04-18 08:49:04 -07:00
Oliver Eggert
f8d7ca470d add java target folder to gitignore 2026-04-18 08:43:36 -07:00
Oliver Eggert
d0187414a5 initial code sample and file structure 2026-04-17 20:16:40 -07:00
oeggert
fed058fe51 Merge pull request #3574 from XRPLF/release-notes-skill
Claude Code Release Notes Skill
2026-04-16 14:44:49 -07:00
Rome Reginelli
c3e898c047 Merge pull request #3555 from XRPLF/dependabot/pip/_code-samples/build-a-desktop-wallet/py/requests-2.33.0
Bump requests from 2.32.4 to 2.33.0 in /_code-samples/build-a-desktop-wallet/py
2026-04-15 17:26:04 -07:00
Rome Reginelli
98db42f996 Merge pull request #3504 from zgrguric/patch-8
LoanBroker LedgerEntry - Change DebtMaximum field from required to optional
2026-04-15 14:41:37 -07:00
Rome Reginelli
3f551f68e3 Merge pull request #3535 from XRPLF/dependabot/go_modules/_code-samples/assign-regular-key/go/golang.org/x/crypto-0.45.0
Bump golang.org/x/crypto from 0.35.0 to 0.45.0 in /_code-samples/assign-regular-key/go
2026-04-15 14:21:06 -07:00
mDuo13
4c5f65ff54 Add README for Assign Regular Key (Go) 2026-04-15 14:20:26 -07:00
Maria Shodunke
2fd46e197b Update amendments in development table (#3607) 2026-04-14 17:20:53 +01:00
Maria Shodunke
cf92ef36ae Merge pull request #3597 from gememerald8/patch-1
Fix Gem Wallet url
2026-04-14 12:44:53 +01:00
oeggert
7b42cbb02a Merge pull request #3600 from XRPLF/update-ai-config
Update AI config on site
2026-04-13 11:28:31 -07:00
Oliver Eggert
0e4ae322f7 update config to include ai search and exclude japanese/spanish from mcp server results 2026-04-11 00:08:58 -07:00
Oliver Eggert
5e500d58ca add release notes section to contribute blog page 2026-04-10 21:32:37 -07:00
Oliver Eggert
cb48d4f789 add optional --output arg to skill file 2026-04-10 21:31:16 -07:00
Max Costa
02b275e157 Fix Gem Wallet url
Gem Wallet is an open-source, self-custody mobile wallet for iOS and Android that supports XRP and XRPL tokens.
https://gemwallet.com/xrp-wallet/

The official website of the wallet is Gemwallet.com
2026-04-10 12:52:58 +03:00
oeggert
06b92f44d6 Merge pull request #3579 from XRPLF/token-escrow
Add tutorial for fungible token escrows.
2026-04-09 12:34:49 -07:00
Oliver Eggert
31ae9f6a00 add reviewer suggestions 2026-04-09 11:16:47 -07:00
Bart
7f907dd168 Update Transaction Set Handling blog post with additional credits
The reporter asked to give credits to additional members of their team, and to add a hyperlink to their company website.
2026-04-09 13:26:24 -04:00
Rome Reginelli
e45149a6c7 Merge pull request #3583 from XRPLF/rr-fix-3570
Fix redundant text in CredentialCreate ref
2026-04-09 09:52:45 -07:00
Bart
1ca0c3371b Update Transaction Set Handling blog post with additional acknowledgements
The reporter asks us to give credits to additional members of their team, and to add a hyperlink to their company website.
2026-04-09 12:26:13 -04:00
rachelflynn
6673e6e0fe Merge pull request #3587 from rachelflynn/fix-calculate-reserves-snippet-rendering
Fix JS and Go code samples rendering issue in calculate account reserves tutorial
2026-04-08 13:07:48 -04:00
rachelflynn
4e1ea13709 Fix JS and Go code samples rendering issue 2026-04-08 09:54:01 -04:00
rachelflynn
61529895af Merge pull request #3573 from rachelflynn/add-calculate-reserves-tutorial
Add calculate reserves tutorial
2026-04-07 14:13:24 -04:00
Rome Reginelli
f31b3e7ca4 Fix redundant text in CredentialCreate ref 2026-04-07 10:03:10 -07:00
rachelflynn
6a5ce20028 Address PR review feedback: adding newlines, text edits, and bumping Python version 2026-04-07 10:54:39 -04:00
Rome Reginelli
7e6234d9cf Merge pull request #3577 from XRPLF/events-updates-030326
Adding new events per xrpl commons requests
2026-04-06 12:13:33 -07:00
Oliver Eggert
601e79ed00 add send fungible token escrows tutorial 2026-04-03 16:51:50 -07:00
Oliver Eggert
e05aa8259b fix comments for code-snippet references 2026-04-03 16:03:05 -07:00
Calvin Jhunjhuwala
b058513278 adding new events per xrpl commons requests 2026-04-03 15:10:39 -07:00
Oliver Eggert
b7be1f878e remove numbered steps from code comments 2026-04-03 13:31:12 -07:00
Oliver Eggert
ac60d7786b add go code sample and readme 2026-04-03 12:47:06 -07:00
Oliver Eggert
394eb4b5d4 add intro comment to code sample files 2026-04-03 12:45:18 -07:00
Oliver Eggert
cd4bb02ae2 update js and py readmes to explain what script does 2026-04-03 12:44:28 -07:00
Oliver Eggert
a309eb51c5 add send_fungible_token_escrow.py and update readme.md 2026-04-03 00:06:17 -07:00
Oliver Eggert
578e8cdefc clean up variable names 2026-04-02 22:48:35 -07:00
Oliver Eggert
c5b161c746 update cancelafter and finishafter to use same now() time 2026-04-02 22:23:40 -07:00
Oliver Eggert
f493ca49cf add token escrow js code sample 2026-04-02 21:15:10 -07:00
Amarantha Kulkarni
5f47643585 Merge pull request #3575 from XRPLF/fix-websocket-tool-error
Verified preview build. merging to fix issue on site.
2026-04-02 20:07:11 -07:00
amarantha-k
7036a75881 Update npm registry 2026-04-02 15:48:08 -07:00
Amarantha Kulkarni
57fde744fd Update package.json
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-02 13:04:44 -07:00
amarantha-k
91b68bae6a Deduped and updated to use recent version of @codemirror/state 2026-04-02 12:25:05 -07:00
Oliver Eggert
b0e99161bb clean up claude code files 2026-04-01 16:38:17 -07:00
Oliver Eggert
a441171000 improve amendment sorting logic, frontmatter and intro descriptions, and bug disclosure text 2026-04-01 15:14:22 -07:00
Oliver Eggert
892714550e more improvements to sorting script and AI instructions 2026-04-01 13:19:12 -07:00
Oliver Eggert
b6388ccb13 improve skills md edit vs write and not chunk context 2026-03-31 18:18:01 -07:00
Oliver Eggert
6ab5de13bb fix bug when passing different date and output years in args 2026-03-28 00:18:10 -07:00
Oliver Eggert
38000f19d6 fix sidebars.yaml entry logic 2026-03-27 23:35:06 -07:00
Oliver Eggert
ad9e5e14fa improve skill.md to prevent losing content 2026-03-27 22:20:46 -07:00
Oliver Eggert
663cd6df6a add skills.md and writing release notes to sidebars.yaml 2026-03-27 21:45:34 -07:00
Oliver Eggert
6bee1983eb append fix to fix amendment names 2026-03-27 20:37:40 -07:00
Oliver Eggert
9df53455e9 add amendment diff fetching for additional sorting context 2026-03-27 20:09:36 -07:00
Oliver Eggert
13dddb8b22 add basic amendment sorting 2026-03-27 12:11:39 -07:00
Oliver Eggert
b47c96d91a update logic for generating entries on commit-only entries and entries with broken PR/Issue links 2026-03-27 11:48:04 -07:00
Oliver Eggert
93abc4dc09 remove sys exit for failed file lookups, and minor cleanup to comments and section header location 2026-03-26 18:30:14 -07:00
Oliver Eggert
ae266aba7f add files for entry context. include commits linked to issues and commits without any links 2026-03-26 18:08:18 -07:00
Oliver Eggert
ab9eec63f5 merge version fetching functions and clean up credits intro 2026-03-26 15:10:46 -07:00
Oliver Eggert
8b8ed4c6ea fix fetch_version_commit to only check buildinfo.cpp 2026-03-26 14:48:30 -07:00
Oliver Eggert
6aaca7f568 ignore ripple users in credits and improve graphql handling of issues vs pulls 2026-03-26 14:07:40 -07:00
Oliver Eggert
a045e9e40c clean up functions 2026-03-25 22:22:40 -07:00
Oliver Eggert
ad9327c4c0 add comments and improve ordering of helpers 2026-03-25 15:05:35 -07:00
Oliver Eggert
53983bf8e2 update to use graphql and remove categorization logic 2026-03-25 14:47:00 -07:00
dependabot[bot]
aa3b5e173c Bump requests in /_code-samples/build-a-desktop-wallet/py
Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 17:18:04 +00:00
Oliver Eggert
4bceb09b1b initial draft of release notes script 2026-03-24 19:06:53 -07:00
Oliver Eggert
0da3a1e13c scaffold claude specific tools and generic agents.md 2026-03-24 12:17:26 -07:00
dependabot[bot]
5d2e8f5f98 Bump golang.org/x/crypto in /_code-samples/assign-regular-key/go
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.35.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.35.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-04 04:43:40 +00:00
zgrguric
d4726e0816 Change DebtMaximum field from required to optional 2026-02-18 15:17:57 +01:00
46 changed files with 3748 additions and 174 deletions

19
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,19 @@
# XRPL Dev Portal — Claude Code Instructions
## Quick Reference
- **Framework:** Redocly Realm
- **Production branch:** `master`
- **Local preview:** `npm start`
## Localization
- Default: `en-US`
- Japanese: `ja`
- Translations mirror `docs/` structure under `@l10n/<language-code>/`
## Navigation
- Update `sidebars.yaml` when adding new doc pages
- Blog posts have a separate `blog/sidebars.yaml`
- Redirects go in `redirects.yaml`

7
.claude/settings.json Normal file
View File

@@ -0,0 +1,7 @@
{
"permissions": {
"deny": [
"Bash(git push *)"
]
}
}

View File

@@ -0,0 +1,117 @@
---
name: generate-release-notes
description: Generate and sort rippled release notes from GitHub commit history
argument-hint: --from <ref> --to <ref> [--date YYYY-MM-DD] [--output <path>]
allowed-tools: Bash, Read, Edit, Write, Grep, Glob
effort: max
---
# Generate rippled Release Notes
This skill generates a draft release notes blog post for a new rippled version, then sorts the entries into the correct subsections.
## Execution constraints
- **Do NOT write scripts** to sort or process the file. Prefer the Edit tool for targeted changes. Use Write only when replacing large sections that are impractical to edit incrementally.
- **Output progress**: Before each major step (generating raw release notes, reviewing file, processing amendments, sorting entries, reformatting, cleanup), output a brief status message so the user can see progress.
## Step 1: Generate the raw release notes
Run the Python script from the repo root. Pass through all arguments from `$ARGUMENTS`:
```bash
python3 tools/generate-release-notes.py $ARGUMENTS
```
If the user didn't provide `--from` or `--to`, ask them for the base and target refs (tags or branches).
The script will:
- Fetch the version string from `BuildInfo.cpp`
- Fetch all commits between the two refs
- Fetch PR details (title, link, labels, files, description) via GraphQL
- Compare `features.macro` between refs to identify amendment changes
- Auto-sort amendment entries into the Amendments section
- Output all other entries as unsorted with full context
## Step 2: Review the generated file
Read the output file (path shown in script output). Note the **Full Changelog** structure:
- **Amendments section**: Contains auto-sorted entries and an HTML comment listing which amendments to include or remove
- **Empty subsections**: Features, Breaking Changes, Bug Fixes, Refactors, Documentation, Testing, CI/Build
- **Unsorted entries**: After the **Bug Bounties and Responsible Disclosures** section is an unsorted list of entries with title, link, labels, files, and description for context
## Step 3: Process amendments
Handle Amendments first, before sorting other entries.
**3a. Process the auto-sorted Amendments subsection:**
The HTML comment contains three lists — follow them exactly:
- **Include**: Keep these entries.
- **Exclude**: Remove these entries.
- Entries on **neither** list: Remove these entries.
**3b. Scan unsorted entries for unreleased amendment work:**
Search through ALL unsorted entries for titles, labels, descriptions, or files that reference amendments on the "Exclude" or "Other amendments not part of this release" lists. Remove entries that directly implement, enable, fix, or refactor these amendments. Keep entries that are general changes that merely reference the amendment as motivation — if the code change is useful on its own regardless of whether the amendment ships, keep it.
**3c. If you disagree with any amendment decisions, make a note to the user but do NOT deviate from the rules.**
## Step 4: Sort remaining unsorted entries into subsections
Move each remaining unsorted entry into the appropriate subsection.
Use these signals to categorize:
**Files changed** (strongest signal):
- Only `.github/`, `CMakeLists.txt`, `conan*`, CI config files → **CI/Build**
- Only `src/test/`, `*_test.cpp` files → **Testing**
- Only `*.md`, `docs/` files → **Documentation**
**Labels** (strong signal):
- `Bug` label → **Bug Fixes**
**Title prefixes** (medium signal):
- `fix:`**Bug Fixes**
- `feat:`**Features**
- `refactor:`**Refactors**
- `docs:`**Documentation**
- `test:`**Testing**
- `ci:`, `build:`, `chore:`**CI/Build**
**Description content** (when other signals are ambiguous):
- Read the PR description to understand the change's purpose
- PRs that change API behavior, remove features, or have "Breaking change" checked in their description → **Breaking Changes**
Additional sorting guidance:
- Watch for revert pairs: If a PR was committed and then reverted (or vice versa), check that the net effect is accounted for — don't include both.
## Step 5: Reformat sorted entries
After sorting, reformat each entry to match the release notes style.
**Amendment entries** should follow this format:
```markdown
- **amendmentName**: Description of what the amendment does. ([#1234](https://github.com/XRPLF/rippled/pull/1234))
```
- Use more detail for amendment descriptions since they are the most important. Use present tense.
- If there are multiple entries for the same amendment, merge into one, prioritizing the entry that describes the actual amendment.
**Feature and Breaking Change entries** should follow this format:
```markdown
- Description of the change. ([#1234](https://github.com/XRPLF/rippled/pull/1234))
```
- Keep the description concise. Use past tense.
**All other entries** should follow this format:
```markdown
- The PR title of the entry. ([#1234](https://github.com/XRPLF/rippled/pull/1234))
```
- Copy the PR title as-is. Only fix capitalization, remove conventional commit prefixes (fix:, feat:, ci:, refactor:, docs:, test:, chore:, build:), and adjust to past tense if needed. Do NOT rewrite, paraphrase, or summarize.
## Step 6: Clean up
- Add a short and generic description of changes to the existing `seo.description` frontmatter, e.g., "This version introduces new amendments and bug fixes." Do not create long lists of detailed changes.
- Add a more detailed summary of the release to the existing "Introducing XRP Ledger Version X.Y.Z" section. Include amendment names (organized in a list if more than 2), featuress, and breaking changes. Limit this to 1 paragraph.
- Do NOT delete the **Credits** or **Bug Bounties and Responsible Disclosures** sections
- Remove empty subsections that have no entries
- Remove all HTML comments (sorting instructions)
- Do a final review of the release notes. If you see anything strange, or were forced to take unintuitive actions by these instructions, notify the user, but don't make changes.

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ yarn-error.log
.venv/
_code-samples/*/js/package-lock.json
_code-samples/*/go/go.sum
_code-samples/*/java/target/
_code-samples/*/*/*[Ss]etup.json
# PHP

View File

@@ -0,0 +1,10 @@
# Assign a Regular Key (Go)
Demonstrates how to assign a regular key pair to an XRP Ledger account. Both WebSocket (`ws/`) and JSON-RPC (`rpc/`) examples are included.
Quick setup and usage:
```sh
go mod tidy
go run ./ws/main.go
```

View File

@@ -1,8 +1,6 @@
module github.com/XRPLF
go 1.23.0
toolchain go1.23.10
go 1.24.0
require github.com/Peersyst/xrpl-go v0.1.11
@@ -20,5 +18,5 @@ require (
github.com/tyler-smith/go-bip32 v1.0.0 // indirect
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/crypto v0.45.0 // indirect
)

View File

@@ -46,8 +46,8 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -1,4 +1,4 @@
xrpl-py>=3.0.0
wxPython==4.2.1
toml==0.10.2
requests==2.32.4
requests==2.33.0

View File

@@ -1,7 +1,5 @@
# Calculate Reserves
This code sample demonstrates how to look up and calculate an XRP Ledger account's reserve requirements.
Look up and calculate an account's reserve requirements.
See the [Calculate Account Reserves tutorial](https://xrpl.org/docs/tutorials/best-practices/account-management/calculate-reserves) for a detailed walkthrough.
Samples are provided in JavaScript, Python, and Go.

View File

@@ -3,6 +3,7 @@
This code sample uses [xrpl-go](https://github.com/Peersyst/xrpl-go) to look up and calculate an XRP Ledger account's reserve requirements.
## Usage
```bash
go mod tidy
go run calculate_reserves.go

View File

@@ -1,4 +1,6 @@
// Set up client ----------------------
package main
import (

View File

@@ -1,3 +1,4 @@
// Set up client ----------------------
import xrpl from 'xrpl'

View File

@@ -6,4 +6,4 @@
"dependencies": {
"xrpl": "^4.4.0"
}
}
}

View File

@@ -1 +1 @@
xrpl-py>=3.0.0
xrpl-py>=4.4.0

View File

@@ -0,0 +1,82 @@
# Credential Example (Java)
This directory contains a Java example demonstrating how to issue a credential, accept a credential, and delete a credential.
## Setup
Install dependencies before running any examples:
```sh
mvn install
```
---
## Manage Credentials
```sh
mvn exec:java -Dexec.mainClass=com.example.xrpl.ManageCredentials
```
The script should output two newly funded accounts, the CredentialCreate transaction, CredentialAccept transaction, and CredentialDelete transaction. Each successful transaction submission includes a link to the transaction metadata on the XRPL Explorer.
```sh
=== Funding issuer and subject accounts on Testnet ===
Issuer: r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL
Subject: rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y
=== Preparing CredentialCreate transaction ===
{
"Account" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
"TransactionType" : "CredentialCreate",
"Fee" : "15",
"Sequence" : 16795444,
"LastLedgerSequence" : 16795464,
"SigningPubKey" : "EDC2C03C393852514C40CCCCF34CB61A8DDB4AECC6C95271468DDF13DE0979DCC7",
"Subject" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
"CredentialType" : "6B79632D747261646572"
}
=== Submitting CredentialCreate transaction ===
CredentialCreate succeeded!
Explorer: https://testnet.xrpl.org/transactions/D7A00CFC8DFFE384F7A5D2DF14B3AC5629E8F8DBFD8BD06BC389363782F296B3
=== Preparing CredentialAccept transaction ===
{
"Account" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
"TransactionType" : "CredentialAccept",
"Fee" : "15",
"Sequence" : 16795444,
"LastLedgerSequence" : 16795466,
"SigningPubKey" : "EDBED812587E0D7D9F965EFE63F4F2B2BB2EB559AD7D1FA9250C239C235CE62726",
"Issuer" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
"CredentialType" : "6B79632D747261646572"
}
=== Submitting CredentialAccept transaction ===
CredentialAccept succeeded!
Explorer: https://testnet.xrpl.org/transactions/C9E55B0A5270FEB37C18E710BDB01C46480530673FE8E4FC39FE3D6B036DF8F5
=== Preparing CredentialDelete transaction ===
{
"Account" : "rs8vtTf3aLZW7XRCh398SUkuuAWBn7q49y",
"TransactionType" : "CredentialDelete",
"Fee" : "15",
"Sequence" : 16795445,
"LastLedgerSequence" : 16795468,
"SigningPubKey" : "EDBED812587E0D7D9F965EFE63F4F2B2BB2EB559AD7D1FA9250C239C235CE62726",
"Issuer" : "r446kRqJA1XGo1zGMiC2RAebtWbQa4duHL",
"CredentialType" : "6B79632D747261646572"
}
=== Submitting CredentialDelete transaction ===
CredentialDelete succeeded!
Explorer: https://testnet.xrpl.org/transactions/0755B4FED0A646D5FB3698891D25DC0374C521DEF00D85D8FCFF58EB09CAB4FE
```

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>credential-samples</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.3.0</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.xrpl</groupId>
<artifactId>xrpl4j-client</artifactId>
<version>6.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,292 @@
package com.example.xrpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.primitives.UnsignedInteger;
import okhttp3.HttpUrl;
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
import org.xrpl.xrpl4j.client.XrplClient;
import org.xrpl.xrpl4j.client.faucet.FaucetClient;
import org.xrpl.xrpl4j.client.faucet.FundAccountRequest;
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
import org.xrpl.xrpl4j.crypto.keys.PrivateKey;
import org.xrpl.xrpl4j.crypto.keys.Seed;
import org.xrpl.xrpl4j.crypto.signing.SignatureService;
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
import org.xrpl.xrpl4j.crypto.signing.bc.BcSignatureService;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequestParams;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
import org.xrpl.xrpl4j.model.client.common.LedgerSpecifier;
import org.xrpl.xrpl4j.model.client.fees.FeeUtils;
import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams;
import org.xrpl.xrpl4j.model.client.transactions.SubmitResult;
import org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams;
import org.xrpl.xrpl4j.model.client.transactions.TransactionResult;
import org.xrpl.xrpl4j.model.jackson.ObjectMapperFactory;
import org.xrpl.xrpl4j.model.transactions.Address;
import org.xrpl.xrpl4j.model.transactions.CredentialAccept;
import org.xrpl.xrpl4j.model.transactions.CredentialCreate;
import org.xrpl.xrpl4j.model.transactions.CredentialDelete;
import org.xrpl.xrpl4j.model.transactions.CredentialType;
import org.xrpl.xrpl4j.model.transactions.Transaction;
import org.xrpl.xrpl4j.model.transactions.TransactionResultCodes;
import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* This code sample demonstrates the Credential lifecycle on the XRPL.
* It issues a credential to a subject, accepts the credential, and then deletes it.
*/
public class ManageCredentials {
private static final HttpUrl NETWORK_URL = HttpUrl.get("https://s.altnet.rippletest.net:51234/");
private static final HttpUrl FAUCET_URL = HttpUrl.get("https://faucet.altnet.rippletest.net");
private static final String EXPLORER_BASE = "https://testnet.xrpl.org/transactions/";
private static final CredentialType CREDENTIAL_TYPE = CredentialType.ofPlainText("kyc-trader");
public static void main(String[] args) {
try {
run();
} catch (Exception e) {
// Unwrap CompletionException so async failures print the same clean message
// as sync failures. CompletableFuture.join() wraps exceptions in CompletionException
Throwable cause = (e instanceof CompletionException && e.getCause() != null)
? e.getCause() : e;
System.err.println("Error: " + cause.getMessage());
System.exit(1);
}
}
private static void run() {
// ----- Connect to Testnet and fund accounts -----
XrplClient xrplClient = new XrplClient(NETWORK_URL);
System.out.println("\n=== Funding issuer and subject accounts on Testnet ===\n");
CompletableFuture<KeyPair> issuerFuture = CompletableFuture.supplyAsync(
() -> createAndFundWallet(xrplClient));
CompletableFuture<KeyPair> subjectFuture = CompletableFuture.supplyAsync(
() -> createAndFundWallet(xrplClient));
CompletableFuture.allOf(issuerFuture, subjectFuture).join();
KeyPair issuer = issuerFuture.join();
KeyPair subject = subjectFuture.join();
Address issuerAddress = issuer.publicKey().deriveAddress();
Address subjectAddress = subject.publicKey().deriveAddress();
System.out.println("Issuer: " + issuerAddress);
System.out.println("Subject: " + subjectAddress);
// ----- Prepare CredentialCreate transaction -----
System.out.println("\n=== Preparing CredentialCreate transaction ===\n");
CredentialCreate createTx = CredentialCreate.builder()
.account(issuerAddress)
.subject(subjectAddress)
.credentialType(CREDENTIAL_TYPE)
.sequence(accountSequence(xrplClient, issuerAddress))
.fee(recommendedFee(xrplClient))
.lastLedgerSequence(lastLedgerSequence(xrplClient))
.signingPublicKey(issuer.publicKey())
.build();
printTransactionJson(createTx);
// ----- Sign, submit, and wait for CredentialCreate validation -----
System.out.println("\n=== Submitting CredentialCreate transaction ===\n");
TransactionResult<CredentialCreate> createResult = signSubmitAndWait(
xrplClient, issuer, createTx, CredentialCreate.class);
requireSuccess(createResult);
// ----- Prepare CredentialAccept transaction -----
System.out.println("\n=== Preparing CredentialAccept transaction ===\n");
CredentialAccept acceptTx = CredentialAccept.builder()
.account(subjectAddress)
.issuer(issuerAddress)
.credentialType(CREDENTIAL_TYPE)
.sequence(accountSequence(xrplClient, subjectAddress))
.fee(recommendedFee(xrplClient))
.lastLedgerSequence(lastLedgerSequence(xrplClient))
.signingPublicKey(subject.publicKey())
.build();
printTransactionJson(acceptTx);
// ----- Sign, Submit, and wait for CredentialAccept validation -----
System.out.println("\n=== Submitting CredentialAccept transaction ===\n");
TransactionResult<CredentialAccept> acceptResult = signSubmitAndWait(
xrplClient, subject, acceptTx, CredentialAccept.class);
requireSuccess(acceptResult);
// ----- Prepare CredentialDelete transaction -----
System.out.println("\n=== Preparing CredentialDelete transaction ===\n");
CredentialDelete deleteTx = CredentialDelete.builder()
.account(subjectAddress)
.issuer(issuerAddress)
.credentialType(CREDENTIAL_TYPE)
.sequence(accountSequence(xrplClient, subjectAddress))
.fee(recommendedFee(xrplClient))
.lastLedgerSequence(lastLedgerSequence(xrplClient))
.signingPublicKey(subject.publicKey())
.build();
printTransactionJson(deleteTx);
// ----- Sign, Submit, and wait for CredentialDelete validation -----
System.out.println("\n=== Submitting CredentialDelete transaction ===\n");
TransactionResult<CredentialDelete> deleteResult = signSubmitAndWait(
xrplClient, subject, deleteTx, CredentialDelete.class);
requireSuccess(deleteResult);
}
// ===== Helper functions =====
// Generates a new Ed25519 keypair, funds it from the Testnet faucet, and
// returns the keypair once the account is visible on a validated ledger.
private static KeyPair createAndFundWallet(XrplClient xrplClient) {
KeyPair keyPair = Seed.ed25519Seed().deriveKeyPair();
Address address = keyPair.publicKey().deriveAddress();
FaucetClient faucetClient = FaucetClient.construct(FAUCET_URL);
faucetClient.fundAccount(FundAccountRequest.of(address));
for (int attempt = 0; attempt < 20; attempt++) {
try {
xrplClient.accountInfo(AccountInfoRequestParams.builder()
.account(address)
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build());
return keyPair;
} catch (JsonRpcClientErrorException notYetVisible) {
try {
Thread.sleep(1_000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Account polling interrupted for " + address + ". " + e.getMessage(), e);
}
}
}
throw new IllegalStateException("Faucet funding for " + address + " did not confirm in time.");
}
// Fetches the next transaction sequence number of an address from
// the latest validated ledger.
private static UnsignedInteger accountSequence(XrplClient xrplClient, Address address) {
try {
AccountInfoResult info = xrplClient.accountInfo(AccountInfoRequestParams.builder()
.account(address)
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build());
return info.accountData().sequence();
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to fetch account sequence for " + address + ". " + e.getMessage(), e);
}
}
// Fetches the current network fee and returns the recommended fee for
// a standard (non-multisig, non-batch) transaction.
private static XrpCurrencyAmount recommendedFee(XrplClient xrplClient) {
try {
return FeeUtils.computeNetworkFees(xrplClient.fee()).recommendedFee();
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to fetch network fee. " + e.getMessage(), e);
}
}
// Computes a safe LastLedgerSequence for a new transaction. The
// latest validated ledger index plus a small buffer (20 ledgers).
private static UnsignedInteger lastLedgerSequence(XrplClient xrplClient) {
try {
UnsignedInteger validatedLedger = xrplClient.ledger(LedgerRequestParams.builder()
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build())
.ledgerIndexSafe()
.unsignedIntegerValue();
return validatedLedger.plus(UnsignedInteger.valueOf(20));
} catch (JsonRpcClientErrorException e) {
throw new RuntimeException("Failed to compute LastLedgerSequence. " + e.getMessage(), e);
}
}
// Prints a transaction as a formatted JSON.
private static void printTransactionJson(Transaction tx) {
try {
System.out.println(ObjectMapperFactory.create().writerWithDefaultPrettyPrinter().writeValueAsString(tx));
} catch (JsonProcessingException e) {
throw new RuntimeException("Failed to serialize transaction JSON. " + e.getMessage(), e);
}
}
// Signs and submits a transaction, then polls the network until
// the transaction reaches a validated state.
private static <T extends Transaction> TransactionResult<T> signSubmitAndWait(
XrplClient xrplClient,
KeyPair signer,
T transaction,
Class<T> transactionType
) {
SignatureService<PrivateKey> signatureService = new BcSignatureService();
UnsignedInteger lastLedgerSequence = transaction.lastLedgerSequence()
.orElseThrow(() -> new IllegalArgumentException(
"Must set LastLedgerSequence for polling expiration"));
try {
SingleSignedTransaction<T> signed = signatureService.sign(signer.privateKey(), transaction);
SubmitResult<T> submit = xrplClient.submit(signed);
if (!TransactionResultCodes.TES_SUCCESS.equals(submit.engineResult())) {
throw new IllegalStateException(
"Submission rejected. " + submit.engineResult() + "" + submit.engineResultMessage());
}
while (true) {
Thread.sleep(1_000L);
// Poll network for validated status using tx hash
try {
TransactionResult<T> result = xrplClient.transaction(
TransactionRequestParams.of(signed.hash()), transactionType);
if (result.validated()) {
return result;
}
} catch (JsonRpcClientErrorException e) {
// Transaction not found; keep polling.
}
// Check if transaction expired before polling again
UnsignedInteger currentLedger = xrplClient.ledger(LedgerRequestParams.builder()
.ledgerSpecifier(LedgerSpecifier.VALIDATED)
.build())
.ledgerIndexSafe()
.unsignedIntegerValue();
if (currentLedger.compareTo(lastLedgerSequence) > 0) {
throw new IllegalStateException("Transaction expired. Current ledger " + currentLedger
+ " passed LastLedgerSequence " + lastLedgerSequence);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Transaction polling interrupted. " + e.getMessage(), e);
} catch (JsonRpcClientErrorException | JsonProcessingException e) {
throw new RuntimeException("Transaction processing failed. " + e.getMessage(), e);
}
}
// Checks for a tesSUCCESS result code. If true, prints an explorer
// link. Otherwise, throws an error.
private static void requireSuccess(TransactionResult<?> result) {
String code = result.metadata().get().transactionResult();
String txType = result.transaction().transactionType().value();
if (!TransactionResultCodes.TES_SUCCESS.equals(code)) {
throw new IllegalStateException(txType + " failed with error code " + code);
}
System.out.println(txType + " succeeded!");
System.out.println("Explorer: " + EXPLORER_BASE + result.hash());
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Quiets xrpl4j's DEBUG chatter so tutorial output stays readable.
Raise xrpl4j to DEBUG to see wire-level transaction details. -->
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.xrpl.xrpl4j" level="WARN"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -0,0 +1,184 @@
# Escrow (Go)
This directory contains Go examples demonstrating how to create, finish, and cancel escrows on the XRP Ledger.
## Setup
All commands should be run from this `go/` directory.
Install dependencies before running any examples:
```sh
go mod tidy
```
---
## Send Fungible Token Escrow
```sh
go run ./send-fungible-token-escrow
```
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
```sh
=== Funding Accounts ===
Funding Issuer account...
Funding Escrow Creator account...
Issuer: rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD
Escrow Creator: rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk
=== Creating MPT ===
{
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"Flags": 8,
"MaximumAmount": "1000000",
"TransactionType": "MPTokenIssuanceCreate"
}
Submitting MPTokenIssuanceCreate transaction...
MPT created: 00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B
=== Escrow Creator Authorizing MPT ===
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"MPTokenIssuanceID": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
"TransactionType": "MPTokenAuthorize"
}
Submitting MPTokenAuthorize transaction...
Escrow Creator authorized for MPT.
=== Issuer Sending MPTs to Escrow Creator ===
{
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"Amount": {
"mpt_issuance_id": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
"value": "5000"
},
"Destination": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"TransactionType": "Payment"
}
Submitting MPT Payment transaction...
Successfully sent 5000 MPTs to Escrow Creator.
=== Creating Conditional MPT Escrow ===
Condition: A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120
Fulfillment: A0228020F16E8A8697ABAE14C60A5D812A2D228F9E6F67B8CA4818DC80BBF539004490DB
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"Amount": {
"mpt_issuance_id": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
"value": "1000"
},
"CancelAfter": 828559916,
"Condition": "A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120",
"Destination": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"TransactionType": "EscrowCreate"
}
Submitting MPT EscrowCreate transaction...
Conditional MPT escrow created. Sequence: 16230848
=== Finishing Conditional MPT Escrow ===
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"Condition": "A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120",
"Fulfillment": "A0228020F16E8A8697ABAE14C60A5D812A2D228F9E6F67B8CA4818DC80BBF539004490DB",
"OfferSequence": 16230848,
"Owner": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"TransactionType": "EscrowFinish"
}
Submitting EscrowFinish transaction...
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/37CD7FECDC71CE70C24927969AD0FDAD55F57F2905A6F62867CB4F5AB2EE27BB
=== Enabling Trust Line Token Escrows on Issuer ===
{
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"SetFlag": 17,
"TransactionType": "AccountSet"
}
Submitting AccountSet transaction...
Trust line token escrows enabled by issuer.
=== Setting Up Trust Line ===
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"LimitAmount": {
"currency": "IOU",
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"value": "10000000"
},
"TransactionType": "TrustSet"
}
Submitting TrustSet transaction...
Trust line successfully created for "IOU" tokens.
=== Issuer Sending IOU Tokens to Escrow Creator ===
{
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"Amount": {
"currency": "IOU",
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"value": "5000"
},
"Destination": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"TransactionType": "Payment"
}
Submitting Trust Line Token payment transaction...
Successfully sent 5000 IOU tokens.
=== Creating Timed Trust Line Token Escrow ===
Escrow will mature after: 04/03/2026, 12:27:38 PM
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"Amount": {
"currency": "IOU",
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"value": "1000"
},
"CancelAfter": 828559948,
"Destination": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
"FinishAfter": 828559658,
"TransactionType": "EscrowCreate"
}
Submitting Trust Line Token EscrowCreate transaction...
Trust Line Token escrow created. Sequence: 16230851
=== Waiting For Timed Trust Line Token Escrow to Mature ===
Waiting for escrow to mature... done.
Latest validated ledger closed at: 04/03/2026, 12:27:41 PM
Escrow confirmed ready to finish.
=== Finishing Timed Trust Line Token Escrow ===
{
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"OfferSequence": 16230851,
"Owner": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
"TransactionType": "EscrowFinish"
}
Submitting EscrowFinish transaction...
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/9D1937BE3ADFC42078F222B1DBAE8571BBC096DDA7A47911C4715221C83EC22D
```

View File

@@ -0,0 +1,23 @@
module github.com/XRPLF
go 1.24.3
require (
github.com/Peersyst/xrpl-go v0.1.17
github.com/go-interledger/cryptoconditions v0.0.0-20180612102545-aba58e59cef1
)
require (
github.com/PromonLogicalis/asn1 v0.0.0-20190312173541-d60463189a56 // indirect
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/kalaspuffar/base64url v0.0.0-20171121144659-483af17b794c // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stevenroose/asn1 v0.0.0-20170613173945-a0d410e3f79f // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/crypto v0.44.0 // indirect
)

View File

@@ -0,0 +1,461 @@
// This example demonstrates how to create escrows that hold fungible tokens.
// It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
package main
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/Peersyst/xrpl-go/pkg/crypto"
"github.com/Peersyst/xrpl-go/xrpl/faucet"
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
ledgerreq "github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
"github.com/Peersyst/xrpl-go/xrpl/transaction"
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
"github.com/Peersyst/xrpl-go/xrpl/wallet"
"github.com/Peersyst/xrpl-go/xrpl/websocket"
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
"github.com/go-interledger/cryptoconditions"
)
func main() {
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.altnet.rippletest.net:51233").
WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Fund an issuer account and an escrow creator account ----------------------
fmt.Printf("\n=== Funding Accounts ===\n\n")
createAndFund := func(label string) wallet.Wallet {
fmt.Printf("Funding %s account...\n", label)
w, err := wallet.New(crypto.ED25519())
if err != nil {
panic(err)
}
if err := client.FundWallet(&w); err != nil {
panic(err)
}
// Poll until account is validated on ledger
funded := false
for range 20 {
_, err := client.Request(&account.InfoRequest{
Account: w.GetAddress(),
LedgerIndex: common.Validated,
})
if err == nil {
funded = true
break
}
time.Sleep(time.Second)
}
if !funded {
panic("Issue funding account: " + w.GetAddress().String())
}
return w
}
issuer := createAndFund("Issuer")
creator := createAndFund("Escrow Creator")
fmt.Printf("Issuer: %s\n", issuer.ClassicAddress)
fmt.Printf("Escrow Creator: %s\n", creator.ClassicAddress)
// ====== Conditional MPT Escrow ======
// Issuer creates an MPT ----------------------
fmt.Printf("\n=== Creating MPT ===\n\n")
maxAmount := types.XRPCurrencyAmount(1000000)
mptCreateTx := transaction.MPTokenIssuanceCreate{
BaseTx: transaction.BaseTx{
Account: issuer.ClassicAddress,
Flags: transaction.TfMPTCanEscrow,
},
MaximumAmount: &maxAmount,
}
// Flatten() converts the struct to a map and adds the TransactionType field
flatMptCreateTx := mptCreateTx.Flatten()
mptCreateTxJSON, _ := json.MarshalIndent(flatMptCreateTx, "", " ")
fmt.Printf("%s\n", string(mptCreateTxJSON))
// Submit, sign, and wait for validation
fmt.Printf("\nSubmitting MPTokenIssuanceCreate transaction...\n")
mptCreateResponse, err := client.SubmitTxAndWait(flatMptCreateTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &issuer,
})
if err != nil {
panic(err)
}
if mptCreateResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("MPTokenIssuanceCreate failed: %s\n", mptCreateResponse.Meta.TransactionResult)
os.Exit(1)
}
// Extract the MPT issuance ID from the transaction result
mptIssuanceID := string(*mptCreateResponse.Meta.MPTIssuanceID)
fmt.Printf("MPT created: %s\n", mptIssuanceID)
// Escrow Creator authorizes the MPT ----------------------
fmt.Printf("\n=== Escrow Creator Authorizing MPT ===\n\n")
mptAuthTx := transaction.MPTokenAuthorize{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
MPTokenIssuanceID: mptIssuanceID,
}
flatMptAuthTx := mptAuthTx.Flatten()
mptAuthTxJSON, _ := json.MarshalIndent(flatMptAuthTx, "", " ")
fmt.Printf("%s\n", string(mptAuthTxJSON))
fmt.Printf("\nSubmitting MPTokenAuthorize transaction...\n")
mptAuthResponse, err := client.SubmitTxAndWait(flatMptAuthTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if mptAuthResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("MPTokenAuthorize failed: %s\n", mptAuthResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Escrow Creator authorized for MPT.\n")
// Issuer sends MPTs to escrow creator ----------------------
fmt.Printf("\n=== Issuer Sending MPTs to Escrow Creator ===\n\n")
mptPaymentTx := transaction.Payment{
BaseTx: transaction.BaseTx{
Account: issuer.ClassicAddress,
},
Destination: creator.ClassicAddress,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptIssuanceID,
Value: "5000",
},
}
flatMptPaymentTx := mptPaymentTx.Flatten()
mptPaymentTxJSON, _ := json.MarshalIndent(flatMptPaymentTx, "", " ")
fmt.Printf("%s\n", string(mptPaymentTxJSON))
fmt.Printf("\nSubmitting MPT Payment transaction...\n")
mptPaymentResponse, err := client.SubmitTxAndWait(flatMptPaymentTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &issuer,
})
if err != nil {
panic(err)
}
if mptPaymentResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("MPT Payment failed: %s\n", mptPaymentResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Successfully sent 5000 MPTs to Escrow Creator.\n")
// Escrow Creator creates a conditional MPT escrow ----------------------
fmt.Printf("\n=== Creating Conditional MPT Escrow ===\n\n")
// Generate crypto-condition
preimage := make([]byte, 32)
if _, err := rand.Read(preimage); err != nil {
panic(err)
}
fulfillment := cryptoconditions.NewPreimageSha256(preimage)
fulfillmentBinary, err := fulfillment.Encode()
if err != nil {
panic(err)
}
conditionBinary, err := fulfillment.Condition().Encode()
if err != nil {
panic(err)
}
fulfillmentHex := strings.ToUpper(hex.EncodeToString(fulfillmentBinary))
conditionHex := strings.ToUpper(hex.EncodeToString(conditionBinary))
fmt.Printf("Condition: %s\n", conditionHex)
fmt.Printf("Fulfillment: %s\n\n", fulfillmentHex)
// Set expiration (300 seconds from now)
cancelAfterRippleTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix()) + 300
mptEscrowCreateTx := transaction.EscrowCreate{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
Destination: issuer.ClassicAddress,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptIssuanceID,
Value: "1000",
},
Condition: conditionHex,
CancelAfter: uint32(cancelAfterRippleTime), // Fungible token escrows require a CancelAfter time
}
flatMptEscrowCreateTx := mptEscrowCreateTx.Flatten()
mptEscrowCreateTxJSON, _ := json.MarshalIndent(flatMptEscrowCreateTx, "", " ")
fmt.Printf("%s\n", string(mptEscrowCreateTxJSON))
fmt.Printf("\nSubmitting MPT EscrowCreate transaction...\n")
mptEscrowResponse, err := client.SubmitTxAndWait(flatMptEscrowCreateTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if mptEscrowResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("MPT EscrowCreate failed: %s\n", mptEscrowResponse.Meta.TransactionResult)
os.Exit(1)
}
// Save the sequence number to identify the escrow later
mptEscrowSeq := mptEscrowResponse.TxJSON.Sequence()
fmt.Printf("Conditional MPT escrow created. Sequence: %d\n", mptEscrowSeq)
// Finish the conditional MPT escrow with the fulfillment ----------------------
fmt.Printf("\n=== Finishing Conditional MPT Escrow ===\n\n")
mptEscrowFinishTx := transaction.EscrowFinish{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
Owner: creator.ClassicAddress,
OfferSequence: mptEscrowSeq,
Condition: conditionHex,
Fulfillment: fulfillmentHex,
}
flatMptEscrowFinishTx := mptEscrowFinishTx.Flatten()
mptEscrowFinishTxJSON, _ := json.MarshalIndent(flatMptEscrowFinishTx, "", " ")
fmt.Printf("%s\n", string(mptEscrowFinishTxJSON))
fmt.Printf("\nSubmitting EscrowFinish transaction...\n")
mptFinishResponse, err := client.SubmitTxAndWait(flatMptEscrowFinishTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if mptFinishResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("MPT EscrowFinish failed: %s\n", mptFinishResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/%s\n", mptFinishResponse.Hash)
// ====== Timed Trust Line Token Escrow ======
// Enable trust line token escrows on the issuer ----------------------
fmt.Printf("\n=== Enabling Trust Line Token Escrows on Issuer ===\n\n")
accountSetTx := transaction.AccountSet{
BaseTx: transaction.BaseTx{
Account: issuer.ClassicAddress,
},
SetFlag: transaction.AsfAllowTrustLineLocking,
}
flatAccountSetTx := accountSetTx.Flatten()
accountSetTxJSON, _ := json.MarshalIndent(flatAccountSetTx, "", " ")
fmt.Printf("%s\n", string(accountSetTxJSON))
fmt.Printf("\nSubmitting AccountSet transaction...\n")
accountSetResponse, err := client.SubmitTxAndWait(flatAccountSetTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &issuer,
})
if err != nil {
panic(err)
}
if accountSetResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("AccountSet failed: %s\n", accountSetResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Trust line token escrows enabled by issuer.\n")
// Escrow Creator sets up a trust line to the issuer ----------------------
fmt.Printf("\n=== Setting Up Trust Line ===\n\n")
currencyCode := "IOU"
trustSetTx := transaction.TrustSet{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
LimitAmount: types.IssuedCurrencyAmount{
Currency: currencyCode,
Issuer: issuer.ClassicAddress,
Value: "10000000",
},
}
flatTrustSetTx := trustSetTx.Flatten()
trustSetTxJSON, _ := json.MarshalIndent(flatTrustSetTx, "", " ")
fmt.Printf("%s\n", string(trustSetTxJSON))
fmt.Printf("\nSubmitting TrustSet transaction...\n")
trustResponse, err := client.SubmitTxAndWait(flatTrustSetTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if trustResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("TrustSet failed: %s\n", trustResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Trust line successfully created for \"%s\" tokens.\n", currencyCode)
// Issuer sends IOU tokens to creator ----------------------
fmt.Printf("\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n\n")
iouPaymentTx := transaction.Payment{
BaseTx: transaction.BaseTx{
Account: issuer.ClassicAddress,
},
Destination: creator.ClassicAddress,
Amount: types.IssuedCurrencyAmount{
Currency: currencyCode,
Value: "5000",
Issuer: issuer.ClassicAddress,
},
}
flatIouPaymentTx := iouPaymentTx.Flatten()
iouPaymentTxJSON, _ := json.MarshalIndent(flatIouPaymentTx, "", " ")
fmt.Printf("%s\n", string(iouPaymentTxJSON))
fmt.Printf("\nSubmitting Trust Line Token payment transaction...\n")
iouPayResponse, err := client.SubmitTxAndWait(flatIouPaymentTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &issuer,
})
if err != nil {
panic(err)
}
if iouPayResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Trust Line Token payment failed: %s\n", iouPayResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Successfully sent 5000 %s tokens.\n", currencyCode)
// Escrow Creator creates a timed trust line token escrow ----------------------
fmt.Printf("\n=== Creating Timed Trust Line Token Escrow ===\n\n")
delay := 10 // seconds
now := time.Now()
finishAfterRippleTime := xrpltime.UnixTimeToRippleTime(now.Unix()) + int64(delay)
matureTime := now.Add(time.Duration(delay) * time.Second).Format("01/02/2006, 03:04:05 PM")
fmt.Printf("Escrow will mature after: %s\n\n", matureTime)
iouCancelAfterRippleTime := xrpltime.UnixTimeToRippleTime(now.Unix()) + 300
iouEscrowCreateTx := transaction.EscrowCreate{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
Destination: issuer.ClassicAddress,
Amount: types.IssuedCurrencyAmount{
Currency: currencyCode,
Value: "1000",
Issuer: issuer.ClassicAddress,
},
FinishAfter: uint32(finishAfterRippleTime),
CancelAfter: uint32(iouCancelAfterRippleTime),
}
flatIouEscrowCreateTx := iouEscrowCreateTx.Flatten()
iouEscrowCreateTxJSON, _ := json.MarshalIndent(flatIouEscrowCreateTx, "", " ")
fmt.Printf("%s\n", string(iouEscrowCreateTxJSON))
fmt.Printf("\nSubmitting Trust Line Token EscrowCreate transaction...\n")
iouEscrowResponse, err := client.SubmitTxAndWait(flatIouEscrowCreateTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if iouEscrowResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Trust Line Token EscrowCreate failed: %s\n", iouEscrowResponse.Meta.TransactionResult)
os.Exit(1)
}
// Save the sequence number to identify the escrow later
iouEscrowSeq := iouEscrowResponse.TxJSON.Sequence()
fmt.Printf("Trust Line Token escrow created. Sequence: %d\n", iouEscrowSeq)
// Wait for the escrow to mature, then finish it --------------------
fmt.Printf("\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n\n")
// Countdown delay until escrow matures
for i := delay; i >= 0; i-- {
fmt.Printf("Waiting for escrow to mature... %ds remaining...\r", i)
time.Sleep(time.Second)
}
fmt.Printf("Waiting for escrow to mature... done. \n")
// Confirm latest validated ledger close time is after the FinishAfter time
escrowReady := false
for !escrowReady {
ledgerResp, err := client.GetLedger(&ledgerreq.Request{
LedgerIndex: common.Validated,
})
if err != nil {
panic(err)
}
ledgerCloseTime := int64(ledgerResp.Ledger.CloseTime)
ledgerCloseLocal := time.Unix(xrpltime.RippleTimeToUnixTime(ledgerCloseTime)/1000, 0).Format("01/02/2006, 03:04:05 PM")
fmt.Printf("Latest validated ledger closed at: %s\n", ledgerCloseLocal)
if ledgerCloseTime > finishAfterRippleTime {
escrowReady = true
fmt.Printf("Escrow confirmed ready to finish.\n")
} else {
timeDifference := finishAfterRippleTime - ledgerCloseTime
if timeDifference == 0 {
timeDifference = 1
}
fmt.Printf("Escrow needs to wait another %ds.\n", timeDifference)
time.Sleep(time.Duration(timeDifference) * time.Second)
}
}
// Finish the timed trust line token escrow --------------------
fmt.Printf("\n=== Finishing Timed Trust Line Token Escrow ===\n\n")
iouEscrowFinishTx := transaction.EscrowFinish{
BaseTx: transaction.BaseTx{
Account: creator.ClassicAddress,
},
Owner: creator.ClassicAddress,
OfferSequence: iouEscrowSeq,
}
flatIouEscrowFinishTx := iouEscrowFinishTx.Flatten()
iouEscrowFinishTxJSON, _ := json.MarshalIndent(flatIouEscrowFinishTx, "", " ")
fmt.Printf("%s\n", string(iouEscrowFinishTxJSON))
fmt.Printf("\nSubmitting EscrowFinish transaction...\n")
iouFinishResponse, err := client.SubmitTxAndWait(flatIouEscrowFinishTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &creator,
})
if err != nil {
panic(err)
}
if iouFinishResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Trust Line Token EscrowFinish failed: %s\n", iouFinishResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/%s\n", iouFinishResponse.Hash)
}

View File

@@ -20,6 +20,173 @@ node send-timed-escrow.js
node send-conditional-escrow.js
```
## Send Fungible Token Escrow
```sh
node sendFungibleTokenEscrow.js
```
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
```sh
=== Funding Accounts ===
Issuer: rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP
Escrow Creator: rGRvH4FanVixca934o3ui4MbcrU56x9Qj4
=== Creating MPT ===
{
"TransactionType": "MPTokenIssuanceCreate",
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"MaximumAmount": "1000000",
"Flags": 8
}
Submitting MPTokenIssuanceCreate transaction...
MPT created: 00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC
=== Escrow Creator Authorizing MPT ===
{
"TransactionType": "MPTokenAuthorize",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"MPTokenIssuanceID": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC"
}
Submitting MPTokenAuthorize transaction...
Escrow Creator authorized for MPT.
=== Issuer Sending MPTs to Escrow Creator ===
{
"TransactionType": "Payment",
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"Destination": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Amount": {
"mpt_issuance_id": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC",
"value": "5000"
}
}
Submitting MPT Payment transaction...
Successfully sent 5000 MPTs to Escrow Creator.
=== Creating Conditional MPT Escrow ===
Condition: A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120
Fulfillment: A0228020CA07971CB0C63ED20C69931B41EEA7C4C8CC6F214183FDE031CDC7413856977F
{
"TransactionType": "EscrowCreate",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Destination": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"Amount": {
"mpt_issuance_id": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC",
"value": "1000"
},
"Condition": "A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120",
"CancelAfter": 828504579
}
Submitting MPT EscrowCreate transaction...
Conditional MPT escrow created. Sequence: 16212899
=== Finishing Conditional MPT Escrow ===
{
"TransactionType": "EscrowFinish",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Owner": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"OfferSequence": 16212899,
"Condition": "A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120",
"Fulfillment": "A0228020CA07971CB0C63ED20C69931B41EEA7C4C8CC6F214183FDE031CDC7413856977F"
}
Submitting EscrowFinish transaction...
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/BB6E8BF8A7F28D15C12C24FFDB215180262ABFAEAD43FB020DCB39E826027078
=== Enabling Trust Line Token Escrows on Issuer ===
{
"TransactionType": "AccountSet",
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"SetFlag": 17
}
Submitting AccountSet transaction...
Trust line token escrows enabled by issuer.
=== Setting Up Trust Line ===
{
"TransactionType": "TrustSet",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"LimitAmount": {
"currency": "IOU",
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"value": "10000000"
}
}
Submitting TrustSet transaction...
Trust line successfully created for "IOU" tokens.
=== Issuer Sending IOU Tokens to Escrow Creator ===
{
"TransactionType": "Payment",
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"Destination": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Amount": {
"currency": "IOU",
"value": "5000",
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP"
}
}
Submitting Trust Line Token payment transaction...
Successfully sent 5000 IOU tokens.
=== Creating Timed Trust Line Token Escrow ===
Escrow will mature after: 4/2/2026, 9:05:12 PM
{
"TransactionType": "EscrowCreate",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Destination": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
"Amount": {
"currency": "IOU",
"value": "1000",
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP"
},
"FinishAfter": 828504312,
"CancelAfter": 828504602
}
Submitting Trust Line Token EscrowCreate transaction...
Trust Line Token escrow created. Sequence: 16212902
=== Waiting For Timed Trust Line Token Escrow to Mature ===
Waiting for escrow to mature... done.
Latest validated ledger closed at: 4/2/2026, 9:05:13 PM
Escrow confirmed ready to finish.
=== Finishing Timed Trust Line Token Escrow ===
{
"TransactionType": "EscrowFinish",
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"Owner": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
"OfferSequence": 16212902
}
Submitting EscrowFinish transaction...
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/136402974863BF553706B0A4A341F24DDA5385BB6F93B905038D8FD9863B6D91
```
## List Escrows
```sh

View File

@@ -0,0 +1,358 @@
// This example demonstrates how to create escrows that hold fungible tokens.
// It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
import xrpl from 'xrpl'
import { PreimageSha256 } from 'five-bells-condition'
import { randomBytes } from 'crypto'
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
// Fund an issuer account and an escrow creator account ----------------------
console.log(`\n=== Funding Accounts ===\n`)
const [
{ wallet: issuer },
{ wallet: creator }
] = await Promise.all([
client.fundWallet(),
client.fundWallet()
])
console.log(`Issuer: ${issuer.address}`)
console.log(`Escrow Creator: ${creator.address}`)
// ====== Conditional MPT Escrow ======
// Issuer creates an MPT ----------------------
console.log('\n=== Creating MPT ===\n')
const mptCreateTx = {
TransactionType: 'MPTokenIssuanceCreate',
Account: issuer.address,
MaximumAmount: '1000000',
Flags: xrpl.MPTokenIssuanceCreateFlags.tfMPTCanEscrow
}
// Validate the transaction structure before submitting
xrpl.validate(mptCreateTx)
console.log(JSON.stringify(mptCreateTx, null, 2))
// Submit, sign, and wait for validation
console.log(`\nSubmitting MPTokenIssuanceCreate transaction...`)
const mptCreateResponse = await client.submitAndWait(mptCreateTx, {
wallet: issuer,
autofill: true
})
if (mptCreateResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`MPTokenIssuanceCreate failed: ${mptCreateResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
// Extract the MPT issuance ID from the transaction result
const mptIssuanceId = mptCreateResponse.result.meta.mpt_issuance_id
console.log(`MPT created: ${mptIssuanceId}`)
// Escrow Creator authorizes the MPT ----------------------
console.log('\n=== Escrow Creator Authorizing MPT ===\n')
const mptAuthTx = {
TransactionType: 'MPTokenAuthorize',
Account: creator.address,
MPTokenIssuanceID: mptIssuanceId
}
xrpl.validate(mptAuthTx)
console.log(JSON.stringify(mptAuthTx, null, 2))
console.log(`\nSubmitting MPTokenAuthorize transaction...`)
const mptAuthResponse = await client.submitAndWait(mptAuthTx, {
wallet: creator,
autofill: true
})
if (mptAuthResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`MPTokenAuthorize failed: ${mptAuthResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log('Escrow Creator authorized for MPT.')
// Issuer sends MPTs to escrow creator ----------------------
console.log('\n=== Issuer Sending MPTs to Escrow Creator ===\n')
const mptPaymentTx = {
TransactionType: 'Payment',
Account: issuer.address,
Destination: creator.address,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '5000'
}
}
xrpl.validate(mptPaymentTx)
console.log(JSON.stringify(mptPaymentTx, null, 2))
console.log(`\nSubmitting MPT Payment transaction...`)
const mptPaymentResponse = await client.submitAndWait(mptPaymentTx, {
wallet: issuer,
autofill: true
})
if (mptPaymentResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`MPT Payment failed: ${mptPaymentResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log('Successfully sent 5000 MPTs to Escrow Creator.')
// Escrow Creator creates a conditional MPT escrow ----------------------
console.log('\n=== Creating Conditional MPT Escrow ===\n')
// Generate crypto-condition
const preimage = randomBytes(32)
const fulfillment = new PreimageSha256()
fulfillment.setPreimage(preimage)
const fulfillmentHex = fulfillment.serializeBinary().toString('hex').toUpperCase()
const conditionHex = fulfillment.getConditionBinary().toString('hex').toUpperCase()
console.log(`Condition: ${conditionHex}`)
console.log(`Fulfillment: ${fulfillmentHex}\n`)
// Set expiration (300 seconds from now)
const cancelAfter = new Date()
cancelAfter.setSeconds(cancelAfter.getSeconds() + 300)
const cancelAfterRippleTime = xrpl.isoTimeToRippleTime(cancelAfter.toISOString())
const mptEscrowCreateTx = {
TransactionType: 'EscrowCreate',
Account: creator.address,
Destination: issuer.address,
Amount: {
mpt_issuance_id: mptIssuanceId,
value: '1000'
},
Condition: conditionHex,
CancelAfter: cancelAfterRippleTime // Fungible token escrows require a CancelAfter time
}
xrpl.validate(mptEscrowCreateTx)
console.log(JSON.stringify(mptEscrowCreateTx, null, 2))
console.log(`\nSubmitting MPT EscrowCreate transaction...`)
const mptEscrowResponse = await client.submitAndWait(mptEscrowCreateTx, {
wallet: creator,
autofill: true
})
if (mptEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`MPT EscrowCreate failed: ${mptEscrowResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
// Save the sequence number to identify the escrow later
const mptEscrowSeq = mptEscrowResponse.result.tx_json.Sequence
console.log(`Conditional MPT escrow created. Sequence: ${mptEscrowSeq}`)
// Finish the conditional MPT escrow with the fulfillment ----------------------
console.log('\n=== Finishing Conditional MPT Escrow ===\n')
const mptEscrowFinishTx = {
TransactionType: 'EscrowFinish',
Account: creator.address,
Owner: creator.address,
OfferSequence: mptEscrowSeq,
Condition: conditionHex,
Fulfillment: fulfillmentHex
}
xrpl.validate(mptEscrowFinishTx)
console.log(JSON.stringify(mptEscrowFinishTx, null, 2))
console.log(`\nSubmitting EscrowFinish transaction...`)
const mptFinishResponse = await client.submitAndWait(mptEscrowFinishTx, {
wallet: creator,
autofill: true
})
if (mptFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`MPT EscrowFinish failed: ${mptFinishResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log(`Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/${mptFinishResponse.result.hash}`)
// ====== Timed Trust Line Token Escrow ======
// Enable trust line token escrows on the issuer ----------------------
console.log('\n=== Enabling Trust Line Token Escrows on Issuer ===\n')
const accountSetTx = {
TransactionType: 'AccountSet',
Account: issuer.address,
SetFlag: xrpl.AccountSetAsfFlags.asfAllowTrustLineLocking
}
xrpl.validate(accountSetTx)
console.log(JSON.stringify(accountSetTx, null, 2))
console.log(`\nSubmitting AccountSet transaction...`)
const accountSetResponse = await client.submitAndWait(accountSetTx, {
wallet: issuer,
autofill: true
})
if (accountSetResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`AccountSet failed: ${accountSetResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log('Trust line token escrows enabled by issuer.')
// Escrow Creator sets up a trust line to the issuer ----------------------
console.log('\n=== Setting Up Trust Line ===\n')
const currencyCode = 'IOU'
const trustSetTx = {
TransactionType: 'TrustSet',
Account: creator.address,
LimitAmount: {
currency: currencyCode,
issuer: issuer.address,
value: '10000000'
}
}
xrpl.validate(trustSetTx)
console.log(JSON.stringify(trustSetTx, null, 2))
console.log(`\nSubmitting TrustSet transaction...`)
const trustResponse = await client.submitAndWait(trustSetTx, {
wallet: creator,
autofill: true
})
if (trustResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`TrustSet failed: ${trustResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log(`Trust line successfully created for "${currencyCode}" tokens.`)
// Issuer sends IOU tokens to creator ----------------------
console.log('\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n')
const iouPaymentTx = {
TransactionType: 'Payment',
Account: issuer.address,
Destination: creator.address,
Amount: {
currency: currencyCode,
value: '5000',
issuer: issuer.address
}
}
xrpl.validate(iouPaymentTx)
console.log(JSON.stringify(iouPaymentTx, null, 2))
console.log(`\nSubmitting Trust Line Token payment transaction...`)
const iouPayResponse = await client.submitAndWait(iouPaymentTx, {
wallet: issuer,
autofill: true
})
if (iouPayResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`Trust Line Token payment failed: ${iouPayResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log(`Successfully sent 5000 ${currencyCode} tokens.`)
// Escrow Creator creates a timed trust line token escrow ----------------------
console.log('\n=== Creating Timed Trust Line Token Escrow ===\n')
const delay = 10 // seconds
const now = new Date()
const finishAfter = new Date(now.getTime() + delay * 1000)
const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString())
console.log(`Escrow will mature after: ${finishAfter.toLocaleString()}\n`)
const iouCancelAfter = new Date(now.getTime() + 300 * 1000)
const iouCancelAfterRippleTime = xrpl.isoTimeToRippleTime(iouCancelAfter.toISOString())
const iouEscrowCreateTx = {
TransactionType: 'EscrowCreate',
Account: creator.address,
Destination: issuer.address,
Amount: {
currency: currencyCode,
value: '1000',
issuer: issuer.address
},
FinishAfter: finishAfterRippleTime,
CancelAfter: iouCancelAfterRippleTime
}
xrpl.validate(iouEscrowCreateTx)
console.log(JSON.stringify(iouEscrowCreateTx, null, 2))
console.log(`\nSubmitting Trust Line Token EscrowCreate transaction...`)
const iouEscrowResponse = await client.submitAndWait(iouEscrowCreateTx, {
wallet: creator,
autofill: true
})
if (iouEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`Trust Line Token EscrowCreate failed: ${iouEscrowResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
// Save the sequence number to identify the escrow later
const iouEscrowSeq = iouEscrowResponse.result.tx_json.Sequence
console.log(`Trust Line Token escrow created. Sequence: ${iouEscrowSeq}`)
// Wait for the escrow to mature, then finish it --------------------
console.log(`\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n`)
// Sleep function to countdown delay until escrow matures
function sleep (delayInSeconds) {
return new Promise((resolve) => setTimeout(resolve, delayInSeconds * 1000))
}
for (let i = delay; i >= 0; i--) {
process.stdout.write(`\rWaiting for escrow to mature... ${i}s remaining...`)
await sleep(1)
}
console.log('\rWaiting for escrow to mature... done. ')
// Confirm latest validated ledger close time is after the FinishAfter time
let escrowReady = false
while (!escrowReady) {
const validatedLedger = await client.request({
command: 'ledger',
ledger_index: 'validated'
})
const ledgerCloseTime = validatedLedger.result.ledger.close_time
console.log(`Latest validated ledger closed at: ${new Date(xrpl.rippleTimeToISOTime(ledgerCloseTime)).toLocaleString()}`)
if (ledgerCloseTime > finishAfterRippleTime) {
escrowReady = true
console.log('Escrow confirmed ready to finish.')
} else {
let timeDifference = finishAfterRippleTime - ledgerCloseTime
if (timeDifference === 0) { timeDifference = 1 }
console.log(`Escrow needs to wait another ${timeDifference}s.`)
await sleep(timeDifference)
}
}
// Finish the timed trust line token escrow --------------------
console.log('\n=== Finishing Timed Trust Line Token Escrow ===\n')
const iouEscrowFinishTx = {
TransactionType: 'EscrowFinish',
Account: creator.address,
Owner: creator.address,
OfferSequence: iouEscrowSeq
}
xrpl.validate(iouEscrowFinishTx)
console.log(JSON.stringify(iouEscrowFinishTx, null, 2))
console.log(`\nSubmitting EscrowFinish transaction...`)
const iouFinishResponse = await client.submitAndWait(iouEscrowFinishTx, {
wallet: creator,
autofill: true
})
if (iouFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
console.error(`Trust Line Token EscrowFinish failed: ${iouFinishResponse.result.meta.TransactionResult}`)
await client.disconnect()
process.exit(1)
}
console.log(`Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/${iouFinishResponse.result.hash}`)
await client.disconnect()

View File

@@ -22,6 +22,187 @@ python send_timed_escrow.py
python send_conditional_escrow.py
```
## Send Fungible Token Escrow
```sh
python send_fungible_token_escrow.py
```
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
```sh
=== Funding Accounts ===
Attempting to fund address rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2
Faucet fund successful.
Attempting to fund address rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU
Faucet fund successful.
Issuer: rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2
Escrow Creator: rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU
=== Creating MPT ===
{
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"TransactionType": "MPTokenIssuanceCreate",
"Flags": 8,
"SigningPubKey": "",
"MaximumAmount": "1000000"
}
Submitting MPTokenIssuanceCreate transaction...
MPT created: 00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18
=== Escrow Creator Authorizing MPT ===
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "MPTokenAuthorize",
"SigningPubKey": "",
"MPTokenIssuanceID": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18"
}
Submitting MPTokenAuthorize transaction...
Escrow Creator authorized for MPT.
=== Issuer Sending MPTs to Escrow Creator ===
{
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"TransactionType": "Payment",
"SigningPubKey": "",
"Amount": {
"mpt_issuance_id": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18",
"value": "5000"
},
"Destination": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU"
}
Submitting MPT Payment transaction...
Successfully sent 5000 MPTs to Escrow Creator.
=== Creating Conditional MPT Escrow ===
Condition: A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120
Fulfillment: A022802079204EBCCCF4816441F0D4F7B15E7003A757675FC90691107AB770044B07697B
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "EscrowCreate",
"SigningPubKey": "",
"Amount": {
"mpt_issuance_id": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18",
"value": "1000"
},
"Destination": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"CancelAfter": 828514495,
"Condition": "A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120"
}
Submitting MPT EscrowCreate transaction...
Conditional MPT escrow created. Sequence: 16216160
=== Finishing Conditional MPT Escrow ===
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "EscrowFinish",
"SigningPubKey": "",
"Owner": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"OfferSequence": 16216160,
"Condition": "A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120",
"Fulfillment": "A022802079204EBCCCF4816441F0D4F7B15E7003A757675FC90691107AB770044B07697B"
}
Submitting EscrowFinish transaction...
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/4FE4B0AD6FD9D8CD968F5AFD43C3E1F2180C40C3A20DE7416B1E16069D2340DD
=== Enabling Trust Line Token Escrows on Issuer ===
{
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"TransactionType": "AccountSet",
"SigningPubKey": "",
"SetFlag": 17
}
Submitting AccountSet transaction...
Trust line token escrows enabled by issuer.
=== Setting Up Trust Line ===
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "TrustSet",
"SigningPubKey": "",
"LimitAmount": {
"currency": "IOU",
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"value": "10000000"
}
}
Submitting TrustSet transaction...
Trust line successfully created for "IOU" tokens.
=== Issuer Sending IOU Tokens to Escrow Creator ===
{
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"TransactionType": "Payment",
"SigningPubKey": "",
"Amount": {
"currency": "IOU",
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"value": "5000"
},
"Destination": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU"
}
Submitting Trust Line Token payment transaction...
Successfully sent 5000 IOU tokens.
=== Creating Timed Trust Line Token Escrow ===
Escrow will mature after: 04/02/2026, 11:50:39 PM
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "EscrowCreate",
"SigningPubKey": "",
"Amount": {
"currency": "IOU",
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"value": "1000"
},
"Destination": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
"CancelAfter": 828514529,
"FinishAfter": 828514239
}
Submitting Trust Line Token EscrowCreate transaction...
Trust Line Token escrow created. Sequence: 16216163
=== Waiting For Timed Trust Line Token Escrow to Mature ===
Waiting for escrow to mature... done.
Latest validated ledger closed at: 04/02/2026, 11:50:42 PM
Escrow confirmed ready to finish.
=== Finishing Timed Trust Line Token Escrow ===
{
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"TransactionType": "EscrowFinish",
"SigningPubKey": "",
"Owner": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
"OfferSequence": 16216163
}
Submitting EscrowFinish transaction...
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/F3FCFE9D0E9A0A7CC6FA804526A509532833AEAD4C7A7BF1978194F4F1CCDED4
```
## List Escrows
```sh

View File

@@ -0,0 +1,301 @@
# This example demonstrates how to create escrows that hold fungible tokens.
# It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
import json
from datetime import datetime, timedelta, UTC
from os import urandom
from time import sleep
from cryptoconditions import PreimageSha256
from xrpl.clients import JsonRpcClient
from xrpl.models import (
AccountSet,
EscrowCreate,
EscrowFinish,
MPTokenAuthorize,
MPTokenIssuanceCreate,
Payment,
TrustSet,
)
from xrpl.models.amounts import IssuedCurrencyAmount, MPTAmount
from xrpl.models.requests import Ledger
from xrpl.models.transactions.account_set import AccountSetAsfFlag
from xrpl.models.transactions.mptoken_issuance_create import MPTokenIssuanceCreateFlag
from xrpl.transaction import submit_and_wait
from xrpl.utils import datetime_to_ripple_time, ripple_time_to_datetime
from xrpl.wallet import generate_faucet_wallet
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
# Fund an issuer account and an escrow creator account ----------------------
print("\n=== Funding Accounts ===\n")
issuer = generate_faucet_wallet(client, debug=True)
creator = generate_faucet_wallet(client, debug=True)
print(f"Issuer: {issuer.address}")
print(f"Escrow Creator: {creator.address}")
# ====== Conditional MPT Escrow ======
# Issuer creates an MPT ----------------------
print("\n=== Creating MPT ===\n")
mpt_create_tx = MPTokenIssuanceCreate(
account=issuer.address,
maximum_amount="1000000",
flags=MPTokenIssuanceCreateFlag.TF_MPT_CAN_ESCROW,
)
print(json.dumps(mpt_create_tx.to_xrpl(), indent=2))
# Submit, sign, and wait for validation
print("\nSubmitting MPTokenIssuanceCreate transaction...")
mpt_create_response = submit_and_wait(mpt_create_tx, client, issuer)
if mpt_create_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"MPTokenIssuanceCreate failed: {mpt_create_response.result['meta']['TransactionResult']}")
exit(1)
# Extract the MPT issuance ID from the transaction result
mpt_issuance_id = mpt_create_response.result["meta"]["mpt_issuance_id"]
print(f"MPT created: {mpt_issuance_id}")
# Escrow Creator authorizes the MPT ----------------------
print("\n=== Escrow Creator Authorizing MPT ===\n")
mpt_auth_tx = MPTokenAuthorize(
account=creator.address,
mptoken_issuance_id=mpt_issuance_id,
)
print(json.dumps(mpt_auth_tx.to_xrpl(), indent=2))
print("\nSubmitting MPTokenAuthorize transaction...")
mpt_auth_response = submit_and_wait(mpt_auth_tx, client, creator)
if mpt_auth_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"MPTokenAuthorize failed: {mpt_auth_response.result['meta']['TransactionResult']}")
exit(1)
print("Escrow Creator authorized for MPT.")
# Issuer sends MPTs to escrow creator ----------------------
print("\n=== Issuer Sending MPTs to Escrow Creator ===\n")
mpt_payment_tx = Payment(
account=issuer.address,
destination=creator.address,
amount=MPTAmount(
mpt_issuance_id=mpt_issuance_id,
value="5000",
),
)
print(json.dumps(mpt_payment_tx.to_xrpl(), indent=2))
print("\nSubmitting MPT Payment transaction...")
mpt_payment_response = submit_and_wait(mpt_payment_tx, client, issuer)
if mpt_payment_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"MPT Payment failed: {mpt_payment_response.result['meta']['TransactionResult']}")
exit(1)
print("Successfully sent 5000 MPTs to Escrow Creator.")
# Escrow Creator creates a conditional MPT escrow ----------------------
print("\n=== Creating Conditional MPT Escrow ===\n")
# Generate crypto-condition
preimage = urandom(32)
fulfillment = PreimageSha256(preimage=preimage)
fulfillment_hex = fulfillment.serialize_binary().hex().upper()
condition_hex = fulfillment.condition_binary.hex().upper()
print(f"Condition: {condition_hex}")
print(f"Fulfillment: {fulfillment_hex}\n")
# Set expiration (300 seconds from now)
cancel_after = datetime.now(tz=UTC) + timedelta(seconds=300)
cancel_after_ripple_time = datetime_to_ripple_time(cancel_after)
mpt_escrow_create_tx = EscrowCreate(
account=creator.address,
destination=issuer.address,
amount=MPTAmount(
mpt_issuance_id=mpt_issuance_id,
value="1000",
),
condition=condition_hex,
cancel_after=cancel_after_ripple_time, # Fungible token escrows require a cancel_after time
)
print(json.dumps(mpt_escrow_create_tx.to_xrpl(), indent=2))
print("\nSubmitting MPT EscrowCreate transaction...")
mpt_escrow_response = submit_and_wait(mpt_escrow_create_tx, client, creator)
if mpt_escrow_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"MPT EscrowCreate failed: {mpt_escrow_response.result['meta']['TransactionResult']}")
exit(1)
# Save the sequence number to identify the escrow later
mpt_escrow_seq = mpt_escrow_response.result["tx_json"]["Sequence"]
print(f"Conditional MPT escrow created. Sequence: {mpt_escrow_seq}")
# Finish the conditional MPT escrow with the fulfillment ----------------------
print("\n=== Finishing Conditional MPT Escrow ===\n")
mpt_escrow_finish_tx = EscrowFinish(
account=creator.address,
owner=creator.address,
offer_sequence=mpt_escrow_seq,
condition=condition_hex,
fulfillment=fulfillment_hex,
)
print(json.dumps(mpt_escrow_finish_tx.to_xrpl(), indent=2))
print("\nSubmitting EscrowFinish transaction...")
mpt_finish_response = submit_and_wait(mpt_escrow_finish_tx, client, creator)
if mpt_finish_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"MPT EscrowFinish failed: {mpt_finish_response.result['meta']['TransactionResult']}")
exit(1)
print(f"Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/{mpt_finish_response.result['hash']}")
# ====== Timed Trust Line Token Escrow ======
# Enable trust line token escrows on the issuer ----------------------
print("\n=== Enabling Trust Line Token Escrows on Issuer ===\n")
account_set_tx = AccountSet(
account=issuer.address,
set_flag=AccountSetAsfFlag.ASF_ALLOW_TRUSTLINE_LOCKING,
)
print(json.dumps(account_set_tx.to_xrpl(), indent=2))
print("\nSubmitting AccountSet transaction...")
account_set_response = submit_and_wait(account_set_tx, client, issuer)
if account_set_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"AccountSet failed: {account_set_response.result['meta']['TransactionResult']}")
exit(1)
print("Trust line token escrows enabled by issuer.")
# Escrow Creator sets up a trust line to the issuer ----------------------
print("\n=== Setting Up Trust Line ===\n")
currency_code = "IOU"
trust_set_tx = TrustSet(
account=creator.address,
limit_amount=IssuedCurrencyAmount(
currency=currency_code,
issuer=issuer.address,
value="10000000",
),
)
print(json.dumps(trust_set_tx.to_xrpl(), indent=2))
print("\nSubmitting TrustSet transaction...")
trust_response = submit_and_wait(trust_set_tx, client, creator)
if trust_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"TrustSet failed: {trust_response.result['meta']['TransactionResult']}")
exit(1)
print(f'Trust line successfully created for "{currency_code}" tokens.')
# Issuer sends IOU tokens to creator ----------------------
print("\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n")
iou_payment_tx = Payment(
account=issuer.address,
destination=creator.address,
amount=IssuedCurrencyAmount(
currency=currency_code,
value="5000",
issuer=issuer.address,
),
)
print(json.dumps(iou_payment_tx.to_xrpl(), indent=2))
print("\nSubmitting Trust Line Token payment transaction...")
iou_pay_response = submit_and_wait(iou_payment_tx, client, issuer)
if iou_pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"Trust Line Token payment failed: {iou_pay_response.result['meta']['TransactionResult']}")
exit(1)
print(f"Successfully sent 5000 {currency_code} tokens.")
# Escrow Creator creates a timed trust line token escrow ----------------------
print("\n=== Creating Timed Trust Line Token Escrow ===\n")
delay = 10 # seconds
now = datetime.now(tz=UTC)
finish_after = now + timedelta(seconds=delay)
finish_after_ripple_time = datetime_to_ripple_time(finish_after)
mature_time = finish_after.astimezone().strftime("%m/%d/%Y, %I:%M:%S %p")
print(f"Escrow will mature after: {mature_time}\n")
iou_cancel_after = now + timedelta(seconds=300)
iou_cancel_after_ripple_time = datetime_to_ripple_time(iou_cancel_after)
iou_escrow_create_tx = EscrowCreate(
account=creator.address,
destination=issuer.address,
amount=IssuedCurrencyAmount(
currency=currency_code,
value="1000",
issuer=issuer.address,
),
finish_after=finish_after_ripple_time,
cancel_after=iou_cancel_after_ripple_time,
)
print(json.dumps(iou_escrow_create_tx.to_xrpl(), indent=2))
print("\nSubmitting Trust Line Token EscrowCreate transaction...")
iou_escrow_response = submit_and_wait(iou_escrow_create_tx, client, creator)
if iou_escrow_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"Trust Line Token EscrowCreate failed: {iou_escrow_response.result['meta']['TransactionResult']}")
exit(1)
# Save the sequence number to identify the escrow later
iou_escrow_seq = iou_escrow_response.result["tx_json"]["Sequence"]
print(f"Trust Line Token escrow created. Sequence: {iou_escrow_seq}")
# Wait for the escrow to mature, then finish it --------------------
print("\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n")
# Countdown delay until escrow matures
for i in range(delay, -1, -1):
print(f"Waiting for escrow to mature... {i}s remaining...", end="\r", flush=True)
sleep(1)
print("Waiting for escrow to mature... done. ")
# Confirm latest validated ledger close time is after the finish_after time
escrow_ready = False
while not escrow_ready:
validated_ledger = client.request(Ledger(ledger_index="validated"))
ledger_close_time = validated_ledger.result["ledger"]["close_time"]
ledger_close_local = ripple_time_to_datetime(ledger_close_time).astimezone().strftime("%m/%d/%Y, %I:%M:%S %p")
print(f"Latest validated ledger closed at: {ledger_close_local}")
if ledger_close_time > finish_after_ripple_time:
escrow_ready = True
print("Escrow confirmed ready to finish.")
else:
time_difference = finish_after_ripple_time - ledger_close_time
if time_difference == 0:
time_difference = 1
print(f"Escrow needs to wait another {time_difference}s.")
sleep(time_difference)
# Finish the timed trust line token escrow --------------------
print("\n=== Finishing Timed Trust Line Token Escrow ===\n")
iou_escrow_finish_tx = EscrowFinish(
account=creator.address,
owner=creator.address,
offer_sequence=iou_escrow_seq,
)
print(json.dumps(iou_escrow_finish_tx.to_xrpl(), indent=2))
print("\nSubmitting EscrowFinish transaction...")
iou_finish_response = submit_and_wait(iou_escrow_finish_tx, client, creator)
if iou_finish_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"Trust Line Token EscrowFinish failed: {iou_finish_response.result['meta']['TransactionResult']}")
exit(1)
print(f"Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/{iou_finish_response.result['hash']}")

View File

@@ -23,7 +23,7 @@ const softwallets = [
{ href: "https://coin.space/", id: "wallet-coin", alt: "Coin Space" },
{ href: "https://crossmark.io/", id: "wallet-crossmark", alt: "Crossmark Wallet" },
{ href: "https://gatehub.net/", id: "wallet-gatehub", alt: "Gatehub", imgclasses: "invertible-img" },
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://gemwallet.com/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://joeywallet.xyz/", id: "wallet-joey", alt: "Joey Wallet" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" }

View File

@@ -20,7 +20,7 @@ This vulnerability disclosure report contains technical details of the XRP Ledge
## Summary of Vulnerability
Two vulnerabilities that affected XRPL's liveness were discovered by **Common Prefix**, which could have prevented the network from making forward progress. A UNL validator would need to have been compromised in order to exploit the bugs. Fixes for both vulnerabilities were released as part of version 3.0.0 of rippled.
Two vulnerabilities that affected XRPL's liveness were discovered by **[Common Prefix](https://www.commonprefix.com)**, which could have prevented the network from making forward progress. A UNL validator would need to have been compromised in order to exploit the bugs. Fixes for both vulnerabilities were released as part of version 3.0.0 of rippled.
## Impact
@@ -30,7 +30,7 @@ If a UNL validator had been compromised before these vulnerabilities were fixed,
### Discovery
**Nikolaos Kamarinakis** from **Common Prefix** reported the vulnerabilities via a responsible disclosure report. Ripple engineering teams validated the report with an independent proof-of-concept that reproduced both bugs in a separate network.
**Nikolaos Kamarinakis**, **Dejan Cabrilo**, **Dr. Dimitris Karakostas**, and **Prof. Zeta Avarikioti** from **Common Prefix** reported the vulnerabilities via a responsible disclosure report. Ripple engineering teams validated the report with an independent proof-of-concept that reproduced both bugs in a separate network.
### Root Cause

View File

@@ -1372,6 +1372,61 @@ const events = [
image: require("../static/img/events/meetup-london.png"),
end_date: "February 18, 2026",
},
{
name: "XRP Ledger Meetup Poland: The Builder's Foundation",
description:
"XRPL Commons brings its ecosystem to Warsaw with a first pilot event alongside Neti. Join us at EXPO XXI during Polish Blockchain Week for a high-impact session with developers and founders shaping the next wave of the Agentic Web.",
type: "meetup",
link: "https://luma.com/boucntsh",
location: "Warsaw, Poland",
date: "March 24, 2026",
image: require("../static/img/events/xrp-ledger-meetup-poland.jpg"),
end_date: "March 24, 2026",
},
{
name: "XRPL Aquarium Demo Day #8 Social Impact",
description:
"The Aquarium Residency is a 12-week program for entrepreneurs & developers building on the XRP Ledger blockchain. Join us at our Paris HQ to connect with our residents, discover their projects focused on Social Impact, and engage with the XRPL community.",
type: "meetup",
link: "https://luma.com/2feub5uj",
location: "Paris, France",
date: "March 25, 2026",
image: require("../static/img/events/aquarium-demo-day-8.jpg"),
end_date: "March 25, 2026",
},
{
name: "XRPL & GDF Stablecoins Round table",
description:
"Invite-only Executive Roundtable on Stablecoins in Paris, hosted by XRPL Commons and Global Digital Finance. Senior leaders from finance, policy, and tech will explore institutional use cases, regulation, and real-world deployments shaping the future of digital money.",
type: "conference",
link: "https://luma.com/tgg0id1d",
location: "Paris, France",
date: "April 7, 2026",
image: require("../static/img/events/gdf-stablecoins-roundtable.jpg"),
end_date: "April 7, 2026",
},
{
name: "HACK THE BLOCK 2026 Paris Blockchain Week XRPL Hackathon",
description:
"The flagship hackathon of Paris Blockchain Week, a 36-hour sprint where developers from around the world turn ideas into real on-chain solutions, backed by leading ecosystems and judged by Europe's top tech entrepreneurs and investors. Teams don't just compete, they get discovered.",
type: "hackathon",
link: "https://luma.com/Hacktheblock2026-PBW-XRPL",
location: "Paris, France",
date: "April 11, 2026",
image: require("../static/img/events/hack-the-block-2026.jpg"),
end_date: "April 12, 2026",
},
{
name: "XRPL Zone Paris",
description:
"Connecting XRPL Builders at Paris Blockchain Week 2026. XRPL Commons invites core ecosystem projects & builders to gather at our headquarters in the heart of Paris for a special edition of XRPL Zone Paris.",
type: "zone",
link: "https://luma.com/780xhfr7",
location: "Paris, France",
date: "April 14, 2026",
image: require("../static/img/events/xrpl-zone-paris.jpg"),
end_date: "April 14, 2026",
},
];
@@ -1453,33 +1508,33 @@ export default function Events() {
<div className="pr-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrpl-hero.png")}
src={require("../static/img/events/xrp-community-night-paris.png")}
className="w-100"
/>
</div>
<div className="pt-5 pr-2 col">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night Denver")}
{translate("XRP Community Night Paris")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
</div>
<p className="mb-4">
{translate(
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
"Attending Paris Blockchain Week? Join us for an evening with the XRP community in Paris. Connect with the users, builders and projects innovating with and utilizing XRP."
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: Denver, CO")}
{translate("Location: Paris, France")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("February 18, 2026")}
{translate("April 15, 2026")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://luma.com/chz145tf?utm_source=xprlorg"
href="https://luma.com/wnkqmmqy?utm_source=xprlorg"
>
{translate("Register Now")}
</a>

View File

@@ -373,6 +373,7 @@
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
[Get Started Using Go]: /docs/tutorials/get-started/get-started-go.md
[Get Started Using Java]: /docs/tutorials/get-started/get-started-java.md
[Get Started Using JavaScript]: /docs/tutorials/get-started/get-started-javascript.md
[Get Started Using Python]: /docs/tutorials/get-started/get-started-python.md
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
@@ -490,6 +491,7 @@
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
[xrpl4j library]: https://github.com/XRPLF/xrpl4j
[xrpl-go library]: https://github.com/XRPLF/xrpl-go
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
[xrpl-py library]: https://github.com/XRPLF/xrpl-py

View File

@@ -66,7 +66,7 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. |
| `DebtTotal` | String | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
| `DebtMaximum` | String | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
| `DebtMaximum` | String | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
| `CoverAvailable` | String | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
| `CoverRateMinimum` | Number | UInt32 | Yes | The 1/10th basis point of the `DebtTotal` that the first-loss capital must cover. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `CoverRateLiquidation`| Number | UInt12 | Yes | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |

View File

@@ -25,8 +25,6 @@ Provisionally issue a [credential](../../../../concepts/decentralized-storage/cr
{% raw-partial file="/docs/_snippets/tx-fields-intro.md" /%}
In addition to the [common fields][], CredentialCreate transactions use the following fields:
| Field | JSON Type | [Internal Type][] | Required? | Description |
|:-----------------|:---------------------|:------------------|:----------|:------------|
| `Subject` | String - [Address][] | AccountID | Yes | The subject of the credential. |

View File

@@ -0,0 +1,151 @@
---
seo:
description: Issue, accept, and delete a credential on the XRP Ledger.
metadata:
indexPage: true
labels:
- Credentials
---
# Manage Credentials
This tutorial shows you how to manage the full lifecycle of [Credentials][] on the XRP Ledger: issuing a credential to a subject, accepting the credential, and deleting it.
{% amendment-disclaimer name="Credentials" /%}
## Goals
By the end of this tutorial, you will be able to:
- Issue a credential to a subject account.
- Accept a credential as the subject.
- Delete a credential from the ledger.
## Prerequisites
To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
- **Java** with the [xrpl4j library][]. See [Get Started Using Java][] for setup steps.
## Source Code
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/credential/" %}code samples section of this website's repository{% /repo-link %}.
## Steps
### 1. Install dependencies
{% tabs %}
{% tab label="Java" %}
From the code sample folder, use `mvn` to install dependencies.
```bash
mvn install
```
{% /tab %}
{% /tabs %}
### 2. Set up client and fund accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL Testnet. This example imports:
{% tabs %}
{% tab label="Java" %}
- `xrpl4j`: Used for XRPL client connection, transaction submission, and wallet handling.
- `OkHttp`, `Guava`, `Jackson`: Used for HTTP URL construction, unsigned integer arithmetic, and JSON serialization.
- `java.util.concurrent`: Used for async operations.
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" before="// ----- Prepare CredentialCreate" /%}
The `createAndFundWallet()` helper generates an Ed25519 keypair, funds it from the Testnet faucet, and polls Testnet until the account is visible on a validated ledger.
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Generates a new Ed25519 keypair" before="// Fetches the next transaction sequence number" /%}
{% /tab %}
{% /tabs %}
### 3. Prepare CredentialCreate transaction
Create the [CredentialCreate transaction][] object.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialCreate" before="// ----- Sign, submit, and wait for CredentialCreate" /%}
{% /tab %}
{% /tabs %}
The credential is identified by the issuer, subject, and credential type (written as a hexadecimal string).
### 4. Submit CredentialCreate transaction
Sign and submit the `CredentialCreate` transaction to the XRP Ledger.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, submit, and wait for CredentialCreate" before="// ----- Prepare CredentialAccept" /%}
The `signSubmitAndWait()` helper signs a transaction, submits it, and polls Testnet until it reaches a validated ledger.
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Signs and submits a transaction" before="// Checks for a tesSUCCESS result code" /%}
The `requireSuccess` helper verifies that the transaction succeeded with a `tesSUCCESS` result code and posts a link to the transaction metadata on the XRPL Explorer.
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// Checks for a tesSUCCESS result code" /%}
{% /tab %}
{% /tabs %}
### 5. Prepare CredentialAccept transaction
Create the [CredentialAccept transaction][] object. The subject account must accept the credential to make it valid.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialAccept" before="// ----- Sign, Submit, and wait for CredentialAccept" /%}
{% /tab %}
{% /tabs %}
### 6. Submit CredentialAccept transaction
Sign and submit the `CredentialAccept` transaction to the XRP Ledger.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, Submit, and wait for CredentialAccept" before="// ----- Prepare CredentialDelete" /%}
{% /tab %}
{% /tabs %}
### 7. Prepare CredentialDelete transaction
Create the [CredentialDelete transaction][] object. Either the issuer or the subject can delete a credential.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Prepare CredentialDelete" before="// ----- Sign, Submit, and wait for CredentialDelete" /%}
{% /tab %}
{% /tabs %}
### 8. Submit CredentialDelete transaction
Sign and submit the `CredentialDelete` transaction to the XRP Ledger.
{% tabs %}
{% tab label="Java" %}
{% code-snippet file="/_code-samples/credential/java/src/main/java/com/example/xrpl/ManageCredentials.java" language="java" from="// ----- Sign, Submit, and wait for CredentialDelete" before="// ===== Helper functions" /%}
{% /tab %}
{% /tabs %}
## See Also
**Concepts**:
- [Credentials][]
**Tutorials**:
- [Verify Credentials](./verify-credentials.md)
**References**:
- [CredentialCreate transaction][]
- [CredentialAccept transaction][]
- [CredentialDelete transaction][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -0,0 +1,301 @@
---
seo:
description: Create and finish escrows that hold fungible tokens (MPTs and trust line tokens) on the XRP Ledger.
metadata:
indexPage: true
labels:
- Escrow
---
# Send Fungible Token Escrows
This tutorial shows you how to create and finish escrows that hold fungible tokens on the XRP Ledger. It covers two types of fungible token escrows:
- **Conditional MPT escrow**: An escrow holding [Multi-Purpose Tokens](../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md) that is released when a crypto-condition is fulfilled.
- **Timed trust line token escrow**: An escrow holding [trust line tokens](../../concepts/tokens/fungible-tokens/trust-line-tokens.md) that is released after a specified time.
{% admonition type="info" name="Note" %}
Though this tutorial covers these two specific scenarios, both fungible token types can be used in either conditional or timed escrows.
{% /admonition %}
{% amendment-disclaimer name="TokenEscrow" /%}
## Goals
By the end of this tutorial, you will be able to:
- Issue an MPT with escrow support enabled.
- Create and finish a conditional escrow that holds MPTs.
- Enable trust line token escrows on an issuer account.
- Create and finish a timed escrow that holds trust line tokens.
## Prerequisites
To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library installed. This page provides examples for the following:
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
## Source Code
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/escrow/" %}code samples section of this website's repository{% /repo-link %}.
## Steps
### 1. Install dependencies
{% tabs %}
{% tab label="JavaScript" %}
From the code sample folder, use `npm` to install dependencies.
```bash
npm install
```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %}
{% tab label="Go" %}
From the code sample folder, use `go` to install dependencies.
```bash
go mod tidy
```
{% /tab %}
{% /tabs %}
### 2. Set up client and fund accounts
Import the necessary libraries, instantiate a client to connect to the XRPL, and fund two new accounts (**Issuer** and **Escrow Creator**). This example imports:
{% tabs %}
{% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `five-bells-condition` and `crypto`: Used to generate a crypto-condition.
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" before="// ====== Conditional MPT Escrow ======" /%}
{% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os` and `cryptoconditions`: Used to generate a crypto-condition.
- `datetime` and `time`: Used for time calculations.
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" before="# ====== Conditional MPT Escrow ======" /%}
{% /tab %}
{% tab label="Go" %}
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
- `encoding/json`, `strings`, and `fmt`: Used for formatting and printing results to the console.
- `cryptoconditions`, `crypto/rand` and `encoding/hex`: Used to generate a crypto-condition.
- `time`: Used for time calculations.
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" before="// ====== Conditional MPT Escrow ======" /%}
{% /tab %}
{% /tabs %}
### 3. Issue an MPT with escrow support
Construct an [MPTokenIssuanceCreate transaction][] with the `tfMPTCanEscrow` flag, which enables the token to be held in escrows. Then, retrieve the MPT issuance ID from the transaction result. This example creates an escrow that sends MPTs back to the original issuer. If you wanted to create an escrow for another account, the issuer would also have to set the `tfMPTCanTransfer` flag.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// ====== Conditional MPT Escrow ======" before="// Escrow Creator authorizes the MPT" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# ====== Conditional MPT Escrow ======" before="# Escrow Creator authorizes the MPT" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// ====== Conditional MPT Escrow ======" before="// Escrow Creator authorizes the MPT" /%}
{% /tab %}
{% /tabs %}
### 4. Authorize the MPT
Before the escrow creator can hold the MPT, they must indicate their willingness to hold it with the [MPTokenAuthorize transaction][].
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Escrow Creator authorizes the MPT" before="// Issuer sends MPTs to escrow creator" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Escrow Creator authorizes the MPT" before="# Issuer sends MPTs to escrow creator" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Escrow Creator authorizes the MPT" before="// Issuer sends MPTs to escrow creator" /%}
{% /tab %}
{% /tabs %}
### 5. Send MPTs to the escrow creator
Send MPTs from the issuer to the escrow creator using a [Payment transaction][].
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Issuer sends MPTs to escrow creator" before="// Escrow Creator creates a conditional MPT escrow" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Issuer sends MPTs to escrow creator" before="# Escrow Creator creates a conditional MPT escrow" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Issuer sends MPTs to escrow creator" before="// Escrow Creator creates a conditional MPT escrow" /%}
{% /tab %}
{% /tabs %}
### 6. Create a condition and fulfillment
Conditional escrows require a fulfillment and its corresponding condition in the format of a PREIMAGE-SHA-256 _crypto-condition_, represented as hexadecimal. To calculate these in the correct format, use a crypto-conditions library. Generally, you want to generate the fulfillment using at least 32 random bytes from a cryptographically secure source of randomness.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Escrow Creator creates a conditional MPT escrow" before="// Set expiration" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Escrow Creator creates a conditional MPT escrow" before="# Set expiration" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Escrow Creator creates a conditional MPT escrow" before="// Set expiration" /%}
{% /tab %}
{% /tabs %}
### 7. Create the conditional MPT escrow
Create a conditional escrow using the generated crypto-condition. Fungible token escrows require an expiration date. This example sets an expiration time of five minutes. After creating the escrow, save the sequence number to reference it later.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Set expiration" before="// Finish the conditional MPT escrow" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Set expiration" before="# Finish the conditional MPT escrow" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Set expiration" before="// Finish the conditional MPT escrow" /%}
{% /tab %}
{% /tabs %}
### 8. Finish the conditional MPT escrow
Finish the escrow by providing the original condition and its matching fulfillment.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Finish the conditional MPT escrow" before="// ====== Timed Trust Line Token Escrow ======" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Finish the conditional MPT escrow" before="# ====== Timed Trust Line Token Escrow ======" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Finish the conditional MPT escrow" before="// ====== Timed Trust Line Token Escrow ======" /%}
{% /tab %}
{% /tabs %}
### 9. Enable trust line token escrows
Token issuers enable trust line token escrows differently from MPTs. Unlike MPTs, which are escrowable at the token level, trust line tokens are escrowable at the account level. When an issuer enables the `asfAllowTrustLineLocking` flag on their account, _all_ trust line tokens issued from that account are escrowable.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// ====== Timed Trust Line Token Escrow ======" before="// Escrow Creator sets up a trust line" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# ====== Timed Trust Line Token Escrow ======" before="# Escrow Creator sets up a trust line" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// ====== Timed Trust Line Token Escrow ======" before="// Escrow Creator sets up a trust line" /%}
{% /tab %}
{% /tabs %}
### 10. Set up a trust line
Establish a trust line between the escrow creator and issuer using the [TrustSet transaction][]. The escrow creator submits this transaction to indicate their willingness to receive the token, defining the currency and maximum amount they're willing to hold.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Escrow Creator sets up a trust line" before="// Issuer sends IOU tokens to creator" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Escrow Creator sets up a trust line" before="# Issuer sends IOU tokens to creator" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Escrow Creator sets up a trust line" before="// Issuer sends IOU tokens to creator" /%}
{% /tab %}
{% /tabs %}
### 11. Send IOU tokens to the escrow creator
Send IOU tokens from the issuer to the escrow creator using a [Payment transaction][].
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Issuer sends IOU tokens to creator" before="// Escrow Creator creates a timed trust line token escrow" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Issuer sends IOU tokens to creator" before="# Escrow Creator creates a timed trust line token escrow" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Issuer sends IOU tokens to creator" before="// Escrow Creator creates a timed trust line token escrow" /%}
{% /tab %}
{% /tabs %}
### 12. Create a timed trust line token escrow
To make a timed escrow, set the maturity time of the escrow, which is a timestamp formatted as [seconds since the Ripple Epoch][]. This example sets a maturity time of ten seconds from the time the code executes. Since it is a fungible token escrow, it also sets an expiration time of five minutes. After submitting the [EscrowCreate transaction][], save the sequence number from the transaction result.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Escrow Creator creates a timed trust line token escrow" before="// Wait for the escrow to mature" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Escrow Creator creates a timed trust line token escrow" before="# Wait for the escrow to mature" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Escrow Creator creates a timed trust line token escrow" before="// Wait for the escrow to mature" /%}
{% /tab %}
{% /tabs %}
### 13. Wait for escrow to mature and finish
Wait for the escrow to mature. Before submitting the [EscrowFinish][] transaction, the code checks the current validated ledger to confirm the close time is after the escrow maturation time. This check ensures the escrow is matured on a validated ledger before trying to finish it.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/escrow/js/sendFungibleTokenEscrow.js" language="js" from="// Wait for the escrow to mature" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/escrow/py/send_fungible_token_escrow.py" language="py" from="# Wait for the escrow to mature" /%}
{% /tab %}
{% tab label="Go" %}
{% code-snippet file="/_code-samples/escrow/go/send-fungible-token-escrow/main.go" language="go" from="// Wait for the escrow to mature" /%}
{% /tab %}
{% /tabs %}
## See Also
**Concepts**:
- [Escrow](../../concepts/payment-types/escrow.md)
- [Multi-Purpose Tokens](../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md)
- [Trust Line Tokens](../../concepts/tokens/fungible-tokens/trust-line-tokens.md)
**Tutorials**:
- [Look up Escrows](./look-up-escrows.md)
- [Cancel an Expired Escrow](./cancel-an-expired-escrow.md)
**References**:
- [EscrowCreate transaction][]
- [EscrowFinish transaction][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}

276
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@codemirror/state": "6.5.2",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.22.2",
"@lezer/highlight": "^1.2.0",
"@redocly/realm": "0.131.2",
@@ -458,15 +458,6 @@
"@lezer/common": "^1.1.0"
}
},
"node_modules/@codemirror/commands/node_modules/@codemirror/state": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@codemirror/lang-css": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz",
@@ -575,9 +566,9 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.2.tgz",
"integrity": "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==",
"version": "6.12.3",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz",
"integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
@@ -620,9 +611,9 @@
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
@@ -641,9 +632,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.40.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.40.0.tgz",
"integrity": "sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==",
"version": "6.41.0",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.41.0.tgz",
"integrity": "sha512-6H/qadXsVuDY219Yljhohglve8xf4B8xJkVOEWfA5uiYKiTFppjqsvsfR5iPA0RbvRBoOyTZpbLIxe9+0UR8xA==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.6.0",
@@ -652,15 +643,6 @@
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@codemirror/view/node_modules/@codemirror/state": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz",
"integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -1534,9 +1516,9 @@
}
},
"node_modules/@libsql/darwin-arm64": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.22.tgz",
"integrity": "sha512-4B8ZlX3nIDPndfct7GNe0nI3Yw6ibocEicWdC4fvQbSs/jdq/RC2oCsoJxJ4NzXkvktX70C1J4FcmmoBy069UA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz",
"integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==",
"cpu": [
"arm64"
],
@@ -1547,9 +1529,9 @@
]
},
"node_modules/@libsql/darwin-x64": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.22.tgz",
"integrity": "sha512-ny2HYWt6lFSIdNFzUFIJ04uiW6finXfMNJ7wypkAD8Pqdm6nAByO+Fdqu8t7sD0sqJGeUCiOg480icjyQ2/8VA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz",
"integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==",
"cpu": [
"x64"
],
@@ -1591,9 +1573,9 @@
}
},
"node_modules/@libsql/linux-arm-gnueabihf": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.22.tgz",
"integrity": "sha512-3Uo3SoDPJe/zBnyZKosziRGtszXaEtv57raWrZIahtQDsjxBVjuzYQinCm9LRCJCUT5t2r5Z5nLDPJi2CwZVoA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz",
"integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==",
"cpu": [
"arm"
],
@@ -1604,9 +1586,9 @@
]
},
"node_modules/@libsql/linux-arm-musleabihf": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.22.tgz",
"integrity": "sha512-LCsXh07jvSojTNJptT9CowOzwITznD+YFGGW+1XxUr7fS+7/ydUrpDfsMX7UqTqjm7xG17eq86VkWJgHJfvpNg==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz",
"integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==",
"cpu": [
"arm"
],
@@ -1617,9 +1599,9 @@
]
},
"node_modules/@libsql/linux-arm64-gnu": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.22.tgz",
"integrity": "sha512-KSdnOMy88c9mpOFKUEzPskSaF3VLflfSUCBwas/pn1/sV3pEhtMF6H8VUCd2rsedwoukeeCSEONqX7LLnQwRMA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz",
"integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==",
"cpu": [
"arm64"
],
@@ -1630,9 +1612,9 @@
]
},
"node_modules/@libsql/linux-arm64-musl": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.22.tgz",
"integrity": "sha512-mCHSMAsDTLK5YH//lcV3eFEgiR23Ym0U9oEvgZA0667gqRZg/2px+7LshDvErEKv2XZ8ixzw3p1IrBzLQHGSsw==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz",
"integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==",
"cpu": [
"arm64"
],
@@ -1643,9 +1625,9 @@
]
},
"node_modules/@libsql/linux-x64-gnu": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.22.tgz",
"integrity": "sha512-kNBHaIkSg78Y4BqAdgjcR2mBilZXs4HYkAmi58J+4GRwDQZh5fIUWbnQvB9f95DkWUIGVeenqLRFY2pcTmlsew==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz",
"integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==",
"cpu": [
"x64"
],
@@ -1656,9 +1638,9 @@
]
},
"node_modules/@libsql/linux-x64-musl": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.22.tgz",
"integrity": "sha512-UZ4Xdxm4pu3pQXjvfJiyCzZop/9j/eA2JjmhMaAhe3EVLH2g11Fy4fwyUp9sT1QJYR1kpc2JLuybPM0kuXv/Tg==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz",
"integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==",
"cpu": [
"x64"
],
@@ -1669,9 +1651,9 @@
]
},
"node_modules/@libsql/win32-x64-msvc": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.22.tgz",
"integrity": "sha512-Fj0j8RnBpo43tVZUVoNK6BV/9AtDUM5S7DF3LB4qTYg1LMSZqi3yeCneUTLJD6XomQJlZzbI4mst89yspVSAnA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz",
"integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==",
"cpu": [
"x64"
],
@@ -3361,9 +3343,9 @@
}
},
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.25.8",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.8.tgz",
"integrity": "sha512-9Rr+liiBmK4xzZHszL+twNRJApthqmITBwDP3emNTtTrkBFN4gHlqfp+nodKmoVt1+bUH1qQCtyqt+7dbDTHiw==",
"version": "4.25.9",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.9.tgz",
"integrity": "sha512-QFAqr+pu6lDmNpAlecODcF49TlsrZ0bj15zPzfhiqSDl+Um3EsDLFLppixC7kFLn+rdDM2LTvVjn5CPvefpRgw==",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
@@ -3388,21 +3370,21 @@
}
},
"node_modules/@uiw/codemirror-theme-material": {
"version": "4.25.8",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.25.8.tgz",
"integrity": "sha512-VlSkd2ZgQ9YXkW0x3fPssyngrPVhy6XAx84wWO55yU/VQHGIHxTr1/0gF2eEcKOy4M/7aQ4EtYzAVVJlSvrUoA==",
"version": "4.25.9",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-theme-material/-/codemirror-theme-material-4.25.9.tgz",
"integrity": "sha512-6f2x+gmj2hHagqy6VkpnPbK7SWyP6kKruGgqpyIy09/f9pAUCqkW8mRY5ZEr28tA+YEGQaSY0Z2IBCHl8OKJog==",
"license": "MIT",
"dependencies": {
"@uiw/codemirror-themes": "4.25.8"
"@uiw/codemirror-themes": "4.25.9"
},
"funding": {
"url": "https://jaywcjlove.github.io/#/sponsor"
}
},
"node_modules/@uiw/codemirror-theme-material/node_modules/@uiw/codemirror-themes": {
"version": "4.25.8",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.8.tgz",
"integrity": "sha512-U6ZSO9A+nsN8zvNddtwhxxpi33J9okb4Li9HdhAItApKjYM22IgC8XSpGfs+ABGfsp1u6NhDSfBR9vAh3oTWXg==",
"version": "4.25.9",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.25.9.tgz",
"integrity": "sha512-DAHKb/L9ELwjY4nCf/MP/mIllHOn4GQe7RR4x8AMJuNeh9nGRRoo1uPxrxMmUL/bKqe6kDmDbIZ2AlhlqyIJuw==",
"license": "MIT",
"dependencies": {
"@codemirror/language": "^6.0.0",
@@ -3438,16 +3420,16 @@
}
},
"node_modules/@uiw/react-codemirror": {
"version": "4.25.8",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.8.tgz",
"integrity": "sha512-A0aLOuJZm2yJ+U9GlMFwxwFciztjd5LhcAG4SMqFxdD58wH+sCQXuY4UU5J2hqgS390qAlShtUgREvJPUonbuQ==",
"version": "4.25.9",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.9.tgz",
"integrity": "sha512-HftqCBUYShAOH0pGi1CHP8vfm5L8fQ3+0j0VI6lQD6QpK+UBu3J7nxfEN5O/BXMilMNf9ZyFJRvRcuMMOLHMng==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.25.8",
"@uiw/codemirror-extensions-basic-setup": "4.25.9",
"codemirror": "^6.0.0"
},
"funding": {
@@ -3527,6 +3509,7 @@
"version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
"deprecated": "this version has critical issues, please update to the latest version",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -3557,12 +3540,12 @@
}
},
"node_modules/@xyflow/react": {
"version": "12.10.1",
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz",
"integrity": "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q==",
"version": "12.10.2",
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz",
"integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==",
"license": "MIT",
"dependencies": {
"@xyflow/system": "0.0.75",
"@xyflow/system": "0.0.76",
"classcat": "^5.0.3",
"zustand": "^4.4.0"
},
@@ -3572,9 +3555,9 @@
}
},
"node_modules/@xyflow/system": {
"version": "0.0.75",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.75.tgz",
"integrity": "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ==",
"version": "0.0.76",
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz",
"integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==",
"license": "MIT",
"dependencies": {
"@types/d3-drag": "^3.0.7",
@@ -3799,14 +3782,14 @@
}
},
"node_modules/axios": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
"integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
"proxy-from-env": "^2.1.0"
}
},
"node_modules/babel-plugin-macros": {
@@ -3850,9 +3833,9 @@
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.8",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz",
"integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==",
"version": "2.10.13",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz",
"integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
@@ -3940,9 +3923,9 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
@@ -3964,9 +3947,9 @@
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"version": "4.28.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
"funding": [
{
"type": "opencollective",
@@ -3983,11 +3966,11 @@
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
"electron-to-chromium": "^1.5.328",
"node-releases": "^2.0.36",
"update-browserslist-db": "^1.2.3"
},
"bin": {
"browserslist": "cli.js"
@@ -4083,9 +4066,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001780",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
"integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
"version": "1.0.30001784",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz",
"integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==",
"funding": [
{
"type": "opencollective",
@@ -4837,9 +4820,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.321",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
"integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
"version": "1.5.331",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz",
"integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==",
"license": "ISC"
},
"node_modules/emoji-regex": {
@@ -6118,9 +6101,9 @@
}
},
"node_modules/libsql": {
"version": "0.5.22",
"resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.22.tgz",
"integrity": "sha512-NscWthMQt7fpU8lqd7LXMvT9pi+KhhmTHAJWUB/Lj6MWa0MKFv0F2V4C6WKKpjCVZl0VwcDz4nOI3CyaT1DDiA==",
"version": "0.5.29",
"resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.29.tgz",
"integrity": "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==",
"cpu": [
"x64",
"arm64",
@@ -6138,15 +6121,15 @@
"detect-libc": "2.0.2"
},
"optionalDependencies": {
"@libsql/darwin-arm64": "0.5.22",
"@libsql/darwin-x64": "0.5.22",
"@libsql/linux-arm-gnueabihf": "0.5.22",
"@libsql/linux-arm-musleabihf": "0.5.22",
"@libsql/linux-arm64-gnu": "0.5.22",
"@libsql/linux-arm64-musl": "0.5.22",
"@libsql/linux-x64-gnu": "0.5.22",
"@libsql/linux-x64-musl": "0.5.22",
"@libsql/win32-x64-msvc": "0.5.22"
"@libsql/darwin-arm64": "0.5.29",
"@libsql/darwin-x64": "0.5.29",
"@libsql/linux-arm-gnueabihf": "0.5.29",
"@libsql/linux-arm-musleabihf": "0.5.29",
"@libsql/linux-arm64-gnu": "0.5.29",
"@libsql/linux-arm64-musl": "0.5.29",
"@libsql/linux-x64-gnu": "0.5.29",
"@libsql/linux-x64-musl": "0.5.29",
"@libsql/win32-x64-msvc": "0.5.29"
}
},
"node_modules/lie": {
@@ -6165,9 +6148,9 @@
"license": "MIT"
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash.debounce": {
@@ -6599,9 +6582,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"version": "2.0.37",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz",
"integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==",
"license": "MIT"
},
"node_modules/normalize-path": {
@@ -6727,9 +6710,9 @@
}
},
"node_modules/openapi-sampler/node_modules/fast-xml-parser": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz",
"integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==",
"version": "5.5.9",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz",
"integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==",
"funding": [
{
"type": "github",
@@ -6739,8 +6722,8 @@
"license": "MIT",
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.1.3",
"strnum": "^2.1.2"
"path-expression-matcher": "^1.2.0",
"strnum": "^2.2.2"
},
"bin": {
"fxparser": "src/cli/cli.js"
@@ -6838,9 +6821,9 @@
"license": "MIT"
},
"node_modules/path-expression-matcher": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz",
"integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==",
"funding": [
{
"type": "github",
@@ -6986,10 +6969,13 @@
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/punycode": {
"version": "2.3.0",
@@ -7453,9 +7439,9 @@
"license": "MIT"
},
"node_modules/react18-json-view": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/react18-json-view/-/react18-json-view-0.2.9.tgz",
"integrity": "sha512-z3JQgCwZRKbmWh54U94loCU6vE0ZoDBK7C8ZpcMYQB8jYMi+mR/fcgMI9jKgATeF0I6+OAF025PD+UKkXIqueQ==",
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/react18-json-view/-/react18-json-view-0.2.10.tgz",
"integrity": "sha512-rYEbaCG/U4THY1qp1xY14/Kbnp9yY3W6Qm3Rmu+jlCIdxzMS5EcD+wI97kCKRoN3CuJyJU8hqkax5xWfl8A4EA==",
"license": "MIT",
"dependencies": {
"copy-to-clipboard": "^3.3.3"
@@ -7907,18 +7893,18 @@
"license": "MIT"
},
"node_modules/slugify": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.8.tgz",
"integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==",
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz",
"integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/smol-toml": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz",
"integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz",
"integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 18"
@@ -8060,9 +8046,9 @@
}
},
"node_modules/strnum": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz",
"integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==",
"funding": [
{
"type": "github",
@@ -8746,9 +8732,9 @@
}
},
"node_modules/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View File

@@ -12,7 +12,7 @@
"keywords": [],
"license": "MIT",
"dependencies": {
"@codemirror/state": "6.5.2",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.22.2",
"@lezer/highlight": "^1.2.0",
"@redocly/realm": "0.131.2",

View File

@@ -28,10 +28,18 @@ responseHeaders:
value: noindex
search:
engine: typesense
ai:
hide: true
filters:
hide: true
aiAssistant:
hide: false
mcp:
hide: false
docs:
hide: false
name: "xrpl.org MCP server"
ignore:
- ja/**
- es-es/**
metadataGlobs:
'docs/**':
redocly_category: Documentation

View File

@@ -23,6 +23,7 @@ The image files used in blog posts are located in the `blog/img` directory.
The blog posts are grouped by year, so all blog posts published in year 2025 are located in the `blog/2025` directory.
## Steps to Create a New Blog Post
To create a new post, follow these steps:
@@ -39,4 +40,43 @@ To create a new post, follow these steps:
6. When the draft is ready for review, save and commit your updates.
7. Create a new PR to merge your changes to master.
7. Create a new PR to merge your changes to master.
### Release Notes
We have streamlined the release notes process with the assistance of **Claude** skills. To create new release notes, follow these steps:
1. Load the `generate-release-notes` skill. **Claude** _should_ auto-load it when you set the working directory to the `xrpl-dev-portal` repository. If not, you can explicitly point to the skill located at `.claude/skills/generate-release-notes/SKILL.md`.
{% admonition type="info" name="Note" %}Although the skill is optimized for **Claude**, you can give this `SKILL.md` file to any coding agent.{% /admonition %}
2. Run the `generate-release-notes` skill.
```bash
/generate-release-notes --from <branch-or-tag> --to <branch-or-tag>
```
The skill accepts these arguments:
| Arguments | Description |
|:-----------|:------------|
| `--from` | (required) Base ref. Must match the exact [tag or branch](https://github.com/XRPLF/rippled/branches) the current version of `rippled` is building from. |
| `--to` | (required) Target ref. Must match the exact [tag or branch](https://github.com/XRPLF/rippled/branches) the upcoming version of `rippled` will be built from. |
| `--date` | (optional) Release date in `YYYY-MM-DD` format. Defaults to current date. |
| `--output` | (optional) Output file path. Defaults to `blog/<year>/rippled-<version>.md`. |
3. The AI executes these steps:
- Runs a Python script that fetches all commits and PR details between the two refs and outputs a draft blog post.
- Processes all amendment-related changes, keeping, removing, or merging entries based on which amendments are part of the release.
- Sorts remaining entries into subsections (Features, Breaking Changes, Bug Fixes, etc.) based on files touched, PR labels, and descriptions.
- Reformats each entry to match the release notes style and writes a summary of the release.
- Cleans up empty sections and adds the post to the sidebar.
{% admonition type="info" name="Note" %}Depending on the size of the release, this process can take upwards of ten minutes.{% /admonition %}
4. Review the final output.
- Check the descriptions of any entries in **Amendments**, **Features**, or **Breaking Changes**.
- Verify the integrity of the package links and add in the SHA-256 hashes.
- Verify the commit linked in the **Install / Upgrade** section points to the version bump commit.
- If you didn't pass in the correct release date using the `--date` argument, update the `date` field in the frontmatter.
5. Create a new PR to merge the release notes to `master`.

View File

@@ -23,6 +23,11 @@ The following is a list of [amendments](../docs/concepts/networks-and-servers/am
|:----------------------------------|:------------------------------------------|:-------------------------------|
| [Hooks][] | {% badge %}In Development: TBD{% /badge %} | [XRPL Hooks](https://hooks.xrpl.org/) |
| [InvariantsV1_1][] | {% badge %}In Development: TBD{% /badge %} | |
| [DynamicMPT][] | {% badge %}In Development: TBD{% /badge %} | [XLS-94 Dynamic MPTs](https://opensource.ripple.com/docs/xls-94-dynamic-mpts) |
| [ConfidentialTransfer][] | {% badge %}In Development: TBD{% /badge %} | [XLS-96 Confidential Transfers](https://opensource.ripple.com/docs/xls-96-confidential-transfers) |
| [MPTokensV2][] | {% badge %}In Development: TBD{% /badge %} | [XLS-82 MPT DEX Integration](https://opensource.ripple.com/docs/xls-82-mpt-dex) |
| [Sponsor][] | {% badge %}In Development: TBD{% /badge %} | [XLS-68 Sponsored Fees and Reserves](https://opensource.ripple.com/docs/xls-68-sponsored-fees-and-reserves) |
| [SmartEscrow][] | {% badge %}In Development: TBD{% /badge %} | [XLS-100 Smart Escrows](https://opensource.ripple.com/docs/xls-100-smart-escrows) |
{% admonition type="success" name="Tip" %}
This list is updated manually. If you're working on an amendment and have a private network to test the changes, you can edit this page to add your in-development amendment to this list. For more information on contributing to the XRP Ledger, see [Contribute Code to the XRP Ledger](contribute-code/index.md).
@@ -203,7 +208,22 @@ Modifies an existing type of ledger entry:
Also extends the `deposit_authorized` API method to check for credential-based auth and extends the `ledger_entry` method to allow lookup of Credential entries.
For more details, see the [XLS-70: Credentials specification](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0070-credentials).
For more details, see [XLS-70: Credentials specification](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0070-credentials).
### ConfidentialTransfer
[ConfidentialTransfer]: #confidentialtransfer
| Amendment | ConfidentialTransfer |
|:-------------|:---------------------|
| Amendment ID | 2110E4A19966E2EF517C0A8C56A5F35099D7665B0BB89D7B126B30D50B86AAD5 |
| Status | In Development |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
Provides institutional-grade privacy for Multi-Purpose Tokens (MPTs) using advanced cryptography (EC-ElGamal and ZKPs). Individual balances and transfer amounts remain shielded from the public ledger while maintaining compliance mechanisms for authorized parties (issuers, auditors, or designated entities) to verify total supply and meet regulatory obligations.
For more details, see [XLS-96: Confidential Transfers](https://opensource.ripple.com/docs/xls-96-confidential-transfers).
### CryptoConditions
@@ -371,6 +391,21 @@ Adds functionality to update the `URI` field of an `NFToken` ledger entry. This
2. `tfMutable`: New flag that enables authorized accounts to modify the `URI` of an NFT. This flag must be enabled when the NFT is initially minted.
### DynamicMPT
[DynamicMPT]: #dynamicmpt
| Amendment | DynamicMPT |
|:-------------|:-----------|
| Amendment ID | 58E92F338758479C06084E1B6BA366BAD8F75E5329A7F0EEAFFFDA51E5106B7F |
| Status | In Development |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
Extends Multi-Purpose Tokens to allow issuers to designate specific properties as mutable during token creation, enabling selected attributes to be updated later as business needs change.
For more details, see [XLS-94: Dynamic MPTs](https://opensource.ripple.com/docs/xls-94-dynamic-mpts).
### EnforceInvariants
[EnforceInvariants]: #enforceinvariants
@@ -1532,6 +1567,21 @@ Implements a new type of fungible token, called a _Multi-Purpose Token_ (MPT). T
- (Updated) `ledger_entry` method - Can look up MPToken and MPTokenIssuance ledger entry types.
### MPTokensV2
[MPTokensV2]: #mptokensv2
| Amendment | MPTokensV2 |
|:-------------|:-----------|
| Amendment ID | BE2D87DF21B690ED1497B593FDC013CC04276302380B1BD50A033DCF8DEFB2EB |
| Status | In Development |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
Extends the XRPL's Decentralized Exchange to natively support Multi-Purpose Tokens (MPTs) as a tradeable asset class. MPTs can be paired with XRP, Trust Line tokens, or other MPTs across existing DEX transactions such as OfferCreate, Payment, AMM, and Checks.
For more details, see [XLS-82: MPT DEX Integration](https://opensource.ripple.com/docs/xls-82-mpt-dex).
### MultiSign
[MultiSign]: #multisign
@@ -1826,6 +1876,21 @@ Creates a structure for aggregating assets from multiple depositors. This is int
Specification: [XLS-65](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0065-single-asset-vault).
### SmartEscrow
[SmartEscrow]: #smartescrow
| Amendment | SmartEscrow |
|:-------------|:--------------|
| Amendment ID | 78ECD9CE17B0BF5B83BB3B275921FB5F5E0F672E9D24BD2E848B7C6277AE296E |
| Status | In Development |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
The Smart Escrows amendment introduces a new programmability layer to the XRPL, powered by a WebAssembly (WASM) engine. Developers can write custom functions that control when an escrow can be finished.
For more details, see [XLS-100: Smart Escrows](https://opensource.ripple.com/docs/xls-100-smart-escrows).
### SortedDirectories
[SortedDirectories]: #sorteddirectories
@@ -1841,6 +1906,21 @@ Sorts the entries in [DirectoryNode ledger objects](../docs/references/protocol/
{% admonition type="danger" name="Warning" %}Older versions of `rippled` that do not know about this amendment may crash when they find a DirectoryNode sorted by the new rules. To avoid this problem, [upgrade](../docs/infrastructure/installation/index.md) to `rippled` version 0.80.0 or later.{% /admonition %}
### Sponsor
[Sponsor]: #sponsor
| Amendment | Sponsor |
|:-------------|:--------|
| Amendment ID | BE1F90581635DBCEBFC4678C4B54FEDDC1A17B50FD02CFE765A4132A342126AC |
| Status | In Development |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
The Sponsor amendment removes onboarding friction by allowing companies, token issuers, and other entities to subsidize transaction costs and reserve requirements for end users. Sponsors can co-sign transactions or pre-fund sponsorships, covering fees and reserves, while sponsees retain full control of their accounts and keys.
For more details, see [XLS-68: Sponsored Fees and Reserves](https://opensource.ripple.com/docs/xls-68-sponsored-fees-and-reserves).
### SusPay
[SusPay]: #suspay

View File

@@ -251,6 +251,7 @@
- page: docs/tutorials/payments/create-trust-line-send-currency-in-python.md
- page: docs/tutorials/payments/send-a-conditional-escrow.md
- page: docs/tutorials/payments/send-a-timed-escrow.md
- page: docs/tutorials/payments/send-fungible-token-escrows.md
- page: docs/tutorials/payments/look-up-escrows.md
- page: docs/tutorials/payments/cancel-an-expired-escrow.md
- page: docs/tutorials/payments/send-a-check.md
@@ -296,6 +297,7 @@
expanded: false
items:
- page: docs/tutorials/compliance-features/require-destination-tags.md
- page: docs/tutorials/compliance-features/manage-credentials.md
- page: docs/tutorials/compliance-features/verify-credentials.md
- page: docs/tutorials/compliance-features/create-permissioned-domains-in-javascript.md
- group: Programmability

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,697 @@
"""
Generate rippled release notes from GitHub commit history.
Usage (from repo root):
python3 tools/generate-release-notes.py --from release-3.0 --to release-3.1 [--date 2026-03-24] [--output path/to/file.md]
Arguments:
--from (required) Base ref — must match exact tag or branch to compare from.
--to (required) Target ref — must match exact tag or branch to compare to.
--date (optional) Release date in YYYY-MM-DD format. Defaults to today.
--output (optional) Output file path. Defaults to blog/<year>/rippled-<version>.md.
Requires: gh CLI (authenticated)
"""
import argparse
import base64
import json
import os
import re
import subprocess
import sys
from datetime import date, datetime
# Emails to exclude from credits (Ripple employees not using @ripple.com).
# Commits from @ripple.com addresses are already filtered automatically.
EXCLUDED_EMAILS = {
"3maisons@gmail.com", # Luc des Trois Maisons
"a1q123456@users.noreply.github.com", # Jingchen Wu
"bthomee@users.noreply.github.com", # Bart Thomee
"21219765+ckeshava@users.noreply.github.com", # Chenna Keshava B S
"gregtatcam@users.noreply.github.com", # Gregory Tsipenyuk
"kuzzz99@gmail.com", # Sergey Kuznetsov
"legleux@users.noreply.github.com", # Michael Legleux
"mathbunnyru@users.noreply.github.com", # Ayaz Salikhov
"mvadari@gmail.com", # Mayukha Vadari
"115580134+oleks-rip@users.noreply.github.com", # Oleksandr Pidskopnyi
"3397372+pratikmankawde@users.noreply.github.com", # Pratik Mankawde
"35279399+shawnxie999@users.noreply.github.com", # Shawn Xie
"5780819+Tapanito@users.noreply.github.com", # Vito Tumas
"13349202+vlntb@users.noreply.github.com", # Valentin Balaschenko
"129996061+vvysokikh1@users.noreply.github.com", # Vladislav Vysokikh
"vvysokikh@gmail.com", # Vladislav Vysokikh
}
# Pre-compiled patterns for skipping version commits
SKIP_PATTERNS = [
re.compile(r"^Set version to", re.IGNORECASE),
re.compile(r"^Version \d", re.IGNORECASE),
re.compile(r"bump version to", re.IGNORECASE),
re.compile(r"^Merge tag ", re.IGNORECASE),
]
# --- API helpers ---
def run_gh_rest(endpoint):
"""Run a gh api REST command and return parsed JSON."""
result = subprocess.run(
["gh", "api", endpoint],
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f"Error running gh api: {result.stderr}", file=sys.stderr)
sys.exit(1)
return json.loads(result.stdout)
def run_gh_graphql(query):
"""Run a gh api graphql command and return parsed JSON.
Handles partial failures (e.g., missing PRs) by returning
whatever data is available alongside errors.
"""
result = subprocess.run(
["gh", "api", "graphql", "-f", f"query={query}"],
capture_output=True,
text=True,
)
try:
return json.loads(result.stdout)
except (json.JSONDecodeError, TypeError):
print(f"Error running graphql: {result.stderr}", file=sys.stderr)
sys.exit(1)
def fetch_commit_files(sha):
"""Fetch list of files changed in a commit via REST API.
Returns empty list on failure instead of exiting.
"""
result = subprocess.run(
["gh", "api", f"repos/XRPLF/rippled/commits/{sha}"],
capture_output=True,
text=True,
)
if result.returncode != 0:
print(f" Warning: Could not fetch files for commit {sha[:7]}", file=sys.stderr)
return []
data = json.loads(result.stdout)
return [f["filename"] for f in data.get("files", [])]
# --- Data fetching ---
def fetch_version_info(ref):
"""Fetch version string and version-setting commit info in a single GraphQL call.
Returns (version_string, formatted_commit_block).
"""
data = run_gh_graphql(f"""
{{
repository(owner: "XRPLF", name: "rippled") {{
file: object(expression: "{ref}:src/libxrpl/protocol/BuildInfo.cpp") {{
... on Blob {{ text }}
}}
ref: object(expression: "{ref}") {{
... on Commit {{
history(first: 1, path: "src/libxrpl/protocol/BuildInfo.cpp") {{
nodes {{
oid
message
author {{
name
email
date
}}
}}
}}
}}
}}
}}
}}
""")
repo = data.get("data", {}).get("repository", {})
# Extract version string from BuildInfo.cpp
file_text = (repo.get("file") or {}).get("text", "")
match = re.search(r'versionString\s*=\s*"([^"]+)"', file_text)
if not match:
print("Warning: Could not find versionString in BuildInfo.cpp. Using placeholder.", file=sys.stderr)
version = match.group(1) if match else "TODO"
# Extract version commit info
nodes = (repo.get("ref") or {}).get("history", {}).get("nodes", [])
if not nodes:
commit_block = "commit TODO\nAuthor: TODO\nDate: TODO\n\n Set version to TODO"
else:
commit = nodes[0]
raw_date = commit["author"]["date"]
try:
dt = datetime.fromisoformat(raw_date)
formatted_date = dt.strftime("%a %b %-d %H:%M:%S %Y %z")
except ValueError:
formatted_date = raw_date
name = commit["author"]["name"]
email = commit["author"]["email"]
sha = commit["oid"]
message = commit["message"].split("\n")[0]
commit_block = f"commit {sha}\nAuthor: {name} <{email}>\nDate: {formatted_date}\n\n {message}"
return version, commit_block
def fetch_commits(from_ref, to_ref):
"""Fetch all commits between two refs using the GitHub compare API."""
commits = []
page = 1
while True:
data = run_gh_rest(
f"repos/XRPLF/rippled/compare/{from_ref}...{to_ref}?per_page=250&page={page}"
)
batch = data.get("commits", [])
commits.extend(batch)
if len(batch) < 250:
break
page += 1
return commits
def parse_features_macro(text):
"""Parse features.macro into {amendment_name: status_string} dict."""
results = {}
for match in re.finditer(
r'XRPL_(FEATURE|FIX)\s*\(\s*(\w+)\s*,\s*Supported::(\w+)\s*,\s*VoteBehavior::(\w+)', text):
macro_type, name, supported, vote = match.groups()
key = f"fix{name}" if macro_type == "FIX" else name
results[key] = f"{supported}, {vote}"
for match in re.finditer(r'XRPL_RETIRE(?:_(FEATURE|FIX))?\s*\(\s*(\w+)\s*\)', text):
macro_type, name = match.groups()
key = f"fix{name}" if macro_type == "FIX" else name
results[key] = "retired"
return results
def fetch_amendment_diff(from_ref, to_ref):
"""Compare features.macro between two refs to find amendment changes.
Returns (changes, unchanged) where:
- changes: {name: True/False} for amendments that changed status
- unchanged: {name: True/False} for amendments with no status change
True = include; False = exclude
"""
macro_path = "repos/XRPLF/rippled/contents/include/xrpl/protocol/detail/features.macro"
from_data = run_gh_rest(f"{macro_path}?ref={from_ref}")
from_text = base64.b64decode(from_data["content"]).decode()
from_amendments = parse_features_macro(from_text)
to_data = run_gh_rest(f"{macro_path}?ref={to_ref}")
to_text = base64.b64decode(to_data["content"]).decode()
to_amendments = parse_features_macro(to_text)
changes = {}
for name, to_status in to_amendments.items():
if name not in from_amendments:
# New amendment — include only if Supported::yes
changes[name] = to_status.startswith("yes")
elif from_amendments[name] != to_status:
# Include if either old or new status involves yes (voting-ready)
from_status = from_amendments[name]
changes[name] = from_status.startswith("yes") or to_status.startswith("yes")
# Removed amendments — include only if they were Supported::yes
for name in from_amendments:
if name not in to_amendments:
changes[name] = from_amendments[name].startswith("yes")
# Unchanged amendments to also exclude (unreleased work)
unchanged = sorted(
name for name, to_status in to_amendments.items()
if name not in changes and to_status != "retired" and not to_status.startswith("yes")
)
return changes, unchanged
def fetch_prs_graphql(pr_numbers):
"""Fetch PR details in batches using GitHub GraphQL API.
Falls back to issue lookup for numbers that aren't PRs.
Returns a dict of {number: {title, body, labels, files, type}}.
"""
results = {}
missing = []
batch_size = 50
pr_list = list(pr_numbers)
# Fetch PRs
for i in range(0, len(pr_list), batch_size):
batch = pr_list[i:i + batch_size]
fragments = []
for pr_num in batch:
fragments.append(f"""
pr{pr_num}: pullRequest(number: {pr_num}) {{
title
body
labels(first: 10) {{
nodes {{ name }}
}}
files(first: 100) {{
nodes {{ path }}
}}
}}
""")
query = f"""
{{
repository(owner: "XRPLF", name: "rippled") {{
{"".join(fragments)}
}}
}}
"""
data = run_gh_graphql(query)
repo_data = data.get("data", {}).get("repository", {})
for alias, pr_data in repo_data.items():
pr_num = int(alias.removeprefix("pr"))
if pr_data:
results[pr_num] = {
"title": pr_data["title"],
"body": clean_pr_body(pr_data.get("body") or ""),
"labels": [l["name"] for l in pr_data.get("labels", {}).get("nodes", [])],
"files": [f["path"] for f in pr_data.get("files", {}).get("nodes", [])],
"type": "pull",
}
else:
missing.append(pr_num)
print(f" Fetched {min(i + batch_size, len(pr_list))}/{len(pr_list)} PRs...")
# Fetch missing numbers as issues
if missing:
print(f" Looking up {len(missing)} missing PR numbers as Issues...")
for i in range(0, len(missing), batch_size):
batch = missing[i:i + batch_size]
fragments = []
for num in batch:
fragments.append(f"""
issue{num}: issue(number: {num}) {{
title
body
labels(first: 10) {{
nodes {{ name }}
}}
}}
""")
query = f"""
{{
repository(owner: "XRPLF", name: "rippled") {{
{"".join(fragments)}
}}
}}
"""
data = run_gh_graphql(query)
repo_data = data.get("data", {}).get("repository", {})
for alias, issue_data in repo_data.items():
if issue_data:
num = int(alias.removeprefix("issue"))
results[num] = {
"title": issue_data["title"],
"body": clean_pr_body(issue_data.get("body") or ""),
"labels": [l["name"] for l in issue_data.get("labels", {}).get("nodes", [])],
"type": "issues",
}
return results
# --- Utilities ---
def clean_pr_body(text):
"""Strip HTML comments and PR template boilerplate from body text."""
# Remove HTML comments
text = re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)
# Remove unchecked checkbox lines, keep checked ones
text = re.sub(r"^- \[ \] .+$", "", text, flags=re.MULTILINE)
# Remove all markdown headings
text = re.sub(r"^#{1,6} .+$", "", text, flags=re.MULTILINE)
# Convert bare GitHub URLs to markdown links
text = re.sub(r"(?<!\()https://github\.com/XRPLF/rippled/(pull|issues)/(\d+)(#[^\s)]*)?", r"[#\2](https://github.com/XRPLF/rippled/\1/\2\3)", text)
# Convert remaining bare PR/issue references (#1234) to full GitHub links
text = re.sub(r"(?<!\[)#(\d+)(?!\])", r"[#\1](https://github.com/XRPLF/rippled/pull/\1)", text)
# Collapse multiple blank lines into one
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip()
def extract_pr_number(commit_message):
"""Extract PR number from commit message like 'Title (#1234)'."""
match = re.search(r"#(\d+)", commit_message)
return int(match.group(1)) if match else None
def should_skip(title):
"""Check if a commit should be skipped."""
return any(pattern.search(title) for pattern in SKIP_PATTERNS)
def is_amendment(files):
"""Check if any file in the list is features.macro."""
return any("features.macro" in f for f in files)
# --- Formatting ---
def format_commit_entry(sha, title, body="", files=None):
"""Format an entry linked to a commit (no PR/Issue found)."""
short_sha = sha[:7]
url = f"https://github.com/XRPLF/rippled/commit/{sha}"
parts = [
f"- **{title.strip()}**",
f" - Link: [{short_sha}]({url})",
]
if files:
parts.append(f" - Files: {', '.join(files)}")
if body:
desc = re.sub(r"\s+", " ", clean_pr_body(body)).strip()
if desc:
parts.append(f" - Description: {desc}")
return "\n".join(parts)
def format_uncategorized_entry(pr_number, title, labels, body, files=None, link_type="pull"):
"""Format an uncategorized entry with full context for AI sorting."""
url = f"https://github.com/XRPLF/rippled/{link_type}/{pr_number}"
parts = [
f"- **{title.strip()}**",
f" - Link: [#{pr_number}]({url})",
]
if labels:
parts.append(f" - Labels: {', '.join(labels)}")
if files:
parts.append(f" - Files: {', '.join(files)}")
if body:
# Collapse to single line to prevent markdown formatting conflicts
desc = re.sub(r"\s+", " ", body).strip()
if desc:
parts.append(f" - Description: {desc}")
return "\n".join(parts)
def generate_markdown(version, release_date, amendment_diff, amendment_unchanged, amendment_entries, entries, authors, version_commit):
"""Generate the full markdown release notes."""
year = release_date.split("-")[0]
parts = []
parts.append(f"""---
category: {year}
date: "{release_date}"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing XRP Ledger version {version}
description: rippled version {version} is now available.
labels:
- rippled Release Notes
markdown:
editPage:
hide: true
---
# Introducing XRP Ledger version {version}
Version {version} of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available.
## Action Required
If you run an XRP Ledger server, upgrade to version {version} as soon as possible to ensure service continuity.
## Install / Upgrade
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
| Package | SHA-256 |
|:--------|:--------|
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-{version}-1.el9.x86_64.rpm) | `TODO` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_{version}-1_amd64.deb) | `TODO` |
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
{version_commit}
```
## Full Changelog
""")
# Amendments section (auto-sorted by features.macro detection with diff guidance for AI)
parts.append("\n### Amendments\n")
if amendment_diff or amendment_unchanged:
included = sorted(name for name, include in amendment_diff.items() if include)
excluded = sorted(name for name, include in amendment_diff.items() if not include)
comment_lines = ["<!-- Amendment sorting instructions. Remove this comment after sorting."]
if included:
comment_lines.append(f"Include: {', '.join(included)}")
if excluded:
comment_lines.append(f"Exclude: {', '.join(excluded)}")
if amendment_unchanged:
comment_lines.append(f"Other amendments not part of this release: {', '.join(amendment_unchanged)}")
comment_lines.append("-->")
parts.append("\n".join(comment_lines) + "\n")
for entry in amendment_entries:
parts.append(entry)
# Remaining empty subsection headings for manual/AI sorting
sections = [
"Features", "Breaking Changes", "Bug Fixes",
"Refactors", "Documentation", "Testing", "CI/Build",
]
for section in sections:
parts.append(f"\n### {section}\n")
# Credits
parts.append("\n\n## Credits\n")
if authors:
parts.append("The following RippleX teams and GitHub users contributed to this release:\n")
else:
parts.append("The following RippleX teams contributed to this release:\n")
parts.append("- RippleX Engineering")
parts.append("- RippleX Docs")
parts.append("- RippleX Product")
for author in sorted(authors):
parts.append(f"- {author}")
parts.append("""
## Bug Bounties and Responsible Disclosures
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
For more information, see:
- [Ripple's Bug Bounty Program](https://ripple.com/legal/bug-bounty/)
- [`rippled` Security Policy](https://github.com/XRPLF/rippled/blob/develop/SECURITY.md)
""")
# Unsorted entries with full context (after all published sections)
parts.append("<!-- Sort the entries below into the Full Changelog subsections. Remove this comment after sorting. -->\n")
for entry in entries:
parts.append(entry)
return "\n".join(parts)
# --- Main ---
def main():
parser = argparse.ArgumentParser(description="Generate rippled release notes")
parser.add_argument("--from", dest="from_ref", required=True, help="Base ref (tag or branch)")
parser.add_argument("--to", dest="to_ref", required=True, help="Target ref (tag or branch)")
parser.add_argument("--date", help="Release date (YYYY-MM-DD). Defaults to today.")
parser.add_argument("--output", help="Output file path (default: blog/<year>/rippled-<version>.md)")
args = parser.parse_args()
args.date = args.date or date.today().isoformat()
try:
date.fromisoformat(args.date)
except ValueError:
print(f"Error: Invalid date format '{args.date}'. Use YYYY-MM-DD.", file=sys.stderr)
sys.exit(1)
print(f"Fetching version info from {args.to_ref}...")
version, version_commit = fetch_version_info(args.to_ref)
print(f"Version: {version}")
year = args.date.split("-")[0]
output_path = args.output or f"blog/{year}/rippled-{version}.md"
print(f"Fetching commits: {args.from_ref}...{args.to_ref}")
commits = fetch_commits(args.from_ref, args.to_ref)
print(f"Found {len(commits)} commits")
# Extract unique PR (in rare cases Issues) numbers and track authors
pr_numbers = {}
pr_shas = {} # PR/issue number → commit SHA (for file lookups on Issues)
pr_bodies = {} # PR/issue number → commit body (for fallback descriptions)
orphan_commits = [] # Commits with no PR/Issues link
authors = set()
for commit in commits:
full_message = commit["commit"]["message"]
message = full_message.split("\n")[0]
body = "\n".join(full_message.split("\n")[1:]).strip()
sha = commit["sha"]
author = commit["commit"]["author"]["name"]
email = commit["commit"]["author"].get("email", "")
# Skip Ripple employees from credits
login = (commit.get("author") or {}).get("login")
if not email.lower().endswith("@ripple.com") and email not in EXCLUDED_EMAILS:
if login:
authors.add(f"@{login}")
else:
authors.add(author)
if should_skip(message):
continue
pr_number = extract_pr_number(message)
if pr_number:
pr_numbers[pr_number] = message
pr_shas[pr_number] = sha
pr_bodies[pr_number] = body
else:
orphan_commits.append({"sha": sha, "message": message, "body": body})
print(f"Unique PRs after filtering: {len(pr_numbers)}")
if orphan_commits:
print(f"Commits without PR or Issue linked: {len(orphan_commits)}")
# Fetch amendment diff between refs
print(f"Comparing features.macro between {args.from_ref} and {args.to_ref}...")
amendment_diff, amendment_unchanged = fetch_amendment_diff(args.from_ref, args.to_ref)
if amendment_diff:
for name, include in sorted(amendment_diff.items()):
status = "include" if include else "exclude"
print(f" Amendment {name}: {status}")
else:
print(" No amendment changes detected")
print(f"Building changelog entries...")
# Fetch all PR details in batches via GraphQL
pr_details = fetch_prs_graphql(list(pr_numbers.keys()))
# Build entries, sorting amendments automatically
amendment_entries = []
entries = []
for pr_number, commit_msg in pr_numbers.items():
pr_data = pr_details.get(pr_number)
if pr_data:
title = pr_data["title"]
body = pr_data.get("body", "")
labels = pr_data.get("labels", [])
files = pr_data.get("files", [])
link_type = pr_data.get("type", "pull")
# For issues (no files from GraphQL), fetch files from the commit
if not files and pr_number in pr_shas:
print(f" Building entry for Issue #{pr_number} via commit...")
files = fetch_commit_files(pr_shas[pr_number])
if is_amendment(files) and amendment_diff:
# Amendment entry — add to amendments section (AI will sort further)
entry = format_uncategorized_entry(pr_number, title, labels, body, link_type=link_type)
amendment_entries.append(entry)
else:
entry = format_uncategorized_entry(pr_number, title, labels, body, files, link_type)
entries.append(entry)
else:
# Fallback to commit lookup for invalid PR and Issues link
sha = pr_shas[pr_number]
print(f" #{pr_number} not found as PR or Issue, building from commit {sha[:7]}...")
files = fetch_commit_files(sha)
if is_amendment(files) and amendment_diff:
entry = format_commit_entry(sha, commit_msg, pr_bodies[pr_number])
amendment_entries.append(entry)
else:
entry = format_commit_entry(sha, commit_msg, pr_bodies[pr_number], files)
entries.append(entry)
# Build entries for orphan commits (no PR/Issue linked)
for orphan in orphan_commits:
sha = orphan["sha"]
print(f" Building commit-only entry for {sha[:7]}...")
files = fetch_commit_files(sha)
if is_amendment(files) and amendment_diff:
entry = format_commit_entry(sha, orphan["message"], orphan["body"])
amendment_entries.append(entry)
else:
entry = format_commit_entry(sha, orphan["message"], orphan["body"], files)
entries.append(entry)
# Generate markdown
markdown = generate_markdown(version, args.date, amendment_diff, amendment_unchanged, amendment_entries, entries, authors, version_commit)
# Write output
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, "w") as f:
f.write(markdown)
print(f"\nRelease notes written to: {output_path}")
# Update blog/sidebars.yaml
sidebars_path = "blog/sidebars.yaml"
# Derive sidebar path and year from actual output path
relative_path = output_path.removeprefix("blog/")
sidebar_year = relative_path.split("/")[0]
new_entry = f" - page: {relative_path}"
try:
with open(sidebars_path, "r") as f:
sidebar_content = f.read()
if relative_path in sidebar_content:
print(f"{sidebars_path} already contains {relative_path}")
else:
# Find the year group and insert at the top of its items
year_marker = f" - group: '{sidebar_year}'"
if year_marker not in sidebar_content:
# Year group doesn't exist — find the right chronological position
new_group = f" - group: '{sidebar_year}'\n expanded: false\n items:\n{new_entry}\n"
# Find all existing year groups and insert before the first one with a smaller year
year_groups = list(re.finditer(r" - group: '(\d{4})'", sidebar_content))
insert_pos = None
for match in year_groups:
existing_year = match.group(1)
if int(sidebar_year) > int(existing_year):
insert_pos = match.start()
break
if insert_pos is not None:
sidebar_content = sidebar_content[:insert_pos] + new_group + sidebar_content[insert_pos:]
else:
# New year is older than all existing — append at the end
sidebar_content = sidebar_content.rstrip() + "\n" + new_group
else:
# Insert after the year group's "items:" line
year_idx = sidebar_content.index(year_marker)
items_idx = sidebar_content.index(" items:", year_idx)
insert_pos = items_idx + len(" items:\n")
sidebar_content = sidebar_content[:insert_pos] + new_entry + "\n" + sidebar_content[insert_pos:]
with open(sidebars_path, "w") as f:
f.write(sidebar_content)
print(f"Added {relative_path} to {sidebars_path}")
except FileNotFoundError:
print(f"Warning: {sidebars_path} not found, skipping sidebar update", file=sys.stderr)
if __name__ == "__main__":
main()