Compare commits

...

120 Commits

Author SHA1 Message Date
mDuo13
4dec5f720d Fix broken links identified by Selenium-based checker 2026-05-26 17:04:00 -07:00
Rome Reginelli
d7456276b9 Merge pull request #3631 from XRPLF/browser-wallet-vite-8
Update browser wallet tutorial dependencies
2026-05-26 10:07:38 -07:00
oeggert
22243abc7c Merge pull request #3653 from XRPLF/remove-hooks
Mark Hooks as inactive
2026-05-21 14:30:33 -07:00
Oliver Eggert
a301253740 completely remove hooks from known amendments and home page 2026-05-21 13:17:39 -07:00
Oliver Eggert
d66fa36f56 mark hooks as inactive 2026-05-19 13:00:23 -07:00
Rome Reginelli
f61d74be6b Merge pull request #3554 from XRPLF/dependabot/go_modules/_code-samples/lending-protocol/go/golang.org/x/crypto-0.45.0
Bump golang.org/x/crypto from 0.44.0 to 0.45.0 in /_code-samples/lending-protocol/go
2026-05-18 17:12:33 -04:00
oeggert
00255ace3a Merge pull request #3633 from XRPLF/rippled-3.1.3
Doc updates for 3.1.3
2026-05-14 13:38:23 -04:00
Oliver Eggert
d1fc03ca37 remove copilot credit 2026-05-14 13:37:14 -04:00
Oliver Eggert
f9aae5f1e3 update blog date 2026-05-14 13:30:17 -04:00
Oliver Eggert
f700fa72ca add additional note about default vote 2026-05-07 21:10:52 -07:00
Oliver Eggert
bade50d826 fix amendment name and add missing release note entries 2026-05-07 19:31:39 -07:00
Oliver Eggert
9eb2742581 first pass of release notes 2026-05-07 17:20:10 -07:00
Oliver Eggert
56455bf27b update default vote 2026-05-07 15:01:21 -07:00
oeggert
4230d16811 Update docs/references/protocol/transactions/types/loanpay.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2026-05-07 14:35:03 -07:00
Oliver Eggert
1064dad9b0 doc updates for 3.1.3 2026-05-06 18:47:38 -07:00
mDuo13
79419d0b49 Browser wallet tutorial: small edits for updates 2026-05-05 14:20:54 -07:00
mDuo13
0e510b349d Browser wallet code sample: update to vite 8.x 2026-05-05 14:20:54 -07:00
dependabot[bot]
087030b39a Bump vite in /_code-samples/build-a-browser-wallet/js
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.14 to 6.4.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.4.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-05 14:20:54 -07:00
Rome Reginelli
03dae02593 Merge pull request #3629 from tetherkim/docs-fix-redundant-credential-tx-descriptions
docs: remove redundant field description sentences from credential transactions
2026-05-05 09:58:27 -07:00
Maria Shodunke
b82df3bb5d Fix amm_info missing request fields (#3620) 2026-05-05 13:52:50 +01:00
tetherkim
9e1bc798ba docs: remove redundant field description sentences from credential transactions
Removed redundant sentences in CredentialAccept and CredentialDelete documentation that were already provided by the 'tx-fields-intro.md' snippet.
2026-05-03 23:07:17 +09:00
Amarantha Kulkarni
c8cb28fd80 Merge pull request #3526 from XRPLF/add-feedback-widget
Add feedback scale widget
2026-05-01 16:58:26 -07:00
amarantha-k
b09fe6c7b3 Update color for feedback boxes to fix white text on a white background in dark mode 2026-05-01 16:18:03 -07:00
mDuo13
28af114305 Fix article bottom border not appearing on preview build 2026-05-01 15:39:08 -07:00
mDuo13
d846367857 Update feedback styles 2026-05-01 15:37:40 -07:00
amarantha-k
8481e05d6e Add feedback scale widget 2026-05-01 15:32:39 -07:00
Rome Reginelli
b0428e30d4 Merge pull request #3626 from XRPLF/revise-transaction-types-landing-page
Revise transaction types landing page
2026-05-01 15:30:22 -07:00
mDuo13
4b65061304 Contrib: fix header hierarchy typo 2026-05-01 15:17:12 -07:00
mDuo13
6d99930c9a Re-gen CSS (rebase after #3622) 2026-05-01 15:13:39 -07:00
mDuo13
84f78d83dd Transaction Types landing: revisions per review 2026-05-01 15:13:06 -07:00
mDuo13
5311ffb3b4 Add contributor documentation for tx landing tags 2026-05-01 15:13:06 -07:00
mDuo13
4f991e14a5 Add tx icon legend component 2026-05-01 15:13:06 -07:00
mDuo13
da17bb427f [JA] fix frontmatter of AccountDelete 2026-05-01 15:13:06 -07:00
mDuo13
c10456f103 [JA] Revise tx type index to use automatic categories 2026-05-01 15:13:06 -07:00
mDuo13
a31124c94b Revise tx type index to use automatic categories 2026-05-01 15:13:06 -07:00
mDuo13
7f2588c514 [JA] Update tx type metadata for use on index page 2026-05-01 15:13:06 -07:00
mDuo13
bba796d818 Update transaction type metadata for use on index page 2026-05-01 15:13:06 -07:00
mDuo13
7d3145b0a1 Add styles for TxRefs components 2026-05-01 15:13:06 -07:00
mDuo13
a874b034d7 Reorg components & impl. TxRef component 2026-05-01 15:13:06 -07:00
rachelflynn
0defd68316 Revised transaction types landing page 2026-05-01 15:13:05 -07:00
Rome Reginelli
a439eef72b Merge pull request #3622 from XRPLF/improve-faucet
Improve faucet page
2026-05-01 15:12:08 -07:00
Rome Reginelli
e94068b0db Edit faucet request text
Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com>
2026-05-01 13:54:03 -07:00
mDuo13
71e5ff4bdb Re-gen CSS (faucet w/ suggested changes) 2026-04-30 13:26:38 -07:00
Rome Reginelli
ccaaa55ca3 Faucet: apply suggested CSS changes
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2026-04-30 13:26:06 -07:00
oeggert
e2da8d58a0 Merge pull request #3618 from XRPLF/java-credentials
Add Java code samples for credentials
2026-04-30 10:58:16 -07:00
Rome Reginelli
e0cc0849ad Merge pull request #3619 from XRPLF/dependabot/pip/_code-samples/delete-account/py/python-dotenv-1.2.2
Bump python-dotenv from 1.2.1 to 1.2.2 in /_code-samples/delete-account/py
2026-04-29 16:53:28 -07:00
mDuo13
19b376be8e Improve XRP faucet w/ custom amount & address (squashed)
Faucet: improve handling of saved address, display of modes

Faucet improvements: suggestions from code review

Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2026-04-29 16:49:11 -07:00
Rome Reginelli
59a119db66 Merge pull request #3617 from XRPLF/rm_unused_code_samples
Remove some unused code samples, bump Redocly to 0.132.1, and related fixes
2026-04-29 16:43:50 -07:00
Oliver Eggert
2041b55e4b add reviewer suggestions 2026-04-29 15:45:29 -07:00
Oliver Eggert
d51da2ff5c fix tab styling in dark/light mode 2026-04-29 13:12:42 -07:00
mDuo13
19aad7809b Bump Redocly to Realm v0.132.1 and update xmldom to resolve security alerts 2026-04-28 13:23:42 -07:00
Oliver Eggert
508a39908c add manage credentials tutorial with java sample 2026-04-24 19:19:12 -07:00
oeggert
aca16f3609 Merge pull request #3625 from XRPLF/clarify-oracle-scale
Clarify AssetPrice and Scale interactions with new vs existing pairs
2026-04-24 12:06:15 -07:00
oeggert
187542fef5 Update docs/references/protocol/transactions/types/oracleset.md
Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com>
2026-04-24 12:00:36 -07:00
oeggert
0972abb0b6 Merge pull request #3599 from XRPLF/ai-resources
Add AI Tools page
2026-04-24 11:40:10 -07:00
Amarantha Kulkarni
6256e1d7c8 Merge pull request #3606 from XRPLF/VODF-172
VODF-172 - Add Batch transaction integration considerations
2026-04-24 11:39:06 -07:00
oeggert
9f7c675517 Update resources/dev-tools/ai-tools.md
Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com>
2026-04-24 11:39:04 -07:00
Maria Shodunke
202a16288c Address review comments 2026-04-24 15:37:46 +01:00
Oliver Eggert
8c68d4a7ad update readme file 2026-04-23 18:10:53 -07:00
Oliver Eggert
5e63775a97 clarify AssetPrice and Scale interactions with new vs existing pairs 2026-04-23 16:18:16 -07:00
rachelflynn
3e96de6323 Merge pull request #3586 from rachelflynn/fix-checks-js-code-samples
Fix JS code samples for checks tutorials and add set up scripts
2026-04-23 15:57:41 -04:00
Oliver Eggert
4e7d0aadc9 add requireSuccess helper and finalize script steps 2026-04-23 01:08:59 -07:00
Oliver Eggert
bbe80b34c9 update helpers for async and sequential error handling/calls 2026-04-22 18:43:44 -07:00
Oliver Eggert
3152430e47 improve error handling and output 2026-04-22 17:23:29 -07:00
Oliver Eggert
81279b4761 more helper cleanup 2026-04-22 16:27:07 -07:00
Oliver Eggert
aaa4668392 update helpers 2026-04-21 21:59:45 -07:00
dependabot[bot]
89de054d4d Bump python-dotenv in /_code-samples/delete-account/py
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-version: 1.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 22:51:49 +00:00
Oliver Eggert
583b169680 update code sample to use up-to-date functions and code conventions 2026-04-18 13:53:00 -07:00
Oliver Eggert
1241a33a5d restructure for java conventions 2026-04-18 09:56:08 -07:00
Oliver Eggert
d0f6d04715 add logback.xml 2026-04-18 08:49:04 -07:00
Oliver Eggert
f8d7ca470d add java target folder to gitignore 2026-04-18 08:43:36 -07:00
Oliver Eggert
d0187414a5 initial code sample and file structure 2026-04-17 20:16:40 -07:00
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
rachelflynn
f60393e9fa Addressed PR review feedback 2026-04-17 17:14:40 -04: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
9b72e6c6ff VODF-172 - Add Batch transaction integration considerations 2026-04-14 12:40:11 +01: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
Oliver Eggert
39f5b9ab66 fix spelling, grammar, and formatting 2026-04-10 16:04:06 -07:00
Oliver Eggert
3d3ac6adb3 second iteration 2026-04-09 16:52:21 -07:00
Oliver Eggert
539cef2510 initial draft 2026-04-09 10:25:00 -07:00
rachelflynn
0da70afdff Added set up scripts for updated code samples 2026-04-09 11:24:15 -04:00
rachelflynn
513c86dff3 Fix JS code samples for Use Checks tutorials: update to ES modules, fix syntax highlighting and indentation rendering, and improve code consistency 2026-04-07 16:47:25 -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
Oliver Eggert
892714550e more improvements to sorting script and AI instructions 2026-04-01 13:19:12 -07:00
Oliver Eggert
b6388ccb13 improve skills md edit vs write and not chunk context 2026-03-31 18:18:01 -07:00
Oliver Eggert
6ab5de13bb fix bug when passing different date and output years in args 2026-03-28 00:18:10 -07:00
Oliver Eggert
38000f19d6 fix sidebars.yaml entry logic 2026-03-27 23:35:06 -07:00
Oliver Eggert
ad9e5e14fa improve skill.md to prevent losing content 2026-03-27 22:20:46 -07:00
Oliver Eggert
663cd6df6a add skills.md and writing release notes to sidebars.yaml 2026-03-27 21:45:34 -07:00
Oliver Eggert
6bee1983eb append fix to fix amendment names 2026-03-27 20:37:40 -07:00
Oliver Eggert
9df53455e9 add amendment diff fetching for additional sorting context 2026-03-27 20:09:36 -07:00
Oliver Eggert
13dddb8b22 add basic amendment sorting 2026-03-27 12:11:39 -07:00
Oliver Eggert
b47c96d91a update logic for generating entries on commit-only entries and entries with broken PR/Issue links 2026-03-27 11:48:04 -07:00
Oliver Eggert
93abc4dc09 remove sys exit for failed file lookups, and minor cleanup to comments and section header location 2026-03-26 18:30:14 -07:00
Oliver Eggert
ae266aba7f add files for entry context. include commits linked to issues and commits without any links 2026-03-26 18:08:18 -07:00
Oliver Eggert
ab9eec63f5 merge version fetching functions and clean up credits intro 2026-03-26 15:10:46 -07:00
Oliver Eggert
8b8ed4c6ea fix fetch_version_commit to only check buildinfo.cpp 2026-03-26 14:48:30 -07:00
Oliver Eggert
6aaca7f568 ignore ripple users in credits and improve graphql handling of issues vs pulls 2026-03-26 14:07:40 -07:00
Oliver Eggert
a045e9e40c clean up functions 2026-03-25 22:22:40 -07:00
Oliver Eggert
ad9327c4c0 add comments and improve ordering of helpers 2026-03-25 15:05:35 -07:00
Oliver Eggert
53983bf8e2 update to use graphql and remove categorization logic 2026-03-25 14:47:00 -07:00
dependabot[bot]
aa3b5e173c Bump requests in /_code-samples/build-a-desktop-wallet/py
Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.33.0)

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

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

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

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

19
.claude/CLAUDE.md Normal file
View File

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

7
.claude/settings.json Normal file
View File

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

View File

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

1
.gitignore vendored
View File

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

View File

@@ -1,6 +1,6 @@
レジャーインデックスは、32ビットの符号なし整数であり、レジャーを識別するために使用します。レジャーインデックスは、レジャーの _シーケンス番号_ と呼ばれることもあります。([アカウントシーケンス](../../references/protocol/data-types/basic-data-types.md#アカウントシーケンス)とは異なります。一番最初のレジャーでは、レジャーインデックスは1でした。新しいレジャーのレジャーインデックスは、その直前のレジャーのレジャーインデックスに1を加算した値になります。
レジャーインデックスは、32ビットの符号なし整数であり、レジャーを識別するために使用します。レジャーインデックスは、レジャーの _シーケンス番号_ と呼ばれることもあります。([アカウントシーケンス](/docs/references/protocol/data-types/basic-data-types.md#アカウントシーケンス)とは異なります。一番最初のレジャーでは、レジャーインデックスは1でした。新しいレジャーのレジャーインデックスは、その直前のレジャーのレジャーインデックスに1を加算した値になります。
レジャーインデックスがレジャーの順番を示すのに対し、[ハッシュ][]値はレジャーの正確なコンテンツを示します。2つのレジャーが同じハッシュ値を持つ場合、それらは必ず同じものです。検証済みレジャーでは、ハッシュ値とレジャーインデックスは等しく有効で、1:1の関係です。しかし、進行中のレジャーに対しては、以下の理由によりその限りでありません。
レジャーインデックスがレジャーの順番を示すのに対し、[ハッシュ](/docs/references/protocol/data-types/basic-data-types.md#ハッシュ)値はレジャーの正確なコンテンツを示します。2つのレジャーが同じハッシュ値を持つ場合、それらは必ず同じものです。検証済みレジャーでは、ハッシュ値とレジャーインデックスは等しく有効で、1:1の関係です。しかし、進行中のレジャーに対しては、以下の理由によりその限りでありません。
* ネットワーク全体でのトランザクションの伝搬遅延が原因で、2つの異なる`rippled`サーバで、同じレジャーインデックスを持つ現行レジャーに対するコンテンツが異なる場合があります。
* 決済済みレジャーバージョンが複数あり、コンセンサスによる検証が競合している場合があります。このようなレジャーバージョンでは、レジャーインデックスは同じですが、コンテンツは異なりますハッシュも異なります。これらの決済済みレジャーのうち、検証済みになるのは1つだけです。

View File

@@ -2,7 +2,9 @@
seo:
description: アカウントの削除
labels:
- アカウント
- Accounts
requiredAmendment: DeletableAccounts
txIcon: cancel
---
# AccountDelete
@@ -10,7 +12,7 @@ labels:
AccountDeleteトランザクションは、XRP Ledgerで[アカウント](../../ledger-data/ledger-entry-types/accountroot.md)と、アカウントが所有するオブジェクトを削除し、可能であれば、アカウントの残りのXRPを指定された送金先アカウントに送信します。アカウントを削除する要件については、[アカウントの削除](../../../../concepts/accounts/deleting-accounts.md)をご覧ください。
_[DeletableAccounts Amendment][]が必要です。_
{% amendment-disclaimer name="DeletableAccounts" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -2,7 +2,8 @@
seo:
description: XRP Ledgerのアカウントのプロパティーを修正します。
labels:
- アカウント
- Accounts
txIcon: modify
---
# AccountSet

View File

@@ -1,10 +1,11 @@
---
html: ammbid.html
parent: transaction-types.html
seo:
description: 自動マーケットメーカーのオークションスロットに入札することで、手数料の割引を受けることができます。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: modify
---
# AMMBid
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMBid.cpp "Source")

View File

@@ -2,8 +2,10 @@
seo:
description: 自動マーケットメーカープールに発行済みトークンを預け入れた保有者から、トークンを回収する。
labels:
- AMM
- Tokens
- AMM
- DEX
requiredAmendment: AMMClawback
txIcon: cancel
---
# AMMClawback

View File

@@ -4,7 +4,10 @@ parent: transaction-types.html
seo:
description: 指定された資産ペアを取引するための新しい自動マーケットメーカーを作成します。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: create
---
# AMMCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMCreate.cpp "Source")

View File

@@ -1,10 +1,11 @@
---
html: ammdelete.html
parent: transaction-types.html
seo:
description: 空のプールを持つ自動マーケットメーカーのインスタンスを削除します。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: cancel
---
# AMMDelete
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMDelete.cpp "Source")

View File

@@ -4,7 +4,10 @@ parent: transaction-types.html
seo:
description: 自動マーケットメーカーに資金を預け、LPTokenを受け取ります。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: send
---
# AMMDeposit
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMDeposit.cpp "Source")

View File

@@ -4,7 +4,10 @@ parent: transaction-types.html
seo:
description: 自動マーケットメーカーインスタンスの取引手数料へ投票する。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: modify
---
# AMMVote
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMVote.cpp "Source")

View File

@@ -4,7 +4,10 @@ parent: transaction-types.html
seo:
description: LPトークを自動マーケットメーカーに返却し、プールが保有する資産の一部と引き換えマス。
labels:
- AMM
- AMM
- DEX
requiredAmendment: AMM
txIcon: send
---
# AMMWithdraw
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/AMMWithdraw.cpp "Source")

View File

@@ -2,15 +2,19 @@
seo:
description: 最大8件のトランザクションをまとめて作成・送信し、それらがすべて成功するか、すべて失敗するようにアトミックに処理されるようにします。
labels:
- Batch
- Transaction Sending
- Transaction Sending
- Other Transactions
requiredAmendment: Batch
status: not_enabled
txIcon: other
---
# Batch
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/Batch.cpp "Source")
`Batch`トランザクションは、最大8つのトランザクションを単一のバッチで送信します。各トランザクションは、4つのモード(全て成功または全て失敗All or Nothing、一つのみ成功Only One、失敗まで継続Until Failure、および独立実行Independent)のいずれかでアトミックに実行されます。
{% amendment-disclaimer name="Batch" /%}
## {% $frontmatter.seo.title %} JSONの例
### 単一アカウントの場合

View File

@@ -1,17 +1,18 @@
---
html: checkcancel.html
parent: transaction-types.html
seo:
description: 未清算のCheckを取り消し、送金を行わずにレジャーから削除します。
labels:
- Checks
- Checks
- Payments
requiredAmendment: Checks
txIcon: cancel
---
# CheckCancel
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CancelCheck.cpp "Source")
未清算のCheckを取り消し、送金を行わずにレジャーから削除します。Checkの送金元または送金先は、いつでもこのトランザクションタイプを使用してCheckを取り消すことができます。有効期限切れのCheckはすべてのアドレスが取り消すことができます。
_[Checks Amendment][]が必要です_
{% amendment-disclaimer name="Checks" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -1,10 +1,11 @@
---
html: checkcash.html
parent: transaction-types.html
seo:
description: レジャーでCheckオブジェクトの清算を試みます。
labels:
- Checks
- Checks
- Payments
requiredAmendment: Checks
txIcon: finish
---
# CheckCash
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CashCheck.cpp "Source")
@@ -13,7 +14,7 @@ labels:
Checkに相当する資金があるとは保証されないため、送金元に十分な残高がないか、または資金を送金できるだけの十分な流動性がないことが原因で、Checkの清算が失敗することがあります。このような状況が発生した場合、Checkはレジャーに残り、送金先は後でこのCheckの換金を再試行するか、または異なる額で換金を試みることができます。
_[Checks Amendment][]が必要です_
{% amendment-disclaimer name="Checks" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -1,17 +1,18 @@
---
html: checkcreate.html
parent: transaction-types.html
seo:
description: レジャーにCheckオブジェクトを作成します
labels:
- Checks
- Checks
- Payments
requiredAmendment: Checks
txIcon: create
---
# CheckCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CreateCheck.cpp "Source")
レジャーにCheckオブジェクトを作成します。これにより指定の送金先は後日換金することができます。このトランザクションの送信者はCheckの送金元です。
_[Checks Amendment][]が必要です_
{% amendment-disclaimer name="Checks" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -2,7 +2,9 @@
seo:
description: 発行したトークンを取り戻します。
labels:
- トークン
- Tokens
requiredAmendment: Clawback
txIcon: cancel
---
# Clawback

View File

@@ -1,12 +1,18 @@
---
seo:
description: アカウントに仮発行された資格情報を承認します。
status: not_enabled
labels:
- Decentralized Storage
- Credentials
requiredAmendment: Credentials
txIcon: finish
---
# CredentialAccept
CredentialAcceptトランザクションは資格情報を承認し、その資格情報を有効にします。資格情報の対象者のみがこの操作を実行できます。
{% amendment-disclaimer name="Credentials" /%}
## CredentialAccept JSONの例
```json

View File

@@ -1,13 +1,19 @@
---
seo:
description: アカウントに対して暫定的に資格情報を発行します。
status: not_enabled
labels:
- Decentralized Storage
- Credentials
requiredAmendment: Credentials
txIcon: create
---
# CredentialCreate
CredentialCreateトランザクションは、レジャーにCredentialを作成します。Credential(資格情報)の発行者はこのトランザクションを使用して、暫定的に資格情報を発行します。Credentialは、その対象アカウントが[CredentialAcceptトランザクション][]で承認するまで有効になりません。
{% amendment-disclaimer name="Credentials" /%}
## CredentialCreate JSONの例
```json

View File

@@ -1,7 +1,11 @@
---
seo:
description: レジャーから認証情報を削除し、事実上失効させます。
status: not_enabled
labels:
- Decentralized Storage
- Credentials
requiredAmendment: Credentials
txIcon: cancel
---
# CredentialDelete

View File

@@ -2,7 +2,10 @@
seo:
description: DepositPreauthトランザクションは別のアカウントに対し、このトランザクションの送信者に支払いを送金することを事前承認します。
labels:
- セキュリティ
- Accounts
- Security
requiredAmendment: DepositPreauth
txIcon: modify
---
# DepositPreauth
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/DepositPreauth.cpp "Source")

View File

@@ -1,21 +1,20 @@
---
html: diddelete.html
parent: transaction-types.html
seo:
description: DIDを削除する。
labels:
- DID
- DID
- Decentralized Storage
requiredAmendment: DID
txIcon: cancel
---
# DIDDelete
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/DID.cpp "ソース")
_([DID Amendment][])_
指定した`Account`フィールドに関連付けられている[DIDレジャーエントリ](../../ledger-data/ledger-entry-types/did.md)を削除します。
{% admonition type="info" name="注記" %}このトランザクションは[共通フィールド][]のみ利用します。{% /admonition %}
{% amendment-disclaimer name="DID" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -1,19 +1,18 @@
---
html: didset.html
parent: transaction-types.html
seo:
description: DIDを作成または更新します。
labels:
- DID
- DID
- Decentralized Storage
requiredAmendment: DID
txIcon: create
---
# DIDSet
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/DID.cpp "ソース")
_([DID Amendment][])_
新しい[DIDレジャーエントリ](../../ledger-data/ledger-entry-types/did.md)を作成したり、既存の項目を更新したりします。
{% amendment-disclaimer name="DID" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -3,6 +3,9 @@ seo:
description: Escrowに留保されているXRPを送金元に返金します。
labels:
- Escrow
- Payments
requiredAmendment: Escrow
txIcon: cancel
---
# EscrowCancel
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/Escrow.cpp "Source")

