Compare commits

..

134 Commits

Author SHA1 Message Date
mDuo13
aed88784d9 Fix sidebar highlight color issues in Realm 0.132 2026-04-17 16:03:55 -07:00
mDuo13
dc13312be6 Fix broken redirects identified by Realm 0.132 2026-04-17 16:03:35 -07:00
mDuo13
a063951f9e Bump Redocly to Realm 0.132.0 2026-04-17 15:57:51 -07:00
mDuo13
6ac6893f4a Remove some modular tutorial files that are no longer used 2026-04-17 15:41:38 -07:00
mDuo13
25bfaca2c0 Remove images & code from removed auction slot modular tutorial 2026-04-17 15:23:49 -07:00
mDuo13
5728345a42 Restore & update old auction slot tutorial 2026-04-17 15:19:50 -07:00
mDuo13
028e523b6d Remove unused Airgapped Wallet code sample 2026-04-17 13:57:52 -07:00
Maria Shodunke
d945d6a5d6 Tutorials landing page v2 (#3572) 2026-04-17 11:52:23 +01: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
rachelflynn
744721d8b3 Address PR review feedback for calculate-reserves tutorial, including:
- Switch all code samples to server_state (integer drops)
- Add drops_to_xrp conversion for display in JS, Python, and Go
- Add Account Management group to sidebar under Best Practices
2026-04-06 13:52:28 -04: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
rachelflynn
b1eaf8c051 Merge master into add-calculate-reserves-tutorial 2026-04-02 10:22:17 -04: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
rachelflynn
f2109aab33 Fix Java code sample for calculate-reserves tutorial 2026-04-01 16:39:34 -04:00
Oliver Eggert
892714550e more improvements to sorting script and AI instructions 2026-04-01 13:19:12 -07:00
rachelflynn
41788b9323 fix: use account instead of address in step 5 2026-04-01 15:32:08 -04:00
rachelflynn
04cfa17880 add: calculate reserves tutorial and code samples 2026-04-01 15:27:58 -04:00
Oliver Eggert
b6388ccb13 improve skills md edit vs write and not chunk context 2026-03-31 18:18:01 -07:00
Rome Reginelli
201a250072 Merge pull request #3543 from XRPLF/redocly_131
Upgrade Redocly to 0.131.2
2026-03-31 09:58:31 -07:00
rachelflynn
c04c9042b8 Merge pull request #3547 from rachelflynn/fix-hot-topics-links
Fix: Make community icons and text clickable links
2026-03-31 10:12:17 -04: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
Maria Shodunke
7f7903a336 Merge pull request #3566 from XRPLF/fix-link-to-issue-mpt-tutorial
Fix link to Issue an MPT Tutorial
2026-03-27 16:48:53 +00:00
Maria Shodunke
78da4d6f3b Fix link to Issue an MPT Tutorial
Minor link fix. Must have been missed when we did the Tutorial revamp.
2026-03-27 11:48:59 +00: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
oeggert
17abd49d92 Merge pull request #3507 from XRPLF/go-code-samples
Add Go code samples/tutorials for Lending Protocol
2026-03-25 10:16:58 -07:00
Oliver Eggert
4bceb09b1b initial draft of release notes script 2026-03-24 19:06:53 -07:00
Oliver Eggert
d0c622abf4 update xrpl-go version and lending-setup code 2026-03-24 13:44:16 -07:00
Amarantha Kulkarni
0ed490da0b Merge pull request #3553 from XRPLF/fix_ammclawback_xrp
Fix AMMClawback asset fields
2026-03-24 13:33:18 -07:00
Amarantha Kulkarni
5fe416654f Merge pull request #3552 from XRPLF/amarantha-k-patch-1
Revert change so Redocly doesn't ignore CODE-OF-CONDUCT.md
2026-03-24 12:21:28 -07:00
Oliver Eggert
0da3a1e13c scaffold claude specific tools and generic agents.md 2026-03-24 12:17:26 -07:00
mDuo13
e0aeab7157 Remove problematic link from Code of Conduct 2026-03-24 11:44:25 -07:00
mDuo13
84987aa2df Fix AMMClawback asset fields (#3520) 2026-03-24 11:32:38 -07:00
Amarantha Kulkarni
175e0a96eb Revert change so Redocly doesn't ignore CODE-OF-CONDUCT.md 2026-03-24 11:29:39 -07:00
Rome Reginelli
d4be722dbc Merge pull request #3529 from XRPLF/new-delete-account-tutorial
Update deletion requirements & add new "Delete an Account" tutorial
2026-03-24 11:15:57 -07:00
mDuo13
839da237b6 Bump Redocly to Realm 0.131.2 & fix one more PluginInstance type 2026-03-24 11:12:30 -07:00
mDuo13
00e636c562 Bump Redocly to Realm 0.131.1 2026-03-24 10:43:20 -07:00
oeggert
3cb15342a7 Merge pull request #3548 from XRPLF/claim-context7
add json file to claim repo
2026-03-23 15:24:51 -07:00
Oliver Eggert
4346cf9a94 add json file to claim repo 2026-03-23 15:21:39 -07:00
rachelflynn
6638ea8f6c fix: make hot topics icons and text clickable links 2026-03-23 14:33:26 -04:00
Bart
476ab17cc5 Merge pull request #3546 from XRPLF/bthomee-patch-1
Create vulnerabilitydisclosurereport-bug-mar2026.md
2026-03-23 13:43:10 -04:00
Bart
7d0b02bae9 Update sidebars.yaml 2026-03-23 13:27:33 -04:00
Bart
7ab575804a Apply suggestions from code review
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-03-23 13:23:07 -04:00
Amarantha Kulkarni
2801055b14 Merge pull request #3532 from interc0der/add-honeycluster
chore: add honeycluster to the list of public servers
2026-03-23 09:57:30 -07:00
Bart
1d52bd8eaa Create vulnerabilitydisclosurereport-bug-mar2026.md
This blog post describes the two liveness bugs reported by Common Prefix last year.
2026-03-23 10:39:02 -04:00
Oliver Eggert
dbabe8171d add go sample to pay-off-a-loan tutorial 2026-03-20 18:31:46 -07:00
Oliver Eggert
66522b160e add go sample for manage-a-loan tutorial 2026-03-20 18:18:48 -07:00
Oliver Eggert
16b63f9c88 add go sample for deposit-and-withdraw-cover tutorial 2026-03-20 17:32:29 -07:00
Oliver Eggert
32b14d2a14 add go sample for create-a-loan 2026-03-20 17:17:14 -07:00
Oliver Eggert
72fb4710ec add go samples to create-a-loan-broker 2026-03-20 16:57:49 -07:00
Oliver Eggert
10431480f1 add Go samples to claw-back-cover tutorial 2026-03-20 16:32:31 -07:00
Oliver Eggert
c5f81cea25 update lending-setup script 2026-03-19 20:18:22 -07:00
Oliver Eggert
9393f40366 update loan flag check to use flag.Contains() 2026-03-19 19:26:37 -07:00
mDuo13
9f4fcc845f Update Redocly plugin syntax for 0.131+ 2026-03-19 12:26:23 -07:00
Rome Reginelli
d54684f45a Apply one more suggestion
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-03-19 10:44:00 -07:00
Oliver Eggert
c505b992e0 update create-loan and README entry 2026-03-18 22:03:37 -07:00
mDuo13
26c966fa51 Bump Redocly to Realm 0.131.0 2026-03-18 11:12:41 -07:00
Rome Reginelli
86618b900f Fix typo in Deleting Accounts intro 2026-03-17 16:05:16 -07:00
Rome Reginelli
b634b6902e Account deletion: Fix off-by-two errors & other suggestions
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-03-17 16:01:37 -07:00
Oliver Eggert
1540e36b8b update library to 0.1.16 2026-03-17 15:07:58 -07:00
Oliver Eggert
598a15eef5 add initial draft for create-loan and lending-setup 2026-03-17 14:59:52 -07:00
Oliver Eggert
7cd8e31a21 add reminder to update flag parsing in loan-manage 2026-03-17 14:59:52 -07:00
Oliver Eggert
e56324c57c add loan-pay go code samples and update readme 2026-03-17 14:59:52 -07:00
Oliver Eggert
5ce6218fd5 add go.sum to gitignore 2026-03-17 14:59:52 -07:00
Oliver Eggert
9934414492 add go.mod and go.sum 2026-03-17 14:59:52 -07:00
Oliver Eggert
ba7694f472 add loan-manage go code sample and update readme 2026-03-17 14:59:52 -07:00
Oliver Eggert
102a7cc8b9 add cover-deposit-and-withdraw go code and update readme 2026-03-17 14:59:52 -07:00
Oliver Eggert
0ac42f6acf add cover-clawback go code and update readme 2026-03-17 14:59:52 -07:00
Oliver Eggert
bc0bcfa89b clean up variable names in create-loan-broker 2026-03-17 14:59:52 -07:00
Oliver Eggert
f9b2bce755 add go code for create-loan-broker and update readme 2026-03-17 14:59:52 -07:00
Rome Reginelli
c53eddfbd8 Merge pull request #3541 from XRPLF/blog_rippled312
Add 3.1.2 release announcement & update link in 3.1.1 build instructions
2026-03-12 17:30:51 -07:00
mDuo13
1aac0d4fa2 Add 3.1.2 release announcement and update link in 3.1.1 build instructions 2026-03-12 17:25:10 -07:00
Rome Reginelli
a8e70dc49b Merge pull request #3540 from XRPLF/update_readme
Update README to remove outdated info & remove redundant translation files
2026-03-11 15:01:56 -07:00
mDuo13
bce839d6b3 Exclude code of conduct (false positive broken link) 2026-03-11 14:46:19 -07:00
mDuo13
295fbc8a4e Update package description 2026-03-11 14:02:27 -07:00
mDuo13
1a3b3d47ac Remove duplicate copies of Code of Conduct / Contributing translations 2026-03-11 13:58:55 -07:00
mDuo13
28c30fad41 Update README to remove outdated info 2026-03-11 13:56:13 -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
interc0der
b20b963c8c chore: add honeycluster to the list of public servers 2026-03-03 12:01:02 -08:00
mDuo13
91380d73e1 Update delete account tutorial with more deletion conditions 2026-03-02 13:09:25 -08:00
mDuo13
1ba708467b Update account deletion docs & code 2026-02-27 21:33:05 -08:00
mDuo13
d1adbd575a New 'Delete Account' tutorial & sample code
Delete account sample code: update JS & add Python

Add account deletion tutorial
2026-02-26 16:19:16 -08:00
zgrguric
d4726e0816 Change DebtMaximum field from required to optional 2026-02-18 15:17:57 +01:00
151 changed files with 9204 additions and 9222 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

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

View File

@@ -1,13 +1,15 @@
import { indexPages } from './plugins/index-pages.js';
import { codeSamples } from './plugins/code-samples.js';
import { blogPosts } from './plugins/blog-posts.js';
import { tutorialLanguages } from './plugins/tutorial-languages.js'
import { tutorialLanguages } from './plugins/tutorial-languages.js';
import { tutorialMetadata } from './plugins/tutorial-metadata.js';
export default function customPlugin() {
const indexPagesInst = indexPages();
const codeSamplesInst = codeSamples();
const blogPostsInst = blogPosts();
const tutorialLanguagesInst = tutorialLanguages();
const tutorialMetadataInst = tutorialMetadata();
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
@@ -18,12 +20,14 @@ export default function customPlugin() {
await codeSamplesInst.processContent?.(content, actions);
await blogPostsInst.processContent?.(content, actions);
await tutorialLanguagesInst.processContent?.(content, actions);
await tutorialMetadataInst.processContent?.(content, actions);
},
afterRoutesCreated: async (content, actions) => {
await indexPagesInst.afterRoutesCreated?.(content, actions);
await codeSamplesInst.afterRoutesCreated?.(content, actions);
await blogPostsInst.afterRoutesCreated?.(content, actions);
await tutorialLanguagesInst.processContent?.(content, actions);
await tutorialMetadataInst.processContent?.(content, actions);
},
};

View File

@@ -7,8 +7,9 @@ import markdoc from '@markdoc/markdoc';
import moment from "moment";
export function blogPosts() {
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
const instance = {
id: 'blog-posts',
processContent: async (actions, { fs, cache }) => {
try {
const posts = [];

View File

@@ -5,8 +5,9 @@ import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text
import { dirname, relative, join as joinPath } from 'path';
export function codeSamples() {
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
const instance = {
id: 'code-samples',
processContent: async (actions, { fs, cache }) => {
try {
const samples = [];

View File

@@ -3,8 +3,9 @@ import { readSharedData } from '@redocly/realm/dist/server/utils/shared-data.js'
const INDEX_PAGE_INFO_DATA_KEY = 'index-page-items';
export function indexPages() {
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
const instance = {
id: 'index-pages',
// hook that gets executed after all routes were created
async afterRoutesCreated(actions, { cache }) {
// get all the routes that are ind pages

View File

@@ -1,7 +1,15 @@
// @ts-check
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js'
/**
* Plugin to detect languages supported in tutorial pages by scanning for tab labels.
* Plugin to detect languages supported in tutorial pages.
*
* Detection methods (in priority order):
* 1. Tab labels in the markdown (for multi-language tutorials)
* 2. Filename patterns like "-js.md", "-py.md" (for single-language tutorials)
* 3. Title containing language name (for single-language tutorials)
*
* This creates shared data that maps tutorial paths to their supported languages.
*/
export function tutorialLanguages() {
@@ -21,7 +29,18 @@ export function tutorialLanguages() {
for (const { relativePath } of tutorialFiles) {
try {
const { data } = await cache.load(relativePath, 'markdown-ast')
const languages = extractLanguagesFromAst(data.ast)
// Try to detect languages from tab labels first
let languages = extractLanguagesFromAst(data.ast)
// Fallback: detect language from filename/title for single-language tutorials
if (languages.length === 0) {
const title = extractFirstHeading(data.ast) || ''
const fallbackLang = detectLanguageFromPathAndTitle(relativePath, title)
if (fallbackLang) {
languages = [fallbackLang]
}
}
if (languages.length > 0) {
// Convert file path to URL path
@@ -54,16 +73,31 @@ function extractLanguagesFromAst(ast) {
const languages = new Set()
visit(ast, (node) => {
// Look for tab nodes with a label attribute
if (isNode(node) && node.type === 'tag' && node.tag === 'tab') {
if (!isNode(node)) return
// Detect languages from tab labels
if (node.type === 'tag' && node.tag === 'tab') {
const label = node.attributes?.label
if (label) {
const normalizedLang = normalizeLanguage(label)
if (normalizedLang) {
languages.add(normalizedLang)
}
const normalized = normalizeLanguage(label)
if (normalized) languages.add(normalized)
}
}
// Detect languages from code-snippet language attributes
if (node.type === 'tag' && node.tag === 'code-snippet') {
const lang = node.attributes?.language
if (lang) {
const normalized = normalizeLanguage(lang)
if (normalized) languages.add(normalized)
}
}
// Detect languages from fenced code blocks (```js, ```python, etc.)
if (node.type === 'fence' && node.attributes?.language) {
const normalized = normalizeLanguage(node.attributes.language)
if (normalized) languages.add(normalized)
}
})
return Array.from(languages)
@@ -98,6 +132,70 @@ function normalizeLanguage(label) {
return null
}
/**
* Detect language from file path and title for single-language tutorials.
* This is a fallback when no tab labels are found in the markdown.
*/
function detectLanguageFromPathAndTitle(relativePath, title) {
const pathLower = relativePath.toLowerCase()
const titleLower = (title || '').toLowerCase()
// Check filename suffixes like "-js.md", "-py.md"
if (pathLower.endsWith('-js.md') || pathLower.includes('-javascript.md') || pathLower.includes('-in-javascript.md')) {
return 'javascript'
}
if (pathLower.endsWith('-py.md') || pathLower.includes('-python.md') || pathLower.includes('-in-python.md')) {
return 'python'
}
if (pathLower.endsWith('-java.md') || pathLower.includes('-in-java.md')) {
return 'java'
}
if (pathLower.endsWith('-go.md') || pathLower.includes('-in-go.md') || pathLower.includes('-golang.md')) {
return 'go'
}
if (pathLower.endsWith('-php.md') || pathLower.includes('-in-php.md')) {
return 'php'
}
// Check title for language indicators
if (titleLower.includes('javascript') || titleLower.includes(' js ') || titleLower.endsWith(' js')) {
return 'javascript'
}
if (titleLower.includes('python')) {
return 'python'
}
if (titleLower.includes('java') && !titleLower.includes('javascript')) {
return 'java'
}
if (titleLower.includes('golang') || (titleLower.includes(' go ') || titleLower.endsWith(' go') || titleLower.includes('using go'))) {
return 'go'
}
if (titleLower.includes('php')) {
return 'php'
}
return null
}
const EXIT = Symbol('Exit visitor')
/**
* Extract the first heading from the markdown AST
*/
function extractFirstHeading(ast) {
let heading = null
visit(ast, (node) => {
if (!isNode(node)) return
if (node.type === 'heading') {
heading = getInnerText([node])
return EXIT
}
})
return heading
}
function isNode(value) {
return !!(value?.$$mdtype === 'Node')
}
@@ -105,14 +203,16 @@ function isNode(value) {
function visit(node, visitor) {
if (!node) return
visitor(node)
const res = visitor(node)
if (res === EXIT) return res
if (node.children) {
for (const child of node.children) {
if (!child || typeof child === 'string') {
continue
}
visit(child, visitor)
const res = visit(child, visitor)
if (res === EXIT) return res
}
}
}

View File

@@ -0,0 +1,207 @@
// @ts-check
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js';
import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = resolve(__dirname, '../..');
/**
* Plugin to extract tutorial metadata including last modified dates.
* Uses Redocly's built-in git integration for dates (same as "Last updated" display).
* Only includes tutorials that appear in the sidebar navigation (sidebars.yaml).
* This creates shared data for displaying "What's New" tutorials and
* auto-generating tutorial sections on the landing page.
*/
export function tutorialMetadata() {
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
const instance = {
processContent: async (actions, { fs, cache }) => {
try {
// Extract tutorial paths and categories from sidebars.yaml.
// Only tutorials present in the sidebar are included.
const { pageCategory, categories } = extractSidebarData();
/** @type {Array<{path: string, title: string, description: string, lastModified: string, category: string}>} */
const tutorials = [];
const allFiles = await fs.scan();
// Find all markdown files in tutorials directory
const tutorialFiles = allFiles.filter((file) =>
file.relativePath.match(/^docs[\/\\]tutorials[\/\\].*\.md$/)
);
for (const { relativePath } of tutorialFiles) {
try {
// Skip tutorials not present in sidebar navigation
const category = pageCategory.get(relativePath);
if (!category) continue;
const { data: { ast } } = await cache.load(relativePath, 'markdown-ast');
const { data: { frontmatter } } = await cache.load(relativePath, 'markdown-frontmatter');
// Get last modified date using Redocly's built-in git integration
const lastModified = await fs.getLastModified(relativePath);
if (!lastModified) continue; // Skip files without dates
// Extract title from first heading
const title = extractFirstHeading(ast) || '';
if (!title) continue;
// Get description from frontmatter or first paragraph
const description = frontmatter?.seo?.description || '';
// Convert file path to URL path
const urlPath = '/' + relativePath
.replace(/[\/\\]index\.md$/, '/')
.replace(/\.md$/, '/')
.replace(/\\/g, '/');
tutorials.push({
path: urlPath,
title,
description,
lastModified,
category,
});
} catch (err) {
continue; // Skip files that can't be parsed
}
}
// Sort by last modified date (newest first) for "What's New"
tutorials.sort((a, b) =>
new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()
);
// Create shared data including sidebar-derived categories
actions.createSharedData('tutorial-metadata', { tutorials, categories });
actions.addRouteSharedData('/docs/tutorials/', 'tutorial-metadata', 'tutorial-metadata');
actions.addRouteSharedData('/ja/docs/tutorials/', 'tutorial-metadata', 'tutorial-metadata');
actions.addRouteSharedData('/es-es/docs/tutorials/', 'tutorial-metadata', 'tutorial-metadata');
} catch (e) {
console.log('[tutorial-metadata] Error:', e);
}
},
};
return instance;
}
/**
* Extract the first heading from the markdown AST
*/
function extractFirstHeading(ast) {
let heading = null;
visit(ast, (node) => {
if (!isNode(node)) return;
if (node.type === 'heading') {
heading = getInnerText([node]);
return EXIT;
}
});
return heading;
}
function isNode(value) {
return !!(value?.$$mdtype === 'Node');
}
const EXIT = Symbol('Exit visitor');
function visit(node, visitor) {
if (!node) return;
const res = visitor(node);
if (res === EXIT) return res;
if (node.children) {
for (const child of node.children) {
if (!child || typeof child === 'string') continue;
const res = visit(child, visitor);
if (res === EXIT) return res;
}
}
}
/**
* Extract tutorial page paths and categories from sidebars.yaml.
*
* Returns:
* - pageCategory: Map of relativePath to category id (slug)
* - categories: Array of { id, title } in sidebar display order
*
* Top-level groups under the tutorials section become categories.
* Pages not inside a group (e.g. public-servers.md) are skipped.
*/
function extractSidebarData() {
/** @type {Map<string, string>} */
const pageCategory = new Map();
/** @type {Array<{id: string, title: string}>} */
const categories = [];
try {
const content = readFileSync(resolve(PROJECT_ROOT, 'sidebars.yaml'), 'utf-8');
const lines = content.split('\n');
let inTutorials = false;
let entryIndent = -1; // indent of the tutorials entry itself
let topItemIndent = -1; // indent of direct children (groups/pages)
let currentCategory = null; // current top-level group { id, title }
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) continue;
const indent = line.search(/\S/);
// Detect the tutorials section
if (trimmed.includes('page: docs/tutorials/index.page.tsx')) {
inTutorials = true;
entryIndent = indent;
continue;
}
if (!inTutorials) continue;
// Exit tutorials when we reach a sibling entry at the same indent
if (indent <= entryIndent && trimmed.startsWith('- ')) {
break;
}
// Detect the indent of top-level items (first `- ` under tutorials items)
if (topItemIndent === -1 && trimmed.startsWith('- ')) {
topItemIndent = indent;
}
// Top-level group - start a new category
if (indent === topItemIndent && trimmed.startsWith('- group:')) {
const title = trimmed.replace('- group:', '').trim();
const id = title.toLowerCase().replace(/\s+/g, '-');
currentCategory = { id, title };
categories.push(currentCategory);
continue;
}
// Top-level page (no group, e.g. public-servers.md) - reset current category
if (indent === topItemIndent && trimmed.startsWith('- page:')) {
currentCategory = null;
continue;
}
// Nested page under a group - assign to current category
if (currentCategory) {
const pageMatch = trimmed.match(/^- page:\s+(docs\/tutorials\/\S+\.md)/);
if (pageMatch) {
pageCategory.set(pageMatch[1], currentCategory.id);
}
}
}
} catch (err) {
console.log('[tutorial-metadata] Warning: Could not read sidebars.yaml:', String(err));
}
return { pageCategory, categories };
}

View File

@@ -233,6 +233,7 @@ ul.nav.navbar-nav {
--footer-title-text-color: var(--color-gray-6);
--menu-item-padding-horizontal: 0px;
--menu-item-bg-color-active: var(--color-gray-2);
--md-list-left-padding: 40px;
--md-list-margin: 0 0 20px 0;
@@ -278,6 +279,8 @@ ul.nav.navbar-nav {
--bg-color-raised: var(--color-gray-8);
--button-content-color-link: black;
--menu-item-bg-color-active: var(--color-gray-8);
--md-table-header-bg-color: var(--color-gray-8);
--md-table-border-color: var(--color-gray-8);
@@ -285,6 +288,7 @@ ul.nav.navbar-nav {
--code-panel-bg-color: var(--color-blue-7);
--layer-color-hover: var(--color-gray-9);
--code-block-text-color: var(--color-gray-1);
--code-block-tokens-comment-color: var(--color-gray-4);
--code-block-tokens-constant-color: var(--color-gray-1);

View File

@@ -1,46 +0,0 @@
# コントリビューター行動規範
## 誓約
私たちコントリビューターとメンテナーは、オープンで友好的な環境を育むために、年齢、体格、身体障害、民族、性的特徴、性自認および性表現、経験の度合い、学歴、社会経済的地位、国籍、容姿、人種、宗教、性的同一性および性的指向などを問わず、誰もが私たちのプロジェクトとコミュニティーに不快な思いをすることなく参加できるよう努めることを誓います。
## 標準
前向きな環境を作り上げることに貢献する行動の例:
* 友好的で差別のない言葉の使用
* 異なる観点や経験の尊重
* 建設的な批判の素直な受け入れ
* コミュニティーにとっての最善への注力
* 他のコミュニティーメンバーへの共感の表示
前向きな環境を作り上げることに貢献しない行動の例:
* 性的な意味を含む言葉や画像の使用、望まない性的注目や誘いかけ
* あおり、侮辱的または軽蔑的なコメント、個人攻撃や政治攻撃
* 公的または私的な嫌がらせ
* 住所やメールアドレスなどの個人情報の、明確な許可なしでの公開
* 職場において不適切であると合理的に考えられる、その他の行為
## 責任
プロジェクトのメンテナーは、許容できる行動の基準を明確にする責任があります。また、許容できない行動がなされた場合に、適切かつ公平な是正処置を講じることが期待されます。
プロジェクトのメンテナーは、この行動規範に沿わないコメント、コミット、コード、wiki編集、issueなどの投稿を削除、編集、拒否する権利と義務を有します。また、他の不適切、脅迫的、攻撃的、嫌がらせと考えられる行動を取ったコントリビューターを一時的もしくは恒久的に追放する権利と義務を有します。
## 適用範囲
この行動規範はすべてのプロジェクトスペース内で適用されます。また、個人がパブリックスペースでプロジェクトやコミュニティーを代表する際にも適用されます。プロジェクトやコミュニティーを代表する際の例としては、プロジェクトの公式メールアドレスを使用すること、公式ソーシャルメディアアカウントで投稿すること、もしくはオンラインまたはオフラインのイベントで、任命された代表者を務めることが挙げられます。プロジェクトを代表する行為については、プロジェクトのメンテナーがさらに細かく定義して明確にすることができます。
## 執行
暴言、嫌がらせ、またはその他の許容できない行動は、プロジェクトチーム(<ripplex@ripple.com>)に連絡して報告することができます。すべての申し立ては確認、調査されたうえで、その状況に対して必要かつ適切と判断された対応が取られます。プロジェクトチームは、事象の報告者に関する秘密を保持する義務があります。特定の執行方針の詳細は、別途掲載される場合があります。
この行動規範を誠実に遵守または執行することができないプロジェクトのメンテナーは、プロジェクトを率いる他のメンバーの判断により、一時的または恒久的な措置が執られることがあります。
## 帰属
この行動規範は、[コントリビューター行動規範][ホームページ]バージョン1.4https://www.contributor-covenant.org/version/1/4/code-of-conduct.htmlから抜粋したものです。
[ホームページ]: https://www.contributor-covenant.org
この行動規範に関するよくある質問と回答については、https://www.contributor-covenant.org/faq をご覧ください。

View File

@@ -1,48 +0,0 @@
# Código de conducta del pacto de contribuidores
## Nuestro compromiso
Con el fin de fomentar un ambiente abierto y acogedor, nosotros, como contribuidores y mantenedores, nos comprometemos a hacer de la participación en nuestro proyecto y nuestra comunidad una experiencia libre de acoso para todos, independientemente de, entre otras, características como la edad, tamaño corporal, discapacidad, origen étnico, características sexuales, identidad y expresión de género, nivel de experiencia, educación, estatus socioeconómico, nacionalidad, apariencia personal, raza, religión o identidad y orientación sexual.
## Nuestros estándares
Ejemplos de comportamiento que contribuyen a crear un ambiente positivo incluyen:
* Utilizar lenguaje acogedor e inclusivo
* Ser respetuoso con los diferentes puntos de vista y experiencias
* Saber aceptar las críticas constructivas
* Centrarse en lo que es lo mejor para la comunidad
* Mostrar empatía hacia otros miembros de la comunidad
Ejemplos de comportamiento que no contribuyen a crear un ambiente positivo incluyen:
* Utilizar un lenguaje o imágenes sexualizadas y atención o insinuaciones sexuales no deseadas
* Trolear, comentario insultantes/peyorativos y ataques personales o políticos
* Acoso público o en privado
* Publicar información privada de otras personas, así cómo direcciones físicas o electrónicas, sin permiso explícito
* Cualquier otra conducta que pueda ser razonablemente considerada inapropiada en un sentido profesional
## Nuestras responsabilidades
Los mantenedores del proyecto son responsables de aclarar los estándares de comportamiento aceptable y se espera que tomen acciones correctivas justas y apropiadas en respuesta a cualquier caso de comportamiento inaceptable.
Los mantenedores del proyecto tienen el derecho y la responsaiblidad de eliminar, editar o rechazar comentarios, commits, código, ediciones de wiki, problemas y otras contribuciones que no estén alineadas con este Código de Conducta, o de expulsar temporal o definitivamente a cualquier colaborador por otros comportamientos que consideren inapropiados, amenazantes, ofensivos, dañinos o que viole de cualquier modo este Código de Conducta.
## Alcance
Este Código de Conducta aplica en todos los espacios del proyecto y también aplica cuando un individuo está representando el proyecto o su comunidad en espacios públicos. Ejemplos de representación de un proyecto o la comunidad incluye usar un correo electrónico oficial del proyecto, publicaciones a través de una cuenta oficial de redes sociales o actuar como representante asignado en un evento en línea o en la vida real. La representación de un proyecto debe ser definida y aclarada con más detalle por los mantenedores del proyecto.
## Aplicación
Los casos de comportamiento abusivo, acoso, o de cualquier otro modo inaceptable se pueden informar contactando con el equipo del proyecto al correo <ripplex@ripple.com>. Todas las quejas serán revisadas e investigadas y resultarán en una resupuesta que se considere adecuada y necesaria a las circunstancias. El equipo del proyecto está obligado a mantener la confidencialidad con respecto al informador del incidente. Podría darse el caso de publicar más detalles sobre políticas de comportamiento específicas.
Los mantenedores de proyecto que no sigan o hagan cumplir el Código de conducta de buena fe podrían enfrentarse a repercusiones temporales o definitivas según lo determinen otros miembros que lideren el proyecto.
## Atribución
Este Código de Conducta está adaptado de el [Pacto del Contribuidores][inicio], versión 1.4, disponible en https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[inicio]: https://www.contributor-covenant.org
Para respuestas a preguntas comunes sobre este código de conducta, visita
https://www.contributor-covenant.org/faq

View File

@@ -1,3 +0,0 @@
# Contribuir
Para obtener información sobre cómo contribuir a este repositorio, consulta [Contribute Documentation (XRPL.org)](https://xrpl.org/es_ES/contribute-documentation.html).

View File

@@ -1,3 +0,0 @@
# コントリビューション
コントリビューションの情報には[「ドキュメントへの貢献」](https://xrpl.org/ja/contribute-documentation.html)をご覧ください。

View File

@@ -1,12 +1,10 @@
# XRPL Dev Portal
The [XRP Ledger Dev Portal](https://xrpl.org) is the authoritative source for XRP Ledger documentation, including the `rippled` server, client libraries, and other open-source XRP Ledger software.
The [XRP Ledger Dev Portal](https://xrpl.org) is the authoritative source for XRP Ledger documentation, including the core server, client libraries, and other open-source XRP Ledger software.
The site is built and published using Redocly.
NOTE: The toolchain used to build and publish the site has recently been migrated from Dactyl to Redocly.
Before you proceed, make sure you have Node version >= 18 LTS.
Before you proceed, make sure you have Node.js and NPM installed. The site is tested with the current LTS release of each.
To build the site locally:
@@ -26,58 +24,18 @@ To build the site locally:
npm start
For more details, see the [contribution guidelines (EN)](CONTRIBUTING.md) ([日本語](CONTRIBUTING.ja.md)) and the [contributor Code of Conduct (EN)](CODE-OF-CONDUCT.md) ([日本語](CODE-OF-CONDUCT.ja.md)).
## Domain Verification Checker
If you make changes to the [Domain Verification Checker](https://xrpl.org/validator-domain-verifier.html) tool and edit the domain-verifier-checker.js file, you will need to do the following:
1. Install [webpack](https://webpack.js.org/) and required libraries via npm:
npm install webpack webpack-cli --save-dev
npm install ripple-binary-codec ripple-address-codec ripple-keypairs
2. From the project root directory (this step may be different depending on how you installed webpack)
cd assets/js
webpack-cli domain-verifier-checker.js --optimize-minimize -o domain-verifier-bundle.js
3. Build the site:
npm start
For more details, see the [contribution guidelines (EN)](CONTRIBUTING.md) ([日本語](@l10n/ja/CONTRIBUTING.md)) and the [contributor Code of Conduct (EN)](CODE-OF-CONDUCT.md) ([日本語](@l10n/ja/CODE-OF-CONDUCT.md)).
### Internationalization
### Localization / Translations
This repo includes English (en) and Japanese (ja) locales.
This is done by setting up the internationalization (@l10n) folders, adding the `i18n` configuration to your `redocly.yaml` file, and adding the translated content in the respective language directory under the @l10n directory.
To add support for a new language:
1. Create a new subdirectory in the @l10n directory of the portal. For example, to add support for Spanish, create a new subdirectory "es-ES".
2. Update the i18n configuration in your `redocly.yaml` file defining the display labels for the different languages you support.
l10n:
defaultLocale: en-US
locales:
- code: en-US
name: English
- code: ja
name: 日本語
- code: es-ES
name: Spanish
3. Add the translated content in the respective language directory under the @l10n directory.
The relative path from the language directory to the translated file must be the same as the relative path from the root of the portal to the file in the default language. For example, if you originally had a file with path `path/to/my/markdown.md`, the file translated to Spanish must be /`@l10n/es-ES/path/to/my/markdown.md`.
The documentation in this repository is created in English first, then translated into other languages by community contributors. Currently, only the Japanese translations are live on the site; Spanish translation efforts are incomplete and not actively used. For information on the process of adding and maintaining translated files, see [Translations](https://xrpl.org/resources/contribute-documentation/documentation-translations).
## Issues, Projects, and Project Boards
Use GitHub Issues under the [`xrpl-dev-portal`](https://github.com/XRPLF/xrpl-dev-portal) repository to report bugs, feature requests, and suggestions for the XRP Ledger Documentation or the `xrpl.org` website.
For issues related to `rippled` or client libraries (`xrpl.js`, `xrpl-py`, and others), use the respective source repository under [`https://github.com/XRPLF`](https://github.com/XRPLF).
For issues related to `xrpld`/`rippled`, Clio, or client libraries (`xrpl.js`, `xrpl-py`, and others), use the respective source repository under [`https://github.com/XRPLF`](https://github.com/XRPLF).
If you are a contributor, use GitHub Projects and Project Boards to plan and track updates to xrpl.org.

View File

@@ -1 +0,0 @@
Wallet/

View File

@@ -1,92 +0,0 @@
# Airgapped Wallet
Airgapped describes a state where a device or a system becomes fully disconnected from other devices and systems. It is the maximum protection for a system against unwanted visitors/viruses, this allows any sensitive data like a private key to be stored without worry of it being compromised as long as reasonable security practices are being practiced.
This airgapped XRP wallet allows users to sign a Payment transaction in a secure environment without the private key being exposed to a machine connected to the internet. The private key and seed is encrypted by password and stored securely.
*Note*: You should not use this airgapped wallet in production, it should only be used for educational purposes only.
This code sample consists of 2 parts:
- `airgapped-wallet.js` - This code should be stored in a standalone airgapped machine, it consist of features to generate a wallet, store a keypair securely, sign a transaction and share the signed transaction via QR code.
- `relay-transaction.js` - This code could be stored in any online machine, no credentials is stored on this code other than a signed transaction which would be sent to an XRPL node for it to be validated on the ledger.
Preferably, `airgapped-wallet.js` should be on a Linux machine while `relay-transaction.js` could be on any operating system.
# Security Practices
Strongly note that an airgapped system's security is not determined by its code alone but the security practices that are being followed by an operator.
There are channels that can be maliciously used by outside parties to infiltrate an airgapped system and steal sensitive information.
There are other ways malware could interact across airgapped networks, but they all involve an infected USB drive or a similar device introducing malware onto the airgapped machine. They could also involve a person physically accessing the computer, compromising it and installing malware or modifying its hardware.
This is why it is also recommended to encrypt sensitive information being stored in an airgapped machine.
The airgapped machine should have a few rules enforced to close any possible channels getting abused to leak information outside of the machine:
### Wifi
- Disable any wireless networking hardware on the airgapped machine. For example, if you have a desktop PC with a Wifi card, open the PC and remove the Wifi hardware. If you cannot do that, you could go to the systems BIOS or UEFI firmware and disable the Wifi hardware.
### BlueTooth
- BlueTooth can be maliciously used by neighboring devices to steal data from an airgapped machine. It is recommended to remove or disable the BlueTooth hardware.
### USB
- The USB port can be used to transfer files in and out of the airgapped machine and this may act as a threat to an airgapped machine if the USB drive is infected with a malware. So after installing & setting up this airgapped wallet, it is highly recommended to block off all USB ports by using a USB blocker and not use them.
Do not reconnect the airgapped machine to a network, even when you need to transfer files! An effective airgapped machine should only serve 1 purpose, which is to store data and never open up a gateway for hackers to abuse and steal data.
# Tutorial
For testing purposes, you would need to have 2 machines and 1 phone in hand to scan the QR code.
1. 1st machine would be airgapped, following the security practices written [here](#security-practices). It stores and manages an XRPL Wallet.
2. 2nd machine would be a normal computer connected to the internet. It relays a signed transaction blob to a rippled node.
3. The phone would be used to scan a QR code, which contains a signed transaction blob. The phone would transmit it to the 2nd machine.
The diagram below shows you the process of submitting a transaction to the XRPL:
<p align="center">
<img src="https://user-images.githubusercontent.com/87929946/197970678-2a1b7f7e-d91e-424e-915e-5ba7d34689cc.png" width=75% height=75%>
</p>
# Setup
- Machine 1 - An airgapped computer (during setup, it must be connected to the internet to download the files)
- Machine 2 - A normal computer connected to the internet
- Phone - A normal phone with a working camera to scan a QR
## Machine 1 Setup
Since this machine will be airgapped, it is best to use Linux as the Operating System.
1. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/airgapped-wallet/js) directory
2. Import all the modules required by running: `npm install`
3. Airgap the machine by following the security practices written [here](#security-practices).
4. Run `node airgapped-wallet.js`
5. Scan the QR code and fund the account using the [testnet faucet](https://test.bithomp.com/faucet/)
6. Re-run the script and input '1' to generate a new transaction by following the instructions.
7. Use your phone to scan the QR code, then to send the signed transaction to Machine 2 for submission
## Phone Setup
The phone requires a working camera that is able to scan a QR code and an internet connection for it to be able to transmit the signed transaction blob to Machine 2.
Once you have signed a transaction in the airgapped machine, a QR code will be generated which will contain the signed transaction blob. Example:
<img src="https://user-images.githubusercontent.com/87929946/196018292-f210a9f2-c5f8-412e-98c1-361a72286378.png" width=20% height=20%>
Scan the QR code using the phone, copy it to the clipboard, and transmit it to Machine 2, which will then be sending it to a rippled node.
You can send a message to yourself using Discord, WhatsApp or even e-mail, then open up the message using Machine 2 to receive the signed transaction blob.
## Machine 2 Setup
This machine will be used to transmit a signed transaction blob from Machine 1, it would require internet access.
1. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/airgapped-wallet/js) directory
2. Import all the modules required by running `npm install`
3. Run `relay-transaction.js` and copy-and-paste the received output of Machine 1 when prompted

View File

@@ -1,223 +0,0 @@
const crypto = require("crypto")
const fs = require('fs')
const fernet = require("fernet");
const open = require('open');
const path = require('path')
const prompt = require('prompt')
const { generateSeed, deriveAddress, deriveKeypair } = require("ripple-keypairs/dist/")
const QRCode = require('qrcode')
const xrpl = require('xrpl')
const demoAccountSeed = 'sskwYQmxT7SA37ceRaGXA5PhQYrDS'
const demoAccountAddress = 'rEDd3Wy76Ta1WqfDP2DcnBKHu31SpSiUQrS'
const demoDestinationSeed = 'sEdVokfq7fVXXjZTii2WhtpqGbJni6s'
const demoDestinationAddress = 'rBgNowfkmPczhMjHRYnBPsuSodDHWHQLdj'
const FEE = '12'
const LEDGER_OFFSET = 300
const WALLET_DIR = 'Wallet'
/**
* Generates a new (unfunded) wallet
*
* @returns {{address: *, seed: *}}
*/
createWallet = function () {
const seed = generateSeed()
const {publicKey, privateKey} = deriveKeypair(seed)
const address = deriveAddress(publicKey)
console.log(
"XRP Wallet Credentials " +
"Wallet Address: " + address +
"Seed: " + seed
)
return {address, seed}
}
/**
* Signs transaction and returns signed transaction blob in QR code
*
* @param xrpAmount
* @param destination
* @param ledgerSequence
* @param walletSequence
* @param password
* @returns {Promise<void>}
*/
signTransaction = async function (xrpAmount, destination, ledgerSequence, walletSequence, password) {
const salt = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt')).toString()
const encodedSeed = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt')).toString()
// Hashing salted password using Password-Based Key Derivation Function 2
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
// Generate a Fernet secret we can use for symmetric encryption
const secret = new fernet.Secret(derivedKey.toString('base64'));
// Generate decryption token
const token = new fernet.Token({
secret: secret,
token: encodedSeed,
ttl: 0
})
const seed = token.decode();
const wallet = xrpl.Wallet.fromSeed(seed)
const paymentTx = {
'TransactionType': 'Payment',
'Account': wallet.classicAddress,
'Amount': xrpl.xrpToDrops(xrpAmount),
'Destination': destination
}
// Normally we would fetch certain needed values like Fee,
// LastLedgerSequence snd programmatically, like so:
//
// const preparedTx = await client.autofill(paymentTx)
//
// But since this is an airgapped wallet without internet
// connection, we have to do it manually:
//
// paymentTx.Sequence is set in setNextValidSequenceNumber() via sugar/autofill
// paymentTx.LastLedgerSequence is set in setLatestValidatedLedgerSequence() via sugar/autofill
// paymentTx.Fee is set in getFeeXrp() via sugar/getFeeXrp
paymentTx.Sequence = walletSequence
paymentTx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
paymentTx.Fee = FEE
const signedTx = wallet.sign(paymentTx)
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'tx_blob.txt'), signedTx.tx_blob)
QRCode.toFile(path.join(__dirname, WALLET_DIR , 'tx_blob.png'), signedTx.tx_blob)
open(path.join(__dirname, WALLET_DIR , 'tx_blob.png'))
}
main = async function () {
if (!fs.existsSync(WALLET_DIR )) {
// Create Wallet directory in case it does not exist yet
fs.mkdirSync(path.join(__dirname, WALLET_DIR ));
}
if (!fs.existsSync(path.join(__dirname, WALLET_DIR , 'address.txt'))) {
// Generate a new (unfunded) Wallet
const {address, seed} = createWallet()
prompt.start();
const {password} = await prompt.get([{
name: 'password',
description: 'Creating a brand new Wallet, please enter a new password \n Enter Password:',
type: 'string',
required: true
}])
prompt.stop();
const salt = crypto.randomBytes(20).toString('hex')
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt'), salt);
// Hashing salted password using Password-Based Key Derivation Function 2
const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
// Generate a Fernet secret we can use for symmetric encryption
const secret = new fernet.Secret(derivedKey.toString('base64'));
// Generate encryption token with secret, time and initialization vector
// In a real-world use case we would have current time and a random IV,
// but for demo purposes being deterministic is just fine
const token = new fernet.Token({
secret: secret,
time: Date.parse(1),
iv: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
})
const privateKey = token.encode(seed)
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt'), privateKey)
fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'address.txt'), address)
QRCode.toFile(path.join(__dirname, WALLET_DIR , 'address.png'), address)
console.log(''
+ 'Finished generating an account.\n'
+ 'Wallet Address: ' + address + '\n'
+ 'Please scan the QR code on your phone and use https://test.bithomp.com/faucet/ to fund the account.\n'
+ 'After that, you\'re able to sign transactions and transmit them to Machine 2 (online machine).')
return
}
prompt.start();
console.log(''
+ '1. Transact XRP.\n'
+ '2. Generate an XRP wallet (read only)\n'
+ '3. Showcase XRP Wallet Address (QR Code)\n'
+ '4. Exit')
const {menu} = await prompt.get([{
name: 'menu',
description: 'Enter Index:',
type: 'integer',
required: true
}])
if (menu === 1) {
const {
password,
xrpAmount,
destinationAddress,
accountSequence,
ledgerSequence
} = await prompt.get([{
name: 'password',
description: 'Enter Password',
type: 'string',
required: true
}, {
name: 'xrpAmount',
description: 'Enter XRP To Send',
type: 'number',
required: true
}, {
name: 'destinationAddress',
description: 'If you just want to try it out, you can use the faucet account rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe. Enter Destination',
type: 'string',
required: true
}, {
name: 'accountSequence',
description: 'Look up the \'Next Sequence\' for the account using test.bithomp.com and enter it',
type: 'integer',
required: true
}, {
name: 'ledgerSequence',
description: 'Look up the latest ledger sequence on testnet.xrpl.org and enter it below!',
type: 'integer',
required: true
}])
await signTransaction(xrpAmount, destinationAddress, ledgerSequence, accountSequence, password)
} else if (menu === 2) {
const {address, seed} = createWallet()
console.log('Generated readonly Wallet (address: ' + address + ' seed: ' + seed + ')')
} else if (menu === 3) {
const address = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'address.txt')).toString()
console.log('Wallet Address: ' + address)
open(path.join(__dirname, WALLET_DIR , 'address.png'))
} else {
return
}
prompt.stop();
}
main()

View File

@@ -1,18 +0,0 @@
{
"name": "airgapped-wallet",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"fernet": "^0.4.0",
"open": "^8.4.0",
"pbkdf2-hmac": "^1.1.0",
"prompt": "^1.3.0",
"qrcode": "^1.5.1",
"xrpl": "^4.0.0"
},
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
}

View File

@@ -1,37 +0,0 @@
const prompt = require('prompt')
const xrpl = require('xrpl')
sendTransaction = async function (tx_blob) {
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
console.log("Connected to node")
const tx = await client.submitAndWait(tx_blob)
const txHash = tx.result.hash
const txDestination = tx.result.Destination
const txXrpAmount = xrpl.dropsToXrp(tx.result.Amount)
const txAccount = tx.result.Account
console.log("XRPL Explorer: https://testnet.xrpl.org/transactions/" + txHash)
console.log("Transaction Hash: " + txHash)
console.log("Transaction Destination: " + txDestination)
console.log("XRP sent: " + txXrpAmount)
console.log("Wallet used: " + txAccount)
await client.disconnect()
}
main = async function () {
const {tx_blob} = await prompt.get([{
name: 'tx_blob',
description: 'Set tx to \'tx_blob\' received from scanning the QR code generated by the airgapped wallet',
type: 'string',
required: true
}])
await sendTransaction(tx_blob)
}
main()

View File

@@ -1,115 +0,0 @@
# Airgapped Wallet
Airgapped describes a state where a device or a system becomes fully disconnected from other devices and systems. It is the maximum protection for a system against unwanted visitors/viruses, this allows any sensitive data like a private key to be stored without worry of it being compromised as long as reasonable security practices are being practiced.
This airgapped XRP wallet allows users to sign a Payment transaction in a secure environment without the private key being exposed to a machine connected to the internet. The private key and seed is encrypted by password and stored securely.
*Note*: You should not use this airgapped wallet in production, it should only be used for educational purposes only.
This code sample consists of 2 parts:
- `airgapped-wallet.py` - This code should be stored in a standalone airgapped machine, it consist of features to generate a wallet, store a keypair securely, sign a transaction and share the signed transaction via QR code.
- `relay-transaction.py` - This code could be stored in any online machine, no credentials is stored on this code other than a signed transaction which would be sent to an XRPL node for it to be validated on the ledger.
Preferably, `airgapped-wallet.py` should be on a Linux machine while `relay-transaction.py` could be on any operating system.
# Security Practices
Strongly note that an airgapped system's security is not determined by its code alone but the security practices that are being followed by an operator.
There are channels that can be maliciously used by outside parties to infiltrate an airgapped system and steal sensitive information.
There are other ways malware could interact across airgapped networks, but they all involve an infected USB drive or a similar device introducing malware onto the airgapped machine. They could also involve a person physically accessing the computer, compromising it and installing malware or modifying its hardware.
This is why it is also recommended to encrypt sensitive information being stored in an airgapped machine.
The airgapped machine should have a few rules enforced to close any possible channels getting abused to leak information outside of the machine:
### Wifi
- Disable any wireless networking hardware on the airgapped machine. For example, if you have a desktop PC with a Wifi card, open the PC and remove the Wifi hardware. If you cannot do that, you could go to the systems BIOS or UEFI firmware and disable the Wifi hardware.
### BlueTooth
- BlueTooth can be maliciously used by neighboring devices to steal data from an airgapped machine. It is recommended to remove or disable the BlueTooth hardware.
### USB
- The USB port can be used to transfer files in and out of the airgapped machine and this may act as a threat to an airgapped machine if the USB drive is infected with a malware. So after installing & setting up this airgapped wallet, it is highly recommended to block off all USB ports by using a USB blocker and not use them.
Do not reconnect the airgapped machine to a network, even when you need to transfer files! An effective airgapped machine should only serve 1 purpose, which is to store data and never open up a gateway for hackers to abuse and steal data.
# Tutorial
For testing purposes, you would need to have 2 machines and 1 phone in hand to scan the QR code.
1. 1st machine would be airgapped, following the security practices written [here](#security-practices). It stores and manages an XRPL Wallet.
2. 2nd machine would be a normal computer connected to the internet. It relays a signed transaction blob to a rippled node.
3. The phone would be used to scan a QR code, which contains a signed transaction blob. The phone would transmit it to the 2nd machine.
The diagram below shows you the process of submitting a transaction to the XRPL:
<p align="center">
<img src="https://user-images.githubusercontent.com/87929946/197970678-2a1b7f7e-d91e-424e-915e-5ba7d34689cc.png" width=75% height=75%>
</p>
# Setup
- Machine 1 - An airgapped computer (during setup, it must be connected to the internet to download the files)
- Machine 2 - A normal computer connected to the internet
- Phone - A normal phone with a working camera to scan a QR
## Machine 1 Setup
Since this machine will be airgapped, it is best to use Linux as the Operating System.
1. Install Python 3.8:
**Linux Command Line**:
```
sudo apt-get update
sudo apt-get install python3.8 python3-pip
```
**Website**: https://www.python.org/downloads/source/
2. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/airgapped-wallet/py) directory
3. Import all the modules required by running:
```
pip install -r requirements.txt
```
4. Airgap the machine by following the security practices written [here](#security-practices).
5. Run `airgapped-wallet.py`
6. Scan the QR code and fund the account using the [testnet faucet](https://test.bithomp.com/faucet/)
7. Re-run the script and input '1' to generate a new transaction by following the instructions.
8. Use your phone to scan the QR code, then to send the signed transaction to Machine 2 for submission
## Machine 2 Setup
This machine will be used to transmit a signed transaction blob from Machine 1, it would require internet access.
1. Install Python 3.8
**Linux Command Line**:
```
sudo apt-get update
sudo apt-get install python3.8 python3-pip
```
**Website**: https://www.python.org/downloads/source/
2. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/airgapped-wallet/py) directory
3. Import all the modules required by running:
```
pip install -r requirements.txt
```
4. Run `relay-transaction.py` with one argument, the signed transaction blob to submit.
## Phone Setup
The phone requires a working camera that is able to scan a QR code and an internet connection for it to be able to transmit the signed transaction blob to Machine 2.
Once you have signed a transaction in the airgapped machine, a QR code will be generated which will contain the signed transaction blob. Example:
<img src="https://user-images.githubusercontent.com/87929946/196018292-f210a9f2-c5f8-412e-98c1-361a72286378.png" width=20% height=20%>
Scan the QR code using the phone and transmit it to Machine 2, which will then be sending it to a rippled node.
You can send a message to yourself using Discord, WhatsApp or even e-mail, then open up the message using Machine 2 to receive the signed transaction blob.

View File

@@ -1,223 +0,0 @@
import os
import base64
import qrcode
import platform
from PIL import Image
from pathlib import Path, PureWindowsPath, PurePath
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from xrpl.core import keypairs
from xrpl.utils import xrp_to_drops
from xrpl.models.transactions import Payment
from xrpl.transaction import sign
from xrpl.wallet.main import Wallet
def create_wallet(silent: False):
"""
Generates a keypair
"""
if not silent:
print("1. Generating seed...")
seed = keypairs.generate_seed()
print("2. Deriving keypair from seed...")
pub, priv = keypairs.derive_keypair(seed)
print("3. Deriving classic addresses from keypair..\n")
address = keypairs.derive_classic_address(pub)
else:
seed = keypairs.generate_seed()
pub, priv = keypairs.derive_keypair(seed)
address = keypairs.derive_classic_address(pub)
return address, seed
def sign_transaction(xrp_amount, destination, ledger_seq, wallet_seq, password):
"""
Signs transaction and returns signed transaction blob in QR code
"""
print("1. Retrieving encrypted private key and salt...")
with open(get_path("/WalletTEST/private.txt"), "r") as f:
seed = f.read()
seed = bytes.fromhex(seed)
with open(get_path("/WalletTEST/salt.txt"), "rb") as f:
salt = f.read()
print("2. Initializing key...")
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
print("3. Decrypting wallet's private key using password")
seed = crypt.decrypt(seed)
print("4. Initializing wallet using decrypted private key")
_wallet = Wallet.from_seed(seed=seed.decode())
validated_seq = ledger_seq
print("5. Constructing payment transaction...")
my_tx_payment = Payment(
account=_wallet.address,
amount=xrp_to_drops(xrp=xrp_amount),
destination=destination,
last_ledger_sequence=validated_seq + 100,
# +100 to catch up with the ledger when we transmit the signed tx blob to Machine 2
sequence=wallet_seq,
fee="10"
)
print("6. Signing transaction...")
my_tx_payment_signed = sign(transaction=my_tx_payment, wallet=_wallet)
img = qrcode.make(my_tx_payment_signed.to_dict())
print("7. Displaying signed transaction blob's QR code on the screen...")
img.save(get_path("/WalletTEST/transactionID.png"))
image = Image.open(get_path("/WalletTEST/transactionID.png"))
image.show()
print(f"RESULT: {my_tx_payment_signed.to_dict()}")
print("END RESULT: Successful")
def get_path(file):
"""
Get path (filesystem management)
"""
global File_
# Checks what OS is being used
OS = platform.system()
usr = Path.home()
# Get PATH format based on the OS
if OS == "Windows":
File_ = PureWindowsPath(str(usr) + file)
else: # Assuming Linux-style file format
File_ = PurePath(str(usr) + file)
return str(File_)
def create_wallet_directory():
global File, Path_
OS = platform.system()
usr = Path.home()
if OS == "Windows":
# If it's Windows, use this path:
print("- OS Detected: Windows")
File = PureWindowsPath(str(usr) + '/WalletTEST')
Path_ = str(PureWindowsPath(str(usr)))
else:
print("- OS Detected: Linux")
# If it's Linux, use this path:
File = PurePath(str(usr) + '/WalletTEST')
Path_ = str(PurePath(str(usr)))
if not os.path.exists(File):
print("1. Generating wallet's keypair...")
pub, seed = create_wallet(silent=True)
print("2. Creating wallet's file directory...")
os.makedirs(File)
print("3. Generating and saving public key's QR code...")
img = qrcode.make(pub)
img.save(get_path("/WalletTEST/public.png"))
print("4. Generating and saving wallet's salt...")
salt = os.urandom(16)
with open(get_path("/WalletTEST/salt.txt"), "wb") as f:
f.write(salt)
print("5. Generating wallet's filesystem password...")
password = "This is a unit test password 123 !@# -+= }{/"
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
print("6. Encrypting and saving private key by password...")
priv = crypt.encrypt(bytes(seed, encoding='utf-8'))
seed = crypt.encrypt(bytes(seed, encoding='utf-8'))
with open(get_path("/WalletTEST/seed.txt"), "w") as f:
f.write(seed.hex())
with open(get_path("/WalletTEST/private.txt"), "w") as f:
f.write(priv.hex())
with open(get_path("/WalletTEST/public.txt"), "w") as f:
f.write(pub)
if os.path.exists(File):
print(f"0. Wallet's filesystem already exist as the unit test has been performed before. Directory: {File}")
def showcase_wallet_address_qr_code():
with open(get_path("/WalletTEST/public.txt"), "r") as f:
print(f"0. Wallet Address: {f.read()}")
__path = get_path("/WalletTEST/public.png")
print(f"1. Getting address from {__path}...")
print("2. Displaying QR code on the screen...")
image = Image.open(get_path("/WalletTEST/public.png"))
image.show()
if __name__ == '__main__':
print("Airgapped Machine Unit Test (5 functions):\n")
print(f"UNIT TEST 1. create_wallet():")
_address, _seed = create_wallet(silent=False)
print(f"-- RESULTS --\n"
f"Address: {_address}\n"
f"Seed: {_seed}\n"
f"END RESULT: Successful"
)
print(f"\nUNIT TEST 2. create_wallet_directory():")
create_wallet_directory()
print("RESULT: Successful")
print("\nUNIT TEST 3. showcase_wallet_address_qr_code():")
showcase_wallet_address_qr_code()
print("RESULT: Successful")
print("\nUNIT TEST 4. get_path():")
print("1. Getting files' path...\n")
txt_file = get_path("/WalletTEST/FILE123.txt")
png_file = get_path("/WalletTEST/PIC321.png")
print(f"-- RESULTS --\n"
f"txt_file: {txt_file}\n"
f"png_file: {png_file}\n"
f"END RESULT: Successful")
print("\nUNIT TEST 5. sign_transaction():")
print("Parameters: xrp_amount, destination, ledger_seq, wallet_seq, password")
sign_transaction(
xrp_amount=10,
destination="rPEpirdT9UCNbnaZMJ4ENwKAwJqrTpvgMQ",
ledger_seq=32602000,
wallet_seq=32600100,
password="This is a unit test password 123 !@# -+= }{/"
)

View File

@@ -1,232 +0,0 @@
import os
import shutil
import base64
import qrcode
import platform
from PIL import Image
from pathlib import Path, PureWindowsPath, PurePath
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from xrpl.wallet import Wallet
from xrpl.core import keypairs
from xrpl.utils import xrp_to_drops
from xrpl.models.transactions import Payment
from xrpl.transaction import sign
def create_wallet():
"""
Generates a keypair
"""
seed = keypairs.generate_seed()
pub, priv = keypairs.derive_keypair(seed)
address = keypairs.derive_classic_address(pub)
print(
f"\n\n XRP WALLET CREDENTIALS"
f"\n Wallet Address: {address}"
f"\n Seed: {seed}"
)
return address, seed
def sign_transaction(_xrp_amount, _destination, _ledger_seq, _wallet_seq, password):
"""
Signs transaction and returns signed transaction blob in QR code
"""
with open(get_path("/Wallet/private.txt"), "r") as f:
_seed = f.read()
_seed = bytes.fromhex(_seed)
with open(get_path("/Wallet/salt.txt"), "rb") as f:
salt = f.read()
# Line 49-58: initialize key
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
# Decrypts the wallet's private key
_seed = crypt.decrypt(_seed)
_wallet = Wallet.from_seed(seed=_seed.decode())
validated_seq = _ledger_seq
# Construct Payment transaction
my_tx_payment = Payment(
account=_wallet.address,
amount=xrp_to_drops(xrp=_xrp_amount),
destination=_destination,
last_ledger_sequence=validated_seq + 100,
# 100 ledgers usually takes about 6 minutes, so you have about that
# long to submit it before it expires. To give more time, increase
# this number; for unlimited time, remove last_ledger entirely.
sequence=_wallet_seq,
fee="10"
)
# Signs transaction and displays the signed_tx blob in QR code
# Scan the QR code and transmit the signed_tx blob to an online machine (Machine 2) to relay it to the XRPL
my_tx_payment_signed = sign(transaction=my_tx_payment, wallet=_wallet)
img = qrcode.make(my_tx_payment_signed.blob())
img.save(get_path("/Wallet/transactionID.png"))
image = Image.open(get_path("/Wallet/transactionID.png"))
image.show()
def get_path(file):
"""
Get path (filesystem management)
"""
global File_
# Checks what OS is being us
OS = platform.system()
usr = Path.home()
# Get PATH format based on the OS
if OS == "Windows":
File_ = PureWindowsPath(str(usr) + file)
else: # Assuming Linux-style file format, use this path:
File_ = PurePath(str(usr) + file)
return str(File_)
def main():
global File, Path_
# Gets the machine's operating system (OS)
OS = platform.system()
usr = Path.home()
if OS == "Windows":
# If it's Windows, use this path:
File = PureWindowsPath(str(usr) + '/Wallet')
Path_ = str(PureWindowsPath(str(usr)))
else: # Assuming Linux-style file format, use this path:
File = PurePath(str(usr) + '/Wallet')
Path_ = str(PurePath(str(usr)))
# If the Wallet's folder already exists, continue on
if os.path.exists(File) and os.path.exists(get_path("/Wallet/public.txt")):
while True:
try:
ask = int(input("\n 1. Transact XRP"
"\n 2. Generate an XRP wallet (read only)"
"\n 3. Showcase XRP Wallet Address (QR Code)"
"\n 4. Exit"
"\n\n Enter Index: "
))
except ValueError:
continue
if ask == 1:
password = str(input(" Enter Password: "))
amount = float(input("\n Enter XRP To Send: "))
destination = input("If you just want to try it out, you can use the faucet account rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"
"\n Enter Destination: ")
wallet_sequence = int(input("Look up the 'Next Sequence' for the account using test.bithomp.com and enter it below!"
"\n Enter Wallet Sequence: "))
ledger_sequence = int(input("Look up the latest ledger sequence on testnet.xrpl.org and enter it below!"
"\n Enter Ledger Sequence: "))
sign_transaction(_xrp_amount=amount,
_destination=destination,
_ledger_seq=ledger_sequence,
_wallet_seq=wallet_sequence,
password=password
)
print("This transaction is expected to expire in ~6 minutes.")
del destination, amount, wallet_sequence, ledger_sequence
if ask == 2:
_pub, _seed = create_wallet()
if ask == 3:
with open(get_path("/Wallet/public.txt"), "r") as f:
print(f"\n Wallet Address: {f.read()}")
image = Image.open(get_path("/Wallet/public.png"))
image.show()
if ask == 4:
return 0
else:
# If the Wallet's folder does not exist, create one and store wallet data (encrypted private key, encrypted seed, account address)
# If the Wallet's directory exists but files are missing, delete it and generate a new wallet
if os.path.exists(File):
confirmation = input(f"We've detected missing files on {File}, would you like to delete your wallet's credentials & generate new wallet credentials? (YES/NO):")
if confirmation == "YES":
confirmation_1 = input(f"All wallet credentials will be lost if you continue, are you sure? (YES/NO): ")
if confirmation_1 == "YES":
shutil.rmtree(File)
else:
print("Aborted: Wallet credentials are still intact")
return 0
else:
print("- Wallet credentials are still intact")
return 0
os.makedirs(File)
pub, seed = create_wallet()
img = qrcode.make(pub)
img.save(get_path("/Wallet/public.png"))
print("\nCreating a brand new Wallet, please enter a new password")
password = str(input("\n Enter Password: "))
salt = os.urandom(16)
with open(get_path("/Wallet/salt.txt"), "wb") as f:
f.write(salt)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
iterations=100000,
salt=salt
)
key = base64.urlsafe_b64encode(kdf.derive(bytes(password.encode())))
crypt = Fernet(key)
priv = crypt.encrypt(bytes(seed, encoding='utf-8'))
seed = crypt.encrypt(bytes(seed, encoding='utf-8'))
with open(get_path("/Wallet/seed.txt"), "w") as f:
f.write(seed.hex())
with open(get_path("/Wallet/private.txt"), "w") as f:
f.write(priv.hex())
with open(get_path("/Wallet/public.txt"), "w") as f:
f.write(pub)
openimg = Image.open(get_path("/Wallet/public.png"))
openimg.show()
print("\nFinished generating an account.")
print(f"\nWallet Address: {pub}")
print("\nPlease scan the QR code on your phone and use https://test.bithomp.com/faucet/ to fund the account."
"\nAfter that, you're able to sign transactions and transmit them to Machine 2 (online machine).")
# Loop back to the start after setup
main()
if __name__ == '__main__':
main()

View File

@@ -1,53 +0,0 @@
from xrpl.clients import JsonRpcClient
from xrpl.models.transactions import Payment
from xrpl.transaction import submit_and_wait
from xrpl.utils import drops_to_xrp
import argparse
def connect_node(_node):
"""
Connects to a node
"""
JSON_RPC_URL = _node
_client = JsonRpcClient(url=JSON_RPC_URL)
print("\n --- Connected to Node")
return _client
def send_transaction(tx_blob):
"""
Connects to a node -> Send Transaction
Main Function to send transaction to the XRPL
"""
client = connect_node("https://s.altnet.rippletest.net:51234/")
# TESTNET: "https://s.altnet.rippletest.net:51234/"
# MAINNET: "https://s2.ripple.com:51234/"
tx = submit_and_wait(transaction=tx_blob, client=client)
tx_account = tx.result["tx_json"]["Account"]
tx_hash = tx.result["hash"]
tx_destination = tx.result["tx_json"]['Destination']
delivered = tx.result["meta"]["delivered_amount"]
if type(delivered) == str:
tx_delivered_amount = f"{drops_to_xrp(delivered)} XRP"
else:
tx_delivered_amount = f"{delivered['value']} {delivered['currency']}.{delivered['issuer']}"
print(f"\n XRPL Explorer: https://testnet.xrpl.org/transactions/{tx_hash}"
f"\n Wallet Used: {tx_account}"
f"\n Transaction Hash: {tx_hash}"
f"\n Transaction Destination: {tx_destination}"
f"\n Amount Delivered: {tx_delivered_amount}"
)
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Submit a signed transaction blob')
p.add_argument('blob', type=str,
help='Transaction blob (in hexadecimal) to submit')
tx_blob = p.parse_args().blob
send_transaction(tx_blob)

View File

@@ -1,4 +0,0 @@
cryptography==44.0.1
Pillow==10.3.0
qrcode==7.2
xrpl-py>=3.0.0

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

@@ -0,0 +1,3 @@
# Use AMM Auction Slot to Save on Fees
Estimate the fees that would be paid for trading through an AMM and use the auction slot to save on fees if applicable.

View File

@@ -1,4 +1,4 @@
const BigNumber = require('bignumber.js')
import BigNumber from 'bignumber.js'
/* Convert a trading fee to a value that can be multiplied
* by a total to "subtract" the fee from the total.
@@ -50,7 +50,7 @@ function feeDecimal(tFee) {
* theoretical input to the pool, it should be rounded
* up (ceiling) to preserve the pool's constant product.
*/
function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
export function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
return ( ( pool_in_bn.multipliedBy(pool_out_bn) ).dividedBy(
pool_out_bn.minus(asset_out_bn)
).minus(pool_in_bn)
@@ -76,7 +76,7 @@ function solveQuadraticEq(a,b,c) {
* @param trading_fee int - The trading fee as an integer {0,1000} where 1000
* represents a 1% fee.
*/
function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
export function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
// convert inputs to BigNumber
const lpTokens = BigNumber(desired_lpt)
const lptAMMBalance = BigNumber(lpt_balance)
@@ -100,7 +100,7 @@ function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
* XLS-30 section 4.1.1, but factors in the increase in the minimum bid as a
* result of having new LP Tokens issued to you from your deposit.
*/
function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
export function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
const tfee_decimal = feeDecimal(trading_fee)
const lptokens = BigNumber(lpt_balance)
const b = BigNumber(old_bid)
@@ -133,7 +133,7 @@ function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
*
* @returns BigNumber - the minimum amount of LP tokens to win the auction slot
*/
function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
export function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
const tfee_decimal = feeDecimal(trading_fee)
const lptokens = BigNumber(lpt_balance)
const min_bid = lptokens.multipliedBy(tfee_decimal).dividedBy(25)
@@ -154,10 +154,3 @@ function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
).minus(lptokens).precision(15, BigNumber.FLOOR)
return rounded_bid
}
module.exports = {
"auctionDeposit": auctionDeposit,
"auctionPrice": auctionPrice,
"ammAssetIn": ammAssetIn,
"swapOut": swapOut,
}

View File

@@ -1,170 +1,165 @@
const xrpl = require('xrpl')
const BigNumber = require('bignumber.js')
const {auctionDeposit, ammAssetIn, swapOut} = require("./amm-formulas.js")
import xrpl from 'xrpl'
import BigNumber from 'bignumber.js'
import { auctionDeposit, ammAssetIn, swapOut } from "./amm-formulas.js"
async function main() {
// Connect ----------------------------------------------------------------
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
console.log("Connecting to Testnet...")
await client.connect()
// // Get credentials from the faucet -------------------------------------
console.log("Requesting test XRP from the faucet...")
const wallet = (await client.fundWallet()).wallet
console.log(`Got address ${wallet.address} / seed ${wallet.seed}.`)
// Connect and get account ----------------------------------------------------
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
console.log("Connecting to Testnet...")
await client.connect()
// Look up AMM status -----------------------------------------------------
const from_asset = {
"currency": "XRP"
}
const to_asset = {
"currency": "TST",
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"
}
const amm_info = (await client.request({
"command": "amm_info",
"asset": from_asset,
"asset2": to_asset
}))
console.dir(amm_info, {depth: null})
const lpt = amm_info.result.amm.lp_token
// XRP is always first if the pool is token←→XRP.
// For a token←→token AMM, you'd need to figure out which asset is first.
const pool_drops = amm_info.result.amm.amount
const pool_tst = amm_info.result.amm.amount2
const full_trading_fee = amm_info.result.amm.trading_fee
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
const old_bid = amm_info.result.amm.auction_slot.price.value
const time_interval = amm_info.result.amm.auction_slot.time_interval
console.log("Requesting test XRP from the faucet...")
const { wallet } = await client.fundWallet()
console.log(`Got address ${wallet.address} / seed ${wallet.seed}.`)
// Calculate price in XRP to get 10 TST from the AMM ----------------------
// Note, this ignores Offers from the non-AMM part of the DEX.
const to_amount = {
"currency": to_asset.currency,
"issuer": to_asset.issuer,
"value": "10.0"
}
// Look up AMM status -----------------------------------------------------
const from_asset = {
"currency": "XRP"
}
const to_asset = {
"currency": "TST",
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"
}
const amm_info = (await client.request({
"command": "amm_info",
"asset": from_asset,
"asset2": to_asset
}))
console.dir(amm_info, {depth: null})
const lpt = amm_info.result.amm.lp_token
// XRP is always first if the pool is token←→XRP.
// For a token←→token AMM, you'd need to figure out which asset is first.
const pool_drops = amm_info.result.amm.amount
const pool_tst = amm_info.result.amm.amount2
const full_trading_fee = amm_info.result.amm.trading_fee
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
const old_bid = amm_info.result.amm.auction_slot.price.value
const time_interval = amm_info.result.amm.auction_slot.time_interval
// Convert values to BigNumbers with the appropriate precision.
// Tokens always have 15 significant digits;
// XRP is precise to integer drops, which can be as high as 10^17
const asset_out_bn = BigNumber(to_amount.value).precision(15)
const pool_in_bn = BigNumber(pool_drops).precision(17)
const pool_out_bn = BigNumber(pool_tst.value).precision(15)
// Calculate price in XRP to get 10 TST from the AMM ----------------------
// Note, this ignores Offers from the non-AMM part of the DEX.
const to_amount = {
"currency": to_asset.currency,
"issuer": to_asset.issuer,
"value": "10.0"
}
if (to_amount.value > pool_out_bn) {
console.log(`Requested ${to_amount.value} ${to_amount.currency} ` +
`but AMM only holds ${pool_tst.value}. Quitting.`)
client.disconnect()
return
}
// Convert values to BigNumbers with the appropriate precision.
// Tokens always have 15 significant digits;
// XRP is precise to integer drops, which can be as high as 10^17
const asset_out_bn = BigNumber(to_amount.value).precision(15)
const pool_in_bn = BigNumber(pool_drops).precision(17)
const pool_out_bn = BigNumber(pool_tst.value).precision(15)
// Use AMM's SwapOut formula to figure out how much XRP we have to pay
// to receive the target amount of TST, under the current trading fee.
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn,
pool_out_bn, full_trading_fee)
// Round XRP to integer drops. Round ceiling to make you pay in enough.
const from_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
console.log(`Expected cost of ${to_amount.value} ${to_amount.currency}: ` +
`${xrpl.dropsToXrp(from_amount)} XRP`)
// Same calculation, but assume we have access to the discounted trading
// fee from the auction slot.
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn,
discounted_fee)
const discounted_from_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
console.log(`Expected cost with auction slot discount: `+
`${xrpl.dropsToXrp(discounted_from_amount)} XRP`)
// The potential savings is the difference between the necessary input
// amounts with the full vs discounted fee.
const potential_savings = from_amount.minus(discounted_from_amount)
console.log(`Potential savings: ${xrpl.dropsToXrp(potential_savings)} XRP`)
// Calculate the cost of winning the auction slot, in LP Tokens -----------
const auction_price = auctionDeposit(old_bid, time_interval,
full_trading_fee, lpt.value
).precision(15)
console.log(`Auction price after deposit: ${auction_price} LP Tokens`)
// Calculate how much XRP to deposit to receive that many LP Tokens -------
const deposit_for_bid = ammAssetIn(pool_in_bn, lpt.value, auction_price,
full_trading_fee
).dp(0, BigNumber.ROUND_CEIL)
console.log(`Auction price as XRP single-asset deposit amount: `+
`${xrpl.dropsToXrp(deposit_for_bid)} XRP`)
// Optional. Allow for costs to be 1% greater than estimated, in case other
// transactions affect the same AMM during this time.
const SLIPPAGE_MULT = BigNumber(1.01)
const deposit_max = deposit_for_bid.multipliedBy(SLIPPAGE_MULT).dp(0)
// Compare price of deposit+bid with potential savings. -------------------
// Don't forget XRP burned as transaction costs.
const fee_response = (await client.request({"command":"fee"}))
const tx_cost_drops = BigNumber(fee_response.result.drops.minimum_fee
).multipliedBy(client.feeCushion).dp(0)
const net_savings = potential_savings.minus(
tx_cost_drops.multipliedBy(2).plus(deposit_max)
)
if (net_savings > 0) {
console.log(`Estimated net savings from the auction slot: ` +
`${xrpl.dropsToXrp(net_savings)} XRP`)
} else {
console.log(`Estimated the auction slot to be MORE EXPENSIVE by `+
`${xrpl.dropsToXrp(net_savings.negated())} XRP. Quitting.`)
client.disconnect()
return
}
// Do a single-asset deposit to get LP Tokens to bid on the auction slot --
const auction_bid = {
"currency": lpt.currency,
"issuer": lpt.issuer,
"value": auction_price.toString()
}
const deposit_result = await client.submitAndWait({
"TransactionType": "AMMDeposit",
"Account": wallet.address,
"Asset": from_asset,
"Asset2": to_asset,
"Amount": deposit_max.toString(),
"LPTokenOut": auction_bid,
"Flags": xrpl.AMMDepositFlags.tfOneAssetLPToken
}, {autofill: true, wallet: wallet}
)
console.log("Deposit result:")
console.dir(deposit_result, {depth: null})
// Actually bid on the auction slot ---------------------------------------
const bid_result = await client.submitAndWait({
"TransactionType": "AMMBid",
"Account": wallet.address,
"Asset": from_asset,
"Asset2": to_asset,
"BidMax": auction_bid,
"BidMin": auction_bid, // So rounding doesn't leave dust amounts of LPT
}, {autofill: true, wallet: wallet}
)
console.log("Bid result:")
console.dir(bid_result, {depth: null})
// Trade using the discount -----------------------------------------------
const spend_drops = discounted_from_amount.multipliedBy(SLIPPAGE_MULT
).dp(0).toString()
const offer_result = await client.submitAndWait({
"TransactionType": "OfferCreate",
"Account": wallet.address,
"TakerPays": to_amount,
"TakerGets": spend_drops
}, {autofill: true, wallet: wallet})
console.log("Offer result:")
console.dir(offer_result, {depth: null})
console.log("Offer balance changes summary:")
console.dir(xrpl.getBalanceChanges(offer_result.result.meta), {depth:null})
// Done.
if (to_amount.value > pool_out_bn) {
console.log(`Requested ${to_amount.value} ${to_amount.currency} ` +
`but AMM only holds ${pool_tst.value}. Quitting.`)
client.disconnect()
} // End of main()
process.exit(1)
}
main()
// Use AMM's SwapOut formula to figure out how much XRP we have to pay
// to receive the target amount of TST, under the current trading fee.
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn,
pool_out_bn, full_trading_fee)
// Round XRP to integer drops. Round ceiling to make you pay in enough.
const from_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
console.log(`Expected cost of ${to_amount.value} ${to_amount.currency}: ` +
`${xrpl.dropsToXrp(from_amount)} XRP`)
// Same calculation, but assume we have access to the discounted trading
// fee from the auction slot.
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn,
discounted_fee)
const discounted_from_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
console.log(`Expected cost with auction slot discount: `+
`${xrpl.dropsToXrp(discounted_from_amount)} XRP`)
// The potential savings is the difference between the necessary input
// amounts with the full vs discounted fee.
const potential_savings = from_amount.minus(discounted_from_amount)
console.log(`Potential savings: ${xrpl.dropsToXrp(potential_savings)} XRP`)
// Calculate the cost of winning the auction slot, in LP Tokens -----------
const auction_price = auctionDeposit(old_bid, time_interval,
full_trading_fee, lpt.value
).precision(15)
console.log(`Auction price after deposit: ${auction_price} LP Tokens`)
// Calculate how much XRP to deposit to receive that many LP Tokens -------
const deposit_for_bid = ammAssetIn(pool_in_bn, lpt.value, auction_price,
full_trading_fee
).dp(0, BigNumber.ROUND_CEIL)
console.log(`Auction price as XRP single-asset deposit amount: `+
`${xrpl.dropsToXrp(deposit_for_bid)} XRP`)
// Optional. Allow for costs to be 1% greater than estimated, in case other
// transactions affect the same AMM during this time.
const SLIPPAGE_MULT = BigNumber(1.01)
const deposit_max = deposit_for_bid.multipliedBy(SLIPPAGE_MULT).dp(0)
// Compare price of deposit+bid with potential savings. -------------------
// Don't forget XRP burned as transaction costs.
const fee_response = (await client.request({"command":"fee"}))
const tx_cost_drops = BigNumber(fee_response.result.drops.minimum_fee
).multipliedBy(client.feeCushion).dp(0)
const net_savings = potential_savings.minus(
tx_cost_drops.multipliedBy(2).plus(deposit_max)
)
if (net_savings > 0) {
console.log(`Estimated net savings from the auction slot: ` +
`${xrpl.dropsToXrp(net_savings)} XRP`)
} else {
console.log(`Estimated the auction slot to be MORE EXPENSIVE by `+
`${xrpl.dropsToXrp(net_savings.negated())} XRP. Quitting.`)
client.disconnect()
process.exit(1)
}
// Do a single-asset deposit to get LP Tokens to bid on the auction slot --
const auction_bid = {
"currency": lpt.currency,
"issuer": lpt.issuer,
"value": auction_price.toString()
}
const deposit_result = await client.submitAndWait({
"TransactionType": "AMMDeposit",
"Account": wallet.address,
"Asset": from_asset,
"Asset2": to_asset,
"Amount": deposit_max.toString(),
"LPTokenOut": auction_bid,
"Flags": xrpl.AMMDepositFlags.tfOneAssetLPToken
}, {autofill: true, wallet: wallet}
)
console.log("Deposit result:")
console.dir(deposit_result, {depth: null})
// Actually bid on the auction slot ---------------------------------------
const bid_result = await client.submitAndWait({
"TransactionType": "AMMBid",
"Account": wallet.address,
"Asset": from_asset,
"Asset2": to_asset,
"BidMax": auction_bid,
"BidMin": auction_bid, // So rounding doesn't leave dust amounts of LPT
}, {autofill: true, wallet: wallet}
)
console.log("Bid result:")
console.dir(bid_result, {depth: null})
// Trade using the discount -----------------------------------------------
const spend_drops = discounted_from_amount.multipliedBy(SLIPPAGE_MULT
).dp(0).toString()
const offer_result = await client.submitAndWait({
"TransactionType": "OfferCreate",
"Account": wallet.address,
"TakerPays": to_amount,
"TakerGets": spend_drops
}, {autofill: true, wallet: wallet})
console.log("Offer result:")
console.dir(offer_result, {depth: null})
console.log("Offer balance changes summary:")
console.dir(xrpl.getBalanceChanges(offer_result.result.meta), {depth:null})
// Done.
client.disconnect()

View File

@@ -1,6 +1,7 @@
{
"dependencies": {
"xrpl": "^4.0.0",
"bignumber.js": "^9.0.0"
}
"xrpl": "^4.6.0",
"bignumber.js": "^10.0.2"
},
"type": "module"
}

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

@@ -0,0 +1,5 @@
# Calculate Reserves
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.

View File

@@ -0,0 +1,10 @@
# Calculate Reserves (Go)
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

@@ -0,0 +1,74 @@
// Set up client ----------------------
package main
import (
"fmt"
"strconv"
"github.com/Peersyst/xrpl-go/xrpl/currency"
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
"github.com/Peersyst/xrpl-go/xrpl/queries/server"
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
"github.com/Peersyst/xrpl-go/xrpl/websocket"
)
func main() {
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://xrplcluster.com"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Look up reserve values ----------------------
res, err := client.Request(&server.StateRequest{})
if err != nil {
panic(err)
}
var serverState server.StateResponse
if err := res.GetResult(&serverState); err != nil {
panic(err)
}
baseReserve := serverState.State.ValidatedLedger.ReserveBase
reserveInc := serverState.State.ValidatedLedger.ReserveInc
baseReserveXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(baseReserve), 10))
if err != nil {
panic(err)
}
reserveIncXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(reserveInc), 10))
if err != nil {
panic(err)
}
fmt.Printf("Base reserve: %v XRP\n", baseReserveXrp)
fmt.Printf("Incremental reserve: %v XRP\n", reserveIncXrp)
// Look up owner count ----------------------
address := types.Address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn") // replace with any address
accountInfo, err := client.GetAccountInfo(&account.InfoRequest{Account: address})
if err != nil {
panic(err)
}
ownerCount := accountInfo.AccountData.OwnerCount
// Calculate total reserve ----------------------
totalReserve := baseReserve + (uint(ownerCount) * reserveInc)
totalReserveXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(totalReserve), 10))
if err != nil {
panic(err)
}
fmt.Printf("Owner count: %v\n", ownerCount)
fmt.Printf("Total reserve: %v XRP\n", totalReserveXrp)
}

View File

@@ -0,0 +1,16 @@
module calculate-reserves
go 1.26.1
require github.com/Peersyst/xrpl-go v0.1.17
require (
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/mitchellh/mapstructure v1.5.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/crypto v0.44.0 // indirect
)

View File

@@ -0,0 +1,30 @@
github.com/Peersyst/xrpl-go v0.1.17 h1:jOI2es/dzzRm6/WMVCjr7H3fci47XB2kRkezfbddDB0=
github.com/Peersyst/xrpl-go v0.1.17/go.mod h1:38j60Mr65poIHdhmjvNXnwbcUFNo8J7CBDot7ZWgrb8=
github.com/bsv-blockchain/go-sdk v1.2.9 h1:LwFzuts+J5X7A+ECx0LNowtUgIahCkNNlXckdiEMSDk=
github.com/bsv-blockchain/go-sdk v1.2.9/go.mod h1:KiHWa/hblo3Bzr+IsX11v0sn1E6elGbNX0VXl5mOq6E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 h1:TvGTmUBHDU75OHro9ojPLK+Yv7gDl2hnUvRocRCjsys=
github.com/decred/dcrd/crypto/ripemd160 v1.0.2/go.mod h1:uGfjDyePSpa75cSQLzNdVmWlbQMBuiJkvXw/MNKRY4M=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,9 @@
# Calculate Reserves (JavaScript)
This code sample uses [xrpl.js](https://js.xrpl.org/) to look up and calculate an XRP Ledger account's reserve requirements.
## Usage
```bash
npm install
node calculate_reserves.js
```

View File

@@ -0,0 +1,32 @@
// Set up client ----------------------
import xrpl from 'xrpl'
const client = new xrpl.Client('wss://xrplcluster.com')
await client.connect()
// Look up reserve values ----------------------
const serverState = await client.request({ command: 'server_state' })
const validatedLedger = serverState.result.state.validated_ledger
const baseReserveDrops = validatedLedger.reserve_base
const reserveIncDrops = validatedLedger.reserve_inc
console.log(`Base reserve: ${xrpl.dropsToXrp(baseReserveDrops)} XRP`)
console.log(`Incremental reserve: ${xrpl.dropsToXrp(reserveIncDrops)} XRP`)
// Look up owner count ----------------------
const address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // replace with any address
const accountInfo = await client.request({ command: 'account_info', account: address })
const ownerCount = accountInfo.result.account_data.OwnerCount
// Calculate total reserve ----------------------
const totalReserveDrops = baseReserveDrops + (ownerCount * reserveIncDrops)
console.log(`Owner count: ${ownerCount}`)
console.log(`Total reserve: ${xrpl.dropsToXrp(totalReserveDrops)} XRP`)
await client.disconnect()

View File

@@ -0,0 +1,9 @@
{
"name": "calculate-reserves",
"version": "1.0.0",
"license": "MIT",
"type": "module",
"dependencies": {
"xrpl": "^4.4.0"
}
}

View File

@@ -0,0 +1,9 @@
# Calculate Reserves (Python)
This code sample uses [xrpl-py](https://xrpl-py.readthedocs.io/) to look up and calculate an XRP Ledger account's reserve requirements.
## Usage
```bash
pip install -r requirements.txt
python calculate_reserves.py
```

View File

@@ -0,0 +1,30 @@
from xrpl.clients import JsonRpcClient
from xrpl.models.requests import ServerState, AccountInfo
from xrpl.utils import drops_to_xrp
# Set up client ----------------------
client = JsonRpcClient("https://xrplcluster.com")
# Look up reserve values ----------------------
response = client.request(ServerState())
validated_ledger = response.result["state"]["validated_ledger"]
base_reserve = validated_ledger["reserve_base"]
reserve_inc = validated_ledger["reserve_inc"]
print(f"Base reserve: {drops_to_xrp(str(base_reserve))} XRP")
print(f"Incremental reserve: {drops_to_xrp(str(reserve_inc))} XRP")
# Look up owner count ----------------------
address = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" # replace with any address
response = client.request(AccountInfo(account=address))
owner_count = response.result["account_data"]["OwnerCount"]
# Calculate total reserve ----------------------
total_reserve = base_reserve + (owner_count * reserve_inc)
print(f"Owner count: {owner_count}")
print(f"Total reserve: {drops_to_xrp(str(total_reserve))} XRP")

View File

@@ -0,0 +1 @@
xrpl-py>=4.4.0

View File

@@ -0,0 +1,3 @@
# Delete Account
Delete an account from the XRP Ledger, removing its data and sending its XRP to another account.

View File

@@ -0,0 +1,4 @@
# Replace the seed with the seed of the account to delete.
ACCOUNT_SEED=s████████████████████████████
# Change to secp256k1 if you generated the seed with that algorithm
ACCOUNT_ALGORITHM=ed25519

View File

@@ -0,0 +1,45 @@
# Delete Account (JavaScript)
JavaScript sample code showing how to delete an account from the XRP Ledger.
## Setup
```sh
npm i
```
## Usage
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
```sh
$ node delete-account.js
Got new account from faucet:
Address: rsuTU7xBF1u8jxKMw5UHvbKkLmvix7zQoe
Seed: sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
Edit the .env file to add this seed, then wait until the account can be deleted.
Account is too new to be deleted.
Account sequence + 255: 15226794
Validated ledger index: 15226538
(Sequence + 255 must be less than or equal to the ledger index)
Estimate: 15 minutes until account can be deleted
OK: Account owner count (0) is low enough.
OK: Account balance (100000000 drops) is high enough.
A total of 1 problem(s) prevent the account from being deleted.
```
Edit the `.env` file to add the seed of the account to delete. For example:
```ini
# Replace the seed with the seed of the account to delete.
ACCOUNT_SEED=sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
# Change to secp256k1 if you generated the seed with that algorithm
ACCOUNT_ALGORITHM=ed25519
```
Then run the script again:
```sh
node delete-account.js
```

View File

@@ -0,0 +1,205 @@
import { Client, Wallet, getBalanceChanges, validate } from 'xrpl'
import 'dotenv/config'
const client = new Client('wss://s.altnet.rippletest.net:51233')
await client.connect()
// Where to send the deleted account's remaining XRP:
const DESTINATION_ACCOUNT = 'rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo' // Testnet faucet
// Load the account to delete from .env file -----------------------------------
// If the seed value is still the default, get a new account from the faucet.
// It won't be deletable immediately.
let wallet
if (!process.env.ACCOUNT_SEED || process.env.ACCOUNT_SEED === 's████████████████████████████') {
console.log("Couldn't load seed from .env; getting account from the faucet.")
wallet = (await client.fundWallet()).wallet
console.log(`Got new account from faucet:
Address: ${wallet.address}
Seed: ${wallet.seed}
`)
console.log('Edit the .env file to add this seed, then wait until the account can be deleted.')
} else {
wallet = Wallet.fromSeed(process.env.ACCOUNT_SEED, { algorithm: process.env.ACCOUNT_ALGORITHM })
console.log(`Loaded account: ${wallet.address}`)
}
// Check account info to see if account can be deleted -------------------------
let acctInfoResp
try {
acctInfoResp = await client.request({
command: 'account_info',
account: wallet.address,
ledger_index: 'validated'
})
} catch (err) {
console.error('account_info failed with error:', err)
client.disconnect()
process.exit(1)
}
let numProblems = 0
// Check if sequence number is too high
const acctSeq = acctInfoResp.result.account_data.Sequence
const lastValidatedLedgerIndex = acctInfoResp.result.ledger_index
if (acctSeq + 255 > lastValidatedLedgerIndex) {
console.error(`Account is too new to be deleted.
Account sequence + 255: ${acctSeq + 255}
Validated ledger index: ${lastValidatedLedgerIndex}
(Sequence + 255 must be less than or equal to the ledger index)`)
// Estimate time until deletability assuming ledgers close every ~3.5 seconds
const estWaitTimeS = (acctSeq + 255 - lastValidatedLedgerIndex) * 3.5
if (estWaitTimeS < 120) {
console.log(`Estimate: ${estWaitTimeS} seconds until account can be deleted`)
} else {
const estWaitTimeM = Math.round(estWaitTimeS / 60, 0)
console.log(`Estimate: ${estWaitTimeM} minutes until account can be deleted`)
}
numProblems += 1
} else {
console.log(`OK: Account sequence number (${acctSeq}) is low enough.`)
}
// Check if owner count is too high
const ownerCount = acctInfoResp.result.account_data.OwnerCount
if (ownerCount > 1000) {
console.error(`Account owns too many objects in the ledger.
Owner count: ${ownerCount}
(Must be 1000 or less)`)
numProblems += 1
} else {
console.log(`OK: Account owner count (${ownerCount}) is low enough.`)
}
// Check if XRP balance is high enough
// Look up current incremental owner reserve to compare vs account's XRP balance
// using server_state so that both are in drops
let serverStateResp
try {
serverStateResp = await client.request({
command: 'server_state'
})
} catch (err) {
console.error('server_state failed with error:', err)
client.disconnect()
process.exit(1)
}
const deletionCost = serverStateResp.result.state.validated_ledger?.reserve_inc
if (!deletionCost) {
console.error("Couldn't get reserve values from server. " +
"Maybe it's not synced to the network?")
client.disconnect()
process.exit(1)
}
const acctBalance = acctInfoResp.result.account_data.Balance
if (acctBalance < deletionCost) {
console.error(`Account does not have enough XRP to pay the cost of deletion.
Balance: ${acctBalance}
Cost of account deletion: ${deletionCost}`)
numProblems += 1
} else {
console.log(`OK: Account balance (${acctBalance} drops) is high enough.`)
}
// Check if FirstNFTSequence is too high
const firstNFTSeq = acctInfoResp.result.account_data.FirstNFTokenSequence || 0
const mintedNFTs = acctInfoResp.result.account_data.MintedNFTokens || 0
if (firstNFTSeq + mintedNFTs + 255 > lastValidatedLedgerIndex) {
console.error(`Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
Current total: ${firstNFTSeq + mintedNFTs + 255}
Validated ledger index: ${lastValidatedLedgerIndex}
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the ledger index)`)
numProblems += 1
} else {
console.log('OK: FirstNFTokenSequence + MintedNFTokens is low enough.')
}
// Check that all issued NFTs have been burned
const burnedNFTs = acctInfoResp.result.account_data.BurnedNFTokens || 0
if (mintedNFTs > burnedNFTs) {
console.error(`Account has issued NFTs outstanding.
Number of NFTs minted: ${mintedNFTs}
Number of NFTs burned: ${burnedNFTs}`)
numProblems += 1
} else {
console.log('OK: No outstanding, un-burned NFTs')
}
// Stop if any problems were found
if (numProblems) {
console.error(`A total of ${numProblems} problem(s) prevent the account from being deleted.`)
client.disconnect()
process.exit(1)
}
// Check for deletion blockers -------------------------------------------------
const blockers = []
let marker
const ledger_index = 'validated'
while (true) {
let accountObjResp
try {
accountObjResp = await client.request({
command: 'account_objects',
account: wallet.address,
deletion_blockers_only: true,
ledger_index,
marker
})
} catch (err) {
console.error('account_objects failed with error:', err)
client.disconnect()
process.exit(1)
}
for (const obj of accountObjResp.result.account_objects) {
blockers.push(obj)
}
if (accountObjResp.result.marker) {
marker = accountObjResp.result.marker
} else {
break
}
}
if (!blockers.length) {
console.log('OK: Account has no deletion blockers.')
} else {
console.log(`Account cannot be deleted until ${blockers.length} blocker(s) are removed:`)
for (const blocker of blockers) {
console.log(JSON.stringify(blocker, null, 2))
}
client.disconnect()
process.exit(1)
}
// Delete the account ----------------------------------------------------------
const accountDeleteTx = {
TransactionType: 'AccountDelete',
Account: wallet.address,
Destination: DESTINATION_ACCOUNT
}
validate(accountDeleteTx)
console.log('Signing and submitting the AccountDelete transaction:',
JSON.stringify(accountDeleteTx, null, 2))
const deleteTxResponse = await client.submitAndWait(accountDeleteTx, { wallet, autofill: true, failHard: true })
// Check result of the AccountDelete transaction -------------------------------
console.log(JSON.stringify(deleteTxResponse.result, null, 2))
const resultCode = deleteTxResponse.result.meta.TransactionResult
if (resultCode !== 'tesSUCCESS') {
console.error(`AccountDelete failed with code ${resultCode}.`)
client.disconnect()
process.exit(1)
}
console.log('Account deleted successfully.')
const balanceChanges = getBalanceChanges(deleteTxResponse.result.meta)
console.log('Balance changes:', JSON.stringify(balanceChanges, null, 2))
client.disconnect()

View File

@@ -0,0 +1,10 @@
{
"name": "delete-account",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"dotenv": "^17.3.1",
"xrpl": "^4.6.0"
},
"type": "module"
}

View File

@@ -0,0 +1,4 @@
# Replace the seed with the seed of the account to delete.
ACCOUNT_SEED=s████████████████████████████
# Change to secp256k1 if you generated the seed with that algorithm
ACCOUNT_ALGORITHM=ed25519

View File

@@ -0,0 +1,47 @@
# Delete Account (Python)
Python sample code showing how to delete an account from the XRP Ledger.
## Setup
```sh
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
## Usage
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
```sh
$ python delete-account.py
Got new account from faucet:
Address: rNqLzC9pVbphwwpTBNPjpx14QSauHH3kzv
Seed: sEdTNEJgK3cVshBEakfVic4MMtWCETY
Edit the .env file to add this seed, then wait until the account can be deleted.
Account is too new to be deleted.
Account sequence + 255: 15226905
Validated ledger index: 15226649
(Sequence + 255 must be less than or equal to the ledger index)
Estimate: 15 minutes until account can be deleted
OK: Account owner count (0) is low enough.
OK: Account balance (100000000 drops) is high enough.
A total of 1 problem(s) prevent the account from being deleted.
```
Edit the `.env` file to add the seed of the account to delete. For example:
```ini
# Replace the seed with the seed of the account to delete.
ACCOUNT_SEED=sEdTNEJgK3cVshBEakfVic4MMtWCETY
# Change to secp256k1 if you generated the seed with that algorithm
ACCOUNT_ALGORITHM=ed25519
```
Then run the script again:
```sh
python delete-account.py
```

View File

@@ -0,0 +1,199 @@
import os
import json
from dotenv import load_dotenv
from xrpl.clients import JsonRpcClient
from xrpl.wallet import Wallet, generate_faucet_wallet
from xrpl.models.requests import AccountInfo, ServerState, AccountObjects
from xrpl.models.transactions import AccountDelete
from xrpl.transaction import submit_and_wait
from xrpl.utils import get_balance_changes
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
# Where to send the deleted account's remaining XRP:
DESTINATION_ACCOUNT = "rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo" # Testnet faucet
# Load the account to delete from .env file -----------------------------------
# If the seed value is still the default, get a new account from the faucet.
# It won't be deletable immediately.
load_dotenv()
account_seed = os.getenv("ACCOUNT_SEED")
account_algorithm = os.getenv("ACCOUNT_ALGORITHM", "ed25519")
if account_seed == "s████████████████████████████" or not account_seed:
print("Couldn't load seed from .env; getting account from the faucet.")
wallet = generate_faucet_wallet(client)
print(
f"Got new account from faucet:\n"
f" Address: {wallet.address}\n"
f" Seed: {wallet.seed}\n"
)
print(
"Edit the .env file to add this seed, then wait until the account can be deleted."
)
else:
wallet = Wallet.from_seed(account_seed, algorithm=account_algorithm)
print(f"Loaded account: {wallet.address}")
# Check account info to see if account can be deleted -------------------------
try:
acct_info_resp = client.request(
AccountInfo(account=wallet.address, ledger_index="validated")
)
except Exception as err:
print(f"account_info failed with error: {err}")
exit(1)
acct_info_result = acct_info_resp.result
num_problems = 0
# Check if sequence number is too high
acct_seq = acct_info_result["account_data"]["Sequence"]
last_validated_ledger_index = acct_info_result["ledger_index"]
if acct_seq + 255 > last_validated_ledger_index:
print(
f"Account is too new to be deleted.\n"
f" Account sequence + 255: {acct_seq + 255}\n"
f" Validated ledger index: {last_validated_ledger_index}\n"
f" (Sequence + 255 must be less than or equal to the ledger index)"
)
# Estimate time until deletability assuming ledgers close every ~3.5 seconds
est_wait_time_s = (acct_seq + 255 - last_validated_ledger_index) * 3.5
if est_wait_time_s < 120:
print(f"Estimate: {est_wait_time_s} seconds until account can be deleted")
else:
est_wait_time_m = round(est_wait_time_s / 60)
print(f"Estimate: {est_wait_time_m} minutes until account can be deleted")
num_problems += 1
else:
print(f"OK: Account sequence number ({acct_seq}) is low enough.")
# Check if owner count is too high
owner_count = acct_info_result["account_data"]["OwnerCount"]
if owner_count > 1000:
print(
f"Account owns too many objects in the ledger.\n"
f" Owner count: {owner_count}\n"
f" (Must be 1000 or less)"
)
num_problems += 1
else:
print(f"OK: Account owner count ({owner_count}) is low enough.")
# Check if XRP balance is high enough
# Look up current incremental owner reserve to compare vs account's XRP balance
# using server_state so that both are in drops
try:
server_state_resp = client.request(ServerState())
except Exception as err:
print("server_state failed with error:", err)
exit(1)
validated_ledger = server_state_resp.result["state"].get("validated_ledger", {})
deletion_cost = validated_ledger.get("reserve_inc")
if not deletion_cost:
print(
"Couldn't get reserve values from server. Maybe it's not synced to the network?"
)
print(json.dumps(server_state_resp.result, indent=2))
exit(1)
acct_balance = int(acct_info_result["account_data"]["Balance"])
if acct_balance < deletion_cost:
print(
f"Account does not have enough XRP to pay the cost of deletion.\n"
f" Balance: {acct_balance}\n"
f" Cost of account deletion: {deletion_cost}"
)
num_problems += 1
else:
print(f"OK: Account balance ({acct_balance} drops) is high enough.")
# Check if FirstNFTSequence is too high
first_nfq_seq = acct_info_result["account_data"].get("FirstNFTokenSequence", 0)
minted_nfts = acct_info_result["account_data"].get("MintedNFTokens", 0)
if first_nfq_seq + minted_nfts + 255 > last_validated_ledger_index:
print(f"""Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
Current total: {first_nfq_seq + minted_nfts + 255}
Validated ledger index: {last_validated_ledger_index}
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the the ledger index)""")
num_problems += 1
else:
print("OK: FirstNFTokenSequence + MintedNFTokens is low enough.")
# Check that all issued NFTs have been burned
burned_nfts = acct_info_result["account_data"].get("BurnedNFTokens", 0)
if minted_nfts > burned_nfts:
print(f"""Account has NFTs outstanding.
Number of NFTs minted: {minted_nfts}
Number of NFTs burned: {burned_nfts}""")
num_problems += 1
else:
print("OK: No outstanding, un-burned NFTs")
# Stop if any problems were found
if num_problems:
print(
f"A total of {num_problems} problem(s) prevent the account from being deleted."
)
exit(1)
# Check for deletion blockers -------------------------------------------------
blockers = []
marker = None
ledger_index = "validated"
while True:
try:
account_obj_resp = client.request(
AccountObjects(
account=wallet.address,
deletion_blockers_only=True,
ledger_index=ledger_index,
marker=marker,
)
)
except Exception as err:
print(f"account_objects failed with error: {err}")
exit(1)
blockers.extend(account_obj_resp.result["account_objects"])
marker = account_obj_resp.result.get("marker")
if not marker:
break
if not blockers:
print("OK: Account has no deletion blockers.")
else:
print(f"Account cannot be deleted until {len(blockers)} blocker(s) are removed:")
for blocker in blockers:
print(json.dumps(blocker, indent=2))
exit(1)
# Delete the account ----------------------------------------------------------
account_delete_tx = AccountDelete(
account=wallet.address, destination=DESTINATION_ACCOUNT
)
print("Signing and submitting the AccountDelete transaction:")
print(json.dumps(account_delete_tx.to_xrpl(), indent=2))
delete_tx_response = submit_and_wait(account_delete_tx, client, wallet, fail_hard=True)
# Check result of the AccountDelete transaction -------------------------------
print(json.dumps(delete_tx_response.result, indent=2))
result_code = delete_tx_response.result["meta"]["TransactionResult"]
if result_code != "tesSUCCESS":
print(f"AccountDelete failed with code {result_code}.")
exit(1)
print("Account deleted successfully.")
balance_changes = get_balance_changes(delete_tx_response.result["meta"])
print("Balance changes:", json.dumps(balance_changes, indent=2))

View File

@@ -0,0 +1,2 @@
xrpl-py==4.5.0
python-dotenv==1.2.1

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

@@ -0,0 +1,395 @@
# Lending Protocol Examples (Go)
This directory contains Go examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
## Setup
All commands should be run from this `go/` directory.
Install dependencies before running any examples:
```sh
go mod tidy
```
---
## Create a Loan Broker
```sh
go run ./create-loan-broker
```
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account.
```sh
Loan broker/vault owner address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
Vault ID: A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9
=== Preparing LoanBrokerSet transaction ===
{
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
"ManagementFeeRate": 1000,
"TransactionType": "LoanBrokerSet",
"VaultID": "A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9"
}
=== Submitting LoanBrokerSet transaction ===
Loan broker created successfully!
=== Loan Broker Information ===
LoanBroker ID: E4D9C485E101FAE449C8ACEC7FD039920CC02D2443687F2593DB397CC8EA670B
LoanBroker Pseudo-Account Address: rMDsnf9CVRLRJzrL12Ex7nhstbni78y8af
```
---
## Claw Back First-loss Capital
```sh
go run ./cover-clawback
```
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback.
```sh
Loan broker address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
MPT issuer address: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW
LoanBrokerID: 373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D
MPT ID: 003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51
=== Cover Available ===
0 TSTUSD
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
"Amount": {
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
"value": "1000"
},
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
"TransactionType": "LoanBrokerCoverDeposit"
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Available After Deposit ===
1000 TSTUSD
=== Verifying Asset Issuer ===
MPT issuer account verified: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW. Proceeding to clawback.
=== Preparing LoanBrokerCoverClawback transaction ===
{
"Account": "rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW",
"Amount": {
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
"value": "1000"
},
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
"TransactionType": "LoanBrokerCoverClawback"
}
=== Submitting LoanBrokerCoverClawback transaction ===
Successfully clawed back 1000 TSTUSD!
=== Final Cover Available After Clawback ===
0 TSTUSD
```
---
## Deposit and Withdraw First-loss Capital
```sh
go run ./cover-deposit-and-withdraw
```
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal.
```sh
Loan broker address: rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV
LoanBrokerID: 633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3
MPT ID: 003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
"Amount": {
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
"value": "2000"
},
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
"TransactionType": "LoanBrokerCoverDeposit"
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Balance ===
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
Cover balance after deposit: 2000 TSTUSD
=== Preparing LoanBrokerCoverWithdraw transaction ===
{
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
"Amount": {
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
"value": "1000"
},
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
"TransactionType": "LoanBrokerCoverWithdraw"
}
=== Submitting LoanBrokerCoverWithdraw transaction ===
Cover withdraw successful!
=== Updated Cover Balance ===
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
Cover balance after withdraw: 1000 TSTUSD
```
---
## Create a Loan
```sh
go run ./create-loan
```
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information.
```sh
Loan broker address: rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX
Borrower address: rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp
LoanBrokerID: 490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09
=== Preparing LoanSet transaction ===
{
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
"Fee": "2",
"GracePeriod": 604800,
"InterestRate": 500,
"LastLedgerSequence": 437349,
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 6905,
"TransactionType": "LoanSet"
}
=== Adding loan broker signature ===
TxnSignature: 9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F
SigningPubKey: ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1
Signed loanSetTx for borrower to sign over:
{
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
"Fee": "2",
"GracePeriod": 604800,
"InterestRate": 500,
"LastLedgerSequence": 437349,
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 6905,
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
"TransactionType": "LoanSet",
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
}
=== Adding borrower signature ===
Borrower TxnSignature: 5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005
Borrower SigningPubKey: ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E
Fully signed LoanSet transaction:
{
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
"CounterpartySignature": {
"SigningPubKey": "ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E",
"TxnSignature": "5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005"
},
"Fee": "2",
"GracePeriod": 604800,
"InterestRate": 500,
"LastLedgerSequence": 437349,
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 6905,
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
"TransactionType": "LoanSet",
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
}
=== Submitting signed LoanSet transaction ===
Loan created successfully!
=== Loan Information ===
{
"Borrower": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
"GracePeriod": 604800,
"InterestRate": 500,
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
"LoanOriginationFee": "100",
"LoanSequence": 4,
"LoanServiceFee": "10",
"NextPaymentDueDate": 829803181,
"PaymentInterval": 2592000,
"PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000",
"StartDate": 827211181,
"TotalValueOutstanding": "1003"
}
```
---
## Manage a Loan
```sh
go run ./loan-manage
```
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
```sh
Loan broker address: rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX
LoanID: 2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C
=== Loan Status ===
Total Amount Owed: 1001 TSTUSD.
Payment Due Date: 2026-03-23 01:23:40
=== Preparing LoanManage transaction to impair loan ===
{
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
"Flags": 131072,
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
"TransactionType": "LoanManage"
}
=== Submitting LoanManage impairment transaction ===
Loan impaired successfully!
New Payment Due Date: 2026-02-21 00:24:10
Grace Period: 60 seconds
=== Countdown until loan can be defaulted ===
Grace period expired. Loan can now be defaulted.
=== Preparing LoanManage transaction to default loan ===
{
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
"Flags": 65536,
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
"TransactionType": "LoanManage"
}
=== Submitting LoanManage default transaction ===
Loan defaulted successfully!
=== Checking final loan status ===
Final loan flags: [tfLoanDefault tfLoanImpair]
```
---
## Pay a Loan
```sh
go run ./loan-pay
```
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry.
```sh
Borrower address: rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2
LoanID: D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A
MPT ID: 003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B
=== Loan Status ===
Amount Owed: 1001 TSTUSD
Loan Service Fee: 10 TSTUSD
Total Payment Due (including fees): 1011 TSTUSD
=== Preparing LoanPay transaction ===
{
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
"Amount": {
"mpt_issuance_id": "003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B",
"value": "1011"
},
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
"TransactionType": "LoanPay"
}
=== Submitting LoanPay transaction ===
Loan paid successfully!
=== Loan Status After Payment ===
Outstanding Loan Balance: Loan fully paid off!
=== Preparing LoanDelete transaction ===
{
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
"TransactionType": "LoanDelete"
}
=== Submitting LoanDelete transaction ===
Loan deleted successfully!
=== Verifying Loan Deletion ===
Loan has been successfully removed from the XRP Ledger!
```

View File

@@ -0,0 +1,210 @@
// IMPORTANT: This example deposits and claws back first-loss capital from a
// preconfigured LoanBroker entry. The first-loss capital is an MPT
// with clawback enabled.
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
"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"
)
// mptIssuanceEntryRequest looks up an MPTIssuance ledger entry by its MPT ID.
// The library's GetLedgerEntry() method only supports lookup by ledger entry ID,
// so this custom type is used with the generic Request() method.
type mptIssuanceEntryRequest struct {
common.BaseRequest
MPTIssuance string `json:"mpt_issuance"`
LedgerIndex common.LedgerSpecifier `json:"ledger_index,omitempty"`
}
func (*mptIssuanceEntryRequest) Method() string { return "ledger_entry" }
func (*mptIssuanceEntryRequest) Validate() error { return nil }
func (*mptIssuanceEntryRequest) APIVersion() int { return 2 }
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and LoanBrokerID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
mptIssuerWallet, err := wallet.FromSecret(setup["depositor"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
loanBrokerID := setup["loanBrokerID"].(string)
mptID := setup["mptID"].(string)
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
fmt.Printf("MPT issuer address: %s\n", mptIssuerWallet.ClassicAddress)
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
fmt.Printf("MPT ID: %s\n", mptID)
// Check cover available ----------------------
fmt.Printf("\n=== Cover Available ===\n\n")
coverInfo, err := client.GetLedgerEntry(&ledger.EntryRequest{
Index: loanBrokerID,
LedgerIndex: common.Validated,
})
if err != nil {
panic(err)
}
currentCoverAvailable := "0"
if ca, ok := coverInfo.Node["CoverAvailable"].(string); ok {
currentCoverAvailable = ca
}
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
coverDepositTx := transaction.LoanBrokerCoverDeposit{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanBrokerID: loanBrokerID,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "1000",
},
}
// Flatten() converts the struct to a map and adds the TransactionType field
flatCoverDepositTx := coverDepositTx.Flatten()
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
fmt.Printf("%s\n", string(coverDepositTxJSON))
// Sign, submit, and wait for deposit validation ----------------------
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Cover deposit successful!\n")
// Extract updated cover available after deposit ----------------------
fmt.Printf("\n=== Cover Available After Deposit ===\n\n")
for _, node := range depositResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
currentCoverAvailable = node.ModifiedNode.FinalFields["CoverAvailable"].(string)
break
}
}
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
// Verify issuer of cover asset matches ----------------------
// Only the issuer of the asset can submit clawback transactions.
// The asset must also have clawback enabled.
fmt.Printf("\n=== Verifying Asset Issuer ===\n\n")
assetIssuerInfo, err := client.Request(&mptIssuanceEntryRequest{
MPTIssuance: mptID,
LedgerIndex: common.Validated,
})
if err != nil {
panic(err)
}
issuer := assetIssuerInfo.Result["node"].(map[string]any)["Issuer"].(string)
if issuer != string(mptIssuerWallet.ClassicAddress) {
fmt.Printf("Error: %s does not match account (%s) attempting clawback!\n", issuer, mptIssuerWallet.ClassicAddress)
os.Exit(1)
}
fmt.Printf("MPT issuer account verified: %s. Proceeding to clawback.\n", mptIssuerWallet.ClassicAddress)
// Prepare LoanBrokerCoverClawback transaction ----------------------
fmt.Printf("\n=== Preparing LoanBrokerCoverClawback transaction ===\n\n")
lbID := types.LoanBrokerID(loanBrokerID)
coverClawbackTx := transaction.LoanBrokerCoverClawback{
BaseTx: transaction.BaseTx{
Account: mptIssuerWallet.ClassicAddress,
},
LoanBrokerID: &lbID,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: currentCoverAvailable,
},
}
flatCoverClawbackTx := coverClawbackTx.Flatten()
coverClawbackTxJSON, _ := json.MarshalIndent(flatCoverClawbackTx, "", " ")
fmt.Printf("%s\n", string(coverClawbackTxJSON))
// Sign, submit, and wait for clawback validation ----------------------
fmt.Printf("\n=== Submitting LoanBrokerCoverClawback transaction ===\n\n")
clawbackResponse, err := client.SubmitTxAndWait(flatCoverClawbackTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &mptIssuerWallet,
})
if err != nil {
panic(err)
}
if clawbackResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to clawback cover: %s\n", clawbackResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Successfully clawed back %s TSTUSD!\n", currentCoverAvailable)
// Extract final cover available ----------------------
fmt.Printf("\n=== Final Cover Available After Clawback ===\n\n")
for _, node := range clawbackResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
finalCover := "0"
if ca, ok := node.ModifiedNode.FinalFields["CoverAvailable"].(string); ok {
finalCover = ca
}
fmt.Printf("%s TSTUSD\n", finalCover)
break
}
}
}

View File

@@ -0,0 +1,151 @@
// IMPORTANT: This example deposits and withdraws first-loss capital from a
// preconfigured LoanBroker entry.
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"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"
)
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and LoanBrokerID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
loanBrokerID := setup["loanBrokerID"].(string)
mptID := setup["mptID"].(string)
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
fmt.Printf("MPT ID: %s\n", mptID)
// Prepare LoanBrokerCoverDeposit transaction ----------------------
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
coverDepositTx := transaction.LoanBrokerCoverDeposit{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanBrokerID: loanBrokerID,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "2000",
},
}
// Flatten() converts the struct to a map and adds the TransactionType field
flatCoverDepositTx := coverDepositTx.Flatten()
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
fmt.Printf("%s\n", string(coverDepositTxJSON))
// Sign, submit, and wait for deposit validation ----------------------
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Cover deposit successful!\n")
// Extract cover balance from the transaction result
fmt.Printf("\n=== Cover Balance ===\n\n")
for _, node := range depositResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
// First-loss capital is stored in the LoanBroker's pseudo-account.
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
fmt.Printf("Cover balance after deposit: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
break
}
}
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
fmt.Printf("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n\n")
coverWithdrawTx := transaction.LoanBrokerCoverWithdraw{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanBrokerID: loanBrokerID,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "1000",
},
}
flatCoverWithdrawTx := coverWithdrawTx.Flatten()
coverWithdrawTxJSON, _ := json.MarshalIndent(flatCoverWithdrawTx, "", " ")
fmt.Printf("%s\n", string(coverWithdrawTxJSON))
// Sign, submit, and wait for withdraw validation ----------------------
fmt.Printf("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n\n")
withdrawResponse, err := client.SubmitTxAndWait(flatCoverWithdrawTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if withdrawResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to withdraw cover: %s\n", withdrawResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Cover withdraw successful!\n")
// Extract updated cover balance from the transaction result
fmt.Printf("\n=== Updated Cover Balance ===\n\n")
for _, node := range withdrawResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
fmt.Printf("Cover balance after withdraw: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
break
}
}
}

View File

@@ -0,0 +1,105 @@
// IMPORTANT: This example creates a loan broker using an existing account
// that has already created a PRIVATE vault.
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
// and loanBroker values with your own.
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"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"
)
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and VaultID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
vaultID := setup["vaultID"].(string)
fmt.Printf("\nLoan broker/vault owner address: %s\n", loanBrokerWallet.ClassicAddress)
fmt.Printf("Vault ID: %s\n", vaultID)
// Prepare LoanBrokerSet transaction ----------------------
fmt.Printf("\n=== Preparing LoanBrokerSet transaction ===\n\n")
mgmtFeeRate := types.InterestRate(1000)
loanBrokerSetTx := transaction.LoanBrokerSet{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
VaultID: vaultID,
ManagementFeeRate: &mgmtFeeRate,
}
// Flatten() converts the struct to a map and adds the TransactionType field
flatLoanBrokerSetTx := loanBrokerSetTx.Flatten()
loanBrokerSetTxJSON, _ := json.MarshalIndent(flatLoanBrokerSetTx, "", " ")
fmt.Printf("%s\n", string(loanBrokerSetTxJSON))
// Submit, sign, and wait for validation ----------------------
fmt.Printf("\n=== Submitting LoanBrokerSet transaction ===\n\n")
loanBrokerSetResponse, err := client.SubmitTxAndWait(flatLoanBrokerSetTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if loanBrokerSetResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to create loan broker: %s\n", loanBrokerSetResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan broker created successfully!\n")
// Extract loan broker information from the transaction result
fmt.Printf("\n=== Loan Broker Information ===\n\n")
for _, node := range loanBrokerSetResponse.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
fmt.Printf("LoanBroker ID: %s\n", node.CreatedNode.LedgerIndex)
fmt.Printf("LoanBroker Pseudo-Account Address: %s\n", node.CreatedNode.NewFields["Account"])
break
}
}
}

View File

@@ -0,0 +1,153 @@
// IMPORTANT: This example creates a loan using a preconfigured
// loan broker, borrower, and private vault.
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"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"
)
// ptr is a helper that returns a pointer to the given value,
// used for setting optional transaction fields in Go.
func ptr[T any](v T) *T { return &v }
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and LoanBrokerID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
loanBrokerID := setup["loanBrokerID"].(string)
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
fmt.Printf("Borrower address: %s\n", borrowerWallet.ClassicAddress)
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
// Prepare LoanSet transaction ----------------------
// Account and Counterparty accounts can be swapped, but determines signing order.
// Account signs first, Counterparty signs second.
fmt.Printf("\n=== Preparing LoanSet transaction ===\n\n")
counterparty := borrowerWallet.ClassicAddress
loanSetTx := transaction.LoanSet{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanBrokerID: loanBrokerID,
PrincipalRequested: "1000",
Counterparty: &counterparty,
InterestRate: ptr(types.InterestRate(500)),
PaymentTotal: ptr(types.PaymentTotal(12)),
PaymentInterval: ptr(types.PaymentInterval(2592000)),
GracePeriod: ptr(types.GracePeriod(604800)),
LoanOriginationFee: ptr(types.XRPLNumber("100")),
LoanServiceFee: ptr(types.XRPLNumber("10")),
}
// Flatten() converts the struct to a map and adds the TransactionType field.
// The result is cast to FlatTransaction, which is required by Autofill and signing methods.
flatLoanSetTx := transaction.FlatTransaction(loanSetTx.Flatten())
// Autofill the transaction
if err := client.Autofill(&flatLoanSetTx); err != nil {
panic(err)
}
loanSetTxJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
fmt.Printf("%s\n", string(loanSetTxJSON))
// Loan broker signs first
fmt.Printf("\n=== Adding loan broker signature ===\n\n")
_, _, err = loanBrokerWallet.Sign(flatLoanSetTx)
if err != nil {
panic(err)
}
fmt.Printf("TxnSignature: %s\n", flatLoanSetTx["TxnSignature"])
fmt.Printf("SigningPubKey: %s\n\n", flatLoanSetTx["SigningPubKey"])
loanBrokerSignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
fmt.Printf("Signed loanSetTx for borrower to sign over:\n%s\n", string(loanBrokerSignedJSON))
// Borrower signs second
fmt.Printf("\n=== Adding borrower signature ===\n\n")
fullySignedBlob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatLoanSetTx, nil)
if err != nil {
panic(err)
}
borrowerSignatures := flatLoanSetTx["CounterpartySignature"].(map[string]any)
fmt.Printf("Borrower TxnSignature: %s\n", borrowerSignatures["TxnSignature"])
fmt.Printf("Borrower SigningPubKey: %s\n", borrowerSignatures["SigningPubKey"])
fullySignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
fmt.Printf("\nFully signed LoanSet transaction:\n%s\n", string(fullySignedJSON))
// Submit and wait for validation ----------------------
fmt.Printf("\n=== Submitting signed LoanSet transaction ===\n\n")
loanSetResponse, err := client.SubmitTxBlobAndWait(fullySignedBlob, false)
if err != nil {
panic(err)
}
if loanSetResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to create loan: %s\n", loanSetResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan created successfully!\n")
// Extract loan information from the transaction result
fmt.Printf("\n=== Loan Information ===\n\n")
for _, node := range loanSetResponse.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
loanJSON, _ := json.MarshalIndent(node.CreatedNode.NewFields, "", " ")
fmt.Printf("%s\n", string(loanJSON))
break
}
}
}

View File

@@ -0,0 +1,19 @@
module github.com/XRPLF
go 1.24.3
require github.com/Peersyst/xrpl-go v0.1.17
require (
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/json-iterator/go v1.1.12 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/crypto v0.44.0 // indirect
)

View File

@@ -0,0 +1,656 @@
// Setup script for lending protocol tutorials
package main
import (
"encoding/hex"
"encoding/json"
"fmt"
"os"
"time"
"github.com/Peersyst/xrpl-go/pkg/crypto"
"github.com/Peersyst/xrpl-go/xrpl/faucet"
ledger "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types"
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
requests "github.com/Peersyst/xrpl-go/xrpl/queries/transactions"
"github.com/Peersyst/xrpl-go/xrpl/rpc"
rpctypes "github.com/Peersyst/xrpl-go/xrpl/rpc/types"
"github.com/Peersyst/xrpl-go/xrpl/transaction"
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
"github.com/Peersyst/xrpl-go/xrpl/wallet"
)
// ptr is a helper that returns a pointer to the given value,
// used for setting optional transaction fields in Go.
func ptr[T any](v T) *T { return &v }
func main() {
fmt.Print("Setting up tutorial: 0/7\r")
// Connect to devnet
cfg, err := rpc.NewClientConfig(
"https://s.devnet.rippletest.net:51234",
rpc.WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
)
if err != nil {
panic(err)
}
client := rpc.NewClient(cfg)
submitOpts := func(w *wallet.Wallet) *rpctypes.SubmitOptions {
return &rpctypes.SubmitOptions{Autofill: true, Wallet: w}
}
// Create and fund wallets concurrently
createAndFund := func(ch chan<- wallet.Wallet) {
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())
}
ch <- w
}
lbWalletCh := make(chan wallet.Wallet, 1)
brWalletCh := make(chan wallet.Wallet, 1)
depWalletCh := make(chan wallet.Wallet, 1)
credWalletCh := make(chan wallet.Wallet, 1)
go createAndFund(lbWalletCh)
go createAndFund(brWalletCh)
go createAndFund(depWalletCh)
go createAndFund(credWalletCh)
loanBrokerWallet := <-lbWalletCh
borrowerWallet := <-brWalletCh
depositorWallet := <-depWalletCh
credIssuerWallet := <-credWalletCh
fmt.Print("Setting up tutorial: 1/7\r")
// Create tickets for parallel transactions
extractTickets := func(resp *requests.TxResponse) []uint32 {
var tickets []uint32
for _, node := range resp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Ticket" {
ticketSeq, _ := node.CreatedNode.NewFields["TicketSequence"].(json.Number).Int64()
tickets = append(tickets, uint32(ticketSeq))
}
}
return tickets
}
ciTicketCh := make(chan []uint32, 1)
lbTicketCh := make(chan []uint32, 1)
brTicketCh := make(chan []uint32, 1)
dpTicketCh := make(chan []uint32, 1)
go func() {
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: credIssuerWallet.GetAddress(),
},
TicketCount: 4,
}).Flatten(), submitOpts(&credIssuerWallet))
if err != nil {
panic(err)
}
ciTicketCh <- extractTickets(resp)
}()
go func() {
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
},
TicketCount: 4,
}).Flatten(), submitOpts(&loanBrokerWallet))
if err != nil {
panic(err)
}
lbTicketCh <- extractTickets(resp)
}()
go func() {
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: borrowerWallet.GetAddress(),
},
TicketCount: 2,
}).Flatten(), submitOpts(&borrowerWallet))
if err != nil {
panic(err)
}
brTicketCh <- extractTickets(resp)
}()
go func() {
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
},
TicketCount: 2,
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
dpTicketCh <- extractTickets(resp)
}()
ciTickets := <-ciTicketCh
lbTickets := <-lbTicketCh
brTickets := <-brTicketCh
dpTickets := <-dpTicketCh
fmt.Print("Setting up tutorial: 2/7\r")
// Issue MPT with depositor
// Set up credentials and domain with credentialIssuer
credentialType := hex.EncodeToString([]byte("KYC-Verified"))
mptData := types.ParsedMPTokenMetadata{
Ticker: "TSTUSD",
Name: "Test USD MPT",
Desc: ptr("A sample non-yield-bearing stablecoin backed by U.S. Treasuries."),
Icon: "https://example.org/tstusd-icon.png",
AssetClass: "rwa",
AssetSubclass: ptr("stablecoin"),
IssuerName: "Example Treasury Reserve Co.",
URIs: []types.ParsedMPTokenMetadataURI{
{
URI: "https://exampletreasury.com/tstusd",
Category: "website",
Title: "Product Page",
},
{
URI: "https://exampletreasury.com/tstusd/reserve",
Category: "docs",
Title: "Reserve Attestation",
},
},
AdditionalInfo: map[string]any{
"reserve_type": "U.S. Treasury Bills",
"custody_provider": "Example Custodian Bank",
"audit_frequency": "Monthly",
"last_audit_date": "2026-01-15",
"pegged_currency": "USD",
},
}
mptMetadataHex, err := types.EncodeMPTokenMetadata(mptData)
if err != nil {
panic(err)
}
mptCh := make(chan *requests.TxResponse, 1)
domainCh := make(chan *requests.TxResponse, 1)
credLbCh := make(chan struct{}, 1)
credBrCh := make(chan struct{}, 1)
credDpCh := make(chan struct{}, 1)
// MPT issuance
go func() {
resp, err := client.SubmitTxAndWait((&transaction.MPTokenIssuanceCreate{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
Flags: transaction.TfMPTCanTransfer | transaction.TfMPTCanClawback | transaction.TfMPTCanTrade,
},
MaximumAmount: ptr(types.XRPCurrencyAmount(100000000)),
TransferFee: ptr(uint16(0)),
MPTokenMetadata: &mptMetadataHex,
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
mptCh <- resp
}()
// PermissionedDomainSet
go func() {
resp, err := client.SubmitTxAndWait((&transaction.PermissionedDomainSet{
BaseTx: transaction.BaseTx{
Account: credIssuerWallet.GetAddress(),
Sequence: 0,
TicketSequence: ciTickets[0],
},
AcceptedCredentials: types.AuthorizeCredentialList{
{
Credential: types.Credential{
Issuer: credIssuerWallet.GetAddress(),
CredentialType: types.CredentialType(credentialType),
},
},
},
}).Flatten(), submitOpts(&credIssuerWallet))
if err != nil {
panic(err)
}
domainCh <- resp
}()
// CredentialCreate for loan broker
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
BaseTx: transaction.BaseTx{
Account: credIssuerWallet.GetAddress(),
Sequence: 0,
TicketSequence: ciTickets[1],
},
CredentialType: types.CredentialType(credentialType),
Subject: loanBrokerWallet.GetAddress(),
}).Flatten(), submitOpts(&credIssuerWallet))
if err != nil {
panic(err)
}
credLbCh <- struct{}{}
}()
// CredentialCreate for borrower
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
BaseTx: transaction.BaseTx{
Account: credIssuerWallet.GetAddress(),
Sequence: 0,
TicketSequence: ciTickets[2],
},
CredentialType: types.CredentialType(credentialType),
Subject: borrowerWallet.GetAddress(),
}).Flatten(), submitOpts(&credIssuerWallet))
if err != nil {
panic(err)
}
credBrCh <- struct{}{}
}()
// CredentialCreate for depositor
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
BaseTx: transaction.BaseTx{
Account: credIssuerWallet.GetAddress(),
Sequence: 0,
TicketSequence: ciTickets[3],
},
CredentialType: types.CredentialType(credentialType),
Subject: depositorWallet.GetAddress(),
}).Flatten(), submitOpts(&credIssuerWallet))
if err != nil {
panic(err)
}
credDpCh <- struct{}{}
}()
mptResp := <-mptCh
domainResp := <-domainCh
<-credLbCh
<-credBrCh
<-credDpCh
// Extract MPT issuance ID
mptID := string(*mptResp.Meta.MPTIssuanceID)
// Extract domain ID from transaction meta
var domainID string
for _, node := range domainResp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "PermissionedDomain" {
domainID = node.CreatedNode.LedgerIndex
break
}
}
fmt.Print("Setting up tutorial: 3/7\r")
// Accept credentials and authorize MPT for each account
lbCredCh := make(chan struct{}, 1)
lbMptCh := make(chan struct{}, 1)
brCredCh := make(chan struct{}, 1)
brMptCh := make(chan struct{}, 1)
depCredCh := make(chan struct{}, 1)
// Loan broker: accept credential
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
Sequence: 0,
TicketSequence: lbTickets[0],
},
CredentialType: types.CredentialType(credentialType),
Issuer: credIssuerWallet.GetAddress(),
}).Flatten(), submitOpts(&loanBrokerWallet))
if err != nil {
panic(err)
}
lbCredCh <- struct{}{}
}()
// Loan broker: authorize MPT
go func() {
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
Sequence: 0,
TicketSequence: lbTickets[1],
},
MPTokenIssuanceID: mptID,
}).Flatten(), submitOpts(&loanBrokerWallet))
if err != nil {
panic(err)
}
lbMptCh <- struct{}{}
}()
// Borrower: accept credential
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
BaseTx: transaction.BaseTx{
Account: borrowerWallet.GetAddress(),
Sequence: 0,
TicketSequence: brTickets[0],
},
CredentialType: types.CredentialType(credentialType),
Issuer: credIssuerWallet.GetAddress(),
}).Flatten(), submitOpts(&borrowerWallet))
if err != nil {
panic(err)
}
brCredCh <- struct{}{}
}()
// Borrower: authorize MPT
go func() {
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
BaseTx: transaction.BaseTx{
Account: borrowerWallet.GetAddress(),
Sequence: 0,
TicketSequence: brTickets[1],
},
MPTokenIssuanceID: mptID,
}).Flatten(), submitOpts(&borrowerWallet))
if err != nil {
panic(err)
}
brMptCh <- struct{}{}
}()
// Depositor: accept credential only
go func() {
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
},
CredentialType: types.CredentialType(credentialType),
Issuer: credIssuerWallet.GetAddress(),
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
depCredCh <- struct{}{}
}()
<-lbCredCh
<-lbMptCh
<-brCredCh
<-brMptCh
<-depCredCh
fmt.Print("Setting up tutorial: 4/7\r")
// Create private vault and distribute MPT to accounts concurrently
vaultCh := make(chan *requests.TxResponse, 1)
distLbCh := make(chan struct{}, 1)
distBrCh := make(chan struct{}, 1)
go func() {
resp, err := client.SubmitTxAndWait((&transaction.VaultCreate{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
Flags: transaction.TfVaultPrivate,
},
Asset: ledger.Asset{MPTIssuanceID: mptID},
DomainID: &domainID,
}).Flatten(), submitOpts(&loanBrokerWallet))
if err != nil {
panic(err)
}
vaultCh <- resp
}()
go func() {
_, err := client.SubmitTxAndWait((&transaction.Payment{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
Sequence: 0,
TicketSequence: dpTickets[0],
},
Destination: loanBrokerWallet.GetAddress(),
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "5000",
},
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
distLbCh <- struct{}{}
}()
go func() {
_, err := client.SubmitTxAndWait((&transaction.Payment{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
Sequence: 0,
TicketSequence: dpTickets[1],
},
Destination: borrowerWallet.GetAddress(),
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "2500",
},
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
distBrCh <- struct{}{}
}()
vaultResp := <-vaultCh
<-distLbCh
<-distBrCh
var vaultID string
for _, node := range vaultResp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Vault" {
vaultID = node.CreatedNode.LedgerIndex
break
}
}
fmt.Print("Setting up tutorial: 5/7\r")
// Create LoanBroker and deposit MPT into vault
lbSetCh := make(chan *requests.TxResponse, 1)
vaultDepCh := make(chan struct{}, 1)
go func() {
resp, err := client.SubmitTxAndWait((&transaction.LoanBrokerSet{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
},
VaultID: vaultID,
}).Flatten(), submitOpts(&loanBrokerWallet))
if err != nil {
panic(err)
}
lbSetCh <- resp
}()
go func() {
_, err := client.SubmitTxAndWait((&transaction.VaultDeposit{
BaseTx: transaction.BaseTx{
Account: depositorWallet.GetAddress(),
},
VaultID: types.Hash256(vaultID),
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: "50000000",
},
}).Flatten(), submitOpts(&depositorWallet))
if err != nil {
panic(err)
}
vaultDepCh <- struct{}{}
}()
lbSetResp := <-lbSetCh
<-vaultDepCh
var loanBrokerID string
for _, node := range lbSetResp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
loanBrokerID = node.CreatedNode.LedgerIndex
break
}
}
fmt.Print("Setting up tutorial: 6/7\r")
// Create 2 identical loans with complete repayment due in 30 days
// Helper function to create, sign, and submit a LoanSet transaction
createLoan := func(ticketSequence uint32) *requests.TxResponse {
counterparty := borrowerWallet.GetAddress()
loanSetTx := &transaction.LoanSet{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.GetAddress(),
Sequence: 0,
TicketSequence: ticketSequence,
},
LoanBrokerID: loanBrokerID,
PrincipalRequested: "1000",
Counterparty: &counterparty,
InterestRate: ptr(types.InterestRate(500)),
PaymentTotal: ptr(types.PaymentTotal(1)),
PaymentInterval: ptr(types.PaymentInterval(2592000)),
LoanOriginationFee: ptr(types.XRPLNumber("100")),
LoanServiceFee: ptr(types.XRPLNumber("10")),
}
flatTx := transaction.FlatTransaction(loanSetTx.Flatten())
if err := client.Autofill(&flatTx); err != nil {
panic(err)
}
// Loan broker signs first
_, _, err := loanBrokerWallet.Sign(flatTx)
if err != nil {
panic(err)
}
// Borrower signs second
blob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatTx, nil)
if err != nil {
panic(err)
}
// Submit and wait for validation
resp, err := client.SubmitTxBlobAndWait(blob, false)
if err != nil {
panic(err)
}
return resp
}
loan1Ch := make(chan *requests.TxResponse, 1)
loan2Ch := make(chan *requests.TxResponse, 1)
go func() { loan1Ch <- createLoan(lbTickets[2]) }()
go func() { loan2Ch <- createLoan(lbTickets[3]) }()
loan1Resp := <-loan1Ch
loan2Resp := <-loan2Ch
var loanID1, loanID2 string
for _, node := range loan1Resp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
loanID1 = node.CreatedNode.LedgerIndex
break
}
}
for _, node := range loan2Resp.Meta.AffectedNodes {
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
loanID2 = node.CreatedNode.LedgerIndex
break
}
}
fmt.Print("Setting up tutorial: 7/7\r")
// Write setup data to JSON file.
// Using a struct to preserve field order.
setupData := struct {
Description string `json:"description"`
LoanBroker any `json:"loanBroker"`
Borrower any `json:"borrower"`
Depositor any `json:"depositor"`
CredentialIssuer any `json:"credentialIssuer"`
DomainID string `json:"domainID"`
MptID string `json:"mptID"`
VaultID string `json:"vaultID"`
LoanBrokerID string `json:"loanBrokerID"`
LoanID1 string `json:"loanID1"`
LoanID2 string `json:"loanID2"`
}{
Description: "This file is auto-generated by lending-setup. It stores XRPL account info for use in lending protocol tutorials.",
LoanBroker: map[string]string{
"address": string(loanBrokerWallet.ClassicAddress),
"seed": loanBrokerWallet.Seed,
},
Borrower: map[string]string{
"address": string(borrowerWallet.ClassicAddress),
"seed": borrowerWallet.Seed,
},
Depositor: map[string]string{
"address": string(depositorWallet.ClassicAddress),
"seed": depositorWallet.Seed,
},
CredentialIssuer: map[string]string{
"address": string(credIssuerWallet.ClassicAddress),
"seed": credIssuerWallet.Seed,
},
DomainID: domainID,
MptID: mptID,
VaultID: vaultID,
LoanBrokerID: loanBrokerID,
LoanID1: loanID1,
LoanID2: loanID2,
}
jsonData, err := json.MarshalIndent(setupData, "", " ")
if err != nil {
panic(err)
}
if err := os.WriteFile("lending-setup.json", jsonData, 0644); err != nil {
panic(err)
}
fmt.Println("Setting up tutorial: Complete!")
}

View File

@@ -0,0 +1,195 @@
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
// After the 60 seconds pass, this example defaults the loan.
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"time"
"github.com/Peersyst/xrpl-go/xrpl/flag"
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
"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/wallet"
"github.com/Peersyst/xrpl-go/xrpl/websocket"
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
)
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and LoanID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
loanID := setup["loanID1"].(string)
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
fmt.Printf("LoanID: %s\n", loanID)
// Check loan status before impairment ----------------------
fmt.Printf("\n=== Loan Status ===\n\n")
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
Index: loanID,
LedgerIndex: common.Validated,
})
if err != nil {
panic(err)
}
fmt.Printf("Total Amount Owed: %s TSTUSD.\n", loanStatus.Node["TotalValueOutstanding"])
// Convert Ripple Epoch timestamp to local date and time
nextPaymentDueDate := int64(loanStatus.Node["NextPaymentDueDate"].(float64))
paymentDue := time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
fmt.Printf("Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
// Prepare LoanManage transaction to impair the loan ----------------------
fmt.Printf("\n=== Preparing LoanManage transaction to impair loan ===\n\n")
loanManageImpair := transaction.LoanManage{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanID: loanID,
}
loanManageImpair.SetLoanImpairFlag()
// Flatten() converts the struct to a map and adds the TransactionType field
flatLoanManageImpair := loanManageImpair.Flatten()
loanManageImpairJSON, _ := json.MarshalIndent(flatLoanManageImpair, "", " ")
fmt.Printf("%s\n", string(loanManageImpairJSON))
// Sign, submit, and wait for impairment validation ----------------------
fmt.Printf("\n=== Submitting LoanManage impairment transaction ===\n\n")
impairResponse, err := client.SubmitTxAndWait(flatLoanManageImpair, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if impairResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to impair loan: %s\n", impairResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan impaired successfully!\n")
// Extract loan impairment info from transaction results ----------------------
var loanNode map[string]any
for _, node := range impairResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
loanNode = node.ModifiedNode.FinalFields
break
}
}
// Check grace period and next payment due date
gracePeriod := int64(loanNode["GracePeriod"].(float64))
nextPaymentDueDate = int64(loanNode["NextPaymentDueDate"].(float64))
defaultTime := nextPaymentDueDate + gracePeriod
paymentDue = time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
fmt.Printf("New Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
fmt.Printf("Grace Period: %d seconds\n", gracePeriod)
// Convert current time to Ripple Epoch timestamp
currentTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix())
// Add a small buffer (5 seconds) to account for ledger close time
secondsUntilDefault := defaultTime - currentTime + 5
// Countdown until loan can be defaulted ----------------------
fmt.Printf("\n=== Countdown until loan can be defaulted ===\n\n")
for secondsUntilDefault >= 0 {
fmt.Printf("\r%d seconds...", secondsUntilDefault)
time.Sleep(time.Second)
secondsUntilDefault--
}
fmt.Print("\rGrace period expired. Loan can now be defaulted.\n")
// Prepare LoanManage transaction to default the loan ----------------------
fmt.Printf("\n=== Preparing LoanManage transaction to default loan ===\n\n")
loanManageDefault := transaction.LoanManage{
BaseTx: transaction.BaseTx{
Account: loanBrokerWallet.ClassicAddress,
},
LoanID: loanID,
}
loanManageDefault.SetLoanDefaultFlag()
flatLoanManageDefault := loanManageDefault.Flatten()
loanManageDefaultJSON, _ := json.MarshalIndent(flatLoanManageDefault, "", " ")
fmt.Printf("%s\n", string(loanManageDefaultJSON))
// Sign, submit, and wait for default validation ----------------------
fmt.Printf("\n=== Submitting LoanManage default transaction ===\n\n")
defaultResponse, err := client.SubmitTxAndWait(flatLoanManageDefault, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &loanBrokerWallet,
})
if err != nil {
panic(err)
}
if defaultResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to default loan: %s\n", defaultResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan defaulted successfully!\n")
// Verify loan default status from transaction results ----------------------
fmt.Printf("\n=== Checking final loan status ===\n\n")
for _, node := range defaultResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
loanNode = node.ModifiedNode.FinalFields
break
}
}
loanFlags := uint32(loanNode["Flags"].(float64))
// Check which loan flags are set
activeFlags := []string{}
if flag.Contains(loanFlags, transaction.TfLoanDefault) {
activeFlags = append(activeFlags, "tfLoanDefault")
}
if flag.Contains(loanFlags, transaction.TfLoanImpair) {
activeFlags = append(activeFlags, "tfLoanImpair")
}
fmt.Printf("Final loan flags: %v\n", activeFlags)
}

View File

@@ -0,0 +1,179 @@
// IMPORTANT: This example pays off an existing loan and then deletes it.
package main
import (
"encoding/json"
"fmt"
"math/big"
"os"
"os/exec"
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
"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"
)
func main() {
// Connect to the network ----------------------
client := websocket.NewClient(
websocket.NewClientConfig().
WithHost("wss://s.devnet.rippletest.net:51233"),
)
defer client.Disconnect()
if err := client.Connect(); err != nil {
panic(err)
}
// Check for setup data; run lending-setup if missing
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
cmd := exec.Command("go", "run", "./lending-setup")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
panic(err)
}
}
// Load preconfigured accounts and LoanID
data, err := os.ReadFile("lending-setup.json")
if err != nil {
panic(err)
}
var setup map[string]any
if err := json.Unmarshal(data, &setup); err != nil {
panic(err)
}
// You can replace these values with your own
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
if err != nil {
panic(err)
}
loanID := setup["loanID2"].(string)
mptID := setup["mptID"].(string)
fmt.Printf("\nBorrower address: %s\n", borrowerWallet.ClassicAddress)
fmt.Printf("LoanID: %s\n", loanID)
fmt.Printf("MPT ID: %s\n", mptID)
// Check initial loan status ----------------------
fmt.Printf("\n=== Loan Status ===\n\n")
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
Index: loanID,
LedgerIndex: common.Validated,
})
if err != nil {
panic(err)
}
totalValueOutstanding := loanStatus.Node["TotalValueOutstanding"].(string)
loanServiceFee := loanStatus.Node["LoanServiceFee"].(string)
outstanding, _ := new(big.Int).SetString(totalValueOutstanding, 10)
serviceFee, _ := new(big.Int).SetString(loanServiceFee, 10)
totalPayment := new(big.Int).Add(outstanding, serviceFee).String()
fmt.Printf("Amount Owed: %s TSTUSD\n", totalValueOutstanding)
fmt.Printf("Loan Service Fee: %s TSTUSD\n", loanServiceFee)
fmt.Printf("Total Payment Due (including fees): %s TSTUSD\n", totalPayment)
// Prepare LoanPay transaction ----------------------
fmt.Printf("\n=== Preparing LoanPay transaction ===\n\n")
loanPayTx := transaction.LoanPay{
BaseTx: transaction.BaseTx{
Account: borrowerWallet.ClassicAddress,
},
LoanID: loanID,
Amount: types.MPTCurrencyAmount{
MPTIssuanceID: mptID,
Value: totalPayment,
},
}
// Flatten() converts the struct to a map and adds the TransactionType field
flatLoanPayTx := loanPayTx.Flatten()
loanPayTxJSON, _ := json.MarshalIndent(flatLoanPayTx, "", " ")
fmt.Printf("%s\n", string(loanPayTxJSON))
// Sign, submit, and wait for payment validation ----------------------
fmt.Printf("\n=== Submitting LoanPay transaction ===\n\n")
payResponse, err := client.SubmitTxAndWait(flatLoanPayTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &borrowerWallet,
})
if err != nil {
panic(err)
}
if payResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to pay loan: %s\n", payResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan paid successfully!\n")
// Extract updated loan info from transaction results ----------------------
fmt.Printf("\n=== Loan Status After Payment ===\n\n")
for _, node := range payResponse.Meta.AffectedNodes {
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
if balance, ok := node.ModifiedNode.FinalFields["TotalValueOutstanding"].(string); ok {
fmt.Printf("Outstanding Loan Balance: %s TSTUSD\n", balance)
} else {
fmt.Printf("Outstanding Loan Balance: Loan fully paid off!\n")
}
break
}
}
// Prepare LoanDelete transaction ----------------------
// Either the loan broker or borrower can submit this transaction.
fmt.Printf("\n=== Preparing LoanDelete transaction ===\n\n")
loanDeleteTx := transaction.LoanDelete{
BaseTx: transaction.BaseTx{
Account: borrowerWallet.ClassicAddress,
},
LoanID: loanID,
}
flatLoanDeleteTx := loanDeleteTx.Flatten()
loanDeleteTxJSON, _ := json.MarshalIndent(flatLoanDeleteTx, "", " ")
fmt.Printf("%s\n", string(loanDeleteTxJSON))
// Sign, submit, and wait for deletion validation ----------------------
fmt.Printf("\n=== Submitting LoanDelete transaction ===\n\n")
deleteResponse, err := client.SubmitTxAndWait(flatLoanDeleteTx, &wstypes.SubmitOptions{
Autofill: true,
Wallet: &borrowerWallet,
})
if err != nil {
panic(err)
}
if deleteResponse.Meta.TransactionResult != "tesSUCCESS" {
fmt.Printf("Error: Unable to delete loan: %s\n", deleteResponse.Meta.TransactionResult)
os.Exit(1)
}
fmt.Printf("Loan deleted successfully!\n")
// Verify loan deletion ----------------------
fmt.Printf("\n=== Verifying Loan Deletion ===\n\n")
_, err = client.GetLedgerEntry(&ledger.EntryRequest{
Index: loanID,
LedgerIndex: common.Validated,
})
if err != nil {
if err.Error() == "entryNotFound" {
fmt.Printf("Loan has been successfully removed from the XRP Ledger!\n")
} else {
panic(err)
}
} else {
fmt.Printf("Warning: Loan still exists in the ledger.\n")
}
}

View File

@@ -1,280 +0,0 @@
<html>
<head>
<title>Create a Conditional Escrow</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<link href="modular-tutorials.css" rel="stylesheet">
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
<script src="account-support.js"></script>
<script src="create-time-escrow.js"></script>
<script src='create-conditional-escrow.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Create a Conditional Escrow</h1>
<form id="theForm">
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
Choose your ledger instance:
</span>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
<label for="dn">Devnet</label>
&nbsp;&nbsp;
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
<label for="tn">Testnet</label>
<br /><br />
<table>
<tr>
<td>
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
</td>
<td>
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
</span>
</td>
<td>
<input type="text" id="account1name" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
<label for="account2name">Account 2 Name</label>
</span>
</td>
<td>
<input type="text" id="account2name" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account1address">Account 1 Address</label>
</span>
</td>
<td>
<input type="text" id="account1address" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account2address">Account 2 Address</label>
</span>
</td>
<td>
<input type="text" id="account2address" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account1seed">Account 1 Seed</label>
</span>
</td>
<td>
<input type="text" id="account1seed" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account2seed">Account 2 Seed</label>
</span>
</td>
<td>
<input type="text" id="account2seed" size="40"></input>
</td>
</tr>
</table>
<hr />
<table>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Name of the currently selected account.">
<label for="accountNameField">Account Name</label>
</span>
</td>
<td>
<input type="text" id="accountNameField" size="40" readonly></input>
<input type="radio" id="account1" name="accounts" value="account1">
<label for="account1">Account 1</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Address of the currently selected account.">
<label for="accountAddressField">Account Address</label>
</span>
</td>
<td>
<input type="text" id="accountAddressField" size="40" readonly></input>
<input type="radio" id="account2" name="accounts" value="account2">
<label for="account2">Account 2</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
<label for="accountSeedField">Account Seed</label>
</span>
</td>
<td>
<input type="text" id="accountSeedField" size="40" readonly></input>
<br>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
<label for="xrpBalanceField">XRP Balance</label>
</span>
</td>
<td>
<input type="text" id="xrpBalanceField" size="40" readonly></input>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Amount of XRP to send.">
<label for="amountField">Amount</label>
</span>
</td>
<td>
<input type="text" id="amountField" size="40"></input>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Destination account address where the escrow is sent.">
<lable for="destinationField">Destination</lable>
</span>
</td>
<td>
<input type="text" id="destinationField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="createConditionalEscrow()">Create Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Condition code used to begin the escrow transaction.">
<lable for="escrowConditionField">Escrow Condition</lable>
</span>
</td>
<td>
<input type="text" id="escrowConditionField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="getEscrows()">Get Escrows</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Fullfillment code to complete the escrow transaction.">
<lable for="escrowFulfillmentField">Escrow Fulfillment</lable>
</span>
</td>
<td>
<input type="text" id="escrowFulfillmentField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="finishConditionalEscrow()">Finish Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow cancel time, in seconds.">
<lable for="escrowCancelDateField">Escrow Cancel Time</lable>
</span>
</td>
<td>
<input type="text" id="escrowCancelDateField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow sequence number, used when finishing the escrow.">
<lable for="escrowSequenceNumberField">Escrow Sequence Number</lable>
</span>
</td>
<td>
<input type="text" id="escrowSequenceNumberField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="getTransaction()">Get Transaction</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow owner, the account that created the escrow.">
<lable for="escrowOwnerField">Escrow Owner</lable>
</span>
</td>
<td>
<input type="text" id="escrowOwnerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Transaction number, used with the Get Transaction button.">
<lable for="transactionField">Transaction</lable>
</span>
</td>
<td>
<input type="text" id="transactionField" size="40"></input>
<br>
</td>
<td>
</td>
</tr>
<tr>
<td colspan="2">
<p align="right">
<textarea id="resultField" cols="80" rows="20"></textarea>
</p>
</td>
<td align="left" valign="top">
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
</td>
</tr>
</table>
</form>
</body>
<script>
const radioButtons = document.querySelectorAll('input[type="radio"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'account1') {
populate1()
} else if (this.value === 'account2') {
populate2()
}
});
});
</script>
</html>

View File

@@ -1,86 +0,0 @@
// *******************************************************
// ************* Create Conditional Escrow ***************
// *******************************************************
async function createConditionalEscrow() {
//------------------------------------------------------Connect to the Ledger
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
const sendAmount = amountField.value
let results = `===Connected to ${net}===\n===Creating conditional escrow.===\n\n`
resultField.value = results
let escrow_cancel_date = new Date()
escrow_cancel_date = addSeconds(parseInt(escrowCancelDateField.value))
// ------------------------------------------------------- Prepare transaction
try {
const escrowTx = await client.autofill({
"TransactionType": "EscrowCreate",
"Account": wallet.address,
"Amount": xrpl.xrpToDrops(sendAmount),
"Destination": destinationField.value,
"CancelAfter": escrow_cancel_date,
"Condition": escrowConditionField.value
})
// ------------------------------------------------ Sign prepared instructions
const signed = wallet.sign(escrowTx)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results = "\n=== *** Sequence Number (Save!): " + tx.result.tx_json.Sequence
results += "\n\n===Balance changes===\n" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
resultField.value += results
}
catch (error) {
results += "\n===Error: " + error.message
resultField.value = results
}
finally {
// -------------------------------------------------------- Disconnect
client.disconnect()
}
} // End of createTimeEscrow()
// *******************************************************
// ************** Finish Conditional Escrow **************
// *******************************************************
async function finishConditionalEscrow() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ${net}===\n===Fulfilling conditional escrow.===\n`
resultField.value = results
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
try {
// ------------------------------------------------------- Prepare transaction
const prepared = await client.autofill({
"TransactionType": "EscrowFinish",
"Account": accountAddressField.value,
"Owner": escrowOwnerField.value,
"OfferSequence": parseInt(escrowSequenceNumberField.value),
"Condition": escrowConditionField.value,
"Fulfillment": escrowFulfillmentField.value
})
const signed = wallet.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
results += "\n===Balance changes===" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
resultField.value = results
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
}
catch (error) {
results += "\n===Error: " + error.message + ".===\n"
resultField.value = results
}
finally {
// -------------------------------------------------------- Disconnect
client.disconnect()
}
} // End of finisConditionalEscrow()

View File

@@ -1,249 +0,0 @@
<html>
<head>
<title>Create Offers</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<link href="modular-tutorials.css" rel="stylesheet">
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
<script src="account-support.js"></script>
<script src='send-xrp.js'></script>
<script src='create-offer.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Create Offers</h1>
<form id="theForm">
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
Choose your ledger instance:
</span>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
<label for="dn">Devnet</label>
&nbsp;&nbsp;
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
<label for="tn">Testnet</label>
<br /><br />
<table>
<tr>
<td>
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
</td>
<td>
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
</span>
</td>
<td>
<input type="text" id="account1name" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
<label for="account2name">Account 2 Name</label>
</span>
</td>
<td>
<input type="text" id="account2name" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account1address">Account 1 Address</label>
</span>
</td>
<td>
<input type="text" id="account1address" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account2address">Account 2 Address</label>
</span>
</td>
<td>
<input type="text" id="account2address" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account1seed">Account 1 Seed</label>
</span>
</td>
<td>
<input type="text" id="account1seed" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account2seed">Account 2 Seed</label>
</span>
</td>
<td>
<input type="text" id="account2seed" size="40"></input>
</td>
</tr>
</table>
<hr />
<table>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Name of the currently selected account.">
<label for="accountNameField">Account Name</label>
</span>
</td>
<td>
<input type="text" id="accountNameField" size="40" readonly></input>
<input type="radio" id="account1" name="accounts" value="account1">
<label for="account1">Account 1</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Address of the currently selected account.">
<label for="accountAddressField">Account Address</label>
</span>
</td>
<td>
<input type="text" id="accountAddressField" size="40" readonly></input>
<input type="radio" id="account2" name="accounts" value="account2">
<label for="account2">Account 2</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
<label for="accountSeedField">Account Seed</label>
</span>
</td>
<td>
<input type="text" id="accountSeedField" size="40" readonly></input>
<br>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
<label for="xrpBalanceField">XRP Balance</label>
</span>
</td>
<td>
<input type="text" id="xrpBalanceField" size="40" readonly></input>
</td>
</tr>
</table>
<table>
<tr>
<td></td>
<td>
<h4 align="center">Taker Pays</h4>
</td>
<td>
<h4 align="center">Taker Gets</h4>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Currency codes for the Pay and Get offers.">
<lable for="payCurrencyField">Currency Code</lable>
</span>
</td>
<td>
<input type="text" id="payCurrencyField" size="40"></input>
</td>
<td>
<input type="text" id="getCurrencyField" size="40"></input>
</td>
<td>
<button type="button" onClick="createOffer()">Create Offer</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Issuers of the offered currencies.">
<lable for="payIssuerField">Issuer</lable>
</span>
</td>
<td>
<input type="text" id="payIssuerField" size="40"></input>&nbsp;&nbsp;
</td>
<td>
<input type="text" id="getIssuerField" size="40"></input>&nbsp;&nbsp;
</td>
<td>
<button type="button" onClick="getOffers()">Get Offers</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Amounts of offered currencies.">
<lable for="amountField">Amount</lable>
</span>
</td>
<td>
<input type="text" id="payAmountField" size="40"></input>
</td>
<td>
<input type="text" id="getAmountField" size="40"></input>
</td>
<td>
<button type="button" onClick="cancelOffer()">Cancel Offer</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Sequence number of the offer.">
<lable for="offerSequenceField">Offer Sequence</lable>
</span>
</td>
<td>
<input type="text" id="offerSequenceField" size="40"></input>
</td>
<td></td>
<td>
<button type="button" onClick="getTokenBalance()">Get Token Balance</button>
</td>
</tr>
<tr>
<td colspan="3">
<p align="right">
<textarea id="resultField" cols="80" rows="20"></textarea>
</p>
</td>
<td align="left" valign="top">
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
</td>
</tr>
</table>
</form>
</body>
<script>
const radioButtons = document.querySelectorAll('input[type="radio"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'account1') {
populate1()
} else if (this.value === 'account2') {
populate2()
}
});
});
</script>
</html>

View File

@@ -1,121 +0,0 @@
/***********************************
*********** Create Offer **********
**********************************/
async function createOffer() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ${net}, getting wallet....===\n`
resultField.value = results
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
try {
if (getCurrencyField.value == 'XRP') {
takerGets = xrpl.xrpToDrops(getAmountField.value)
}
else {
takerGetsString = '{"currency": "' + getCurrencyField.value + '",\n' +
'"issuer": "' + getIssuerField.value + '",\n' +
'"value": "' + getAmountField.value + '"}'
takerGets = JSON.parse(takerGetsString)
}
if (payCurrencyField.value == 'XRP') {
takerPays = xrpl.xrpToDrops(payAmountField.value)
} else {
takerPaysString = '{"currency": "' + payCurrencyField.value + '",\n' +
'"issuer": "' + payIssuerField.value + '",\n' +
'"value": "' + payAmountField.value + '"}'
takerPays = JSON.parse(takerPaysString)
}
const prepared = await client.autofill({
"TransactionType": "OfferCreate",
"Account": wallet.address,
"TakerGets": takerGets,
"TakerPays": takerPays
})
const signed = wallet.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
results = '\n\n===Offer created===\n\n' +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
resultField.value += results
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
} catch (err) {
console.error('Error creating offer:', err);
results = `\nError: ${err.message}\n`
resultField.value += results
throw err; // Re-throw the error to be handled by the caller
}
finally {
// Disconnect from the client
client.disconnect()
}
} // End of createOffer()
/***********************************
************ Get Offers ***********
**********************************/
async function getOffers() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ' + ${net}, getting offers....===\n`
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
resultField.value = results
results += '\n\n=== Offers ===\n'
let offers
try {
offers = await client.request({
method: "account_offers",
account: wallet.address,
ledger_index: "validated"
})
results = JSON.stringify(offers, null, 2)
resultField.value += results
} catch (err) {
console.error('Error getting offers:', err);
results = `\nError: ${err.message}\n`
resultField.value += results
throw err; // Re-throw the error to be handled by the caller
}
finally {
client.disconnect()
}
}// End of getOffers()
/***********************************
*********** Cancel Offer **********
**********************************/
async function cancelOffer() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ${net}, canceling offer.===\n`
resultField.value = results
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
try {
// OfferSequence is the _seq_ value from getOffers.
const prepared = await client.autofill({
"TransactionType": "OfferCancel",
"Account": wallet.address,
"OfferSequence": parseInt(offerSequenceField.value)
})
const signed = wallet.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nOffer canceled. Balance changes: \n" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
resultField.value = results
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
}
catch (err) {
console.error('Error canceling offer:', err);
results = `\nError: ${err.message}\n`
resultField.value += results
throw err; // Re-throw the error to be handled by the caller
}
finally {
// Disconnect from the client
client.disconnect()
}
}// End of cancelOffer()

View File

@@ -1,269 +0,0 @@
<html>
<head>
<title>Create a Time-based Escrow</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<link href="modular-tutorials.css" rel="stylesheet">
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
<script src="account-support.js"></script>
<script src='create-time-escrow.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Create a Time-based Escrow</h1>
<form id="theForm">
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
Choose your ledger instance:
</span>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
<label for="dn">Devnet</label>
&nbsp;&nbsp;
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
<label for="tn">Testnet</label>
<br /><br />
<table>
<tr>
<td>
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
</td>
<td>
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
</span>
</td>
<td>
<input type="text" id="account1name" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
<label for="account2name">Account 2 Name</label>
</span>
</td>
<td>
<input type="text" id="account2name" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account1address">Account 1 Address</label>
</span>
</td>
<td>
<input type="text" id="account1address" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account2address">Account 2 Address</label>
</span>
</td>
<td>
<input type="text" id="account2address" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account1seed">Account 1 Seed</label>
</span>
</td>
<td>
<input type="text" id="account1seed" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account2seed">Account 2 Seed</label>
</span>
</td>
<td>
<input type="text" id="account2seed" size="40"></input>
</td>
</tr>
</table>
<hr />
<table>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Name of the currently selected account.">
<label for="accountNameField">Account Name</label>
</span>
</td>
<td>
<input type="text" id="accountNameField" size="40" readonly></input>
<input type="radio" id="account1" name="accounts" value="account1">
<label for="account1">Account 1</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Address of the currently selected account.">
<label for="accountAddressField">Account Address</label>
</span>
</td>
<td>
<input type="text" id="accountAddressField" size="40" readonly></input>
<input type="radio" id="account2" name="accounts" value="account2">
<label for="account2">Account 2</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
<label for="accountSeedField">Account Seed</label>
</span>
</td>
<td>
<input type="text" id="accountSeedField" size="40" readonly></input>
<br>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
<label for="xrpBalanceField">XRP Balance</label>
</span>
</td>
<td>
<input type="text" id="xrpBalanceField" size="40" readonly></input>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Amount of XRP to send.">
<label for="amountField">Amount</label>
</span>
</td>
<td>
<input type="text" id="amountField" size="40"></input>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Destination account address where the escrow is sent.">
<lable for="destinationField">Destination</lable>
</span>
</td>
<td>
<input type="text" id="destinationField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="createTimeBasedEscrow()">Create Time-based Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow finish time, in seconds.">
<lable for="escrowFinishTimeField">Escrow Finish Time</lable>
</span>
</td>
<td>
<input type="text" id="escrowFinishTimeField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="getEscrows()">Get Escrows</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow cancel time, in seconds.">
<lable for="escrowCancelTimeField">Escrow Cancel Time</lable>
</span>
</td>
<td>
<input type="text" id="escrowCancelTimeField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="finishTimeBasedEscrow()">Finish Time-based Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow sequence number, used when finishing the escrow.">
<lable for="escrowSequenceNumberField">Escrow Sequence Number</lable>
</span>
</td>
<td>
<input type="text" id="escrowSequenceNumberField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Escrow owner, the account that created the escrow.">
<lable for="escrowOwnerField">Escrow Owner</lable>
</span>
</td>
<td>
<input type="text" id="escrowOwnerField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="getTransaction()">Get Transaction</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Transaction number, used with the Get Transaction button.">
<lable for="transactionField">Transaction</lable>
</span>
</td>
<td>
<input type="text" id="transactionField" size="40"></input>
<br>
</td>
<td>
</td>
</tr>
<tr>
<td colspan="2">
<p align="right">
<textarea id="resultField" cols="80" rows="20"></textarea>
</p>
</td>
<td align="left" valign="top">
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
</td>
</tr>
</table>
</form>
</body>
<script>
const radioButtons = document.querySelectorAll('input[type="radio"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'account1') {
populate1()
} else if (this.value === 'account2') {
populate2()
}
});
});
</script>
</html>

View File

@@ -1,177 +0,0 @@
// *******************************************************
// ************* Add Seconds to Current Date *************
// *******************************************************
function addSeconds(numOfSeconds, date = new Date()) {
date.setSeconds(date.getSeconds() + numOfSeconds);
date = Math.floor(date / 1000)
date = date - 946684800
return date;
}
// *******************************************************
// ************* Create Time-based Escrow ****************
// *******************************************************
async function createTimeBasedEscrow() {
//-------------------------------------------- Prepare Finish and Cancel Dates
let escrow_finish_date = new Date()
let escrow_cancel_date = new Date()
escrow_finish_date = addSeconds(parseInt(escrowFinishTimeField.value))
escrow_cancel_date = addSeconds(parseInt(escrowCancelTimeField.value))
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ${net}.===\n\n===Creating time-based escrow.===\n`
resultField.value = results
try {
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
const sendAmount = amountField.value
const escrowTx = await client.autofill({
"TransactionType": "EscrowCreate",
"Account": wallet.address,
"Amount": xrpl.xrpToDrops(sendAmount),
"Destination": destinationField.value,
"FinishAfter": escrow_finish_date,
"CancelAfter": escrow_cancel_date
})
const signed = wallet.sign(escrowTx)
const tx = await client.submitAndWait(signed.tx_blob)
results += "\n===Success! === *** Save this sequence number: " + tx.result.tx_json.Sequence
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
resultField.value = results
}
catch (error) {
results += "\n===Error: " + error.message
resultField.value = results
}
finally {
client.disconnect()
}
} // End of createTimeEscrow()
// *******************************************************
// ***************** Finish Time- Based Escrow ***********
// *******************************************************
async function finishTimeBasedEscrow() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `===Connected to ${net}. Finishing escrow.===\n`
resultField.value = results
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
try {
const prepared = await client.autofill({
"TransactionType": "EscrowFinish",
"Account": accountAddressField.value,
"Owner": escrowOwnerField.value,
"OfferSequence": parseInt(escrowSequenceNumberField.value)
})
const signed = wallet.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
results += "\n===Balance changes===" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
resultField.value = results
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
}
catch (error) {
results += "\n===Error: " + error.message + "==="
resultField.value = results
}
finally {
client.disconnect()
}
} // End of finishTimeBasedEscrow()
// *******************************************************
// ******************* Get Escrows ***********************
// *******************************************************
async function getEscrows() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `\n===Connected to ${net}.\nGetting account escrows.===\n`
resultField.value = results
try {
const escrow_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": accountAddressField.value,
"ledger_index": "validated",
"type": "escrow"
})
results += JSON.stringify(escrow_objects.result, null, 2)
resultField.value = results
}
catch (error) {
results += "\nError: " + error.message
resultField.value = results
}
finally {
client.disconnect()
}
}
// *******************************************************
// ************** Get Transaction Info *******************
// *******************************************************
async function getTransaction() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `\n===Connected to ${net}.===\n===Getting transaction information.===\n`
resultField.value = results
try {
const tx_info = await client.request({
"id": 1,
"command": "tx",
"transaction": transactionField.value,
})
results += JSON.stringify(tx_info.result, null, 2)
resultField.value = results
}
catch (error) {
results += "\nError: " + error.message
resultField.value = results
}
finally {
client.disconnect()
}
} // End of getTransaction()
// *******************************************************
// ****************** Cancel Escrow **********************
// *******************************************************
async function cancelEscrow() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `\n===Connected to ${net}. Cancelling escrow.===`
resultField.value = results
try {
const prepared = await client.autofill({
"TransactionType": "EscrowCancel",
"Account": accountAddressField.value,
"Owner": escrowOwnerField.value,
"OfferSequence": parseInt(escrowSequenceNumberField.value)
})
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
const signed = wallet.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
results += "\n===Balance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
resultField.value = results
}
catch (error) {
results += "\n===Error: " + error.message
resultField.value = results
}
finally {
client.disconnect()
}
}

View File

@@ -1,24 +0,0 @@
const cc = require('five-bells-condition');
const crypto = require('crypto');
// 1. Generate a random 32-byte seed
const preimageData = crypto.randomBytes(32);
// 2. Create a PreimageSha256 fulfillment object
const fulfillment = new cc.PreimageSha256();
// 3. Set the preimage
fulfillment.setPreimage(preimageData);
// 4. Generate the condition (binary)
const conditionBinary = fulfillment.getConditionBinary();
// 5. Generate the fulfillment (binary)
const fulfillmentBinary = fulfillment.serializeBinary();
// Convert to hex for easier use
const conditionHex = conditionBinary.toString('hex').toUpperCase();
const fulfillmentHex = fulfillmentBinary.toString('hex').toUpperCase();
console.log('Condition (hex):', conditionHex);
console.log('Fulfillment (hex):', fulfillmentHex);

View File

@@ -1,247 +0,0 @@
<html>
<head>
<title>Send Checks</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<link href="modular-tutorials.css" rel="stylesheet">
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
<script src="account-support.js"></script>
<script src='send-xrp.js'></script>
<script src='send-currency.js'></script>
<script src='send-checks.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Send Checks</h1>
<form id="theForm">
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
Choose your ledger instance:
</span>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
<label for="dn">Devnet</label>
&nbsp;&nbsp;
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
<label for="tn">Testnet</label>
<br /><br />
<table>
<tr>
<td>
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
</td>
<td>
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
</td>
<td>
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
</span>
</td>
<td>
<input type="text" id="account1name" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
<label for="account2name">Account 2 Name</label>
</span>
</td>
<td>
<input type="text" id="account2name" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account1address">Account 1 Address</label>
</span>
</td>
<td>
<input type="text" id="account1address" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Identifying address for the account.">
<label for="account2address">Account 2 Address</label>
</span>
</td>
<td>
<input type="text" id="account2address" size="40"></input>
</td>
</tr>
<tr>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account1seed">Account 1 Seed</label>
</span>
</td>
<td>
<input type="text" id="account1seed" size="40"></input>
</td>
<td>
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
<label for="account2seed">Account 2 Seed</label>
</span>
</td>
<td>
<input type="text" id="account2seed" size="40"></input>
</td>
</tr>
</table>
<hr />
<table>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Name of the currently selected account.">
<label for="accountNameField">Account Name</label>
</span>
</td>
<td>
<input type="text" id="accountNameField" size="40" readonly></input>
<input type="radio" id="account1" name="accounts" value="account1">
<label for="account1">Account 1</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Address of the currently selected account.">
<label for="accountAddressField">Account Address</label>
</span>
</td>
<td>
<input type="text" id="accountAddressField" size="40" readonly></input>
<input type="radio" id="account2" name="accounts" value="account2">
<label for="account2">Account 2</label>
</td>
</tr>
<tr valign="top">
<td align="right">
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
<label for="accountSeedField">Account Seed</label>
</span>
</td>
<td>
<input type="text" id="accountSeedField" size="40" readonly></input>
<br>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
<label for="xrpBalanceField">XRP Balance</label>
</span>
</td>
<td>
<input type="text" id="xrpBalanceField" size="40" readonly></input>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Currency code for the check.">
<lable for="currencyField">Currency Code</lable>
</span>
</td>
<td>
<input type="text" id="currencyField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="sendCheck()">Send Check</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Issuing account for the currency.">
<lable for="issuerField">Issuer</lable>
</span>
</td>
<td>
<input type="text" id="issuerField" size="40"></input>
<br>
</td>
<td>
<button type="button" onClick="cashCheck()">Cash Check</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Amount of XRP to send.">
<label for="amountField">Amount</label>
</span>
</td>
<td>
<input type="text" id="amountField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="getChecks()">Get Checks</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Destination account address where XRP is sent.">
<lable for="destinationField">Destination</lable>
</span>
</td>
<td>
<input type="text" id="destinationField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="cancelCheck()">Cancel Check</button>
</td>
</tr>
<tr>
<td align="right">
<span class="tooltip" tooltip-data="Check ID.">
<lable for="checkIdField">Check ID</lable>
</span>
</td>
<td>
<input type="text" id="checkIdField" size="40"></input>
<br>
</td>
<td align="left" valign="top">
<button type="button" onClick="getTokenBalance()">Get Token Balance</button>
</td>
</tr>
<tr>
<td colspan="2">
<p align="right">
<textarea id="resultField" cols="80" rows="20"></textarea>
</p>
</td>
<td align="left" valign="top">
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
</td>
</tr>
</table>
</form>
</body>
<script>
const radioButtons = document.querySelectorAll('input[type="radio"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', function() {
if (this.value === 'account1') {
populate1()
} else if (this.value === 'account2') {
populate2()
}
});
});
</script>
</html>

View File

@@ -1,152 +0,0 @@
// *******************************************************
// ***************** Send Check **************************
// *******************************************************
async function sendCheck() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
results = `\n===Connected to ${net}.===\n===Sending check.===\n`
resultField.value = results
try {
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
let check_amount = amountField.value
if (currencyField.value != "XRP") {
check_amount = {
"currency": currencyField.value,
"value": amountField.value,
"issuer": wallet.address
}
}
const send_check_tx = {
"TransactionType": "CheckCreate",
"Account": wallet.address,
"SendMax": check_amount,
"Destination": destinationField.value
}
const check_prepared = await client.autofill(send_check_tx)
const check_signed = wallet.sign(check_prepared)
results = '\n===Sending ' + amountField.value + ' ' + currencyField.
value + ' to ' + destinationField.value + '.===\n'
resultField.value += results
const check_result = await client.submitAndWait(check_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += '===Transaction succeeded===\n\n'
resultField.value += JSON.stringify(check_result.result, null, 2)
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
}
}
catch (error) {
results = `Error sending transaction: ${error}`
resultField.value += results
}
finally {
client.disconnect()
}
} // end of sendCheck()
// *******************************************************
// ********************* Get Checks **********************
// *******************************************************
async function getChecks() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
let results = `\n===Connected to ${net}.===\n===Getting account checks.===\n\n`
resultField.value = results
try {
const check_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": accountAddressField.value,
"ledger_index": "validated",
"type": "check"
})
resultField.value += JSON.stringify(check_objects.result, null, 2)
} catch (error) {
results = `Error getting checks: ${error}`
resultField.value += results
}
finally {
client.disconnect()
}
} // End of getChecks()
// *******************************************************
// ******************** Cash Check **********************
// *******************************************************
async function cashCheck() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
results = `\n===Connected to ${net}.===\n===Cashing check.===\n`
resultField.value = results
try {
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
let check_amount = amountField.value
if (currencyField.value != "XRP") {
check_amount = {
"value": amountField.value,
"currency": currencyField.value,
"issuer": issuerField.value
}
}
const cash_check_tx = {
"TransactionType": "CheckCash",
"Account": wallet.address,
"Amount": check_amount,
"CheckID": checkIdField.value
}
const cash_prepared = await client.autofill(cash_check_tx)
const cash_signed = wallet.sign(cash_prepared)
results = ' Receiving ' + amountField.value + ' ' + currencyField.value + '.\n'
resultField.value += results
const check_result = await client.submitAndWait(cash_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results = '===Transaction succeeded===\n' + JSON.stringify(check_result.result, null, 2)
resultField.value += results
}
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
} catch (error) {
results = `Error sending transaction: ${error}`
resultField.value += results
}
finally {
client.disconnect()
}
} // end of cashCheck()
// *******************************************************
// **************** Cancel Check *************************
// *******************************************************
async function cancelCheck() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
results = `\n===Connected to ${net}.===\n===Cancelling check.===\n`
resultField.value = results
try {
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
const cancel_check_tx = {
"TransactionType": "CheckCancel",
"Account": wallet.address,
"CheckID": checkIdField.value
}
const cancel_prepared = await client.autofill(cancel_check_tx)
const cancel_signed = wallet.sign(cancel_prepared)
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += `===Transaction succeeded===\n${check_result.result.meta.TransactionResult}`
resultField.value = results
}
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
} catch (error) {
results = `Error sending transaction: ${error}`
resultField.value += results
}
finally {
client.disconnect()
}
} // end of cancelCheck()

