mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-06-07 10:46:45 +00:00
Compare commits
225 Commits
mcp-config
...
xrpld-3.2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7efd34ec28 | ||
|
|
66b53112ca | ||
|
|
1f13b68a34 | ||
|
|
3de10d242f | ||
|
|
f298b5a38b | ||
|
|
6c27a27ab2 | ||
|
|
22b015df0c | ||
|
|
356b91297a | ||
|
|
d7456276b9 | ||
|
|
22243abc7c | ||
|
|
a301253740 | ||
|
|
6a4b564fbc | ||
|
|
9f76d9efde | ||
|
|
994286cfb2 | ||
|
|
d66fa36f56 | ||
|
|
6c0fa9bf2d | ||
|
|
f61d74be6b | ||
|
|
00255ace3a | ||
|
|
d1fc03ca37 | ||
|
|
f9aae5f1e3 | ||
|
|
f700fa72ca | ||
|
|
bade50d826 | ||
|
|
9eb2742581 | ||
|
|
56455bf27b | ||
|
|
4230d16811 | ||
|
|
1064dad9b0 | ||
|
|
79419d0b49 | ||
|
|
0e510b349d | ||
|
|
087030b39a | ||
|
|
03dae02593 | ||
|
|
b82df3bb5d | ||
|
|
9e1bc798ba | ||
|
|
c8cb28fd80 | ||
|
|
b09fe6c7b3 | ||
|
|
28af114305 | ||
|
|
d846367857 | ||
|
|
8481e05d6e | ||
|
|
b0428e30d4 | ||
|
|
4b65061304 | ||
|
|
6d99930c9a | ||
|
|
84f78d83dd | ||
|
|
5311ffb3b4 | ||
|
|
4f991e14a5 | ||
|
|
da17bb427f | ||
|
|
c10456f103 | ||
|
|
a31124c94b | ||
|
|
7f2588c514 | ||
|
|
bba796d818 | ||
|
|
7d3145b0a1 | ||
|
|
a874b034d7 | ||
|
|
0defd68316 | ||
|
|
a439eef72b | ||
|
|
e94068b0db | ||
|
|
71e5ff4bdb | ||
|
|
ccaaa55ca3 | ||
|
|
e2da8d58a0 | ||
|
|
e0cc0849ad | ||
|
|
19b376be8e | ||
|
|
59a119db66 | ||
|
|
2041b55e4b | ||
|
|
d51da2ff5c | ||
|
|
19aad7809b | ||
|
|
508a39908c | ||
|
|
aca16f3609 | ||
|
|
187542fef5 | ||
|
|
0972abb0b6 | ||
|
|
6256e1d7c8 | ||
|
|
9f7c675517 | ||
|
|
202a16288c | ||
|
|
8c68d4a7ad | ||
|
|
5e63775a97 | ||
|
|
3e96de6323 | ||
|
|
4e7d0aadc9 | ||
|
|
bbe80b34c9 | ||
|
|
3152430e47 | ||
|
|
81279b4761 | ||
|
|
aaa4668392 | ||
|
|
89de054d4d | ||
|
|
583b169680 | ||
|
|
1241a33a5d | ||
|
|
d0f6d04715 | ||
|
|
f8d7ca470d | ||
|
|
d0187414a5 | ||
|
|
aed88784d9 | ||
|
|
dc13312be6 | ||
|
|
a063951f9e | ||
|
|
6ac6893f4a | ||
|
|
25bfaca2c0 | ||
|
|
5728345a42 | ||
|
|
f60393e9fa | ||
|
|
028e523b6d | ||
|
|
d945d6a5d6 | ||
|
|
fed058fe51 | ||
|
|
c3e898c047 | ||
|
|
98db42f996 | ||
|
|
3f551f68e3 | ||
|
|
4c5f65ff54 | ||
|
|
2fd46e197b | ||
|
|
cf92ef36ae | ||
|
|
9b72e6c6ff | ||
|
|
7b42cbb02a | ||
|
|
0e4ae322f7 | ||
|
|
5e500d58ca | ||
|
|
cb48d4f789 | ||
|
|
39f5b9ab66 | ||
|
|
02b275e157 | ||
|
|
3d3ac6adb3 | ||
|
|
06b92f44d6 | ||
|
|
31ae9f6a00 | ||
|
|
7f907dd168 | ||
|
|
539cef2510 | ||
|
|
e45149a6c7 | ||
|
|
1ca0c3371b | ||
|
|
0da70afdff | ||
|
|
6673e6e0fe | ||
|
|
4e1ea13709 | ||
|
|
513c86dff3 | ||
|
|
61529895af | ||
|
|
f31b3e7ca4 | ||
|
|
6a5ce20028 | ||
|
|
7e6234d9cf | ||
|
|
744721d8b3 | ||
|
|
601e79ed00 | ||
|
|
e05aa8259b | ||
|
|
b058513278 | ||
|
|
b7be1f878e | ||
|
|
ac60d7786b | ||
|
|
394eb4b5d4 | ||
|
|
cd4bb02ae2 | ||
|
|
a309eb51c5 | ||
|
|
578e8cdefc | ||
|
|
c5b161c746 | ||
|
|
f493ca49cf | ||
|
|
5f47643585 | ||
|
|
7036a75881 | ||
|
|
57fde744fd | ||
|
|
91b68bae6a | ||
|
|
b1eaf8c051 | ||
|
|
b0e99161bb | ||
|
|
a441171000 | ||
|
|
f2109aab33 | ||
|
|
892714550e | ||
|
|
41788b9323 | ||
|
|
04cfa17880 | ||
|
|
b6388ccb13 | ||
|
|
201a250072 | ||
|
|
c04c9042b8 | ||
|
|
6ab5de13bb | ||
|
|
38000f19d6 | ||
|
|
ad9e5e14fa | ||
|
|
663cd6df6a | ||
|
|
6bee1983eb | ||
|
|
9df53455e9 | ||
|
|
13dddb8b22 | ||
|
|
b47c96d91a | ||
|
|
7f7903a336 | ||
|
|
78da4d6f3b | ||
|
|
93abc4dc09 | ||
|
|
ae266aba7f | ||
|
|
ab9eec63f5 | ||
|
|
8b8ed4c6ea | ||
|
|
6aaca7f568 | ||
|
|
a045e9e40c | ||
|
|
ad9327c4c0 | ||
|
|
53983bf8e2 | ||
|
|
aa3b5e173c | ||
|
|
0328e75280 | ||
|
|
17abd49d92 | ||
|
|
4bceb09b1b | ||
|
|
d0c622abf4 | ||
|
|
0ed490da0b | ||
|
|
5fe416654f | ||
|
|
0da3a1e13c | ||
|
|
e0aeab7157 | ||
|
|
84987aa2df | ||
|
|
175e0a96eb | ||
|
|
d4be722dbc | ||
|
|
839da237b6 | ||
|
|
00e636c562 | ||
|
|
3cb15342a7 | ||
|
|
4346cf9a94 | ||
|
|
6638ea8f6c | ||
|
|
476ab17cc5 | ||
|
|
7d0b02bae9 | ||
|
|
7ab575804a | ||
|
|
2801055b14 | ||
|
|
1d52bd8eaa | ||
|
|
dbabe8171d | ||
|
|
66522b160e | ||
|
|
16b63f9c88 | ||
|
|
32b14d2a14 | ||
|
|
72fb4710ec | ||
|
|
10431480f1 | ||
|
|
c5f81cea25 | ||
|
|
9393f40366 | ||
|
|
9f4fcc845f | ||
|
|
d54684f45a | ||
|
|
c505b992e0 | ||
|
|
26c966fa51 | ||
|
|
86618b900f | ||
|
|
b634b6902e | ||
|
|
1540e36b8b | ||
|
|
598a15eef5 | ||
|
|
7cd8e31a21 | ||
|
|
e56324c57c | ||
|
|
5ce6218fd5 | ||
|
|
9934414492 | ||
|
|
ba7694f472 | ||
|
|
102a7cc8b9 | ||
|
|
0ac42f6acf | ||
|
|
bc0bcfa89b | ||
|
|
f9b2bce755 | ||
|
|
c53eddfbd8 | ||
|
|
1aac0d4fa2 | ||
|
|
a8e70dc49b | ||
|
|
bce839d6b3 | ||
|
|
295fbc8a4e | ||
|
|
1a3b3d47ac | ||
|
|
28c30fad41 | ||
|
|
5d2e8f5f98 | ||
|
|
b20b963c8c | ||
|
|
91380d73e1 | ||
|
|
1ba708467b | ||
|
|
d1adbd575a | ||
|
|
d4726e0816 |
19
.claude/CLAUDE.md
Normal file
19
.claude/CLAUDE.md
Normal 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
7
.claude/settings.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"permissions": {
|
||||
"deny": [
|
||||
"Bash(git push *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
117
.claude/skills/generate-release-notes/SKILL.md
Normal file
117
.claude/skills/generate-release-notes/SKILL.md
Normal 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.
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,6 +8,8 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/go/go.sum
|
||||
_code-samples/*/java/target/
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
seo:
|
||||
description: XRP Ledgerのアカウントのプロパティーを修正します。
|
||||
labels:
|
||||
- アカウント
|
||||
- Accounts
|
||||
txIcon: modify
|
||||
---
|
||||
# AccountSet
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
seo:
|
||||
description: 自動マーケットメーカープールに発行済みトークンを預け入れた保有者から、トークンを回収する。
|
||||
labels:
|
||||
- AMM
|
||||
- Tokens
|
||||
- AMM
|
||||
- DEX
|
||||
requiredAmendment: AMMClawback
|
||||
txIcon: cancel
|
||||
---
|
||||
# AMMClawback
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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の例
|
||||
|
||||
### 単一アカウントの場合
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
seo:
|
||||
description: 発行したトークンを取り戻します。
|
||||
labels:
|
||||
- トークン
|
||||
- Tokens
|
||||
requiredAmendment: Clawback
|
||||
txIcon: cancel
|
||||
---
|
||||
# Clawback
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
seo:
|
||||
description: レジャーから認証情報を削除し、事実上失効させます。
|
||||
status: not_enabled
|
||||
labels:
|
||||
- Decentralized Storage
|
||||
- Credentials
|
||||
requiredAmendment: Credentials
|
||||
txIcon: cancel
|
||||
---
|
||||
# CredentialDelete
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 /%}
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
seo:
|
||||
description: 新しいMulti-Purpose Tokenを発行します。
|
||||
labels:
|
||||
- Multi-Purpose Token, MPT
|
||||
- Multi-purpose Tokens, MPTs
|
||||
- Tokens
|
||||
requiredAmendment: MPTokensV1
|
||||
txIcon: create
|
||||
---
|
||||
|
||||
# MPTokenIssuanceCreate
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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)を作成します。
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 "ソース")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の型 | [内部の型][] | 説明 |
|
||||
|:-------------|:----------|:------------------|:------------------------------|
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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枚以上のチケットを所有することはできません。 |
|
||||
|
||||
@@ -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)を作成または変更します。
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -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の例
|
||||
|
||||
|
||||
@@ -1,274 +0,0 @@
|
||||
---
|
||||
html: use-tickets.html
|
||||
parent: manage-account-settings.html
|
||||
seo:
|
||||
description: チケットは、通常のシーケンス順序以外でトランザクションを送信するために使用します。
|
||||
embed_xrpl_js: true
|
||||
filters:
|
||||
- interactive_steps
|
||||
labels:
|
||||
- アカウント
|
||||
steps: ['Generate', 'Connect', 'Check Sequence', 'Prepare & Sign', 'Submit', 'Wait', 'Intermission', 'Check Tickets', 'Prepare Ticketed Tx', 'Submit Ticketed Tx', 'Wait Again']
|
||||
---
|
||||
# チケットの使用
|
||||
|
||||
[チケット](../../../references/protocol/ledger-data/ledger-entry-types/ticket.md)は、通常の順序ではないトランザクションを送信する方法を提供します。このチュートリアルでは、チケットを作成し、それを使って別のトランザクションを送信する手順を説明します。
|
||||
|
||||
## 前提条件
|
||||
|
||||
<!-- Source for this tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/use-tickets.js"></script>
|
||||
|
||||
このページでは、[xrpl.js](https://js.xrpl.org/)ライブラリを使用したJavaScriptのサンプルを提供しています。設定方法は、[JavaScriptを使ってみよう](/docs/tutorials/get-started/get-started-javascript.md)をご覧ください。
|
||||
|
||||
JavaScriptはWebブラウザ上で動作するため、セットアップなしで読み進められ、インタラクティブな手順を利用することができます。
|
||||
|
||||
|
||||
|
||||
## 手順
|
||||
|
||||
このチュートリアルはいくつかの段階に分かれています。
|
||||
|
||||
- (Steps 1-2) **準備:** XRP Ledgerのアドレスとシークレットが必要です。本番環境では、同じアドレスとシークレットを一貫して使用することができます。このチュートリアルでは、必要に応じて新しいテスト認証情報を生成することができます。また、ネットワークに接続されている必要があります。
|
||||
- (Steps 3-6) **チケットの作成:** トランザクションを送信して、いくつかのチケットを確保します。
|
||||
- (任意) **休憩:** チケットを作成した後、以下のステップの前、中、後にいつでも様々な他のトランザクションを送信することができます。
|
||||
- (Steps 7-10) **チケットの使用:** 設定されているチケットのうち1枚を使ってトランザクションを送信します。使用するチケットが1枚でも残っていれば、前の部分を飛ばしてこの手順を繰り返すことができます。
|
||||
|
||||
### 1. クレデンシャルの入手
|
||||
|
||||
XRP Ledgerでトランザクションを送信するには、アドレスと秘密鍵、そしてXRPが必要です。開発用には、[Testnet](../../../concepts/networks-and-servers/parallel-networks.md)で以下のようなインターフェースを使ってこれらを入手することができます。
|
||||
|
||||
{% partial file="/@l10n/ja/docs/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
[本番環境のソフトウェアを作成する場合](/docs/tutorials)には、既存のアカウントを使用し、[安全な署名](../../../concepts/transactions/secure-signing.md)を使用して鍵を管理する必要があります。
|
||||
|
||||
|
||||
### 2. ネットワークへの接続
|
||||
|
||||
トランザクションをネットワークに送信するには、ネットワークに接続している必要があります。チケットは今のところDevnetでしか利用できないので、Devnetサーバに接続する必要があります。例えば、以下のようになります。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Connect to" before="// Get credentials" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% admonition type="info" name="注記" %}このチュートリアルのコードサンプルでは、JavaScriptの[`async`/`await`パターン](https://javascript.info/async-await)を使用しています。`await`は`async`関数の中で使用する必要があるため、残りのコードサンプルはここから始まる`main()`関数の中で続けるように書かれています。なお、`async`/`await`の代わりにPromiseのメソッド`.then()`や`.catch()`を使うこともできます。{% /admonition %}
|
||||
|
||||
このチュートリアルでは、以下のボタンをクリックして接続します。
|
||||
|
||||
{% partial file="/@l10n/ja/docs/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. シーケンス番号の確認
|
||||
|
||||
チケットを作成する前に、自分のアカウントの[シーケンス番号][]を確認しておきましょう。次のステップのために現在のシーケンス番号が必要であり、設定されるチケットのシーケンス番号はこの番号から始まります。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Sequence" before="// Prepare and Sign TicketCreate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Check Sequence" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-sequence" class="btn btn-primary previous-steps-required">Check Sequence Number</button>
|
||||
|
||||
{% loading-icon message="Querying..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 4. TicketCreateの準備と署名
|
||||
|
||||
前のステップで決定したシーケンス番号を使用して、[TicketCreateトランザクション][]を構築します。`TicketCount`フィールドを使って、作成するチケットの枚数を指定します。例えば、10枚のチケットを作成するトランザクションを準備するには、次のようにします。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign TicketCreate" before="// Submit TicketCreate" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
トランザクションのハッシュと`LastLedgerSequence`の値を記録しておけば、[後で検証されたかどうかを確認](../../../concepts/transactions/reliable-transaction-submission.md)することができます。
|
||||
|
||||
|
||||
{% interactive-block label="Prepare & Sign" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="prepare-and-sign" class="btn btn-primary previous-steps-required">Prepare & Sign</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 5. TicketCreateの提出
|
||||
|
||||
前のステップで作成した署名付きトランザクションBlobを送信します。例えば、以下のようになります。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit TicketCreate" before="// Wait for Validation" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Submit" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="ticketcreate-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob" data-wait-step-name="Wait">Submit</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. 検証の待機
|
||||
|
||||
ほとんどのトランザクションは、送信された後に次の台帳のバージョンに受け入れられます。つまり、トランザクションの結果が確定するまでに4~7秒かかることがあります。XRP Ledgerが混雑している場合や、ネットワークの接続性が悪いためにトランザクションがネットワーク全体に中継されない場合は、トランザクションが確定するまでに時間がかかることがあります。(トランザクションの有効期限を設定する方法については、[信頼できるトランザクションの送信](../../../concepts/transactions/reliable-transaction-submission.md)をご覧ください)。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Wait for Validation" before="// Check Available" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% partial file="/@l10n/ja/docs/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### (任意) 休憩
|
||||
|
||||
チケットの強みは、チケットを使ったトランザクションの準備をしている間も、アカウントの業務を通常通り行うことができる点にあります。チケットを使用してトランザクションを送信する場合、別のチケットを使用しているものも含め、他のトランザクションの送信と並行して行うことができ、いつでもチケット付きトランザクションを送信することができます。唯一の制約は、1つのチケットは1回しか使用できないということです。
|
||||
|
||||
{% admonition type="success" name="ヒント" %}以下のステップの間または途中で、ここに戻ってきてシーケンス取引を送信することができますが、その際、チケット取引の成功を妨げることはありません。{% /admonition %}
|
||||
|
||||
{% interactive-block label="Intermission" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="intermission-payment" class="btn btn-primary previous-steps-required">Payment</button>
|
||||
<button id="intermission-escrowcreate" class="btn btn-primary previous-steps-required">EscrowCreate</button>
|
||||
<button id="intermission-accountset" class="btn btn-primary previous-steps-required">AccountSet</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
|
||||
### 7. 有効なチケットの確認
|
||||
|
||||
チケット付きのトランザクションを送信したい場合、どのチケットシーケンス番号を使用するかを知る必要があります。アカウントを注意深く管理していれば、どのチケットを持っているかはすでにわかっていると思いますが、よくわからない場合は、[account_objectsメソッド][]を使って、利用可能なチケットを調べることができます。例えば、以下のようになります。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Check Available Tickets" before="// Prepare and Sign Ticketed" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
|
||||
{% interactive-block label="Check Tickets" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="check-tickets" class="btn btn-primary previous-steps-required">Check Tickets</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
{% admonition type="success" name="ヒント" %}チケットが残っている限り、ここから最後まで同じ手順を繰り返すことができます。{% /admonition %}
|
||||
|
||||
### 8. チケット付きトランザクションの準備
|
||||
|
||||
チケットが利用できるようになったので、それを使用するトランザクションを準備します。
|
||||
|
||||
ここでは、好きな[トランザクションのタイプ](../../../references/protocol/transactions/types/index.md)を使用することができます。次の例では、何も行わない[AccountSetトランザクション][]を使用していますが、これはレジャーに他の設定を必要としないからです。`Sequence`フィールドを`0`に設定して、利用可能なチケットの1つのチケットシーケンス番号を持つ`TicketSequence`フィールドを含めます。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Prepare and Sign Ticketed" before="// Submit Ticketed Transaction" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% admonition type="success" name="ヒント" %}
|
||||
TicketCreateトランザクションをすぐに送信する予定がない場合は、トランザクションが期限切れにならないように、`LastLedgerSequence`を設定しないようにする必要があります。これを行う方法はライブラリによって異なります。
|
||||
|
||||
- **xrpl.js:** トランザクションの自動入力の際に、`"LastLedgerSequence": null`を指定する。
|
||||
- **`rippled`:** 用意された指示から`LastLedgerSequence`を省略します。サーバはデフォルトでは値を提供しません。
|
||||
{% /admonition %}
|
||||
|
||||
{% interactive-block label="Prepare Ticketed Tx" steps=$frontmatter.steps %}
|
||||
|
||||
<div id="ticket-selector">
|
||||
<h4>Select a Ticket:</h4>
|
||||
<div class="form-area"></div>
|
||||
</div>
|
||||
<button id="prepare-ticketed-tx" class="btn btn-primary previous-steps-required">Prepare Ticketed Transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 9. チケット付きトランザクションの送信
|
||||
|
||||
前のステップで作成した署名付きトランザクションBlobを送信します。例えば、以下のようになります。
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/use-tickets/js/use-tickets.js" from="// Submit Ticketed Transaction" before="// Wait for Validation (again)" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Submit Ticketed Tx" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="ticketedtx-submit" class="btn btn-primary previous-steps-required" data-tx-blob-from="#tx_blob_t" data-wait-step-name="Wait Again">Submit</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 10. 検証の待機
|
||||
|
||||
チケット付きトランザクションは、シーケンス付きトランザクションと同じようにコンセンサスプロセスを経ます。
|
||||
|
||||
{% partial file="/@l10n/ja/docs/_snippets/interactive-tutorials/wait-step.md" variables={label: "Wait Again"} /%}
|
||||
|
||||
## マルチシグで使用する
|
||||
|
||||
チケットの主な使用例としては、複数の[マルチシグ](../../../concepts/accounts/multi-signing.md)を並行して集めることができます。チケットを使用することで、複数署名されたトランザクションが完全に署名されて準備が整った時点で、どれが先に準備されるかを気にすることなく送信することができます。
|
||||
|
||||
このシナリオでは、[step8,「チケット付きトランザクションの準備」](#8-チケット付きトランザクションの準備)が若干異なります。準備と署名を一度に行うのではなく、[任意のマルチシグトランザクションの送信](../key-management/send-a-multi-signed-transaction.md)の手順に従うことになります。まずトランザクションを準備し、次に信頼できる署名者の間でトランザクションを循環させて署名を集め、最後に署名を組み合わせて最終的なマルチシグトランザクションを作成します。
|
||||
|
||||
複数の異なるトランザクションを処理する場合、それぞれが異なるチケットを使用する限り、この作業を並行して行うことができます。
|
||||
|
||||
|
||||
## 関連項目
|
||||
|
||||
- **Concepts:**
|
||||
- [チケット](../../../concepts/accounts/tickets.md)
|
||||
- [マルチシグ](../../../concepts/accounts/multi-signing.md)
|
||||
- **Tutorials:**
|
||||
- [マルチシグの設定](../key-management/set-up-multi-signing.md)
|
||||
- [信頼出来るトランザクションの送信](../../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- **References:**
|
||||
- [account_objectsメソッド][]
|
||||
- [sign_forメソッド][]
|
||||
- [submit_multisignedメソッド][]
|
||||
- [TicketCreateトランザクション][]
|
||||
- [トランザクションの共通フィールド](../../../references/protocol/transactions/common-fields.md)
|
||||
|
||||
{% raw-partial file="/@l10n/ja/docs/_snippets/common-links.md" /%}
|
||||
@@ -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'
|
||||
|
||||
26
@theme/components/ChildPages.tsx
Normal file
26
@theme/components/ChildPages.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
11
@theme/components/CodePageName.tsx
Normal file
11
@theme/components/CodePageName.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
28
@theme/components/CopyableUrl.tsx
Normal file
28
@theme/components/CopyableUrl.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
45
@theme/components/InteractiveBlock.tsx
Normal file
45
@theme/components/InteractiveBlock.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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";
|
||||
|
||||
14
@theme/components/NotEnabled.tsx
Normal file
14
@theme/components/NotEnabled.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
22
@theme/components/RepoLink.tsx
Normal file
22
@theme/components/RepoLink.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
82
@theme/components/TxRefs.tsx
Normal file
82
@theme/components/TxRefs.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
58
@theme/components/WSToolButtons.tsx
Normal file
58
@theme/components/WSToolButtons.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, "");
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInnerText } from '@redocly/realm/dist/server/plugins/markdown/markdoc/helpers/get-inner-text.js';
|
||||
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js';
|
||||
|
||||
import { dirname, relative, join as joinPath } from 'path';
|
||||
import markdoc from '@markdoc/markdoc';
|
||||
import moment from "moment";
|
||||
|
||||
export function blogPosts() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'blog-posts',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const posts = [];
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInnerText } from '@redocly/realm/dist/server/plugins/markdown/markdoc/helpers/get-inner-text.js';
|
||||
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js';
|
||||
|
||||
import { dirname, relative, join as joinPath } from 'path';
|
||||
|
||||
export function codeSamples() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'code-samples',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const samples = [];
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
// 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';
|
||||
|
||||
export function indexPages() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'index-pages',
|
||||
// hook that gets executed after all routes were created
|
||||
async afterRoutesCreated(actions, { cache }) {
|
||||
// get all the routes that are ind pages
|
||||
@@ -25,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',
|
||||
@@ -62,6 +77,7 @@ export function indexPages() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
function findItemDeep(items, fsPath) {
|
||||
for (const item of items) {
|
||||
if (item.fsPath === fsPath) {
|
||||
@@ -69,6 +85,7 @@ function findItemDeep(items, fsPath) {
|
||||
}
|
||||
|
||||
if (item.items) {
|
||||
// @ts-ignore
|
||||
const found = findItemDeep(item.items, fsPath);
|
||||
if (found) {
|
||||
return found;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
207
@theme/plugins/tutorial-metadata.js
Normal file
207
@theme/plugins/tutorial-metadata.js
Normal 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 };
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# コントリビューター行動規範
|
||||
|
||||
## 誓約
|
||||
|
||||
私たちコントリビューターとメンテナーは、オープンで友好的な環境を育むために、年齢、体格、身体障害、民族、性的特徴、性自認および性表現、経験の度合い、学歴、社会経済的地位、国籍、容姿、人種、宗教、性的同一性および性的指向などを問わず、誰もが私たちのプロジェクトとコミュニティーに不快な思いをすることなく参加できるよう努めることを誓います。
|
||||
|
||||
## 標準
|
||||
|
||||
前向きな環境を作り上げることに貢献する行動の例:
|
||||
|
||||
* 友好的で差別のない言葉の使用
|
||||
* 異なる観点や経験の尊重
|
||||
* 建設的な批判の素直な受け入れ
|
||||
* コミュニティーにとっての最善への注力
|
||||
* 他のコミュニティーメンバーへの共感の表示
|
||||
|
||||
前向きな環境を作り上げることに貢献しない行動の例:
|
||||
|
||||
* 性的な意味を含む言葉や画像の使用、望まない性的注目や誘いかけ
|
||||
* あおり、侮辱的または軽蔑的なコメント、個人攻撃や政治攻撃
|
||||
* 公的または私的な嫌がらせ
|
||||
* 住所やメールアドレスなどの個人情報の、明確な許可なしでの公開
|
||||
* 職場において不適切であると合理的に考えられる、その他の行為
|
||||
|
||||
## 責任
|
||||
|
||||
プロジェクトのメンテナーは、許容できる行動の基準を明確にする責任があります。また、許容できない行動がなされた場合に、適切かつ公平な是正処置を講じることが期待されます。
|
||||
|
||||
プロジェクトのメンテナーは、この行動規範に沿わないコメント、コミット、コード、wiki編集、issueなどの投稿を削除、編集、拒否する権利と義務を有します。また、他の不適切、脅迫的、攻撃的、嫌がらせと考えられる行動を取ったコントリビューターを一時的もしくは恒久的に追放する権利と義務を有します。
|
||||
|
||||
## 適用範囲
|
||||
|
||||
この行動規範はすべてのプロジェクトスペース内で適用されます。また、個人がパブリックスペースでプロジェクトやコミュニティーを代表する際にも適用されます。プロジェクトやコミュニティーを代表する際の例としては、プロジェクトの公式メールアドレスを使用すること、公式ソーシャルメディアアカウントで投稿すること、もしくはオンラインまたはオフラインのイベントで、任命された代表者を務めることが挙げられます。プロジェクトを代表する行為については、プロジェクトのメンテナーがさらに細かく定義して明確にすることができます。
|
||||
|
||||
## 執行
|
||||
|
||||
暴言、嫌がらせ、またはその他の許容できない行動は、プロジェクトチーム(<ripplex@ripple.com>)に連絡して報告することができます。すべての申し立ては確認、調査されたうえで、その状況に対して必要かつ適切と判断された対応が取られます。プロジェクトチームは、事象の報告者に関する秘密を保持する義務があります。特定の執行方針の詳細は、別途掲載される場合があります。
|
||||
|
||||
この行動規範を誠実に遵守または執行することができないプロジェクトのメンテナーは、プロジェクトを率いる他のメンバーの判断により、一時的または恒久的な措置が執られることがあります。
|
||||
|
||||
## 帰属
|
||||
|
||||
この行動規範は、[コントリビューター行動規範][ホームページ]バージョン1.4(https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)から抜粋したものです。
|
||||
|
||||
[ホームページ]: https://www.contributor-covenant.org
|
||||
この行動規範に関するよくある質問と回答については、https://www.contributor-covenant.org/faq をご覧ください。
|
||||
@@ -1,48 +0,0 @@
|
||||
# Código de conducta del pacto de contribuidores
|
||||
|
||||
## Nuestro compromiso
|
||||
|
||||
Con el fin de fomentar un ambiente abierto y acogedor, nosotros, como contribuidores y mantenedores, nos comprometemos a hacer de la participación en nuestro proyecto y nuestra comunidad una experiencia libre de acoso para todos, independientemente de, entre otras, características como la edad, tamaño corporal, discapacidad, origen étnico, características sexuales, identidad y expresión de género, nivel de experiencia, educación, estatus socioeconómico, nacionalidad, apariencia personal, raza, religión o identidad y orientación sexual.
|
||||
|
||||
## Nuestros estándares
|
||||
|
||||
Ejemplos de comportamiento que contribuyen a crear un ambiente positivo incluyen:
|
||||
|
||||
* Utilizar lenguaje acogedor e inclusivo
|
||||
* Ser respetuoso con los diferentes puntos de vista y experiencias
|
||||
* Saber aceptar las críticas constructivas
|
||||
* Centrarse en lo que es lo mejor para la comunidad
|
||||
* Mostrar empatía hacia otros miembros de la comunidad
|
||||
|
||||
Ejemplos de comportamiento que no contribuyen a crear un ambiente positivo incluyen:
|
||||
|
||||
* Utilizar un lenguaje o imágenes sexualizadas y atención o insinuaciones sexuales no deseadas
|
||||
* Trolear, comentario insultantes/peyorativos y ataques personales o políticos
|
||||
* Acoso público o en privado
|
||||
* Publicar información privada de otras personas, así cómo direcciones físicas o electrónicas, sin permiso explícito
|
||||
* Cualquier otra conducta que pueda ser razonablemente considerada inapropiada en un sentido profesional
|
||||
|
||||
## Nuestras responsabilidades
|
||||
|
||||
Los mantenedores del proyecto son responsables de aclarar los estándares de comportamiento aceptable y se espera que tomen acciones correctivas justas y apropiadas en respuesta a cualquier caso de comportamiento inaceptable.
|
||||
|
||||
Los mantenedores del proyecto tienen el derecho y la responsaiblidad de eliminar, editar o rechazar comentarios, commits, código, ediciones de wiki, problemas y otras contribuciones que no estén alineadas con este Código de Conducta, o de expulsar temporal o definitivamente a cualquier colaborador por otros comportamientos que consideren inapropiados, amenazantes, ofensivos, dañinos o que viole de cualquier modo este Código de Conducta.
|
||||
|
||||
## Alcance
|
||||
|
||||
Este Código de Conducta aplica en todos los espacios del proyecto y también aplica cuando un individuo está representando el proyecto o su comunidad en espacios públicos. Ejemplos de representación de un proyecto o la comunidad incluye usar un correo electrónico oficial del proyecto, publicaciones a través de una cuenta oficial de redes sociales o actuar como representante asignado en un evento en línea o en la vida real. La representación de un proyecto debe ser definida y aclarada con más detalle por los mantenedores del proyecto.
|
||||
|
||||
## Aplicación
|
||||
|
||||
Los casos de comportamiento abusivo, acoso, o de cualquier otro modo inaceptable se pueden informar contactando con el equipo del proyecto al correo <ripplex@ripple.com>. Todas las quejas serán revisadas e investigadas y resultarán en una resupuesta que se considere adecuada y necesaria a las circunstancias. El equipo del proyecto está obligado a mantener la confidencialidad con respecto al informador del incidente. Podría darse el caso de publicar más detalles sobre políticas de comportamiento específicas.
|
||||
|
||||
Los mantenedores de proyecto que no sigan o hagan cumplir el Código de conducta de buena fe podrían enfrentarse a repercusiones temporales o definitivas según lo determinen otros miembros que lideren el proyecto.
|
||||
|
||||
## Atribución
|
||||
|
||||
Este Código de Conducta está adaptado de el [Pacto del Contribuidores][inicio], versión 1.4, disponible en https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[inicio]: https://www.contributor-covenant.org
|
||||
|
||||
Para respuestas a preguntas comunes sobre este código de conducta, visita
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -1,3 +0,0 @@
|
||||
# Contribuir
|
||||
|
||||
Para obtener información sobre cómo contribuir a este repositorio, consulta [Contribute Documentation (XRPL.org)](https://xrpl.org/es_ES/contribute-documentation.html).
|
||||
@@ -1,3 +0,0 @@
|
||||
# コントリビューション
|
||||
|
||||
コントリビューションの情報には[「ドキュメントへの貢献」](https://xrpl.org/ja/contribute-documentation.html)をご覧ください。
|
||||
54
README.md
54
README.md
@@ -1,12 +1,10 @@
|
||||
# XRPL Dev Portal
|
||||
|
||||
The [XRP Ledger Dev Portal](https://xrpl.org) is the authoritative source for XRP Ledger documentation, including the `rippled` server, client libraries, and other open-source XRP Ledger software.
|
||||
The [XRP Ledger Dev Portal](https://xrpl.org) is the authoritative source for XRP Ledger documentation, including the core server, client libraries, and other open-source XRP Ledger software.
|
||||
|
||||
The site is built and published using Redocly.
|
||||
|
||||
NOTE: The toolchain used to build and publish the site has recently been migrated from Dactyl to Redocly.
|
||||
|
||||
Before you proceed, make sure you have Node version >= 18 LTS.
|
||||
Before you proceed, make sure you have Node.js and NPM installed. The site is tested with the current LTS release of each.
|
||||
|
||||
To build the site locally:
|
||||
|
||||
@@ -26,58 +24,18 @@ To build the site locally:
|
||||
|
||||
npm start
|
||||
|
||||
For more details, see the [contribution guidelines (EN)](CONTRIBUTING.md) ([日本語](CONTRIBUTING.ja.md)) and the [contributor Code of Conduct (EN)](CODE-OF-CONDUCT.md) ([日本語](CODE-OF-CONDUCT.ja.md)).
|
||||
|
||||
## Domain Verification Checker
|
||||
|
||||
If you make changes to the [Domain Verification Checker](https://xrpl.org/validator-domain-verifier.html) tool and edit the domain-verifier-checker.js file, you will need to do the following:
|
||||
|
||||
1. Install [webpack](https://webpack.js.org/) and required libraries via npm:
|
||||
|
||||
npm install webpack webpack-cli --save-dev
|
||||
npm install ripple-binary-codec ripple-address-codec ripple-keypairs
|
||||
|
||||
2. From the project root directory (this step may be different depending on how you installed webpack)
|
||||
|
||||
cd assets/js
|
||||
webpack-cli domain-verifier-checker.js --optimize-minimize -o domain-verifier-bundle.js
|
||||
|
||||
3. Build the site:
|
||||
|
||||
npm start
|
||||
For more details, see the [contribution guidelines (EN)](CONTRIBUTING.md) ([日本語](@l10n/ja/CONTRIBUTING.md)) and the [contributor Code of Conduct (EN)](CODE-OF-CONDUCT.md) ([日本語](@l10n/ja/CODE-OF-CONDUCT.md)).
|
||||
|
||||
|
||||
### Internationalization
|
||||
### Localization / Translations
|
||||
|
||||
This repo includes English (en) and Japanese (ja) locales.
|
||||
|
||||
This is done by setting up the internationalization (@l10n) folders, adding the `i18n` configuration to your `redocly.yaml` file, and adding the translated content in the respective language directory under the @l10n directory.
|
||||
|
||||
To add support for a new language:
|
||||
|
||||
1. Create a new subdirectory in the @l10n directory of the portal. For example, to add support for Spanish, create a new subdirectory "es-ES".
|
||||
|
||||
2. Update the i18n configuration in your `redocly.yaml` file defining the display labels for the different languages you support.
|
||||
|
||||
l10n:
|
||||
defaultLocale: en-US
|
||||
locales:
|
||||
- code: en-US
|
||||
name: English
|
||||
- code: ja
|
||||
name: 日本語
|
||||
- code: es-ES
|
||||
name: Spanish
|
||||
|
||||
3. Add the translated content in the respective language directory under the @l10n directory.
|
||||
|
||||
The relative path from the language directory to the translated file must be the same as the relative path from the root of the portal to the file in the default language. For example, if you originally had a file with path `path/to/my/markdown.md`, the file translated to Spanish must be /`@l10n/es-ES/path/to/my/markdown.md`.
|
||||
The documentation in this repository is created in English first, then translated into other languages by community contributors. Currently, only the Japanese translations are live on the site; Spanish translation efforts are incomplete and not actively used. For information on the process of adding and maintaining translated files, see [Translations](https://xrpl.org/resources/contribute-documentation/documentation-translations).
|
||||
|
||||
## Issues, Projects, and Project Boards
|
||||
|
||||
Use GitHub Issues under the [`xrpl-dev-portal`](https://github.com/XRPLF/xrpl-dev-portal) repository to report bugs, feature requests, and suggestions for the XRP Ledger Documentation or the `xrpl.org` website.
|
||||
|
||||
For issues related to `rippled` or client libraries (`xrpl.js`, `xrpl-py`, and others), use the respective source repository under [`https://github.com/XRPLF`](https://github.com/XRPLF).
|
||||
For issues related to `xrpld`/`rippled`, Clio, or client libraries (`xrpl.js`, `xrpl-py`, and others), use the respective source repository under [`https://github.com/XRPLF`](https://github.com/XRPLF).
|
||||
|
||||
If you are a contributor, use GitHub Projects and Project Boards to plan and track updates to xrpl.org.
|
||||
|
||||
|
||||
1
_code-samples/airgapped-wallet/js/.gitignore
vendored
1
_code-samples/airgapped-wallet/js/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
Wallet/
|
||||
@@ -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 system’s 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
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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 system’s 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.
|
||||
@@ -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 !@# -+= }{/"
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -1,4 +0,0 @@
|
||||
cryptography==44.0.1
|
||||
Pillow==10.3.0
|
||||
qrcode==7.2
|
||||
xrpl-py>=3.0.0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user