View File

@@ -3,6 +3,9 @@ seo:
description: Escrowプロセスが終了または取り消されるまでXRPを隔離します。
labels:
- Escrow
- Payments
requiredAmendment: Escrow
txIcon: create
---
# EscrowCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/Escrow.cpp "Source")

View File

@@ -3,6 +3,9 @@ seo:
description: エスクローされたXRPを受取人へ送金します。
labels:
- Escrow
- Payments
requiredAmendment: Escrow
txIcon: finish
---
# EscrowFinish
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/Escrow.cpp "Source")

View File

@@ -1,22 +1,40 @@
---
html: transaction-types.html
parent: transaction-formats.html
seo:
description: トランザクションのタイプは、どういったタイプの操作を実行することが想定されているのかを示します。
metadata:
indexPage: true
indexPage: true
labels:
- ブロックチェーン
- ブロックチェーン
---
# トランザクションのタイプ
トランザクションのタイプ(`TransactionType`フィールド)は、トランザクションに関する最も基本的な情報です。トランザクションで、どういったタイプの操作を実行することが想定されているのかを示します。
トランザクションのタイプ(`TransactionType`フィールド)は、どういったタイプの操作を実行することが想定されているのかを示します。すべてのトランザクションに、[共通フィールド](../common-fields.md)が含まれています。
すべてのトランザクションに、特定の共通フィールドが含まれています。
## アカウント
* [共通フィールド](../common-fields.md)
{% tx-category name="Accounts" /%}
トランザクションのタイプごとに、実行される操作のタイプに関連した追加のフィールドが含まれています。
## 支払い
{% tx-category name="Payments" /%}
## トークン
{% tx-category name="Tokens" /%}
## 分散型取引所DEX
{% tx-category name="DEX" /%}
## 分散型ストレージ
{% tx-category name="Decentralized Storage" /%}
## XRPLサイドチェーン
{% tx-category name="Interoperability" /%}
## その他
{% tx-category name="Other Transactions" /%}
{% child-pages /%}