View File

@@ -1,192 +0,0 @@
<html>
<head>
<title>Token Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Token Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="testnet">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="devnet">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table>
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="right">
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="sendXRP()">Send XRP&#62;</button>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>
<table>
<tr>
<td align="center" valign="top">
<button type="button" onClick="oPsendXRP()">&#60;Send XRP</button>
</td>
<td align="right">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,278 +0,0 @@
<html>
<head>
<title>Token Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex10-check.js'></script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Token Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="testnet">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="devnet">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table>
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Issuer
</td>
<td>
<input type="text" id="standbyIssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Check ID
</td>
<td>
<input type="text" id="standbyCheckID" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="true"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="standbyCurrencyField" size="40" value="USD"></input>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="sendXRP()">Send XRP&#62;</button>
<br/><br/>
<button type="button" onClick="sendCheck()">Send Check</button>
<br/>
<button type="button" onClick="getChecks()">Get Checks</button>
<br/>
<button type="button" onClick="cashCheck()">Cash Check</button>
<br/>
<button type="button" onClick="cancelCheck()">Cancel Check</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>
<td>
<table>
<tr>
<td align="center" valign="top">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/><br/>
<button type="button" onClick="opSendCheck()">Send Check</button>
<br/>
<button type="button" onClick="opGetChecks()">Get Checks</button>
<br/>
<button type="button" onClick="opCashCheck()">Cash Check</button>
<br/>
<button type="button" onClick="opCancelCheck()">Cancel Check</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
</td>
<td valign="top" align="right">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Issuer
</td>
<td>
<input type="text" id="operationalIssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Check ID
</td>
<td>
<input type="text" id="operationalCheckIDField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="true"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="operationalCurrencyField" size="40" value="USD"></input>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,302 +0,0 @@
<html>
<head>
<title>Create AMM Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex11-create-amm.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Create AMM Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="testnet">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="devnet">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table style="padding-bottom: 400px;">
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="true"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="standbyCurrencyField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="60" rows="20" style="resize: none;"></textarea>
</p>
<table>
<tr valign="top">
<td align="right">
Asset 1 Currency
</td>
<td>
<input type="text" id="asset1CurrencyField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 1 Issuer
</td>
<td>
<input type="text" id="asset1IssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 1 Amount
</td>
<td>
<input type="text" id="asset1AmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Currency
</td>
<td>
<input type="text" id="asset2CurrencyField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Issuer
</td>
<td>
<input type="text" id="asset2IssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Amount
</td>
<td>
<input type="text" id="asset2AmountField" size="40"></input>
<br>
</td>
</tr>
</table>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top" style="padding-top: 165px;">
<br>
<button type="button" onClick="sendXRP()">Send XRP&#62;</button>
<br/><br/>
<button type="button" onClick="createTrustline()">Create TrustLine</button>
<br/>
<button type="button" onClick="sendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()" style="margin-bottom: 160px;">Get Balances</button>
<br/>
<button type="button" onClick="checkAMM()">Check AMM</button>
<br/>
<button type="button" onClick="createAMM()">Create AMM</button>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table style="padding-bottom: 350px;">
<tr>
<td>
<td>
<table>
<tr>
<td align="center" valign="top" style="padding-bottom: 100px;">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/><br/>
<button type="button" onClick="oPcreateTrustline()">Create TrustLine</button>
<br/>
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
</td>
<td valign="top" align="right">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="true"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="operationalCurrencyField" size="40"></input>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="60" rows="20" style="resize: none;"></textarea>
<br><br>
<textarea id="ammInfoField" cols="60" rows="20" style="resize: none;"></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,354 +0,0 @@
<html>
<head>
<title>Trade with Auction Slot Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='https://unpkg.com/bignumber.js@9.1.2/bignumber.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex11-create-amm.js'></script>
<script src='ripplex12-add-to-amm.js'></script>
<script src='ripplex13a-trade-with-auction-slot.js'></script>
<script src='ripplex13b-amm-formulas.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Trade with Auction Slot Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="testnet">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="devnet">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table style="padding-bottom: 400px;">
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="true"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="standbyCurrencyField" size="40"></input>
<br>
</td>
</tr>
</table>
<table>
<tr>
<td align="right">
Taker Pays<br/><br/>
Currency <input type="text" id="standbyTakerPaysCurrencyField" size="15"></input><br/>
Issuer <input type="text" id="standbyTakerPaysIssuerField" size="15"></input><br/>
Amount <input type="text" id="standbyTakerPaysAmountField" size="15"></input>
</td>
<td align="right">
Taker Gets<br/><br/>
Currency <input type="text" id="standbyTakerGetsCurrencyField" size="15"></input><br/>
Issuer <input type="text" id="standbyTakerGetsIssuerField" size="15"></input><br/>
Amount <input type="text" id="standbyTakerGetsAmountField" size="15"></input><br/>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="60" rows="20" style="resize: none;"></textarea>
</p>
<table>
<tr valign="top">
<td align="right">
Asset 1 Currency
</td>
<td>
<input type="text" id="asset1CurrencyField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 1 Issuer
</td>
<td>
<input type="text" id="asset1IssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 1 Amount
</td>
<td>
<input type="text" id="asset1AmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Currency
</td>
<td>
<input type="text" id="asset2CurrencyField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Issuer
</td>
<td>
<input type="text" id="asset2IssuerField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Asset 2 Amount
</td>
<td>
<input type="text" id="asset2AmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Trading Fee
</td>
<td>
<input type="text" id="standbyFeeField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
LP Tokens
</td>
<td>
<input type="text" id="standbyLPField" size="40"></input>
<br>
</td>
</tr>
</table>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top" style="padding-top: 450px;">
<br>
<button type="button" onClick="sendXRP()">Send XRP&#62;</button>
<br/><br/>
<button type="button" onClick="createTrustline()">Create TrustLine</button>
<br/>
<button type="button" onClick="sendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/><br/>
<button type="button" onClick="estimateCost()">Estimate Cost</button>
<br/>
<button type="button" onClick="swapTokens()" style="margin-bottom: 40px;">Swap Tokens</button>
<br/><br/>
<button type="button" onClick="checkAMM()">Check AMM</button>
<br/>
<button type="button" onClick="createAMM()">Create AMM</button>
<br/>
<button type="button" onClick="addAssets()">Add to AMM</button>
<br/>
<button type="button" onClick="voteFees()">Vote on Fee</button>
<br/><br/>
<button type="button" onClick="calculateLP()">Get LP Value</button>
<br/>
<button type="button" onClick="redeemLP()">Redeem LP</button>
<br/>
<button type="button" onClick="bidAuction()">Bid Auction Slot</button>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table style="padding-bottom: 430px;">
<tr>
<td>
<td>
<table>
<tr>
<td align="center" valign="top" style="padding-top: 60px;">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/><br/>
<button type="button" onClick="oPcreateTrustline()">Create TrustLine</button>
<br/>
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
</td>
<td valign="top" align="right" style="padding-bottom: 15px;">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="true"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="operationalCurrencyField" size="40"></input>
</td>
</tr>
</table>
<p align="right" style="padding-top: 170px;">
<textarea id="operationalResultField" cols="60" rows="20" style="resize: none;"></textarea>
<br><br>
<textarea id="ammInfoField" cols="60" rows="20" style="resize: none;"></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,313 +0,0 @@
<html>
<head>
<title>Token Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex3a-create-offers.js'></script>
<script src='ripplex3b-NameFieldSupport.js'></script>
<script>
if (typeof module !== "undefined") {
const xrpl = require('xrpl')
}
</script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Token Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="testnet">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="devnet">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="55" rows= "4"></textarea>
<table>
<tr valign="top">
<td>
<table>
<tr valign="top">
<td>
<button type="button" onClick="getAccount('standby')">Get New Account</button>
<table>
<tr valign="top">
<td align="right">
Account Name
</td>
<td>
<input type="text" id="standbyNameField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td align="right">
Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="false"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="standbyCurrencyField" size="40" value="USD"></input>
</td>
</tr>
<tr>
<td align="right">
Offer Sequence
</td>
<td>
<input type="text" id="standbyOfferSequenceField" size="10"></input>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<table>
<tr>
<td>
Taker Pays:<br/>
Currency: <input type="text" id="standbyTakerPaysCurrencyField" size="10"></input><br/>
Issuer: <input type="text" id="standbyTakerPaysIssuerField" size="35"></input><br/>
Value: <input type="text" id="standbyTakerPaysValueField" size="10"></input>
</td>
<td>
Taker Gets:<br/>
Currency: <input type="text" id="standbyTakerGetsCurrencyField" size="10"></input><br/>
Issuer: <input type="text" id="standbyTakerGetsIssuerField" size="35"></input><br/>
Value: <input type="text" id="standbyTakerGetsValueField" size="10"></input><br/>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</tr>
</table>
</td>
<!-- Standby Buttons, Column 2 -->
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="sendXRP()">Send XRP&#62;</button>
<br/>
<button type="button" onClick="createTrustline()">Create Trust Line</button>
<br/>
<button type="button" onClick="getTrustLines()">Get Trust Lines</button>
<br/>
<button type="button" onClick="sendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/><br/>
<button type="button" onClick="createOffer()">Create Offer</button>
<br/>
<button type="button" onClick="getOffers()">Get Offers</button>
<br/>
<button type="button" onClick="cancelOffer()">Cancel Offer</button>
</td>
</tr>
</table>
</td>
<!-- Operational Buttons, Column 3 -->
<td>
<table>
<tr valign="bottom">
<td align="center" valign="middle">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/>
<button type="button" onClick="oPcreateTrustline()">Create Trust Line</button>
<br/>
<button type="button" onClick="oPgetTrustLines()">Get Trust Lines</button>
<br/>
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
<br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/><br/>
<button type="button" onClick="oPcreateOffer()">Create Offer</button>
<br/>
<button type="button" onClick="oPgetOffers()">Get Offers</button>
<br/>
<button type="button" onClick="oPcancelOffer()">Cancel Offer</button>
</td>
</tr>
</table>
</td>
<!-- Operational fields, Column 4 -->
<td valign="top" align="right">
<button type="button" onClick="getAccount('operational')">Get New Account</button>
<table>
<tr valign="top">
<td align="left">
Account Name
</td>
<td align="left">
<input type="text" id="operationalNameField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td align="right">
Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td></td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="false"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Currency
</td>
<td>
<input type="text" id="operationalCurrencyField" size="40" value="USD"></input>
</td>
</tr>
<tr>
<td align="right">
Offer Sequence
</td>
<td>
<input type="text" id="operationalOfferSequenceField" size="10"></input>
</td>
</tr>
</table>
<table>
<tr>
<td>
Taker Pays:<br/>
Currency: <input type="text" id="operationalTakerPaysCurrencyField" size="10"></input><br/>
Issuer: <input type="text" id="operationalTakerPaysIssuerField" size="35"></input><br/>
Value: <input type="text" id="operationalTakerPaysValueField" size="10"></input>
</td>
<td>
Taker Gets:<br/>
Currency: <input type="text" id="operationalTakerGetsCurrencyField" size="10"></input><br/>
Issuer: <input type="text" id="operationalTakerGetsIssuerField" size="35"></input><br/>
Value: <input type="text" id="operationalTakerGetsValueField" size="10"></input><br/>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,269 +0,0 @@
<html>
<head>
<title>Time-based Escrow Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex8-escrow.js'></script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Time-based Escrow Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="tn">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="dn">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table>
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination Account
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Finish (seconds)
</td>
<td>
<input type="text" id="standbyEscrowFinishDateField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Cancel (seconds)
</td>
<td>
<input type="text" id="standbyEscrowCancelDateField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Sequence Number
</td>
<td>
<input type="text" id="standbyEscrowSequenceNumberField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="true"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="sendXRP()">Send XRP &#62;</button>
<br/><br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/>
<button type="button" onClick="createTimeEscrow()">Create Time-based Escrow</button>
<br/>
<button type="button" onClick="getStandbyEscrows()">Get Escrows</button>
<br/>
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/><br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/>
<button type="button" onClick="finishEscrow()">Finish Time-based Escrow</button>
<br/>
<button type="button" onClick="getOperationalEscrows()">Get Escrows</button>
<br/>
<button type="button" onClick="getTransaction()">Get Transaction</button>
</td>
<td valign="top" align="right">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination
</td>
<td>
<input type="text" id="operationalDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<tr>
<td align="right">
Escrow Sequence Number
</td>
<td>
<input type="text" id="operationalEscrowSequenceField" size="40"></input>
<br>
</td>
</tr>
<tr> <td align="right">
Transaction to Look Up
</td>
<td>
<input type="text" id="operationalTransactionField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="true"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,270 +0,0 @@
<html>
<head>
<title>Conditional Escrow Test Harness</title>
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
<style>
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
h1{font-weight: bold;}
input, button {padding: 6px;margin-bottom: 8px;}
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
td{vertical-align: middle;}
</style>
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
<script src='ripplex1-send-xrp.js'></script>
<script src='ripplex2-send-currency.js'></script>
<script src='ripplex8-escrow.js'></script>
<script src='ripplex9-escrow-condition.js'></script>
</head>
<!-- ************************************************************** -->
<!-- ********************** The Form ****************************** -->
<!-- ************************************************************** -->
<body>
<h1>Conditional Escrow Test Harness</h1>
<form id="theForm">
Choose your ledger instance:
&nbsp;&nbsp;
<input type="radio" id="tn" name="server"
value="wss://s.altnet.rippletest.net:51233" checked>
<label for="tn">Testnet</label>
&nbsp;&nbsp;
<input type="radio" id="dn" name="server"
value="wss://s.devnet.rippletest.net:51233">
<label for="dn">Devnet</label>
<br/><br/>
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
<br/>
<textarea id="seeds" cols="40" rows= "2"></textarea>
<br/><br/>
<table>
<tr valign="top">
<td>
<table>
<tr valign="top">
<td>
<td>
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
<table>
<tr valign="top">
<td align="right">
Standby Account
</td>
<td>
<input type="text" id="standbyAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="standbyBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="standbyAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Destination Account
</td>
<td>
<input type="text" id="standbyDestinationField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Condition
</td>
<td>
<input type="text" id="standbyEscrowConditionField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Cancel (seconds)
</td>
<td>
<input type="text" id="standbyEscrowCancelDateField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Escrow Sequence Number
</td>
<td>
<input type="text" id="standbyEscrowSequenceNumberField" size="40"></input>
<br>
</td>
</tr>
<tr valign="top">
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
<td>
<input type="checkbox" id="standbyDefault" checked="true"/>
<label for="standbyDefault">Allow Rippling</label>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="standbySeedField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="left">
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="sendXRP()">Send XRP &#62;</button>
<br/><br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/>
<button type="button" onClick="createConditionalEscrow()">Create Conditional Escrow</button>
<br/>
<button type="button" onClick="getStandbyEscrows()">Get Escrows</button>
<br/>
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td>
<table>
<tr>
<td>
<td>
<table>
<tr valign="top">
<td align="center" valign="top">
<button type="button" onClick="oPsendXRP()">&#60; Send XRP</button>
<br/><br/>
<button type="button" onClick="getBalances()">Get Balances</button>
<br/>
<button type="button" onClick="finishConditionalEscrow()">Finish Conditional Escrow</button>
<br/>
<button type="button" onClick="getOperationalEscrows()">Get Escrows</button>
<br/>
<button type="button" onClick="getTransaction()">Get Transaction</button>
</td>
<td valign="top" align="right">
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
<table>
<tr valign="top">
<td align="right">
Operational Account
</td>
<td>
<input type="text" id="operationalAccountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
XRP Balance
</td>
<td>
<input type="text" id="operationalBalanceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Amount
</td>
<td>
<input type="text" id="operationalAmountField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Fulfillment Code
</td>
<td>
<input type="text" id="operationalFulfillmentField" size="40"></input>
<br>
</td>
</tr>
<tr>
<tr>
<td align="right">
Escrow Sequence Number
</td>
<td>
<input type="text" id="operationalEscrowSequenceField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td align="right">
Transaction to Look Up
</td>
<td>
<input type="text" id="operationalTransactionField" size="40"></input>
<br>
</td>
</tr>
<tr>
<td>
</td>
<td align="right">
<input type="checkbox" id="operationalDefault" checked="true"/>
<label for="operationalDefault">Allow Rippling</label>
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
</td>
</tr>
<tr>
<td align="right">
Seed
</td>
<td>
<input type="text" id="operationalSeedField" size="40"></input>
<br>
</td>
</tr>
</table>
<p align="right">
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
</p>
</td>
</td>
</tr>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</form>
</body>
</html>

View File

@@ -1,329 +0,0 @@
// *******************************************************
// ************* Standby Send Check **********************
// *******************************************************
async function sendCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
var check_amount = standbyAmountField.value
if (standbyCurrencyField.value != "XRP") {
check_amount = {
"currency": standbyCurrencyField.value,
"value": standbyAmountField.value,
"issuer": standby_wallet.address
}
}
const send_check_tx = {
"TransactionType": "CheckCreate",
"Account": standby_wallet.address,
"SendMax": check_amount,
"Destination": standbyDestinationField.value
}
const check_prepared = await client.autofill(send_check_tx)
const check_signed = standby_wallet.sign(check_prepared)
results += 'Sending ' + check_amount + ' ' + standbyCurrencyField + ' to ' +
standbyDestinationField.value + '...'
standbyResultField.value = results
const check_result = await client.submitAndWait(check_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${check_signed.hash}'
standbyResultField.value = JSON.stringify(check_result.result, null, 2)
} else {
results += 'Transaction failed: See JavaScript console for details.'
standbyResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // end of sendCheck()
// *******************************************************
// *************** Standby Get Checks ********************
// *******************************************************
async function getChecks() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
results= "\nGetting standby account checks...\n"
const check_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": standbyAccountField.value,
"ledger_index": "validated",
"type": "check"
})
standbyResultField.value = JSON.stringify(check_objects.result, null, 2)
client.disconnect()
} // End of getChecks()
// *******************************************************
// ************* Standby Cash Check **********************
// *******************************************************
async function cashCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
var check_amount = standbyAmountField.value
if (standbyCurrencyField.value != "XRP") {
check_amount = {
"value": standbyAmountField.value,
"currency": standbyCurrencyField.value,
"issuer": standbyIssuerField.value
}
}
const cash_check_tx = {
"TransactionType": "CheckCash",
"Account": standby_wallet.address,
"Amount": check_amount,
"CheckID": standbyCheckID.value
}
const cash_prepared = await client.autofill(cash_check_tx)
const cash_signed = standby_wallet.sign(cash_prepared)
results += ' Receiving ' + standbyAmountField.value + ' ' + standbyCurrencyField.value + '.\n'
standbyResultField.value = results
const check_result = await client.submitAndWait(cash_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
standbyResultField.value = results
} else {
results += 'Transaction failed: See JavaScript console for details.'
standbyResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // end of cashCheck()
// *******************************************************
// *************** Standby Cancel Check ******************
// *******************************************************
async function cancelCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const cancel_check_tx = {
"TransactionType": "CheckCancel",
"Account": standby_wallet.address,
"CheckID": standbyCheckID.value
}
const cancel_prepared = await client.autofill(cancel_check_tx)
const cancel_signed = standby_wallet.sign(cancel_prepared)
results += ' Cancelling check.\n'
standbyResultField.value = results
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
standbyResultField.value = results
} else {
results += 'Transaction failed: See JavaScript console for details.'
standbyResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // end of cancelCheck()
// *******************************************************
// ************ Operational Send Check *******************
// *******************************************************
async function opSendCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const issue_quantity = operationalAmountField.value
var check_amount = operationalAmountField.value
if (operationalCurrencyField.value != "XRP") {
check_amount = {
"currency": operationalCurrencyField.value,
"value": operationalAmountField.value,
"issuer": operational_wallet.address
}
}
const send_check_tx = {
"TransactionType": "CheckCreate",
"Account": operational_wallet.address,
"SendMax": check_amount,
"Destination": operationalDestinationField.value
}
const check_prepared = await client.autofill(send_check_tx)
const check_signed = operational_wallet.sign(check_prepared)
results += '\nSending check to ' +
operationalDestinationField.value + '...'
operationalResultField.value = results
const check_result = await client.submitAndWait(check_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${check_signed.hash}'
operationalResultField.value = JSON.stringify(check_result.result, null, 2)
} else {
results += 'Transaction failed: See JavaScript console for details.'
operationalResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // end of opSendCheck()
// *******************************************************
// ************ Operational Get Checks *******************
// *******************************************************
async function opGetChecks() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
results= "\nGetting standby account checks...\n"
const check_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": operationalAccountField.value,
"ledger_index": "validated",
"type": "check"
})
operationalResultField.value = JSON.stringify(check_objects.result, null, 2)
client.disconnect()
} // End of opGetChecks()
// *******************************************************
// ************* Operational Cash Check ******************
// *******************************************************
async function opCashCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
var check_amount = operationalAmountField.value
if (operationalCurrencyField.value != "XRP") {
check_amount = {
"value": operationalAmountField.value,
"currency": operationalCurrencyField.value,
"issuer": operationalIssuerField.value
}
}
const cash_check_tx = {
"TransactionType": "CheckCash",
"Account": operational_wallet.address,
"Amount": check_amount,
"CheckID": operationalCheckIDField.value
}
const cash_prepared = await client.autofill(cash_check_tx)
const cash_signed = operational_wallet.sign(cash_prepared)
results += ' Receiving ' + operationalAmountField.value + ' ' + operationalCurrencyField.value + '.\n'
operationalResultField.value = results
const check_result = await client.submitAndWait(cash_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
operationalResultField.value = results
} else {
results += 'Transaction failed: See JavaScript console for details.'
operationalResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
}
// end of opCashCheck()
// *******************************************************
// ************* Operational Cancel Check ****************
// *******************************************************
async function opCancelCheck() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const cancel_check_tx = {
"TransactionType": "CheckCancel",
"Account": operational_wallet.address,
"CheckID": operationalCheckIDField.value
}
const cancel_prepared = await client.autofill(cancel_check_tx)
const cancel_signed = operational_wallet.sign(cancel_prepared)
results += ' Cancelling check.\n'
operationalResultField.value = results
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
operationalResultField.value = results
} else {
results += 'Transaction failed: See JavaScript console for details.'
operationalResultField.value = results
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
}
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // end of cancelCheck()