View File

@@ -2,9 +2,11 @@
seo:
description: アカウントが特定のMPTの残高を保持することを許可します。
labels:
- Multi-Purpose Token, MPT
- Multi-purpose Tokens, MPTs
- Tokens
requiredAmendment: MPTokensV1
txIcon: modify
---
# MPTokenAuthorize
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/MPTokenAuthorize.cpp "ソース")

View File

@@ -2,7 +2,10 @@
seo:
description: 新しいMulti-Purpose Tokenを発行します。
labels:
- Multi-Purpose Token, MPT
- Multi-purpose Tokens, MPTs
- Tokens
requiredAmendment: MPTokensV1
txIcon: create
---
# MPTokenIssuanceCreate

View File

@@ -2,7 +2,10 @@
seo:
description: Multi-Purpose Tokenを削除します。
labels:
- Multi-Purpose Token, MPT
- Multi-purpose Tokens, MPTs
- Tokens
requiredAmendment: MPTokensV1
txIcon: cancel
---
# MPTokenIssuanceDestroy
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/MPTokenIssuanceDestroy.cpp "ソース")

View File

@@ -2,7 +2,10 @@
seo:
description: MPTの変更可能なプロパティを設定します。
labels:
- Multi-Purpose Token, MPT
- Multi-purpose Tokens, MPTs
- Tokens
requiredAmendment: MPTokensV1
txIcon: modify
---
# MPTokenIssuanceSet
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp "ソース")