View File

@@ -1,194 +0,0 @@
// Create AMM function
async function createAMM() {
let net = getNet()
const client = new xrpl.Client(net)
results = `\n\nConnecting to ${getNet()} ...`
standbyResultField.value = results
await client.connect()
results += '\n\nConnected.'
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const asset1_currency = asset1CurrencyField.value
const asset1_issuer = asset1IssuerField.value
const asset1_amount = asset1AmountField.value
const asset2_currency = asset2CurrencyField.value
const asset2_issuer = asset2IssuerField.value
const asset2_amount = asset2AmountField.value
let ammCreate = null
results += '\n\nCreating AMM ...'
standbyResultField.value = results
// AMMCreate requires burning one owner reserve. We can look up that amount
// (in drops) on the current network using server_state:
const ss = await client.request({"command": "server_state"})
const amm_fee_drops = ss.result.state.validated_ledger.reserve_inc.toString()
if (asset1_currency == 'XRP') {
ammCreate = {
"TransactionType": "AMMCreate",
"Account": standby_wallet.address,
"Amount": JSON.stringify(asset1_amount * 1000000), // convert XRP to drops
"Amount2": {
"currency": asset2_currency,
"issuer": asset2_issuer,
"value": asset2_amount
},
"TradingFee": 500, // 500 = 0.5%
"Fee": amm_fee_drops
}
} else if (asset2_currency =='XRP') {
ammCreate = {
"TransactionType": "AMMCreate",
"Account": standby_wallet.address,
"Amount": {
"currency": asset1_currency,
"issuer": asset1_issuer,
"value": asset1_amount
},
"Amount2": JSON.stringify(asset2_amount * 1000000), // convert XRP to drops
"TradingFee": 500, // 500 = 0.5%
"Fee": amm_fee_drops
}
} else {
ammCreate = {
"TransactionType": "AMMCreate",
"Account": standby_wallet.address,
"Amount": {
"currency": asset1_currency,
"issuer": asset1_issuer,
"value": asset1_amount
},
"Amount2": {
"currency": asset2_currency,
"issuer": asset2_issuer,
"value": asset2_amount
},
"TradingFee": 500, // 500 = 0.5%
"Fee": amm_fee_drops
}
}
try {
const prepared_create = await client.autofill(ammCreate)
results += `\n\nPrepared transaction:\n${JSON.stringify(prepared_create, null, 2)}`
standbyResultField.value = results
standbyResultField.scrollTop = standbyResultField.scrollHeight
const signed_create = standby_wallet.sign(prepared_create)
results += `\n\nSending AMMCreate transaction ...`
standbyResultField.value = results
standbyResultField.scrollTop = standbyResultField.scrollHeight
const amm_create = await client.submitAndWait(signed_create.tx_blob)
if (amm_create.result.meta.TransactionResult == "tesSUCCESS") {
results += `\n\nTransaction succeeded.`
} else {
results += `\n\nError sending transaction: ${JSON.stringify(amm_create.result.meta.TransactionResult, null, 2)}`
}
} catch (error) {
results += `\n\n${error.message}`
}
standbyResultField.value = results
standbyResultField.scrollTop = standbyResultField.scrollHeight
checkAMM()
client.disconnect()
}
// Check AMM function
async function checkAMM() {
let net = getNet()
const client = new xrpl.Client(net)
await client.connect()
// Gets the issuer and currency code
const asset1_currency = asset1CurrencyField.value
const asset1_issuer = asset1IssuerField.value
const asset2_currency = asset2CurrencyField.value
const asset2_issuer = asset2IssuerField.value
let amm_info_request = null
// Get AMM info transaction
if (asset1_currency == 'XRP') {
amm_info_request = {
"command": "amm_info",
"asset": {
"currency": "XRP"
},
"asset2": {
"currency": asset2_currency,
"issuer": asset2_issuer
},
"ledger_index": "validated"
}
} else if (asset2_currency =='XRP') {
amm_info_request = {
"command": "amm_info",
"asset": {
"currency": asset1_currency,
"issuer": asset1_issuer
},
"asset2": {
"currency": "XRP"
},
"ledger_index": "validated"
}
} else {
amm_info_request = {
"command": "amm_info",
"asset": {
"currency": asset1_currency,
"issuer": asset1_issuer
},
"asset2": {
"currency": asset2_currency,
"issuer": asset2_issuer
},
"ledger_index": "validated"
}
}
try {
const amm_info_result = await client.request(amm_info_request)
ammInfo = `AMM Info:\n\n${JSON.stringify(amm_info_result.result.amm, null, 2)}`
} catch(error) {
ammInfo = `AMM Info:\n\n${error}`
}
ammInfoField.value = ammInfo
client.disconnect()
}