View File

@@ -2,7 +2,11 @@
seo:
description: NFTokenの購入または売却のオファーを受け入れる。
labels:
- NFT, 非代替性トークン
- Non-fungible Tokens, NFTs
- Tokens
- DEX
requiredAmendment: NonFungibleTokensV1_1
txIcon: finish
---
# NFTokenAcceptOffer
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/NFTokenAcceptOffer.cpp "ソース")

View File

@@ -1,10 +1,11 @@
---
html: nftokenburn.html
parent: transaction-types.html
seo:
description: TokenBurnを使用して、NFTを永久に破棄します。
labels:
- 非代替性トークン, NFT
- Non-fungible Tokens, NFTs
- Tokens
requiredAmendment: NonFungibleTokensV1_1
txIcon: cancel
---
# NFTokenBurn

View File

@@ -1,10 +1,12 @@
---
html: nftokencanceloffer.html
parent: transaction-types.html
seo:
description: NFTokenの売買のための既存のトークンへのオファーをキャンセルする。
labels:
- NFT, 非代替性トークン
- Non-fungible Tokens, NFTs
- Tokens
- DEX
requiredAmendment: NonFungibleTokensV1_1
txIcon: cancel
---
# NFTokenCancelOffer
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/NFTokenCancelOffer.cpp "ソース")

View File

@@ -1,10 +1,12 @@
---
html: nftokencreateoffer.html
parent: transaction-types.html
seo:
description: NFTの売買のオファーを作成する。
labels:
- 非代替性トークン, NFT
- Non-fungible Tokens, NFTs
- Tokens
- DEX
requiredAmendment: NonFungibleTokensV1_1
txIcon: create
---
# NFTokenCreateOffer
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/NFTokenCreateOffer.cpp "ソース")

View File

@@ -2,7 +2,10 @@
seo:
description: NFTokenMintを使用して新規NFTを発行する。
labels:
- 非代替性トークン, NFT
- Non-fungible Tokens, NFTs
- Tokens
requiredAmendment: NonFungibleTokensV1_1
txIcon: create
---
# NFTokenMint
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/NFTokenMint.cpp "Source")

View File

@@ -2,15 +2,18 @@
seo:
description: ダイナミックNFTを変更します。
labels:
- 非代替性トークン, トークン, NFT
title:
- NFTokenModify
- Non-fungible Tokens, NFTs
- Tokens
requiredAmendment: DynamicNFT
txIcon: modify
---
# NFTokenModify
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/NFTokenModify.cpp "ソース")
`NFTokenModify`は、NFTの`URI`フィールドを別のURIに変更し、NFTのサポートデータを更新するために使用されます。NFTは、`tfMutable`フラグが設定された状態でミントされている必要があります。[ダイナミックNFT](../../../../concepts/tokens/nfts/dynamic-nfts.md)をご覧ください。
{% amendment-disclaimer name="DynamicNFT" /%}
## {% $frontmatter.seo.title %} JSONの例

View File

@@ -1,16 +1,15 @@
---
html: offercancel.html
parent: transaction-types.html
seo:
description: XRP LedgerからOfferオブジェクトを削除します。
description: 分散型取引所からオファーを削除します。
labels:
- 分散型取引所
- DEX
txIcon: cancel
---
# OfferCancel
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CancelOffer.cpp "Source")
OfferCancelトランザクションは、XRP LedgerからOfferオブジェクトを削除します。
[分散型取引所](../../../../concepts/tokens/decentralized-exchange/index.md)からオファーを削除します。
## {% $frontmatter.seo.title %}のJSONの例

View File

@@ -2,10 +2,10 @@
seo:
description: 通貨交換の注文を作成します。
labels:
- 分散型取引所
- DEX
txIcon: create
---
# OfferCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CreateOffer.cpp "ソース")
OfferCreateトランザクションは[分散型取引所](../../../../concepts/tokens/decentralized-exchange/index.md)で[注文](../../../../concepts/tokens/decentralized-exchange/offers.md)を作成します。

View File

@@ -2,15 +2,17 @@
seo:
description: 既存の価格オラクルを削除します。
labels:
- オラクル
- Oracle
- Decentralized Storage
requiredAmendment: PriceOracle
txIcon: cancel
---
# OracleDelete
_([PriceOracle Amendment][])_
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/DeleteOracle.cpp "ソース")
既存の`Oracle`レジャーエントリを削除します。
{% amendment-disclaimer name="PriceOracle" /%}
## OracleDeleteのJSONの例

View File

@@ -2,15 +2,17 @@
seo:
description: 価格オラクルを作成または更新します。
labels:
- オラクル
- Oracle
- Decentralized Storage
requiredAmendment: PriceOracle
txIcon: create
---
# OracleSet
_([PriceOracle Amendment][])_
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/SetOracle.cpp "ソース")
Oracle Document ID を使用して、新しい`Oracle`レジャーエントリを作成するか、既存のフィールドを更新します。
{% amendment-disclaimer name="PriceOracle" /%}
## OracleSetのJSONの例

View File

@@ -1,11 +1,12 @@
---
seo:
description: アカウント間での価値の移動します。
description: アカウント間での価値の移動します、またはアカウントを作成する
labels:
- 支払い
- XRP
- クロスカレンシー
- トークン
- Accounts
- Payments
- XRP
- Cross-Currency
txIcon: send
---
# Payment
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/Payment.cpp "ソース")

View File

@@ -2,7 +2,10 @@
seo:
description: Payment Channelに対しXRPを請求します。
labels:
- Payment Channel
- Payment Channels
- Payments
requiredAmendment: PayChan
txIcon: finish
---
# PaymentChannelClaim
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/PayChan.cpp "Source")

View File

@@ -2,7 +2,10 @@
seo:
description: 新しいペイメントチャネルを作成します。
labels:
- Payment Channel
- Payment Channels
- Payments
requiredAmendment: PayChan
txIcon: create
---
# PaymentChannelCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/PayChan.cpp "ソース")

View File

@@ -2,7 +2,10 @@
seo:
description: Payment ChannelにXRPを追加します。
labels:
- Payment Channel
- Payment Channels
- Payments
requiredAmendment: PayChan
txIcon: modify
---
# PaymentChannelFund
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/PayChan.cpp "Source")

View File

@@ -2,15 +2,18 @@
seo:
description: 許可型ドメインのレジャーエントリを削除する
labels:
- コンプライアンス
- 許可型ドメイン
- Compliance
- Permissioned Domains
- Decentralized Storage
requiredAmendment: PermissionedDomains
txIcon: cancel
---
# PermissionedDomainDelete
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/PermissionedDomainDelete.cpp "ソース")
所有する[許可型ドメイン][]を削除します。
_([PermissionedDomains amendment][]が必要です {% not-enabled /%})_
{% amendment-disclaimer name="PermissionedDomains" /%}
## {% $frontmatter.seo.title %}のJSONの例

View File

@@ -2,15 +2,18 @@
seo:
description: 許可型ドメインを作成または更新する
labels:
- コンプライアンス
- 許可型ドメイン
- Compliance
- Permissioned Domains
- Decentralized Storage
requiredAmendment: PermissionedDomains
txIcon: create
---
# PermissionedDomainSet
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/PermissionedDomainSet.cpp "ソース")
[許可型ドメイン][]を作成するか、所有するドメインを変更します。
_([PermissionedDomains amendment][]が必要です {% not-enabled /%})_
{% amendment-disclaimer name="PermissionedDomains" /%}
## {% $frontmatter.seo.title %}のJSONの例

View File

@@ -1,10 +1,10 @@
---
html: setregularkey.html
parent: transaction-types.html
seo:
description: アカウントに関連付けられているレギュラーキーペアの割り当て、変更、削除を行います。
labels:
- セキュリティ
- Security
- Accounts
txIcon: modify
---
# SetRegularKey
@@ -29,7 +29,6 @@ labels:
{% tx-example txid="6AA6F6EAAAB56E65F7F738A9A2A8A7525439D65BA990E9BA08F6F4B1C2D349B4" /%}
{% raw-partial file="/@l10n/ja/docs/_snippets/tx-fields-intro.md" /%}
<!--{# fix md highlighting_ #}-->
| フィールド | JSONの型 | [内部の型][] | 説明 |
|:-------------|:----------|:------------------|:------------------------------|

View File

@@ -1,17 +1,19 @@
---
html: signerlistset.html
parent: transaction-types.html
seo:
description: トランザクションのマルチシグに使用できる署名者のリストを作成、置換、削除します。
labels:
- セキュリティ
- Security
- Accounts
requiredAmendment: MultiSign
txIcon: modify
---
# SignerListSet
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/SetSignerList.cpp "ソース")
SignerListSetトランザクションは、トランザクションの[マルチシグ](../../../../concepts/accounts/multi-signing.md)に使用できる署名者のリストを作成、置換、削除します。このトランザクションタイプは[MultiSign Amendment][]により導入されました。
{% amendment-disclaimer name="MultiSign" /%}
## {% $frontmatter.seo.title %}のJSONの例
```json

View File

@@ -1,19 +1,19 @@
---
html: ticketcreate.html
parent: transaction-types.html
seo:
description: チケットとして1つ以上のシーケンス番号を確保する。
labels:
- Transaction Sending
- Transaction Sending
- Accounts
requiredAmendment: TicketBatch
txIcon: create
---
# TicketCreate
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/CreateTicket.cpp "Source")
_([TicketBatch amendment][]が必要です)_
TicketCreateトランザクションは、1つまたは複数の[シーケンス番号](../../data-types/basic-data-types.md#アカウントシーケンス)を[Tickets](../../ledger-data/ledger-entry-types/ticket.md)として確保します。
{% amendment-disclaimer name="TicketBatch" /%}
## {% $frontmatter.seo.title %}JSONの例
```json
@@ -28,9 +28,6 @@ TicketCreateトランザクションは、1つまたは複数の[シーケンス
{% tx-example txid="738AEF36B48CA4A2D85C2B74910DC34DDBBCA4C83643F2DB84A58785ED5AD3E3" /%}
{% raw-partial file="/@l10n/ja/docs/_snippets/tx-fields-intro.md" /%}
<!--{# fix md highlighting_ #}-->
| フィールド | JSONの型 | [内部の型][] | 説明 |
|:-----------------|:-----------------|:------------------|:-------------------|
| `TicketCount` | 数値 | UInt32 | 作成するチケットの枚数。これは正の数でなければならず、このトランザクションの実行の結果、アカウントが250枚以上のチケットを所有することはできません。 |

View File

@@ -1,13 +1,11 @@
---
html: trustset.html
parent: transaction-types.html
seo:
description: トラストラインを作成または変更します。
labels:
- トークン
- Tokens
txIcon: modify
---
# TrustSet
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/app/tx/detail/SetTrust.cpp "Source")
2つのアカウントをリンクする[トラストライン](../../../../concepts/tokens/fungible-tokens/index.md)を作成または変更します。

View File

@@ -1,23 +1,20 @@
---
html: xchainaccountcreatecommit.html
parent: transaction-types.html
seo:
description: ブリッジが接続するチェーンの一つでアカウントを作成します。このアカウントがそのチェーンのブリッジの入り口となります。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: create
---
# XChainAccountCreateCommit
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L466-L474 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
このトランザクションはXRP-XRPブリッジにのみ使用できます。
`XChainAccountCreateCommit`トランザクションは、発行チェーンにトランザクションを送信するために、Witnessサーバ用の新しいアカウントを作成します。
発行チェーンにトランザクションを送信するために、Witnessサーバ用の新しいアカウントを作成します。このトランザクションはXRP-XRPブリッジにのみ使用できます。
{% admonition type="warning" name="注意" %}このトランザクションは、Witnessの証明書が送信先チェーンに確実に送信される場合にのみ実行されるべきです。署名が送信されない場合、証明書が受信されるまでアカウント作成はブロックされます。XRP-XRPブリッジでこのトランザクションを無効にするには、ブリッジの`MinAccountCreateAmount`フィールドを省略します。{% /admonition %}
{% amendment-disclaimer name="XChainBridge" /%}
## XChainAccountCreateCommit JSONの例

View File

@@ -1,18 +1,16 @@
---
html: xchainaddaccountcreateattestation.html
parent: transaction-types.html
seo:
description: XChainAddAccountCreateAttestationトランザクションは他のチェーンでXChainAccountCreateCommitトランザクションが発生した証明をWitnessサーバから提示します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: create
---
# XChainAddAccountCreateAttestation
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L447-L464 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainAddAccountCreateAttestation`トランザクションは、`XChainAccountCreateCommit`トランザクションがもう一方のチェーンで発生したというWitnessサーバからの証明を提示します。
`XChainAccountCreateCommit`トランザクションがもう一方のチェーンで発生したというWitnessサーバからの証明を提示します。
この署名は署名が提供された時点のドアの署名者リストにある鍵の一つでなければなりません。署名が提出されてから定足数に達するまでの間に署名者リストが変更された場合、新しい署名セットが使用され、現在収集されている署名の一部が削除される可能性があります。
@@ -20,6 +18,7 @@ _[XChainBridge Amendment][] {% not-enabled /%} が必要です_
{% admonition type="info" name="注記" %}報酬は現在のリストにある鍵を持っているアカウントにのみ送られます。署名者の定足数は`SignatureReward`に一致する必要があります。より大きな報酬を得ようとして、一つのWitnessサーバがこの値に不正な値を指定することはできません。{% /admonition %}
{% amendment-disclaimer name="XChainBridge" /%}
## XChainAddAccountCreateAttestation JSONの例

View File

@@ -1,18 +1,16 @@
---
html: xchainaddclaimattestation.html
parent: transaction-types.html
seo:
description: 送信元チェーンで発生したイベントを、送信先チェーンに証明(アテスト)します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: modify
---
# XChainAddClaimAttestation
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L429-L445 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainAddClaimAttestation`トランザクションは`XChainCommit`トランザクションを証明するWitnessサーバの署名を提供します。
`XChainCommit`トランザクションを証明するWitnessサーバの署名を提供します。
この署名は、署名が提出された時点のドアの署名者リストにある鍵の一つでなければなりません。ただし、署名が提出されてから定足数に達するまでの間に署名者リストが変更された場合は、新しい署名セットが使用され、現在収集されている署名の一部が削除されることがあります。
@@ -20,6 +18,7 @@ _[XChainBridge Amendment][] {% not-enabled /%} が必要です_
{% admonition type="info" name="注記" %}報酬は現在のリストにある鍵を持っているアカウントにのみ送られます。署名者の定足数は`SignatureReward`に一致する必要があります。より大きな報酬を得ようとして、一つのWitnessサーバがこの値に不正な値を指定することはできません。{% /admonition %}
{% amendment-disclaimer name="XChainBridge" /%}
## XChainAddClaimAttestation JSONの例

View File

@@ -1,21 +1,20 @@
---
html: xchainclaim.html
parent: transaction-types.html
seo:
description: 送信先チェーンで金額を請求することで、クロスチェーンでの価値移転を完了させます。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: finish
---
# XChainClaim
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L418-L427 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainClaim`トランザクションはクロスチェーンでの価値の移転を完了させます。`XChainClaim`トランザクションにより、ユーザは送信元チェーンでロックされた価値と同等の価値を送信先チェーンで請求することができます。ユーザは、送金元チェーンでロックされた価値に関連付けられたクロスチェーン請求ID`Account`フィールド)を所有している場合にのみ、その価値を請求することができます。ユーザは誰にでも資金を送ることができます(`Destination`フィールド)。このトランザクションが必要になるのは`XChainCommit`トランザクションで`OtherChainDestination`が指定されていない場合、または自動送金で何か問題が発生した場合のみです。
トランザクションによって送金に成功すると、対象の`XChainOwnedClaimID`レジャーオブジェクトは削除されます。これはトランザクションのリプレイを防ぎます。トランザクションが失敗した場合、`XChainOwnedClaimID`は削除されず、異なるパラメータでトランザクションを再実行できます。
{% amendment-disclaimer name="XChainBridge" /%}
## XChainClaim JSONの例

View File

@@ -1,19 +1,18 @@
---
html: xchaincommit.html
parent: transaction-types.html
seo:
description: クロスチェーンでの価値移転を開始します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: send
---
# XChainCommit
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L408-L416 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainCommit`はクロスチェーン送金の2番目のステップです。`XChainCommit`は発行チェーンでラップできるようにロックチェーンで資産を保管したり、ロックチェーンで返却できるように発行チェーンでラップされた資産をバーンしたりします。
クロスチェーン送金の2番目のステップです。`XChainCommit`は発行チェーンでラップできるようにロックチェーンで資産を保管したり、ロックチェーンで返却できるように発行チェーンでラップされた資産をバーンしたりします。
{% amendment-disclaimer name="XChainBridge" /%}
## XChainCommit JSONの例

View File

@@ -1,18 +1,16 @@
---
html: xchaincreatebridge.html
parent: transaction-types.html
seo:
description: 2つのチェーン間にブリッジを作成します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: create
---
# XChainCreateBridge
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L381-L388 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainCreateBridge`トランザクションは新しい`Bridge`レジャーオブジェクトを作成し、トランザクショ ンが送信されたチェーン上に新しいクロスチェーンブリッジの入り口を定義します。これにはブリッジのドアアカウントと資産に関する情報が含まれます。
新しい`Bridge`レジャーオブジェクトを作成し、トランザクショ ンが送信されたチェーン上に新しいクロスチェーンブリッジの入り口を定義します。これにはブリッジのドアアカウントと資産に関する情報が含まれます。
このトランザクションは、ロックチェーンのドアアカウントが最初に送信する必要があります。有効なブリッジをセットアップするには、Witnessサーバのセットアップに加えて、両チェーンのドアアカウントがこのトランザクションを送信しなければなりません。
@@ -20,6 +18,7 @@ _[XChainBridge Amendment][] {% not-enabled /%} が必要です_
{% admonition type="info" name="注記" %}各ドアアカウントは1つのブリッジしか持つことができません。これにより、同じ資産に対して複数のブリッジが作成され、いずれかのチェーンで資産が不一致となるのを防ぐことができます。{% /admonition %}
{% amendment-disclaimer name="XChainBridge" /%}
## XChainCreateBridge JSONの例

View File

@@ -1,23 +1,22 @@
---
html: xchaincreateclaimid.html
parent: transaction-types.html
seo:
description: クロスチェーン送金に使用するクロスチェーン請求IDを作成します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: create
---
# XChainCreateClaimID
[[ソース]](https://github.com/XRPLF/rippled/blob/master/src/ripple/protocol/impl/TxFormats.cpp#L399-L406 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainCreateClaimID`トランザクションはクロスチェーン送金に使われる新しいクロスチェーン請求IDを作成します。クロスチェーン請求IDは*1つの*クロスチェーン送金を表します。
このトランザクションはクロスチェーン送金の最初のステップであり、送金元チェーンではなく、送金先チェーンで送信されます。
また、送金元チェーン上の資金をロックまたはバーンする送金元チェーン上のアカウントも含まれます。
{% amendment-disclaimer name="XChainBridge" /%}
## XChainCreateClaimID JSONの例

View File

@@ -1,23 +1,22 @@
---
html: xchainmodifybridge.html
parent: transaction-types.html
seo:
description: ブリッジの設定を変更します。
labels:
- 相互運用性
- Interoperability
requiredAmendment: XChainBridge
status: not_enabled
txIcon: modify
---
# XChainModifyBridge
[[ソース]](https://github.com/XRPLF/rippled/blob/develop/src/ripple/protocol/impl/TxFormats.cpp#L390-L397 "ソース")
_[XChainBridge Amendment][] {% not-enabled /%} が必要です_
`XChainModifyBridge`トランザクションでは、ブリッジ管理者がブリッジの設定を変更することができます。変更できるのは`SignatureReward``MinAccountCreateAmount`だけです。
このトランザクションはドアアカウントから送信される必要があり、Witnessサーバを管理するエンティティがこのトランザクションのために協調し、署名を提供する必要があります。この調整はレジャーの外部で行われます。
{% admonition type="info" name="注記" %}このトランザクションでブリッジの署名者リストを変更することはできません。署名者リストはドアアカウント自体にあり、署名者リストがアカウント上で変更されるのと同じ方法で変更されます(`SignerListSet`トランザクションを利用)。{% /admonition %}
{% amendment-disclaimer name="XChainBridge" /%}
## XChainModifyBridge JSONの例

View File

@@ -1,3 +1,5 @@
// Components related to XRPL Amendment previews and statuses
import * as React from 'react'
import { Link } from '@redocly/theme/components/Link/Link'
import { useThemeHooks } from '@redocly/theme/core/hooks'

View File

@@ -0,0 +1,26 @@
// Component for {% child-pages /%} markdoc tag.
// Return a list of children of the current page.
import { useThemeHooks } from '@redocly/theme/core/hooks'
import { Link } from '@redocly/theme/components/Link/Link'
import NotEnabled from './NotEnabled'
export default function ChildPages() {
const { usePageSharedData } = useThemeHooks()
const data = usePageSharedData('index-page-items') as any[]
return (
<div className="children-display">
<ul>
{data?.map((item: any) => (
<li className="level-1" key={item.slug}>
<Link to={item.slug}>{item.title}</Link>
{
item.status === "not_enabled" ? (<NotEnabled />) : ""
}
<p className="blurb child-blurb">{item.seo?.description}</p>
</li>
))}
</ul>
</div>
)
}

View File

@@ -0,0 +1,11 @@
// Component for {% code-page-name /%} Markdoc tag.
// Returns the current page title in monospace (code) font.
// Useful in includes / templates that may be reused across pages.
export default function CodePageName(props: {
name: string
}) {
return (
<code>{props.name}</code>
)
}

View File

@@ -0,0 +1,28 @@
import { useState } from "react"
// Copyable URL component with click-to-copy functionality
export default function CopyableUrl({ url, translate }: { url: string; translate: (text: string) => string }) {
const [copied, setCopied] = useState(false)
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(url)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (err) {
console.error("Failed to copy:", err)
}
}
return (
<button
type="button"
className={`quick-ref-value-btn ${copied ? "copied" : ""}`}
onClick={handleCopy}
title={copied ? translate("Copied!") : translate("Click to copy")}
>
<code className="quick-ref-value">{url}</code>
<span className="copy-icon">{copied ? "✓" : ""}</span>
</button>
)
}

View File

@@ -0,0 +1,45 @@
// Component for {% interactive-block %} markdoc tag. Used in legacy interactive
// tutorials; not recommended for new tutorials.
import * as React from 'react'
import { useLocation } from 'react-router-dom'
import dynamicReact from '@markdoc/markdoc/dist/react'
import { idify } from '../helpers'
export default function InteractiveBlock(props: {
children: React.ReactNode
label: string
steps: string[]
}) {
const stepId = idify(props.label)
const { pathname } = useLocation()
return (
// add key={pathname} to ensure old step state gets rerendered on page navigation
<div className="interactive-block" id={'interactive-' + stepId} key={pathname}>
<div className="interactive-block-inner">
<div className="breadcrumbs-wrap">
<ul
className="breadcrumb tutorial-step-crumbs"
id={'bc-ul-' + stepId}
data-steplabel={props.label}
data-stepid={stepId}
>
{props.steps?.map((step, idx) => {
const iterStepId = idify(step).toLowerCase()
let className = `breadcrumb-item bc-${iterStepId}`
if (idx > 0) className += ' disabled'
if (iterStepId === stepId) className += ' current'
return (
<li className={className} key={iterStepId}>
<a href={`#interactive-${iterStepId}`}>{step}</a>
</li>
)
})}
</ul>
</div>
<div className="interactive-block-ui">{dynamicReact(props.children, React, {})}</div>
</div>
</div>
)
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
// Replaces Redocly's built-in language picker with our custom language picker component.
import styled from 'styled-components';
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';

View File

@@ -1,4 +1,6 @@
import * as React from "react";
// Replaces the top navbar with our custom XRPL.org top navbar
import React from 'react'
import { useThemeConfig, useThemeHooks } from "@redocly/theme/core/hooks";
import { LanguagePicker } from "@redocly/theme/components/LanguagePicker/LanguagePicker";
import { slugify } from "../../helpers";

View File

@@ -0,0 +1,14 @@
// Component for {% not-enabled /%} markdoc tag. Shows a flask icon with a
// tooltip so you can indicate that a feature is not enabled on the
// XRP Ledger Mainnet. Legacy usage, mostly; prefer {% amendment-disclaimer %}
// for most cases.
import { useThemeHooks } from '@redocly/theme/core/hooks'
export default function NotEnabled() {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
return (
<span className="status not_enabled" title={translate("This feature is not currently enabled on the production XRP Ledger.")}><i className="fa fa-flask"></i></span>
)
}

View File

@@ -0,0 +1,22 @@
// Create a link into the source code repository for this project.
// This is supposed to adjust so that PR builds use the branch+fork of the PR,
// but that part wasn't implemented for Redocly builds.
import * as React from 'react'
import dynamicReact from '@markdoc/markdoc/dist/react'
import { Link } from '@redocly/theme/components/Link/Link'
export default function RepoLink(props: {
children: React.ReactNode
path: string
github_fork: string
github_branch: string
}) {
const treeblob = props.path.indexOf(".") >= 0 ? "blob/" : "tree/"
const sep = props.github_fork[-1] == "/" ? "" : "/"
const href = props.github_fork+sep+treeblob+props.github_branch+"/"+props.path
return (
<Link to={href}>{dynamicReact(props.children, React, {})}</Link>
)
}

View File

@@ -1,5 +1,5 @@
// This component replaces the default Redocly Tabs functionality.
// Original Tabs styling is preserved, but this adds full-page tab
// Replaces Redocly's built-in {% tabs %} component.
// Uses the existing Tabs styling, but adds full-page tab
// switching and preserves tab preferences between pages.
import React, { useRef, useState, useEffect, useCallback, useMemo } from 'react'

View File

@@ -0,0 +1,82 @@
// Component for {% tx-category %} Markdoc tag. Shows a list (table?) of child pages
// with the matching labels in the frontmatter.
// Requires the index-pages Redocly plugin to get child page data.
import { useThemeHooks } from '@redocly/theme/core/hooks'
import { Link } from '@redocly/theme/components/Link/Link'
import { AmendmentDisclaimer } from './Amendments'
interface TxCategoryProps {
name: string,
}
export function TxCategory(props: TxCategoryProps) {
const { usePageSharedData } = useThemeHooks()
const data = usePageSharedData('index-page-items') as any[]
const matchingItems = data?.filter( (page) => {
if (page.labels && page.labels.includes(props.name)) {
return true
}
return false
})
return (
<div className="tx-type-list">
{
matchingItems?.map((item: any) => (
<TxTypeLink key={item.slug} page={item} />
))
}
</div>
)
}
const txIcons = {
"create": require('../../static/img/tx-icons/TransactionCreateIcon.svg'),
"modify": require('../../static/img/tx-icons/TransactionModifyIcon.svg'),
"finish": require('../../static/img/tx-icons/TransactionFinishIcon.svg'),
"cancel": require('../../static/img/tx-icons/TransactionCancelIcon.svg'),
"send": require('../../static/img/tx-icons/TransactionSendIcon.svg'),
"other": require('../../static/img/tx-icons/TransactionUnknownIcon.svg'),
}
function TxTypeLink(props: {page: any}) {
const page = props.page
let txIcon = txIcons["other"]
if (page.txIcon && txIcons[page.txIcon.toLowerCase()]) {
txIcon = txIcons[page.txIcon]
}
return (
<div className="tx-type">
<Link to={page.slug} className="tx-title"><img className="tx-type-icon" src={txIcon} alt="" /> {page.title}</Link>
{
page.requiredAmendment &&
<div className="required-amendment">
Requires: <AmendmentDisclaimer name={page.requiredAmendment} compact={true} mode="" />
</div>
}
{
page.seo?.description &&
<div className="tx-desc">{page.seo.description}</div>
}
</div>
)
}
export function TxIconLegend() {
return (
<div className="tx-icon-legend">
<h4 className="tx-icon-title">Icon Legend</h4>
<div className="d-flex flex-wrap">
{ Object.entries(txIcons).map( ([iconName, txIcon]) => {
return (
<div className="tx-legend-item" key={iconName}>
<img className="tx-type-icon" src={txIcon} alt="" />
{iconName}
</div>
)
})}
</div>
</div>
)
}

View File

@@ -0,0 +1,58 @@
import { Link } from '@redocly/theme/components/Link/Link'
import { useThemeHooks } from '@redocly/theme/core/hooks'
type TryItServer = 's1' | 's2' | 'xrplcluster' | 'testnet' | 'devnet' | 'testnet-clio' | 'devnet-clio'
export function TryIt(props: {
method: string,
server?: TryItServer
}) {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
let use_server = ""
if (props.server == "s1") {
use_server = "?server=wss%3A%2F%2Fs1.ripple.com%2F"
} else if (props.server == "s2") {
use_server = "?server=wss%3A%2F%2Fs2.ripple.com%2F"
} else if (props.server == "xrplcluster") {
use_server = "?server=wss%3A%2F%2Fxrplcluster.com%2F"
} else if (props.server == 'devnet') {
use_server = "?server=wss%3A%2F%2Fs.devnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet') {
use_server = "?server=wss%3A%2F%2Fs.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'devnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.devnet.rippletest.net%3A51233%2F"
}
const to_path = `/resources/dev-tools/websocket-api-tool${use_server}#${props.method}`
return (
<Link style={{marginBottom: "1rem", textDecoration: "none"}} className="btn btn-primary btn-arrow" to={to_path} target="_blank" role="button">{translate("component.tryit", "Try it!")}</Link>
)
}
export function TxExample(props: {
txid: string,
server?: TryItServer
}) {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
let use_server = ""
if (props.server == "s1") {
use_server = "&server=wss%3A%2F%2Fs1.ripple.com%2F"
} else if (props.server == "s2") {
use_server = "&server=wss%3A%2F%2Fs2.ripple.com%2F"
} else if (props.server == "xrplcluster") {
use_server = "&server=wss%3A%2F%2Fxrplcluster.com%2F"
} else if (props.server == 'devnet') {
use_server = "&server=wss%3A%2F%2Fs.devnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet') {
use_server = "&server=wss%3A%2F%2Fs.altnet.rippletest.net%3A51233%2F"
}
const ws_req = `req=%7B%22id%22%3A%22example_tx_lookup%22%2C%22command%22%3A%22tx%22%2C%22transaction%22%3A%22${props.txid}%22%2C%22binary%22%3Afalse%2C%22api_version%22%3A2%7D`
const to_path = `/resources/dev-tools/websocket-api-tool?${ws_req}${use_server}`
return (
<Link style={{marginBottom: "1rem", textDecoration: "none"}} className="btn btn-primary btn-arrow" to={to_path} target="_blank" role="button">{translate("component.queryexampletx", "Query example transaction")}</Link>
)
}

View File

@@ -1,3 +1,7 @@
// Components for creating grids of cards in the current site style.
// Used in both custom .page.tsx files as well as markdoc tags {% card-grid %}
// and {% xrpl-card %}.
import * as React from 'react';
import dynamicReact from '@markdoc/markdoc/dist/react';
import { Link } from '@redocly/theme/components/Link/Link';

View File

@@ -1,8 +1,8 @@
import * as React from 'react';
// Loading animation component used in various custom pages.
export interface XRPLoaderProps {
message?: string
show: boolean
show?: boolean
}
export default function XRPLoader(props: XRPLoaderProps) {

View File

@@ -3,7 +3,7 @@
*/
import { useEffect, useState } from 'react';
export const useThemeFromClassList = (classNames) => {
export const useThemeFromClassList = (classNames: Array<string>) => {
const [currentTheme, setCurrentTheme] = useState(null);
useEffect(() => {
@@ -40,7 +40,7 @@ export const useThemeFromClassList = (classNames) => {
return currentTheme;
};
export function slugify(s) {
export function slugify(s: string) {
const unacceptable_chars = /[^A-Za-z0-9._ ]+/g;
const whitespace_regex = /\s+/g;
s = s.replace(unacceptable_chars, "");

View File

@@ -1,158 +1,11 @@
import * as React from 'react'
import { useLocation } from 'react-router-dom'
// @ts-ignore
import dynamicReact from '@markdoc/markdoc/dist/react'
import { Link } from '@redocly/theme/components/Link/Link'
import { useThemeHooks } from '@redocly/theme/core/hooks'
import { idify } from '../helpers'
import { Button } from '@redocly/theme/components/Button/Button'
export { default as XRPLoader } from '../components/XRPLoader'
export { XRPLCard, CardGrid } from '../components/XRPLCard'
export { AmendmentsTable, AmendmentDisclaimer, Badge } from '../components/Amendments'
export { Tabs } from '../components/SyncedTabs'
export function IndexPageItems() {
const { usePageSharedData } = useThemeHooks()
const data = usePageSharedData('index-page-items') as any[]
return (
<div className="children-display">
<ul>
{data?.map((item: any) => (
<li className="level-1" key={item.slug}>
<Link to={item.slug}>{item.title}</Link>
{
item.status === "not_enabled" ? (<NotEnabled />) : ""
}
<p className="blurb child-blurb">{item.seo?.description}</p>
</li>
))}
</ul>
</div>
)
}
export function InteractiveBlock(props: {
children: React.ReactNode
label: string
steps: string[]
}) {
const stepId = idify(props.label)
const { pathname } = useLocation()
return (
// add key={pathname} to ensure old step state gets rerendered on page navigation
<div className="interactive-block" id={'interactive-' + stepId} key={pathname}>
<div className="interactive-block-inner">
<div className="breadcrumbs-wrap">
<ul
className="breadcrumb tutorial-step-crumbs"
id={'bc-ul-' + stepId}
data-steplabel={props.label}
data-stepid={stepId}
>
{props.steps?.map((step, idx) => {
const iterStepId = idify(step).toLowerCase()
let className = `breadcrumb-item bc-${iterStepId}`
if (idx > 0) className += ' disabled'
if (iterStepId === stepId) className += ' current'
return (
<li className={className} key={iterStepId}>
<a href={`#interactive-${iterStepId}`}>{step}</a>
</li>
)
})}
</ul>
</div>
<div className="interactive-block-ui">{dynamicReact(props.children, React, {})}</div>
</div>
</div>
)
}
export function RepoLink(props: {
children: React.ReactNode
path: string
github_fork: string
github_branch: string
}) {
const treeblob = props.path.indexOf(".") >= 0 ? "blob/" : "tree/"
const sep = props.github_fork[-1] == "/" ? "" : "/"
const href = props.github_fork+sep+treeblob+props.github_branch+"/"+props.path
return (
<Link to={href}>{dynamicReact(props.children, React, {})}</Link>
)
}
export function CodePageName(props: {
name: string
}) {
return (
<code>{props.name}</code>
)
}
type TryItServer = 's1' | 's2' | 'xrplcluster' | 'testnet' | 'devnet' | 'testnet-clio' | 'devnet-clio'
export function TryIt(props: {
method: string,
server?: TryItServer
}) {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
let use_server = ""
if (props.server == "s1") {
use_server = "?server=wss%3A%2F%2Fs1.ripple.com%2F"
} else if (props.server == "s2") {
use_server = "?server=wss%3A%2F%2Fs2.ripple.com%2F"
} else if (props.server == "xrplcluster") {
use_server = "?server=wss%3A%2F%2Fxrplcluster.com%2F"
} else if (props.server == 'devnet') {
use_server = "?server=wss%3A%2F%2Fs.devnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet') {
use_server = "?server=wss%3A%2F%2Fs.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'devnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.devnet.rippletest.net%3A51233%2F"
}
const to_path = `/resources/dev-tools/websocket-api-tool${use_server}#${props.method}`
return (
<Link style={{marginBottom: "1rem", textDecoration: "none"}} className="btn btn-primary btn-arrow" to={to_path} target="_blank" role="button">{translate("component.tryit", "Try it!")}</Link>
)
}
export function TxExample(props: {
txid: string,
server?: TryItServer
}) {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
let use_server = ""
if (props.server == "s1") {
use_server = "&server=wss%3A%2F%2Fs1.ripple.com%2F"
} else if (props.server == "s2") {
use_server = "&server=wss%3A%2F%2Fs2.ripple.com%2F"
} else if (props.server == "xrplcluster") {
use_server = "&server=wss%3A%2F%2Fxrplcluster.com%2F"
} else if (props.server == 'devnet') {
use_server = "&server=wss%3A%2F%2Fs.devnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet') {
use_server = "&server=wss%3A%2F%2Fs.altnet.rippletest.net%3A51233%2F"
}
const ws_req = `req=%7B%22id%22%3A%22example_tx_lookup%22%2C%22command%22%3A%22tx%22%2C%22transaction%22%3A%22${props.txid}%22%2C%22binary%22%3Afalse%2C%22api_version%22%3A2%7D`
const to_path = `/resources/dev-tools/websocket-api-tool?${ws_req}${use_server}`
return (
<Link style={{marginBottom: "1rem", textDecoration: "none"}} className="btn btn-primary btn-arrow" to={to_path} target="_blank" role="button">{translate("component.queryexampletx", "Query example transaction")}</Link>
)
}
export function NotEnabled() {
const { useTranslate } = useThemeHooks()
const { translate } = useTranslate()
return (
<span className="status not_enabled" title={translate("This feature is not currently enabled on the production XRP Ledger.")}><i className="fa fa-flask"></i></span>
)
}
export { default as ChildPages } from '../components/ChildPages'
export { default as NotEnabled } from '../components/NotEnabled'
export { default as InteractiveBlock } from '../components/InteractiveBlock'
export { default as RepoLink } from '../components/RepoLink'
export { default as CodePageName } from '../components/CodePageName'
export { TryIt, TxExample } from '../components/WSToolButtons'
export { TxCategory, TxIconLegend } from '../components/TxRefs'

View File

@@ -1,8 +1,8 @@
import { Schema, Tag } from '@markdoc/markdoc';
export const indexPageList: Schema & { tagName: string } = {
export const childPages: Schema & { tagName: string } = {
tagName: 'child-pages',
render: 'IndexPageItems',
render: 'ChildPages',
selfClosing: true,
};
@@ -243,3 +243,21 @@ export const amendmentDisclaimer: Schema & { tagName: string } = {
render: 'AmendmentDisclaimer',
selfClosing: true
}
export const txCategory: Schema & { tagName: string } = {
tagName: 'tx-category',
attributes: {
name: {
type: 'String',
required: true
}
},
render: 'TxCategory',
selfClosing: true,
};
export const txIconLegend: Schema & { tagName: string } = {
tagName: 'tx-icon-legend',
render: 'TxIconLegend',
selfClosing: true,
};

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

@@ -1,3 +1,10 @@
// The purpose of this plugin is to get a list of pages that are "children" of
// the current page and pass along their frontmatter as well as the path that
// can be used to link to those pages from other components.
// It uses experimental Redocly plugin interface stuff that is expected
// to be exposed in a "nicer" way in some theoretical future release.
// The ts-ignore and TODOs are more for Redocly's notes than for XRPLF.
// @ts-check
import { readSharedData } from '@redocly/realm/dist/server/utils/shared-data.js'; // TODO: export function from root package
const INDEX_PAGE_INFO_DATA_KEY = 'index-page-items';
@@ -26,14 +33,21 @@ export function indexPages() {
const item = findItemDeep(sidebar.items, route.fsPath);
const childrenPaths = (item.items || [])
.map((item) => item.fsPath)
.map(
// @ts-ignore
(item) => item.fsPath
)
.filter(Boolean);
const childRoutes = childrenPaths.map((fsPath) =>
const childRoutes = childrenPaths.map(
// @ts-ignore
(fsPath) =>
actions.getRouteByFsPath(fsPath),
);
const childRoutesData = await Promise.all(
childRoutes.map(async (route) => {
childRoutes.map(
// @ts-ignore
async (route) => {
const { data } = await cache.load(
route.fsPath,
'markdown-frontmatter',
@@ -63,6 +77,7 @@ export function indexPages() {
return instance;
}
// @ts-ignore
function findItemDeep(items, fsPath) {
for (const item of items) {
if (item.fsPath === fsPath) {
@@ -70,6 +85,7 @@ function findItemDeep(items, fsPath) {
}
if (item.items) {
// @ts-ignore
const found = findItemDeep(item.items, fsPath);
if (found) {
return found;

View File

@@ -1,12 +1,21 @@
// @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() {
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
const instance = {
id: 'tutorial-languages',
processContent: async (actions, { fs, cache }) => {
try {
/** @type {Record<string, string[]>} */
@@ -21,7 +30,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 +74,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 +133,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 +204,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

@@ -183,9 +183,10 @@ ul.nav.navbar-nav {
--link-color-visited: white;
--link-visited-decoration: underline;
--bg-color: var(--color-gray-10);
--bg-color: var(--color-gray-1); /* was --color-gray-10 */
--bg-color-raised: var(--color-gray-2);
--background-color: var(--bg-color);
--font-family-base: 'Work Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
--heading-font-family: var(--font-family-base);
@@ -233,6 +234,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;
@@ -261,7 +263,9 @@ ul.nav.navbar-nav {
--code-block-bg-color: var(--color-gray-8);
--code-block-controls-bg-color: var(--color-gray-8);
--code-block-controls-border: none;
--md-tabs-active-tab-bg-color: var(--color-gray-7);
--md-tabs-hover-tab-bg-color: var(--color-gray-8);
--inline-code-bg-color: var(--color-gray-8);
@@ -273,11 +277,15 @@ ul.nav.navbar-nav {
--language-picker-background-color: var(--color-gray-8);
--select-list-bg-color: var(--color-gray-8);
--button-bg-color-secondary: var(--color-gray-8);
--footer-title-text-color: black;
--bg-color: var(--color-gray-9);
--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 +293,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);
@@ -318,6 +327,9 @@ ul.nav.navbar-nav {
--code-block-tokens-keyword-color: var(--color-magenta-8);
--code-block-tokens-string-color: var(--color-blue-8);
--md-tabs-active-tab-bg-color: var(--color-gray-4);
--md-tabs-hover-tab-bg-color: var(--color-gray-3);
--code-panel-bg-color: var(--color-blue-3);
--layer-color-hover: var(--color-gray-3);
--bg-raised-gradient: "";
@@ -327,6 +339,12 @@ ul.nav.navbar-nav {
color: var(--text-color);
}
/* Keep active tab visually static on hover */
button[role="tab"].active {
--md-tabs-hover-tab-bg-color: var(--md-tabs-active-tab-bg-color);
--md-tabs-hover-tab-text-color: var(--md-tabs-active-tab-text-color);
}
/* Fix unnecessary horizontal scrolling of tables in Japanese */
[lang="ja"] table.md {
word-break: break-word;

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()

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