View File

@@ -1,365 +0,0 @@
// Function to estimate cost to swap for specified token value.
async function estimateCost() {
let net = getNet()
const client = new xrpl.Client(net)
results = `\n\nConnecting to ${getNet()} ...`
standbyResultField.value = results
await client.connect()
results += '\n\nConnected.'
standbyResultField.value = results
try {
const asset1_currency = asset1CurrencyField.value
const asset1_issuer = asset1IssuerField.value
const asset2_currency = asset2CurrencyField.value
const asset2_issuer = asset2IssuerField.value
// Look up AMM info
let asset1_info = null
let asset2_info = null
if ( asset1_currency == 'XRP' ) {
asset1_info = {
"currency": "XRP"
}
} else {
asset1_info = {
"currency": asset1_currency,
"issuer": asset1_issuer
}
}
if ( asset2_currency == 'XRP' ) {
asset2_info = {
"currency": "XRP"
}
} else {
asset2_info = {
"currency": asset2_currency,
"issuer": asset2_issuer
}
}
const amm_info = (await client.request({
"command": "amm_info",
"asset": asset1_info,
"asset2": asset2_info
}))
// Save relevant AMM info for calculations
const lpt = amm_info.result.amm.lp_token
const pool_asset1 = amm_info.result.amm.amount
const pool_asset2 = amm_info.result.amm.amount2
const full_trading_fee = amm_info.result.amm.trading_fee
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
const old_bid = amm_info.result.amm.auction_slot.price.value
const time_interval = amm_info.result.amm.auction_slot.time_interval
results += `\n\nTrading Fee: ${full_trading_fee/1000}%\nDiscounted Fee: ${discounted_fee/1000}%`
// Save taker pays and gets values.
const takerPays = {
"currency": standbyTakerPaysCurrencyField.value,
"issuer": standbyTakerPaysIssuerField.value,
"amount": standbyTakerPaysAmountField.value
}
const takerGets = {
"currency": standbyTakerGetsCurrencyField.value,
"issuer": standbyTakerGetsIssuerField.value,
"amount": standbyTakerGetsAmountField.value
}
// Get amount of assets in the pool.
// Convert values to BigNumbers with the appropriate precision.
// Tokens always have 15 significant digits;
// XRP is precise to integer drops, which can be as high as 10^17
let asset_out_bn = null
let pool_in_bn = null
let pool_out_bn = null
let isAmmAsset1Xrp = false
let isAmmAsset2Xrp = false
if ( takerPays.currency == 'XRP' ) {
asset_out_bn = BigNumber(xrpl.xrpToDrops(takerPays.amount)).precision(17)
} else {
asset_out_bn = BigNumber(takerPays.amount).precision(15)
}
if ( takerGets.currency == 'XRP' && asset1_currency == 'XRP' ) {
pool_in_bn = BigNumber(pool_asset1).precision(17)
isAmmAsset1Xrp = true
} else if ( takerGets.currency == 'XRP' && asset2_currency == 'XRP' ) {
pool_in_bn = BigNumber(pool_asset2).precision(17)
isAmmAsset2Xrp = true
} else if ( takerGets.currency == asset1_currency ) {
pool_in_bn = BigNumber(pool_asset1.value).precision(15)
} else {
pool_in_bn = BigNumber(pool_asset2.value).precision(15)
}
if (takerPays.currency == 'XRP' && asset1_currency == 'XRP' ) {
pool_out_bn = BigNumber(pool_asset1).precision(17)
} else if ( takerPays.currency == 'XRP' && asset2_currency == 'XRP' ) {
pool_out_bn = BigNumber(pool_asset2).precision(17)
} else if ( takerPays.currency == asset1_currency ) {
pool_out_bn = BigNumber(pool_asset1.value).precision(15)
} else {
pool_out_bn = BigNumber(pool_asset2.value).precision(15)
}
if ( takerPays.currency == 'XRP' && parseFloat(takerPays.amount) > parseFloat(xrpl.dropsToXrp(pool_out_bn)) ) {
results += `\n\nRequested ${takerPays.amount} ${takerPays.currency}, but AMM only holds ${xrpl.dropsToXrp(pool_out_bn)}. Quitting.`
standbyResultField.value = results
client.disconnect()
return
} else if ( parseFloat(takerPays.amount) > parseFloat(pool_out_bn) ) {
results += `\n\nRequested ${takerPays.amount} ${takerPays.currency}, but AMM only holds ${pool_out_bn}. Quitting.`
standbyResultField.value = results
client.disconnect()
return
}
// Use AMM's SwapOut formula to figure out how much of the takerGets asset
// you have to pay to receive the target amount of takerPays asset
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn, pool_out_bn, full_trading_fee)
// Drop decimal places and round ceiling to ensure you pay in enough.
const swap_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
// Helper function to convert drops to XRP in log window
function convert(currency, amount) {
if ( currency == 'XRP' ) {
amount = xrpl.dropsToXrp(amount)
}
return amount
}
results += `\n\nExpected cost for ${takerPays.amount} ${takerPays.currency}: ${convert(takerGets.currency, swap_amount)} ${takerGets.currency}`
// Use SwapOut to calculate discounted swap amount with auction slot
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn, discounted_fee)
const discounted_swap_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
results += `\n\nExpected cost with auction slot for ${takerPays.amount} ${takerPays.currency}: ${convert(takerGets.currency, discounted_swap_amount)} ${takerGets.currency}`
// Calculate savings by using auction slot
const potential_savings = swap_amount.minus(discounted_swap_amount)
results += `\nPotential savings: ${convert(takerGets.currency, potential_savings)} ${takerGets.currency}`
// Calculate the cost of winning the auction slot, in LP Tokens.
const auction_price = auctionDeposit(old_bid, time_interval, full_trading_fee, lpt.value).dp(3, BigNumber.ROUND_CEIL)
results += `\n\nYou can win the current auction slot by bidding ${auction_price} LP Tokens.`
// Calculate how much to add for a single-asset deposit to receive the target LP Token amount
let deposit_for_bid_asset1 = null
let deposit_for_bid_asset2 = null
if ( isAmmAsset1Xrp == true ) {
deposit_for_bid_asset1 = xrpl.dropsToXrp(ammAssetIn(pool_asset1, lpt.value, auction_price, full_trading_fee).dp(0, BigNumber.ROUND_CEIL))
} else {
deposit_for_bid_asset1 = ammAssetIn(pool_asset1.value, lpt.value, auction_price, full_trading_fee).dp(15, BigNumber.ROUND_CEIL)
}
if ( isAmmAsset2Xrp == true ) {
deposit_for_bid_asset2 = xrpl.dropsToXrp(ammAssetIn(pool_asset2, lpt.value, auction_price, full_trading_fee).dp(0, BigNumber.ROUND_CEIL))
} else {
deposit_for_bid_asset2 = ammAssetIn(pool_asset2.value, lpt.value, auction_price, full_trading_fee).dp(15, BigNumber.ROUND_CEIL)
}
if ( isAmmAsset1Xrp == true ) {
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} XRP or ${deposit_for_bid_asset2} ${pool_asset2.currency} to get the required LP Tokens.`
} else if ( isAmmAsset2Xrp == true ) {
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} ${pool_asset1.currency} or ${deposit_for_bid_asset2} XRP to get the required LP Tokens.`
} else {
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} ${pool_asset1.currency} or ${deposit_for_bid_asset2} ${pool_asset2.currency} to get the required LP Tokens.`
}
} catch (error) {
results += `\n\n${error.message}`
}
standbyResultField.value = results
client.disconnect()
}
// Bid on the auction slot
async function bidAuction() {
let net = getNet()
const client = new xrpl.Client(net)
results = `\n\nConnecting to ${getNet()} ...`
standbyResultField.value = results
await client.connect()
results += '\n\nConnected.'
standbyResultField.value = results
try {
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const asset1_currency = asset1CurrencyField.value
const asset1_issuer = asset1IssuerField.value
const asset2_currency = asset2CurrencyField.value
const asset2_issuer = asset2IssuerField.value
const valueLPT = standbyLPField.value
// Look up AMM info
let asset1_info = null
let asset2_info = null
if ( asset1_currency == 'XRP' ) {
asset1_info = {
"currency": "XRP"
}
} else {
asset1_info = {
"currency": asset1_currency,
"issuer": asset1_issuer
}
}
if ( asset2_currency == 'XRP' ) {
asset2_info = {
"currency": "XRP"
}
} else {
asset2_info = {
"currency": asset2_currency,
"issuer": asset2_issuer
}
}
const amm_info = (await client.request({
"command": "amm_info",
"asset": asset1_info,
"asset2": asset2_info
}))
// Save relevant AMM info for calculations
const lpt = amm_info.result.amm.lp_token
results += '\n\nBidding on auction slot ...'
standbyResultField.value = results
const bid_result = await client.submitAndWait({
"TransactionType": "AMMBid",
"Account": standby_wallet.address,
"Asset": asset1_info,
"Asset2": asset2_info,
"BidMax": {
"currency": lpt.currency,
"issuer": lpt.issuer,
"value": valueLPT
},
"BidMin": {
"currency": lpt.currency,
"issuer": lpt.issuer,
"value": valueLPT
} // So rounding doesn't leave dust amounts of LPT
}, {autofill: true, wallet: standby_wallet})
if (bid_result.result.meta.TransactionResult == "tesSUCCESS") {
results += `\n\nTransaction succeeded.`
checkAMM()
} else {
results += `\n\nError sending transaction: ${JSON.stringify(bid_result.result.meta.TransactionResult, null, 2)}`
}
} catch (error) {
results += `\n\n${error.message}`
}
standbyResultField.value = results
client.disconnect()
}
// Swap tokens with AMM
async function swapTokens() {
let net = getNet()
const client = new xrpl.Client(net)
results = `\n\nConnecting to ${getNet()} ...`
standbyResultField.value = results
await client.connect()
results += '\n\nConnected.'
standbyResultField.value = results
try {
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const takerPaysCurrency = standbyTakerPaysCurrencyField.value
const takerPaysIssuer = standbyTakerPaysIssuerField.value
const takerPaysAmount = standbyTakerPaysAmountField.value
const takerGetsCurrency = standbyTakerGetsCurrencyField.value
const takerGetsIssuer = standbyTakerGetsIssuerField.value
const takerGetsAmount = standbyTakerGetsAmountField.value
let takerPays = null
let takerGets = null
if ( takerPaysCurrency == 'XRP' ) {
takerPays = xrpl.xrpToDrops(takerPaysAmount)
} else {
takerPays = {
"currency": takerPaysCurrency,
"issuer": takerPaysIssuer,
"value": takerPaysAmount
}
}
if ( takerGetsCurrency == 'XRP' ) {
takerGets = xrpl.xrpToDrops(takerGetsAmount)
} else {
takerGets = {
"currency": takerGetsCurrency,
"issuer": takerGetsIssuer,
"value": takerGetsAmount
}
}
results += '\n\nSwapping tokens ...'
standbyResultField.value = results
const offer_result = await client.submitAndWait({
"TransactionType": "OfferCreate",
"Account": standby_wallet.address,
"TakerPays": takerPays,
"TakerGets": takerGets
}, {autofill: true, wallet: standby_wallet})
if (offer_result.result.meta.TransactionResult == "tesSUCCESS") {
results += `\n\nTransaction succeeded.`
checkAMM()
} else {
results += `\n\nError sending transaction: ${JSON.stringify(offer_result.result.meta.TransactionResult, null, 2)}`
}
} catch (error) {
results += `\n\n${error.message}`
}
standbyResultField.value = results
client.disconnect()
}

View File

@@ -1,154 +0,0 @@
/* Convert a trading fee to a value that can be multiplied
* by a total to "subtract" the fee from the total.
* @param tFee int {0, 1000}
* such that 1 = 1/100,000 and 1000 = 1% fee
* @returns BigNumber (1 - fee) as a decimal
*/
function feeMult(tFee) {
return BigNumber(1).minus( feeDecimal(tFee) )
}
/* Same as feeMult, but with half the trading fee. Single-asset deposits and
* withdrawals use this because half of the deposit is treated as being
* "swapped" for the other asset in the AMM's pool.
* @param tFee int {0, 1000}
* such that 1 = 1/100,000 and 1000 = 1% fee
* @returns BigNumber (1 - (fee/2)) as a decimal
*/
function feeMultHalf(tFee) {
return BigNumber(1).minus( feeDecimal(tFee).dividedBy(2) )
}
/* Convert a trading fee to a decimal BigNumber value,
* for example 1000 becomes 0.01
* @param tFee int {0, 1000}
* such that 1 = 1/100,000 and 1000 = 1% fee
* @returns BigNumber(fee) as a decimal
*/
function feeDecimal(tFee) {
const AUCTION_SLOT_FEE_SCALE_FACTOR = 100000
return BigNumber(tFee).dividedBy(AUCTION_SLOT_FEE_SCALE_FACTOR)
}
/* Implement the AMM SwapOut formula, as defined in XLS-30 section 2.4 AMM
* Swap, formula 10. The asset weights WA/WB are currently always 1/1 so
* they're canceled out.
* C++ source: https://github.com/XRPLF/rippled/blob/2d1854f354ff8bb2b5671fd51252c5acd837c433/src/ripple/app/misc/AMMHelpers.h#L253-L258
* @param asset_out_bn BigNumber - The target amount to receive from the AMM.
* @param pool_in_bn BigNumber - The amount of the input asset in the AMM's
* pool before the swap.
* @param pool_out_bn BigNumber - The amount of the output asset in the AMM's
* pool before the swap.
* @param trading_fee int - The trading fee as an integer {0, 1000} where 1000
* represents a 1% fee.
* @returns BigNumber - The amount of the input asset that must be swapped in
* to receive the target output amount. Unrounded, because
* the number of decimals depends on if this is drops of
* XRP or a decimal amount of a token; since this is a
* theoretical input to the pool, it should be rounded
* up (ceiling) to preserve the pool's constant product.
*/
function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
return ( ( pool_in_bn.multipliedBy(pool_out_bn) ).dividedBy(
pool_out_bn.minus(asset_out_bn)
).minus(pool_in_bn)
).dividedBy(feeMult(trading_fee))
}
/* Compute the quadratic formula. Helper function for ammAssetIn.
* Params and return value are BigNumber instances.
*/
function solveQuadraticEq(a,b,c) {
const b2minus4ac = b.multipliedBy(b).minus(
a.multipliedBy(c).multipliedBy(4)
)
return ( b.negated().plus(b2minus4ac.sqrt()) ).dividedBy(a.multipliedBy(2))
}
/* Implement the AMM single-asset deposit formula to calculate how much to
* put in so that you receive a specific number of LP Tokens back.
* C++ source: https://github.com/XRPLF/rippled/blob/2d1854f354ff8bb2b5671fd51252c5acd837c433/src/ripple/app/misc/impl/AMMHelpers.cpp#L55-L83
* @param pool_in string - Quantity of input asset the pool already has
* @param lpt_balance string - Quantity of LP Tokens already issued by the AMM
* @param desired_lpt string - Quantity of new LP Tokens you want to receive
* @param trading_fee int - The trading fee as an integer {0,1000} where 1000
* represents a 1% fee.
*/
function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
// convert inputs to BigNumber
const lpTokens = BigNumber(desired_lpt)
const lptAMMBalance = BigNumber(lpt_balance)
const asset1Balance = BigNumber(pool_in)
const f1 = feeMult(trading_fee)
const f2 = feeMultHalf(trading_fee).dividedBy(f1)
const t1 = lpTokens.dividedBy(lptAMMBalance)
const t2 = t1.plus(1)
const d = f2.minus( t1.dividedBy(t2) )
const a = BigNumber(1).dividedBy( t2.multipliedBy(t2))
const b = BigNumber(2).multipliedBy(d).dividedBy(t2).minus(
BigNumber(1).dividedBy(f1)
)
const c = d.multipliedBy(d).minus( f2.multipliedBy(f2) )
return asset1Balance.multipliedBy(solveQuadraticEq(a,b,c))
}
/* Calculate how much to deposit, in terms of LP Tokens out, to be able to win
* the auction slot. This is based on the slot pricing algorithm defined in
* XLS-30 section 4.1.1, but factors in the increase in the minimum bid as a
* result of having new LP Tokens issued to you from your deposit.
*/
function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
const tfee_decimal = feeDecimal(trading_fee)
const lptokens = BigNumber(lpt_balance)
const b = BigNumber(old_bid)
let outbidAmount = BigNumber(0) // This is the case if time_interval >= 20
if (time_interval == 0) {
outbidAmount = b.multipliedBy("1.05")
} else if (time_interval <= 19) {
const t60 = BigNumber(time_interval).multipliedBy("0.05").exponentiatedBy(60)
outbidAmount = b.multipliedBy("1.05").multipliedBy(BigNumber(1).minus(t60))
}
const new_bid = lptokens.plus(outbidAmount).dividedBy(
BigNumber(25).dividedBy(tfee_decimal).minus(1)
).plus(outbidAmount)
// Significant digits for the deposit are limited by total LPTokens issued
// so we calculate lptokens + deposit - lptokens to determine where the
// rounding occurs. We use ceiling/floor to make sure the amount we receive
// after rounding is still enough to win the auction slot.
const rounded_bid = new_bid.plus(lptokens).precision(15, BigNumber.CEILING
).minus(lptokens).precision(15, BigNumber.FLOOR)
return rounded_bid
}
/* Calculate the necessary bid to win the AMM Auction slot, per the pricing
* algorithm defined in XLS-30 section 4.1.1, if you already hold LP Tokens.
*
* NOT USED in the Auction Slot tutorial, which assumes the user does not hold
* any LP Tokens.
*
* @returns BigNumber - the minimum amount of LP tokens to win the auction slot
*/
function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
const tfee_decimal = feeDecimal(trading_fee)
const lptokens = BigNumber(lpt_balance)
const min_bid = lptokens.multipliedBy(tfee_decimal).dividedBy(25)
const b = BigNumber(old_bid)
let new_bid = min_bid
if (time_interval == 0) {
new_bid = b.multipliedBy("1.05").plus(min_bid)
} else if (time_interval <= 19) {
const t60 = BigNumber(time_interval).multipliedBy("0.05"
).exponentiatedBy(60)
new_bid = b.multipliedBy("1.05").multipliedBy(
BigNumber(1).minus(t60)
).plus(min_bid)
}
const rounded_bid = new_bid.plus(lptokens).precision(15, BigNumber.CEILING
).minus(lptokens).precision(15, BigNumber.FLOOR)
return rounded_bid
}

View File

@@ -1,270 +0,0 @@
/***********************************
*********** Create Offer **********
**********************************/
async function createOffer() {
let takerGets = ''
let takerPays = ''
let net = getNet()
let results = 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected. Getting wallets.\n"
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
results += standbyNameField.value + " account address: " + standby_wallet.address + "\n"
standbyResultField.value = results
if (standbyTakerGetsCurrencyField.value == 'XRP') {
takerGets = standbyTakerGetsValueField.value
} else {
takerGetsString = '{"currency": "' + standbyTakerGetsCurrencyField.value +'",\n' +
'"issuer": "' + standbyTakerGetsIssuerField.value + '",\n' +
'"value": "' + standbyTakerGetsValueField.value + '"}'
takerGets = JSON.parse(takerGetsString)
}
if (standbyTakerPaysCurrencyField.value == 'XRP') {
takerPays = standbyTakerPaysValueField.value
} else {
takerPaysString = '{"currency": "' + standbyTakerPaysCurrencyField.value + '",\n' +
'"issuer": "' + standbyTakerPaysIssuerField.value + '",\n' +
'"value": "' + standbyTakerPaysValueField.value + '"}'
takerPays = JSON.parse(takerPaysString)
}
// -------------------------------------------------------- Prepare transaction
const prepared = await client.autofill({
"TransactionType": "OfferCreate",
"Account": standby_wallet.address,
"TakerGets": takerGets,
"TakerPays": takerPays
})
// ------------------------------------------------- Sign prepared instructions
const signed = standby_wallet.sign(prepared)
results += "\nSubmitting transaction...."
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
standbyResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
getOffers()
client.disconnect()
} // End of createOffer()
/***********************************
************ Get Offers ***********
**********************************/
async function getOffers() {
let net = getNet()
let results = 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected.\n"
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
results += standbyNameField.value + " account: " + standby_wallet.address
// -------------------------------------------------------- Prepare request
results += '\n\n*** Offers ***\n'
let offers
try {
const offers = await client.request({
method: "account_offers",
account: standby_wallet.address,
ledger_index: "validated"
})
results += JSON.stringify(offers,null,2)
} catch (err) {
results += err
}
standbyResultField.value = results
client.disconnect()
}// End of getOffers()
/***********************************
*********** Cancel Offer **********
**********************************/
async function cancelOffer() {
let results = "Connecting to the selected ledger.\n"
standbyResultField.value = results
let net = getNet()
results += 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected.\n"
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
results += "standby_wallet.address: = " + standby_wallet.address
standbyResultField.value = results
// -------------------------------------------------------- Prepare transaction
/* OfferSequence is the Seq value when you getOffers. */
const prepared = await client.autofill({
"TransactionType": "OfferCancel",
"Account": standby_wallet.address,
"OfferSequence": parseInt(standbyOfferSequenceField.value)
})
// ------------------------------------------------- Sign prepared instructions
const signed = standby_wallet.sign(prepared)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: \n" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
standbyResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
client.disconnect()
} // End of cancelOffer()
/*********************************************
************* Reciprocal Functions **********
********************************************/
/***********************************
********* OP Create Offer *********
**********************************/
async function oPcreateOffer() {
let takerGets = ''
let takerPays = ''
operationalResultField.value = ''
let net = getNet()
let results = 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected. Getting wallets.\n"
operationalResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
results += operationalNameField.value + " account address: " + operational_wallet.address + "\n"
operationalResultField.value = results
if (operationalTakerGetsCurrencyField.value == 'XRP') {
takerGets = operationalTakerGetsValueField.value
} else {
takerGetsString = '{"currency": "' + operationalTakerGetsCurrencyField.value +'",\n' +
'"issuer": "' + operationalTakerGetsIssuerField.value + '",\n' +
'"value": "' + operationalTakerGetsValueField.value + '"}'
takerGets = JSON.parse(takerGetsString)
}
if (operationalTakerPaysCurrencyField.value == 'XRP') {
takerPays = operationalTakerPaysValueField.value
} else {
takerPaysString = '{"currency": "' + operationalTakerPaysCurrencyField.value + '",\n' +
'"issuer": "' + operationalTakerPaysIssuerField.value + '",\n' +
'"value": "' + operationalTakerPaysValueField.value + '"}'
takerPays = JSON.parse(takerPaysString)
}
// -------------------------------------------------------- Prepare transaction
const prepared = await client.autofill({
"TransactionType": "OfferCreate",
"Account": operational_wallet.address,
"TakerGets": takerGets,
"TakerPays": takerPays
})
// ------------------------------------------------- Sign prepared instructions
const signed = operational_wallet.sign(prepared)
results += "\nSubmitting transaction...."
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
operationalResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
getOffers()
client.disconnect()
} // End of oPcreateOffer()
/***********************************
********** OP Get Offers ***********
***********************************/
async function oPgetOffers() {
let results = "Connecting to the selected ledger.\n"
operationalResultField.value = results
let net = getNet()
results = 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected.\n"
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
results += operationalNameField.value + " account: " + operational_wallet.address
operationalResultField.value = results
// -------------------------------------------------------- Prepare request
results += '\n\n*** Offers ***\n'
let offers
try {
const offers = await client.request({
method: "account_offers",
account: operational_wallet.address,
ledger_index: "validated"
})
results += JSON.stringify(offers,null,2)
} catch (err) {
results += err
}
operationalResultField.value = results
client.disconnect()
}// End of oPgetOffers()
/************************************
********** Op Cancel Offer *********
***********************************/
async function oPcancelOffer() {
let net = getNet()
let results = 'Connecting to ' + net + '....\n'
const client = new xrpl.Client(net)
await client.connect()
results += "Connected.\n"
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
results += "wallet.address: = " + operational_wallet.address
operationalResultField.value = results
// -------------------------------------------------------- Prepare transaction
/* OfferSequence is the Seq value when you getOffers. */
const prepared = await client.autofill({
"TransactionType": "OfferCancel",
"Account": operational_wallet.address,
"OfferSequence": parseInt(operationalOfferSequenceField.value)
})
// ------------------------------------------------- Sign prepared instructions
const signed = operational_wallet.sign(prepared)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: \n" + tx.result + "\n" +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
operationalResultField.value = results
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // End of oPcancelOffer()

View File

@@ -1,240 +0,0 @@
// *******************************************************
// ************* Add Seconds to Current Date *************
// *******************************************************
function addSeconds(numOfSeconds, date = new Date()) {
date.setSeconds(date.getSeconds() + numOfSeconds);
date = Math.floor(date / 1000)
date = date - 946684800
return date;
}
// *******************************************************
// ***************** Create Time Escrow ******************
// *******************************************************
async function createTimeEscrow() {
//-------------------------------------------- Prepare Finish and Cancel Dates
let escrow_finish_date = new Date()
let escrow_cancel_date = new Date()
escrow_finish_date = addSeconds(parseInt(standbyEscrowFinishDateField.value))
escrow_cancel_date = addSeconds(parseInt(standbyEscrowCancelDateField.value))
//------------------------------------------------------Connect to the Ledger
results = "Connecting to the selected ledger.\n"
standbyResultField.value = results
let net = getNet()
results = "Connecting to " + net + "....\n"
const client = new xrpl.Client(net)
await client.connect()
results += "Connected. Creating time-based escrow.\n"
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const sendAmount = standbyAmountField.value
results += "\nstandby_wallet.address: = " + standby_wallet.address
standbyResultField.value = results
// ------------------------------------------------------- Prepare transaction
const escrowTx = await client.autofill({
"TransactionType": "EscrowCreate",
"Account": standby_wallet.address,
"Amount": xrpl.xrpToDrops(sendAmount),
"Destination": standbyDestinationField.value,
"FinishAfter": escrow_finish_date,
"CancelAfter": escrow_cancel_date
})
// ------------------------------------------------ Sign prepared instructions
const signed = standby_wallet.sign(escrowTx)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nSequence Number (Save!): " + JSON.stringify(tx.result.Sequence)
results += "\n\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
standbyResultField.value = results
// ----------------------------------------------Disconnect from the XRP Ledger
client.disconnect()
} // End of createTimeEscrow()
// *******************************************************
// ***************** Finish Time Escrow ******************
// *******************************************************
async function finishEscrow() {
results = "Connecting to the selected ledger.\n"
operationalResultField.value = results
let net = getNet()
results = 'Connecting to ' + getNet() + '....'
const client = new xrpl.Client(net)
await client.connect()
results += "\nConnected. Finishing escrow.\n"
operationalResultField.value = results
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const sendAmount = operationalAmountField.value
results += "\noperational_wallet.address: = " + operational_wallet.address
operationalResultField.value = results
// ------------------------------------------------------- Prepare transaction
// Note that the destination is hard coded.
const prepared = await client.autofill({
"TransactionType": "EscrowFinish",
"Account": operationalAccountField.value,
"Owner": standbyAccountField.value,
"OfferSequence": parseInt(operationalEscrowSequenceField.value)
})
// ------------------------------------------------ Sign prepared instructions
const signed = operational_wallet.sign(prepared)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
operationalResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // End of finishEscrow()
// *******************************************************
// ************** Get Standby Escrows ********************
// *******************************************************
async function getStandbyEscrows() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
results= "\nGetting standby account escrows...\n"
const escrow_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": standbyAccountField.value,
"ledger_index": "validated",
"type": "escrow"
})
results += JSON.stringify(escrow_objects.result, null, 2)
standbyResultField.value = results
client.disconnect()
} // End of getStandbyEscrows()
// *******************************************************
// ***************** Get Op Escrows **********************
// *******************************************************
async function getOperationalEscrows() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
results= "\nGetting operational account escrows...\n"
const escrow_objects = await client.request({
"id": 5,
"command": "account_objects",
"account": operationalAccountField.value,
"ledger_index": "validated",
"type": "escrow"
})
results += JSON.stringify(escrow_objects.result, null, 2)
operationalResultField.value = results
client.disconnect()
} // End of getOperationalEscrows()
// *******************************************************
// ************** Get Transaction Info *******************
// *******************************************************
async function getTransaction() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
operationalResultField.value = results
await client.connect()
results += '\nConnected.'
operationalResultField.value = results
results= "\nGetting transaction information...\n"
const tx_info = await client.request({
"id": 1,
"command": "tx",
"transaction": operationalTransactionField.value,
})
results += JSON.stringify(tx_info.result, null, 2)
operationalResultField.value = results
client.disconnect()
} // End of getTransaction()
// *******************************************************
// ****************** Cancel Escrow **********************
// *******************************************************
async function cancelEscrow() {
let net = getNet()
const client = new xrpl.Client(net)
results = 'Connecting to ' + getNet() + '....'
standbyResultField.value = results
await client.connect()
results += '\nConnected.'
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
// ------------------------------------------------------- Prepare transaction
const prepared = await client.autofill({
"TransactionType": "EscrowCancel",
"Account": standby_wallet.address,
"Owner": standbyAccountField.value,
"OfferSequence": parseInt(standbyEscrowSequenceNumberField.value)
})
// ------------------------------------------------ Sign prepared instructions
const signed = standby_wallet.sign(prepared)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
standbyResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
}

View File

@@ -1,101 +0,0 @@
// *******************************************************
// ************* Create Conditional Escrow ***************
// *******************************************************
async function createConditionalEscrow() {
//------------------------------------------------------Connect to the Ledger
results = "Connecting to the selected ledger.\n"
standbyResultField.value = results
let net = getNet()
results = "Connecting to " + net + "....\n"
const client = new xrpl.Client(net)
await client.connect()
results += "Connected. Creating conditional escrow.\n"
standbyResultField.value = results
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const sendAmount = standbyAmountField.value
results += "\nstandby_wallet.address: = " + standby_wallet.address
standbyResultField.value = results
let escrow_cancel_date = new Date()
escrow_cancel_date = addSeconds(parseInt(standbyEscrowCancelDateField.value))
// ------------------------------------------------------- Prepare transaction
const escrowTx = await client.autofill({
"TransactionType": "EscrowCreate",
"Account": standby_wallet.address,
"Amount": xrpl.xrpToDrops(sendAmount),
"Destination": standbyDestinationField.value,
"CancelAfter": escrow_cancel_date,
"Condition": standbyEscrowConditionField.value
})
// ------------------------------------------------ Sign prepared instructions
const signed = standby_wallet.sign(escrowTx)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nSequence Number (Save!): " + JSON.stringify(tx.result.Sequence)
results += "\n\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
standbyResultField.value = results
// ----------------------------------------------Disconnect from the XRP Ledger
client.disconnect()
} // End of createTimeEscrow()
// *******************************************************
// ************** Finish Conditional Escrow **************
// *******************************************************
async function finishConditionalEscrow() {
results = "Connecting to the selected ledger.\n"
operationalResultField.value = results
let net = getNet()
results += 'Connecting to ' + getNet() + '....'
const client = new xrpl.Client(net)
await client.connect()
results += "\nConnected. Finishing escrow.\n"
operationalResultField.value = results
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
const sendAmount = operationalAmountField.value
results += "\noperational_wallet.address: = " + operational_wallet.address
operationalResultField.value = results
// ------------------------------------------------------- Prepare transaction
const prepared = await client.autofill({
"TransactionType": "EscrowFinish",
"Account": operationalAccountField.value,
"Owner": standbyAccountField.value,
"OfferSequence": parseInt(operationalEscrowSequenceField.value),
"Condition": standbyEscrowConditionField.value,
"Fulfillment": operationalFulfillmentField.value
})
// ------------------------------------------------ Sign prepared instructions
const signed = operational_wallet.sign(prepared)
// -------------------------------------------------------- Submit signed blob
const tx = await client.submitAndWait(signed.tx_blob)
results += "\nBalance changes: " +
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
operationalResultField.value = results
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
client.disconnect()
} // End of finishEscrow()``

View File

@@ -1,11 +0,0 @@
from os import urandom
from cryptoconditions import PreimageSha256
secret = urandom(32)
fulfillment = PreimageSha256(preimage=secret)
print("Condition", fulfillment.condition_binary.hex().upper())
# Keep secret until you want to finish the escrow
print("Fulfillment", fulfillment.serialize_binary().hex().upper())

View File

@@ -1,318 +0,0 @@
import tkinter as tk
import xrpl
import json
from mod1 import get_account, get_account_info, send_xrp
from mod2 import get_balance
from mod10 import send_check, cash_check, cancel_check, get_checks
#############################################
## Handlers #################################
#############################################
## Mod 10 Handlers
def standby_send_check():
results=send_check(
ent_standby_seed.get(),
ent_standby_amount.get(),
ent_standby_destination.get(),
ent_standby_currency.get(),
ent_standby_issuer.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def standby_cash_check():
results=cash_check(
ent_standby_seed.get(),
ent_standby_amount.get(),
ent_standby_check_id.get(),
ent_standby_currency.get(),
ent_standby_issuer.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def standby_cancel_check():
results=cancel_check(
ent_standby_seed.get(),
ent_standby_check_id.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def standby_get_checks():
results=get_checks(
ent_standby_account.get(),
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def standby_get_balance():
results=get_balance(
ent_standby_seed.get(),
ent_operational_seed.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def operational_send_check():
results=send_check(
ent_operational_seed.get(),
ent_operational_amount.get(),
ent_operational_destination.get(),
ent_operational_currency.get(),
ent_operational_issuer.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def operational_cash_check():
results=cash_check(
ent_operational_seed.get(),
ent_operational_amount.get(),
ent_operational_check_id.get(),
ent_operational_currency.get(),
ent_operational_issuer.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def operational_cancel_check():
results=cancel_check(
ent_operational_seed.get(),
ent_operational_check_id.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def operational_get_checks():
results=get_checks(
ent_operational_account.get(),
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def operational_get_balance():
results=get_balance(
ent_operational_seed.get(),
ent_standby_seed.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
## Mod 8 Handlers
def operational_get_transaction():
results=get_transaction(ent_operational_account.get(),
ent_operational_look_up.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
## Mod 1 Handlers
def get_standby_account():
new_wallet=get_account(ent_standby_seed.get())
ent_standby_account.delete(0, tk.END)
ent_standby_seed.delete(0, tk.END)
ent_standby_account.insert(0, new_wallet.classic_address)
ent_standby_seed.insert(0, new_wallet.seed)
def get_standby_account_info():
accountInfo=get_account_info(ent_standby_account.get())
ent_standby_balance.delete(0, tk.END)
ent_standby_balance.insert(0,accountInfo['Balance'])
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
def standby_send_xrp():
response=send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
ent_standby_destination.get())
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
get_standby_account_info()
get_operational_account_info()
def get_operational_account():
new_wallet=get_account(ent_operational_seed.get())
ent_operational_account.delete(0, tk.END)
ent_operational_account.insert(0, new_wallet.classic_address)
ent_operational_seed.delete(0, tk.END)
ent_operational_seed.insert(0, new_wallet.seed)
def get_operational_account_info():
accountInfo=get_account_info(ent_operational_account.get())
ent_operational_balance.delete(0, tk.END)
ent_operational_balance.insert(0,accountInfo['Balance'])
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
def operational_send_xrp():
response=send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
ent_operational_destination.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
get_standby_account_info()
get_operational_account_info()
# Create a new window with the title "Conditional Escrow Example"
window=tk.Tk()
window.title("Check Example")
# Form frame
frm_form=tk.Frame(relief=tk.SUNKEN, borderwidth=3)
frm_form.pack()
# Create the Label and Entry widgets for "Standby Account"
lbl_standy_seed=tk.Label(master=frm_form, text="Standby Seed")
ent_standby_seed=tk.Entry(master=frm_form, width=50)
lbl_standby_account=tk.Label(master=frm_form, text="Standby Account")
ent_standby_account=tk.Entry(master=frm_form, width=50)
lbl_standby_balance=tk.Label(master=frm_form, text="XRP Balance")
ent_standby_balance=tk.Entry(master=frm_form, width=50)
lbl_standy_amount=tk.Label(master=frm_form, text="Amount")
ent_standby_amount=tk.Entry(master=frm_form, width=50)
lbl_standby_destination=tk.Label(master=frm_form, text="Destination")
ent_standby_destination=tk.Entry(master=frm_form, width=50)
lbl_standby_issuer=tk.Label(master=frm_form, text="Issuer")
ent_standby_issuer=tk.Entry(master=frm_form, width=50)
lbl_standby_check_id=tk.Label(master=frm_form, text="Check ID")
ent_standby_check_id=tk.Entry(master=frm_form, width=50)
lbl_standby_currency=tk.Label(master=frm_form, text="Currency")
ent_standby_currency=tk.Entry(master=frm_form, width=50)
lbl_standby_results=tk.Label(master=frm_form, text="Results")
text_standby_results=tk.Text(master=frm_form, height=20, width=65)
# Place fields in a grid.
lbl_standy_seed.grid(row=0, column=0, sticky="e")
ent_standby_seed.grid(row=0, column=1)
lbl_standby_account.grid(row=2, column=0, sticky="e")
ent_standby_account.grid(row=2, column=1)
lbl_standby_balance.grid(row=3, column=0, sticky="e")
ent_standby_balance.grid(row=3, column=1)
lbl_standy_amount.grid(row=4, column=0, sticky="e")
ent_standby_amount.grid(row=4, column=1)
lbl_standby_destination.grid(row=5, column=0, sticky="e")
ent_standby_destination.grid(row=5, column=1)
lbl_standby_issuer.grid(row=6, column=0, sticky="e")
ent_standby_issuer.grid(row=6, column=1)
lbl_standby_check_id.grid(row=7, column=0, sticky="e")
ent_standby_check_id.grid(row=7, column=1)
lbl_standby_currency.grid(row=8, column=0, sticky="e")
ent_standby_currency.grid(row=8, column=1)
lbl_standby_results.grid(row=9, column=0, sticky="ne")
text_standby_results.grid(row=9, column=1, sticky="nw")
###############################################
## Operational Account ########################
###############################################
# Create the Label and Entry widgets for "Operational Account"
lbl_operational_seed=tk.Label(master=frm_form, text="Operational Seed")
ent_operational_seed=tk.Entry(master=frm_form, width=50)
lbl_operational_account=tk.Label(master=frm_form, text="Operational Account")
ent_operational_account=tk.Entry(master=frm_form, width=50)
lbl_operational_balance=tk.Label(master=frm_form, text="XRP Balance")
ent_operational_balance=tk.Entry(master=frm_form, width=50)
lbl_operational_amount=tk.Label(master=frm_form, text="Amount")
ent_operational_amount=tk.Entry(master=frm_form, width=50)
lbl_operational_destination=tk.Label(master=frm_form, text="Destination")
ent_operational_destination=tk.Entry(master=frm_form, width=50)
lbl_operational_issuer=tk.Label(master=frm_form, text="Issuer")
ent_operational_issuer=tk.Entry(master=frm_form, width=50)
lbl_operational_check_id=tk.Label(master=frm_form, text="Check ID")
ent_operational_check_id=tk.Entry(master=frm_form, width=50)
lbl_operational_currency=tk.Label(master=frm_form, text="Currency")
ent_operational_currency=tk.Entry(master=frm_form, width=50)
lbl_operational_results=tk.Label(master=frm_form,text='Results')
text_operational_results=tk.Text(master=frm_form, height=20, width=65)
#Place the widgets in a grid
lbl_operational_seed.grid(row=0, column=4, sticky="e")
ent_operational_seed.grid(row=0, column=5, sticky="w")
lbl_operational_account.grid(row=2,column=4, sticky="e")
ent_operational_account.grid(row=2,column=5, sticky="w")
lbl_operational_balance.grid(row=3, column=4, sticky="e")
ent_operational_balance.grid(row=3, column=5, sticky="w")
lbl_operational_amount.grid(row=4, column=4, sticky="e")
ent_operational_amount.grid(row=4, column=5, sticky="w")
lbl_operational_destination.grid(row=5, column=4, sticky="e")
ent_operational_destination.grid(row=5, column=5, sticky="w")
lbl_operational_issuer.grid(row=6, column=4, sticky="e")
ent_operational_issuer.grid(row=6, column=5, sticky="w")
lbl_operational_check_id.grid(row=7, column=4, sticky="e")
ent_operational_check_id.grid(row=7, column=5, sticky="w")
lbl_operational_currency.grid(row=8, column=4, sticky="e")
ent_operational_currency.grid(row=8, column=5)
lbl_operational_results.grid(row=9, column=4, sticky="ne")
text_operational_results.grid(row=9, column=5, sticky="nw")
#############################################
## Buttons ##################################
#############################################
# Create the Get Standby Account Buttons
btn_get_standby_account=tk.Button(master=frm_form, text="Get Standby Account",
command=get_standby_account)
btn_get_standby_account.grid(row=0, column=2, sticky="nsew")
btn_get_standby_account_info=tk.Button(master=frm_form,
text="Get Standby Account Info",
command=get_standby_account_info)
btn_get_standby_account_info.grid(row=1, column=2, sticky="nsew")
btn_standby_send_xrp=tk.Button(master=frm_form, text="Send XRP >",
command=standby_send_xrp)
btn_standby_send_xrp.grid(row=2, column=2, sticky="nsew")
btn_standby_send_check=tk.Button(master=frm_form, text="Send Check",
command=standby_send_check)
btn_standby_send_check.grid(row=4, column=2, sticky="nsew")
btn_standby_get_checks=tk.Button(master=frm_form, text="Get Checks",
command=standby_get_checks)
btn_standby_get_checks.grid(row=5, column=2, sticky="nsew")
btn_standby_cash_check=tk.Button(master=frm_form, text="Cash Check",
command=standby_cash_check)
btn_standby_cash_check.grid(row=6, column=2, sticky="nsew")
btn_standby_cancel_check=tk.Button(master=frm_form, text="Cancel Check",
command=standby_cancel_check)
btn_standby_cancel_check.grid(row=7, column=2, sticky="nsew")
btn_standby_get_balances=tk.Button(master=frm_form, text="Get Balances",
command=standby_get_balance)
btn_standby_get_balances.grid(row=8, column=2, sticky="nsew")
# Create the Operational Account Buttons
btn_get_operational_account=tk.Button(master=frm_form,
text="Get Operational Account",
command=get_operational_account)
btn_get_operational_account.grid(row=0, column=3, sticky="nsew")
btn_get_op_account_info=tk.Button(master=frm_form, text="Get Op Account Info",
command=get_operational_account_info)
btn_get_op_account_info.grid(row=1, column=3, sticky="nsew")
btn_op_send_xrp=tk.Button(master=frm_form, text="< Send XRP",
command=operational_send_xrp)
btn_op_send_xrp.grid(row=2, column=3, sticky="nsew")
btn_op_send_check=tk.Button(master=frm_form, text="Send Check",
command=operational_send_check)
btn_op_send_check.grid(row=4, column=3, sticky="nsew")
btn_op_get_checks=tk.Button(master=frm_form, text="Get Checks",
command=operational_get_checks)
btn_op_get_checks.grid(row=5, column=3, sticky="nsew")
btn_op_cash_check=tk.Button(master=frm_form, text="Cash Check",
command=operational_cash_check)
btn_op_cash_check.grid(row=6, column=3, sticky="nsew")
btn_op_cancel_check=tk.Button(master=frm_form, text="Cancel Check",
command=operational_cancel_check)
btn_op_cancel_check.grid(row=7, column=3, sticky="nsew")
btn_op_get_balances=tk.Button(master=frm_form, text="Get Balances",
command=operational_get_balance)
btn_op_get_balances.grid(row=8, column=3, sticky="nsew")
# Start the application
window.mainloop()

View File

@@ -1,251 +0,0 @@
import tkinter as tk
import xrpl
import json
from mod1 import get_account, get_account_info, send_xrp
from mod8 import create_time_escrow, finish_time_escrow, get_escrows, cancel_time_escrow, get_transaction
#############################################
## Handlers #################################
#############################################
## Mod 8 Handlers
def standby_create_time_escrow():
results = create_time_escrow(
ent_standby_seed.get(),
ent_standby_amount.get(),
ent_standby_destination.get(),
ent_standby_escrow_finish.get(),
ent_standby_escrow_cancel.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def operational_finish_time_escrow():
results = finish_time_escrow(
ent_operational_seed.get(),
ent_operational_escrow_owner.get(),
ent_operational_sequence_number.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def operational_get_escrows():
results = get_escrows(ent_operational_account.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def standby_cancel_time_escrow():
results = cancel_time_escrow(
ent_standby_seed.get(),
ent_standby_escrow_owner.get(),
ent_standby_escrow_sequence_number.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def operational_get_transaction():
results = get_transaction(ent_operational_account.get(),
ent_operational_look_up.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
## Mod 1 Handlers
def get_standby_account():
new_wallet = get_account(ent_standby_seed.get())
ent_standby_account.delete(0, tk.END)
ent_standby_seed.delete(0, tk.END)
ent_standby_account.insert(0, new_wallet.classic_address)
ent_standby_seed.insert(0, new_wallet.seed)
def get_standby_account_info():
accountInfo = get_account_info(ent_standby_account.get())
ent_standby_balance.delete(0, tk.END)
ent_standby_balance.insert(0,accountInfo['Balance'])
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
def standby_send_xrp():
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
ent_standby_destination.get())
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
get_standby_account_info()
get_operational_account_info()
def get_operational_account():
new_wallet = get_account(ent_operational_seed.get())
ent_operational_account.delete(0, tk.END)
ent_operational_account.insert(0, new_wallet.classic_address)
ent_operational_seed.delete(0, tk.END)
ent_operational_seed.insert(0, new_wallet.seed)
def get_operational_account_info():
accountInfo = get_account_info(ent_operational_account.get())
ent_operational_balance.delete(0, tk.END)
ent_operational_balance.insert(0,accountInfo['Balance'])
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
def operational_send_xrp():
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
ent_operational_destination.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
get_standby_account_info()
get_operational_account_info()
# Create a new window with the title "Time-based Escrow Example"
window = tk.Tk()
window.title("Time-based Escrow Example")
# Form frame
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
frm_form.pack()
# Create the Label and Entry widgets for "Standby Account"
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
ent_standby_seed = tk.Entry(master=frm_form, width=50)
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
ent_standby_account = tk.Entry(master=frm_form, width=50)
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
ent_standby_amount = tk.Entry(master=frm_form, width=50)
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
ent_standby_destination = tk.Entry(master=frm_form, width=50)
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
ent_standby_balance = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_finish = tk.Label(master=frm_form, text="Escrow Finish (seconds)")
ent_standby_escrow_finish = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
lbl_standby_results = tk.Label(master=frm_form, text="Results")
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
# Place fields in a grid.
lbl_standy_seed.grid(row=0, column=0, sticky="e")
ent_standby_seed.grid(row=0, column=1)
lbl_standby_account.grid(row=2, column=0, sticky="e")
ent_standby_account.grid(row=2, column=1)
lbl_standy_amount.grid(row=3, column=0, sticky="e")
ent_standby_amount.grid(row=3, column=1)
lbl_standby_destination.grid(row=4, column=0, sticky="e")
ent_standby_destination.grid(row=4, column=1)
lbl_standby_balance.grid(row=5, column=0, sticky="e")
ent_standby_balance.grid(row=5, column=1)
lbl_standby_escrow_finish.grid(row=6, column=0, sticky="e")
ent_standby_escrow_finish.grid(row=6, column=1)
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
ent_standby_escrow_cancel.grid(row=7, column=1)
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
ent_standby_escrow_sequence_number.grid(row=8, column=1)
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
ent_standby_escrow_owner.grid(row=9, column=1)
lbl_standby_results.grid(row=10, column=0, sticky="ne")
text_standby_results.grid(row=10, column=1, sticky="nw")
###############################################
## Operational Account ########################
###############################################
# Create the Label and Entry widgets for "Operational Account"
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
ent_operational_seed = tk.Entry(master=frm_form, width=50)
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
ent_operational_account = tk.Entry(master=frm_form, width=50)
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
ent_operational_amount = tk.Entry(master=frm_form, width=50)
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
ent_operational_destination = tk.Entry(master=frm_form, width=50)
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
ent_operational_balance = tk.Entry(master=frm_form, width=50)
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
lbl_operational_results = tk.Label(master=frm_form,text='Results')
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
#Place the widgets in a grid
lbl_operational_seed.grid(row=0, column=4, sticky="e")
ent_operational_seed.grid(row=0, column=5, sticky="w")
lbl_operational_account.grid(row=2,column=4, sticky="e")
ent_operational_account.grid(row=2,column=5, sticky="w")
lbl_operational_amount.grid(row=3, column=4, sticky="e")
ent_operational_amount.grid(row=3, column=5, sticky="w")
lbl_operational_destination.grid(row=4, column=4, sticky="e")
ent_operational_destination.grid(row=4, column=5, sticky="w")
lbl_operational_balance.grid(row=5, column=4, sticky="e")
ent_operational_balance.grid(row=5, column=5, sticky="w")
lbl_operational_sequence_number.grid(row=6, column=4, sticky="e")
ent_operational_sequence_number.grid(row=6, column=5, sticky="w")
lbl_operational_escrow_owner.grid(row=7, column=4, sticky="e")
ent_operational_escrow_owner.grid(row=7, column=5, sticky="w")
lbl_operational_look_up.grid(row=8, column=4, sticky="e")
ent_operational_look_up.grid(row=8, column=5, sticky="w")
lbl_operational_results.grid(row=10, column=4, sticky="ne")
text_operational_results.grid(row=10, column=5, sticky="nw")
#############################################
## Buttons ##################################
#############################################
# Create the Get Standby Account Buttons
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
command = get_standby_account)
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
btn_get_standby_account_info = tk.Button(master=frm_form,
text="Get Standby Account Info",
command = get_standby_account_info)
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
command = standby_send_xrp)
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Time-based Escrow",
command = standby_create_time_escrow)
btn_standby_create_escrow.grid(row = 4, column = 2, sticky="nsew")
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Time-based Escrow",
command = standby_cancel_time_escrow)
btn_standby_cancel_escrow.grid(row=5,column = 2, sticky="nsew")
# Create the Operational Account Buttons
btn_get_operational_account = tk.Button(master=frm_form,
text="Get Operational Account",
command = get_operational_account)
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
command = get_operational_account_info)
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
command = operational_send_xrp)
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
command = operational_finish_time_escrow)
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
btn_op_finish_escrow = tk.Button(master=frm_form, text="Get Escrows",
command = operational_get_escrows)
btn_op_finish_escrow.grid(row = 5, column = 3, sticky="nsew")
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
command = operational_get_transaction)
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
# Start the application
window.mainloop()

View File

@@ -1,269 +0,0 @@
import tkinter as tk
import xrpl
import json
from mod1 import get_account, get_account_info, send_xrp
from mod8 import get_escrows, cancel_time_escrow, get_transaction
from mod9 import create_conditional_escrow, finish_conditional_escrow, generate_condition
#############################################
## Handlers #################################
#############################################
## Mod 9 Handlers
def get_condition():
results = generate_condition()
ent_standby_escrow_condition.delete(0, tk.END)
ent_standby_escrow_condition.insert(0, results[0])
ent_operational_escrow_fulfillment.delete(0, tk.END)
ent_operational_escrow_fulfillment.insert(0, results[1])
def standby_create_conditional_escrow():
results = create_conditional_escrow(
ent_standby_seed.get(),
ent_standby_amount.get(),
ent_standby_destination.get(),
ent_standby_escrow_cancel.get(),
ent_standby_escrow_condition.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def operational_finish_conditional_escrow():
results = finish_conditional_escrow(
ent_operational_seed.get(),
ent_operational_escrow_owner.get(),
ent_operational_sequence_number.get(),
ent_standby_escrow_condition.get(),
ent_operational_escrow_fulfillment.get()
)
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
## Mod 8 Handlers
def operational_get_escrows():
results = get_escrows(ent_operational_account.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
def standby_cancel_time_escrow():
results = cancel_time_escrow(
ent_standby_seed.get(),
ent_standby_escrow_owner.get(),
ent_standby_escrow_sequence_number.get()
)
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0", json.dumps(results, indent=4))
def operational_get_transaction():
results = get_transaction(ent_operational_account.get(),
ent_operational_look_up.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0", json.dumps(results, indent=4))
## Mod 1 Handlers
def get_standby_account():
new_wallet = get_account(ent_standby_seed.get())
ent_standby_account.delete(0, tk.END)
ent_standby_seed.delete(0, tk.END)
ent_standby_account.insert(0, new_wallet.classic_address)
ent_standby_seed.insert(0, new_wallet.seed)
def get_standby_account_info():
accountInfo = get_account_info(ent_standby_account.get())
ent_standby_balance.delete(0, tk.END)
ent_standby_balance.insert(0,accountInfo['Balance'])
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
def standby_send_xrp():
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
ent_standby_destination.get())
text_standby_results.delete("1.0", tk.END)
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
get_standby_account_info()
get_operational_account_info()
def get_operational_account():
new_wallet = get_account(ent_operational_seed.get())
ent_operational_account.delete(0, tk.END)
ent_operational_account.insert(0, new_wallet.classic_address)
ent_operational_seed.delete(0, tk.END)
ent_operational_seed.insert(0, new_wallet.seed)
def get_operational_account_info():
accountInfo = get_account_info(ent_operational_account.get())
ent_operational_balance.delete(0, tk.END)
ent_operational_balance.insert(0,accountInfo['Balance'])
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
def operational_send_xrp():
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
ent_operational_destination.get())
text_operational_results.delete("1.0", tk.END)
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
get_standby_account_info()
get_operational_account_info()
# Create a new window with the title "Conditional Escrow Example"
window = tk.Tk()
window.title("Conditional Escrow Example")
# Form frame
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
frm_form.pack()
# Create the Label and Entry widgets for "Standby Account"
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
ent_standby_seed = tk.Entry(master=frm_form, width=50)
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
ent_standby_account = tk.Entry(master=frm_form, width=50)
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
ent_standby_amount = tk.Entry(master=frm_form, width=50)
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
ent_standby_destination = tk.Entry(master=frm_form, width=50)
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
ent_standby_balance = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_condition = tk.Label(master=frm_form, text="Escrow Condition")
ent_standby_escrow_condition = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
lbl_standby_results = tk.Label(master=frm_form, text="Results")
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
# Place fields in a grid.
lbl_standy_seed.grid(row=0, column=0, sticky="e")
ent_standby_seed.grid(row=0, column=1)
lbl_standby_account.grid(row=2, column=0, sticky="e")
ent_standby_account.grid(row=2, column=1)
lbl_standy_amount.grid(row=3, column=0, sticky="e")
ent_standby_amount.grid(row=3, column=1)
lbl_standby_destination.grid(row=4, column=0, sticky="e")
ent_standby_destination.grid(row=4, column=1)
lbl_standby_balance.grid(row=5, column=0, sticky="e")
ent_standby_balance.grid(row=5, column=1)
lbl_standby_escrow_condition.grid(row=6, column=0, sticky="e")
ent_standby_escrow_condition.grid(row=6, column=1)
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
ent_standby_escrow_cancel.grid(row=7, column=1)
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
ent_standby_escrow_sequence_number.grid(row=8, column=1)
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
ent_standby_escrow_owner.grid(row=9, column=1)
lbl_standby_results.grid(row=10, column=0, sticky="ne")
text_standby_results.grid(row=10, column=1, sticky="nw")
###############################################
## Operational Account ########################
###############################################
# Create the Label and Entry widgets for "Operational Account"
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
ent_operational_seed = tk.Entry(master=frm_form, width=50)
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
ent_operational_account = tk.Entry(master=frm_form, width=50)
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
ent_operational_amount = tk.Entry(master=frm_form, width=50)
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
ent_operational_destination = tk.Entry(master=frm_form, width=50)
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
ent_operational_balance = tk.Entry(master=frm_form, width=50)
lbl_operational_escrow_fulfillment = tk.Label(master=frm_form, text="Escrow Fulfillment")
ent_operational_escrow_fulfillment = tk.Entry(master=frm_form, width=50)
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
lbl_operational_results = tk.Label(master=frm_form,text='Results')
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
#Place the widgets in a grid
lbl_operational_seed.grid(row=0, column=4, sticky="e")
ent_operational_seed.grid(row=0, column=5, sticky="w")
lbl_operational_account.grid(row=2,column=4, sticky="e")
ent_operational_account.grid(row=2,column=5, sticky="w")
lbl_operational_amount.grid(row=3, column=4, sticky="e")
ent_operational_amount.grid(row=3, column=5, sticky="w")
lbl_operational_destination.grid(row=4, column=4, sticky="e")
ent_operational_destination.grid(row=4, column=5, sticky="w")
lbl_operational_balance.grid(row=5, column=4, sticky="e")
ent_operational_balance.grid(row=5, column=5, sticky="w")
lbl_operational_escrow_fulfillment.grid(row=6, column=4, sticky="e")
ent_operational_escrow_fulfillment.grid(row=6, column=5, sticky="w")
lbl_operational_sequence_number.grid(row=7, column=4, sticky="e")
ent_operational_sequence_number.grid(row=7, column=5, sticky="w")
lbl_operational_escrow_owner.grid(row=8, column=4, sticky="e")
ent_operational_escrow_owner.grid(row=8, column=5, sticky="w")
lbl_operational_look_up.grid(row=9, column=4, sticky="e")
ent_operational_look_up.grid(row=9, column=5, sticky="w")
lbl_operational_results.grid(row=10, column=4, sticky="ne")
text_operational_results.grid(row=10, column=5, sticky="nw")
#############################################
## Buttons ##################################
#############################################
# Create the Get Standby Account Buttons
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
command = get_standby_account)
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
btn_get_standby_account_info = tk.Button(master=frm_form,
text="Get Standby Account Info",
command = get_standby_account_info)
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
command = standby_send_xrp)
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
btn_standby_get_condition = tk.Button(master=frm_form, text="Get Condition",
command = get_condition)
btn_standby_get_condition.grid(row=4, column=2, sticky="nsew")
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Conditional Escrow",
command = standby_create_conditional_escrow)
btn_standby_create_escrow.grid(row=5, column = 2, sticky="nsew")
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Escrow",
command = standby_cancel_time_escrow)
btn_standby_cancel_escrow.grid(row=6,column = 2, sticky="nsew")
# Create the Operational Account Buttons
btn_get_operational_account = tk.Button(master=frm_form,
text="Get Operational Account",
command = get_operational_account)
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
command = get_operational_account_info)
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
command = operational_send_xrp)
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
command = operational_finish_conditional_escrow)
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
btn_op_get_escrows = tk.Button(master=frm_form, text="Get Escrows",
command = operational_get_escrows)
btn_op_get_escrows.grid(row = 5, column = 3, sticky="nsew")
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
command = operational_get_transaction)
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
# Start the application
window.mainloop()

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

@@ -92,7 +92,7 @@ On supported platforms, see the [instructions on installing or updating `rippled
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.1-1.el9.x86_64.rpm) | `c6d028db1e2a4da898df68e5a92a893bebf1d167a0539d15ae27435f2155ccb2` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.1-1_amd64.deb) | `cc30c33012bd83ed793b38738870cf931a96ae106fde60b71685c766da1d22e3` |
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:
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/release-3.1/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
commit c5988233d05bedddac28866ed37607f4869855f9

Some files were not shown because too many files have changed in this diff Show More