mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-18 09:42:29 +00:00
Compare commits
134 Commits
mcp-config
...
rm_unused_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed88784d9 | ||
|
|
dc13312be6 | ||
|
|
a063951f9e | ||
|
|
6ac6893f4a | ||
|
|
25bfaca2c0 | ||
|
|
5728345a42 | ||
|
|
028e523b6d | ||
|
|
d945d6a5d6 | ||
|
|
fed058fe51 | ||
|
|
c3e898c047 | ||
|
|
98db42f996 | ||
|
|
3f551f68e3 | ||
|
|
4c5f65ff54 | ||
|
|
2fd46e197b | ||
|
|
cf92ef36ae | ||
|
|
7b42cbb02a | ||
|
|
0e4ae322f7 | ||
|
|
5e500d58ca | ||
|
|
cb48d4f789 | ||
|
|
02b275e157 | ||
|
|
06b92f44d6 | ||
|
|
31ae9f6a00 | ||
|
|
7f907dd168 | ||
|
|
e45149a6c7 | ||
|
|
1ca0c3371b | ||
|
|
6673e6e0fe | ||
|
|
4e1ea13709 | ||
|
|
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 | ||
|
|
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.
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/go/go.sum
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,8 +7,9 @@ import markdoc from '@markdoc/markdoc';
|
||||
import moment from "moment";
|
||||
|
||||
export function blogPosts() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'blog-posts',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const posts = [];
|
||||
|
||||
@@ -5,8 +5,9 @@ import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text
|
||||
import { dirname, relative, join as joinPath } from 'path';
|
||||
|
||||
export function codeSamples() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'code-samples',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const samples = [];
|
||||
|
||||
@@ -3,8 +3,9 @@ import { readSharedData } from '@redocly/realm/dist/server/utils/shared-data.js'
|
||||
const INDEX_PAGE_INFO_DATA_KEY = 'index-page-items';
|
||||
|
||||
export function indexPages() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'index-pages',
|
||||
// hook that gets executed after all routes were created
|
||||
async afterRoutesCreated(actions, { cache }) {
|
||||
// get all the routes that are ind pages
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js'
|
||||
|
||||
/**
|
||||
* Plugin to detect languages supported in tutorial pages by scanning for tab labels.
|
||||
* Plugin to detect languages supported in tutorial pages.
|
||||
*
|
||||
* Detection methods (in priority order):
|
||||
* 1. Tab labels in the markdown (for multi-language tutorials)
|
||||
* 2. Filename patterns like "-js.md", "-py.md" (for single-language tutorials)
|
||||
* 3. Title containing language name (for single-language tutorials)
|
||||
*
|
||||
* This creates shared data that maps tutorial paths to their supported languages.
|
||||
*/
|
||||
export function tutorialLanguages() {
|
||||
@@ -21,7 +29,18 @@ export function tutorialLanguages() {
|
||||
for (const { relativePath } of tutorialFiles) {
|
||||
try {
|
||||
const { data } = await cache.load(relativePath, 'markdown-ast')
|
||||
const languages = extractLanguagesFromAst(data.ast)
|
||||
|
||||
// Try to detect languages from tab labels first
|
||||
let languages = extractLanguagesFromAst(data.ast)
|
||||
|
||||
// Fallback: detect language from filename/title for single-language tutorials
|
||||
if (languages.length === 0) {
|
||||
const title = extractFirstHeading(data.ast) || ''
|
||||
const fallbackLang = detectLanguageFromPathAndTitle(relativePath, title)
|
||||
if (fallbackLang) {
|
||||
languages = [fallbackLang]
|
||||
}
|
||||
}
|
||||
|
||||
if (languages.length > 0) {
|
||||
// Convert file path to URL path
|
||||
@@ -54,16 +73,31 @@ function extractLanguagesFromAst(ast) {
|
||||
const languages = new Set()
|
||||
|
||||
visit(ast, (node) => {
|
||||
// Look for tab nodes with a label attribute
|
||||
if (isNode(node) && node.type === 'tag' && node.tag === 'tab') {
|
||||
if (!isNode(node)) return
|
||||
|
||||
// Detect languages from tab labels
|
||||
if (node.type === 'tag' && node.tag === 'tab') {
|
||||
const label = node.attributes?.label
|
||||
if (label) {
|
||||
const normalizedLang = normalizeLanguage(label)
|
||||
if (normalizedLang) {
|
||||
languages.add(normalizedLang)
|
||||
}
|
||||
const normalized = normalizeLanguage(label)
|
||||
if (normalized) languages.add(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect languages from code-snippet language attributes
|
||||
if (node.type === 'tag' && node.tag === 'code-snippet') {
|
||||
const lang = node.attributes?.language
|
||||
if (lang) {
|
||||
const normalized = normalizeLanguage(lang)
|
||||
if (normalized) languages.add(normalized)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect languages from fenced code blocks (```js, ```python, etc.)
|
||||
if (node.type === 'fence' && node.attributes?.language) {
|
||||
const normalized = normalizeLanguage(node.attributes.language)
|
||||
if (normalized) languages.add(normalized)
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(languages)
|
||||
@@ -98,6 +132,70 @@ function normalizeLanguage(label) {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect language from file path and title for single-language tutorials.
|
||||
* This is a fallback when no tab labels are found in the markdown.
|
||||
*/
|
||||
function detectLanguageFromPathAndTitle(relativePath, title) {
|
||||
const pathLower = relativePath.toLowerCase()
|
||||
const titleLower = (title || '').toLowerCase()
|
||||
|
||||
// Check filename suffixes like "-js.md", "-py.md"
|
||||
if (pathLower.endsWith('-js.md') || pathLower.includes('-javascript.md') || pathLower.includes('-in-javascript.md')) {
|
||||
return 'javascript'
|
||||
}
|
||||
if (pathLower.endsWith('-py.md') || pathLower.includes('-python.md') || pathLower.includes('-in-python.md')) {
|
||||
return 'python'
|
||||
}
|
||||
if (pathLower.endsWith('-java.md') || pathLower.includes('-in-java.md')) {
|
||||
return 'java'
|
||||
}
|
||||
if (pathLower.endsWith('-go.md') || pathLower.includes('-in-go.md') || pathLower.includes('-golang.md')) {
|
||||
return 'go'
|
||||
}
|
||||
if (pathLower.endsWith('-php.md') || pathLower.includes('-in-php.md')) {
|
||||
return 'php'
|
||||
}
|
||||
|
||||
// Check title for language indicators
|
||||
if (titleLower.includes('javascript') || titleLower.includes(' js ') || titleLower.endsWith(' js')) {
|
||||
return 'javascript'
|
||||
}
|
||||
if (titleLower.includes('python')) {
|
||||
return 'python'
|
||||
}
|
||||
if (titleLower.includes('java') && !titleLower.includes('javascript')) {
|
||||
return 'java'
|
||||
}
|
||||
if (titleLower.includes('golang') || (titleLower.includes(' go ') || titleLower.endsWith(' go') || titleLower.includes('using go'))) {
|
||||
return 'go'
|
||||
}
|
||||
if (titleLower.includes('php')) {
|
||||
return 'php'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const EXIT = Symbol('Exit visitor')
|
||||
|
||||
/**
|
||||
* Extract the first heading from the markdown AST
|
||||
*/
|
||||
function extractFirstHeading(ast) {
|
||||
let heading = null
|
||||
|
||||
visit(ast, (node) => {
|
||||
if (!isNode(node)) return
|
||||
if (node.type === 'heading') {
|
||||
heading = getInnerText([node])
|
||||
return EXIT
|
||||
}
|
||||
})
|
||||
|
||||
return heading
|
||||
}
|
||||
|
||||
function isNode(value) {
|
||||
return !!(value?.$$mdtype === 'Node')
|
||||
}
|
||||
@@ -105,14 +203,16 @@ function isNode(value) {
|
||||
function visit(node, visitor) {
|
||||
if (!node) return
|
||||
|
||||
visitor(node)
|
||||
const res = visitor(node)
|
||||
if (res === EXIT) return res
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
if (!child || typeof child === 'string') {
|
||||
continue
|
||||
}
|
||||
visit(child, visitor)
|
||||
const res = visit(child, visitor)
|
||||
if (res === EXIT) return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
@@ -233,6 +233,7 @@ ul.nav.navbar-nav {
|
||||
--footer-title-text-color: var(--color-gray-6);
|
||||
|
||||
--menu-item-padding-horizontal: 0px;
|
||||
--menu-item-bg-color-active: var(--color-gray-2);
|
||||
|
||||
--md-list-left-padding: 40px;
|
||||
--md-list-margin: 0 0 20px 0;
|
||||
@@ -278,6 +279,8 @@ ul.nav.navbar-nav {
|
||||
--bg-color-raised: var(--color-gray-8);
|
||||
--button-content-color-link: black;
|
||||
|
||||
--menu-item-bg-color-active: var(--color-gray-8);
|
||||
|
||||
--md-table-header-bg-color: var(--color-gray-8);
|
||||
--md-table-border-color: var(--color-gray-8);
|
||||
|
||||
@@ -285,6 +288,7 @@ ul.nav.navbar-nav {
|
||||
--code-panel-bg-color: var(--color-blue-7);
|
||||
--layer-color-hover: var(--color-gray-9);
|
||||
|
||||
|
||||
--code-block-text-color: var(--color-gray-1);
|
||||
--code-block-tokens-comment-color: var(--color-gray-4);
|
||||
--code-block-tokens-constant-color: var(--color-gray-1);
|
||||
|
||||
@@ -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
|
||||
10
_code-samples/assign-regular-key/go/README.md
Normal file
10
_code-samples/assign-regular-key/go/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Assign a Regular Key (Go)
|
||||
|
||||
Demonstrates how to assign a regular key pair to an XRP Ledger account. Both WebSocket (`ws/`) and JSON-RPC (`rpc/`) examples are included.
|
||||
|
||||
Quick setup and usage:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
go run ./ws/main.go
|
||||
```
|
||||
@@ -1,8 +1,6 @@
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.10
|
||||
go 1.24.0
|
||||
|
||||
require github.com/Peersyst/xrpl-go v0.1.11
|
||||
|
||||
@@ -20,5 +18,5 @@ require (
|
||||
github.com/tyler-smith/go-bip32 v1.0.0 // indirect
|
||||
github.com/tyler-smith/go-bip39 v1.1.0 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.35.0 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
)
|
||||
|
||||
@@ -46,8 +46,8 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
||||
golang.org/x/crypto v0.0.0-20170613210332-850760c427c5/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
3
_code-samples/auction-slot/README.md
Normal file
3
_code-samples/auction-slot/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Use AMM Auction Slot to Save on Fees
|
||||
|
||||
Estimate the fees that would be paid for trading through an AMM and use the auction slot to save on fees if applicable.
|
||||
@@ -1,4 +1,4 @@
|
||||
const BigNumber = require('bignumber.js')
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
/* Convert a trading fee to a value that can be multiplied
|
||||
* by a total to "subtract" the fee from the total.
|
||||
@@ -50,7 +50,7 @@ function feeDecimal(tFee) {
|
||||
* theoretical input to the pool, it should be rounded
|
||||
* up (ceiling) to preserve the pool's constant product.
|
||||
*/
|
||||
function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
|
||||
export function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
|
||||
return ( ( pool_in_bn.multipliedBy(pool_out_bn) ).dividedBy(
|
||||
pool_out_bn.minus(asset_out_bn)
|
||||
).minus(pool_in_bn)
|
||||
@@ -76,7 +76,7 @@ function solveQuadraticEq(a,b,c) {
|
||||
* @param trading_fee int - The trading fee as an integer {0,1000} where 1000
|
||||
* represents a 1% fee.
|
||||
*/
|
||||
function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
|
||||
export function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
|
||||
// convert inputs to BigNumber
|
||||
const lpTokens = BigNumber(desired_lpt)
|
||||
const lptAMMBalance = BigNumber(lpt_balance)
|
||||
@@ -100,7 +100,7 @@ function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
|
||||
* XLS-30 section 4.1.1, but factors in the increase in the minimum bid as a
|
||||
* result of having new LP Tokens issued to you from your deposit.
|
||||
*/
|
||||
function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
export function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
const tfee_decimal = feeDecimal(trading_fee)
|
||||
const lptokens = BigNumber(lpt_balance)
|
||||
const b = BigNumber(old_bid)
|
||||
@@ -133,7 +133,7 @@ function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
*
|
||||
* @returns BigNumber - the minimum amount of LP tokens to win the auction slot
|
||||
*/
|
||||
function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
export function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
const tfee_decimal = feeDecimal(trading_fee)
|
||||
const lptokens = BigNumber(lpt_balance)
|
||||
const min_bid = lptokens.multipliedBy(tfee_decimal).dividedBy(25)
|
||||
@@ -154,10 +154,3 @@ function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
).minus(lptokens).precision(15, BigNumber.FLOOR)
|
||||
return rounded_bid
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
"auctionDeposit": auctionDeposit,
|
||||
"auctionPrice": auctionPrice,
|
||||
"ammAssetIn": ammAssetIn,
|
||||
"swapOut": swapOut,
|
||||
}
|
||||
|
||||
@@ -1,170 +1,165 @@
|
||||
const xrpl = require('xrpl')
|
||||
const BigNumber = require('bignumber.js')
|
||||
const {auctionDeposit, ammAssetIn, swapOut} = require("./amm-formulas.js")
|
||||
import xrpl from 'xrpl'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { auctionDeposit, ammAssetIn, swapOut } from "./amm-formulas.js"
|
||||
|
||||
async function main() {
|
||||
// Connect ----------------------------------------------------------------
|
||||
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
|
||||
console.log("Connecting to Testnet...")
|
||||
await client.connect()
|
||||
|
||||
// // Get credentials from the faucet -------------------------------------
|
||||
console.log("Requesting test XRP from the faucet...")
|
||||
const wallet = (await client.fundWallet()).wallet
|
||||
console.log(`Got address ${wallet.address} / seed ${wallet.seed}.`)
|
||||
// Connect and get account ----------------------------------------------------
|
||||
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
|
||||
console.log("Connecting to Testnet...")
|
||||
await client.connect()
|
||||
|
||||
// Look up AMM status -----------------------------------------------------
|
||||
const from_asset = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
const to_asset = {
|
||||
"currency": "TST",
|
||||
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"
|
||||
}
|
||||
const amm_info = (await client.request({
|
||||
"command": "amm_info",
|
||||
"asset": from_asset,
|
||||
"asset2": to_asset
|
||||
}))
|
||||
console.dir(amm_info, {depth: null})
|
||||
const lpt = amm_info.result.amm.lp_token
|
||||
// XRP is always first if the pool is token←→XRP.
|
||||
// For a token←→token AMM, you'd need to figure out which asset is first.
|
||||
const pool_drops = amm_info.result.amm.amount
|
||||
const pool_tst = amm_info.result.amm.amount2
|
||||
const full_trading_fee = amm_info.result.amm.trading_fee
|
||||
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
|
||||
const old_bid = amm_info.result.amm.auction_slot.price.value
|
||||
const time_interval = amm_info.result.amm.auction_slot.time_interval
|
||||
console.log("Requesting test XRP from the faucet...")
|
||||
const { wallet } = await client.fundWallet()
|
||||
console.log(`Got address ${wallet.address} / seed ${wallet.seed}.`)
|
||||
|
||||
// Calculate price in XRP to get 10 TST from the AMM ----------------------
|
||||
// Note, this ignores Offers from the non-AMM part of the DEX.
|
||||
const to_amount = {
|
||||
"currency": to_asset.currency,
|
||||
"issuer": to_asset.issuer,
|
||||
"value": "10.0"
|
||||
}
|
||||
// Look up AMM status -----------------------------------------------------
|
||||
const from_asset = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
const to_asset = {
|
||||
"currency": "TST",
|
||||
"issuer": "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd"
|
||||
}
|
||||
const amm_info = (await client.request({
|
||||
"command": "amm_info",
|
||||
"asset": from_asset,
|
||||
"asset2": to_asset
|
||||
}))
|
||||
console.dir(amm_info, {depth: null})
|
||||
const lpt = amm_info.result.amm.lp_token
|
||||
// XRP is always first if the pool is token←→XRP.
|
||||
// For a token←→token AMM, you'd need to figure out which asset is first.
|
||||
const pool_drops = amm_info.result.amm.amount
|
||||
const pool_tst = amm_info.result.amm.amount2
|
||||
const full_trading_fee = amm_info.result.amm.trading_fee
|
||||
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
|
||||
const old_bid = amm_info.result.amm.auction_slot.price.value
|
||||
const time_interval = amm_info.result.amm.auction_slot.time_interval
|
||||
|
||||
// Convert values to BigNumbers with the appropriate precision.
|
||||
// Tokens always have 15 significant digits;
|
||||
// XRP is precise to integer drops, which can be as high as 10^17
|
||||
const asset_out_bn = BigNumber(to_amount.value).precision(15)
|
||||
const pool_in_bn = BigNumber(pool_drops).precision(17)
|
||||
const pool_out_bn = BigNumber(pool_tst.value).precision(15)
|
||||
// Calculate price in XRP to get 10 TST from the AMM ----------------------
|
||||
// Note, this ignores Offers from the non-AMM part of the DEX.
|
||||
const to_amount = {
|
||||
"currency": to_asset.currency,
|
||||
"issuer": to_asset.issuer,
|
||||
"value": "10.0"
|
||||
}
|
||||
|
||||
if (to_amount.value > pool_out_bn) {
|
||||
console.log(`Requested ${to_amount.value} ${to_amount.currency} ` +
|
||||
`but AMM only holds ${pool_tst.value}. Quitting.`)
|
||||
client.disconnect()
|
||||
return
|
||||
}
|
||||
// Convert values to BigNumbers with the appropriate precision.
|
||||
// Tokens always have 15 significant digits;
|
||||
// XRP is precise to integer drops, which can be as high as 10^17
|
||||
const asset_out_bn = BigNumber(to_amount.value).precision(15)
|
||||
const pool_in_bn = BigNumber(pool_drops).precision(17)
|
||||
const pool_out_bn = BigNumber(pool_tst.value).precision(15)
|
||||
|
||||
// Use AMM's SwapOut formula to figure out how much XRP we have to pay
|
||||
// to receive the target amount of TST, under the current trading fee.
|
||||
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn,
|
||||
pool_out_bn, full_trading_fee)
|
||||
// Round XRP to integer drops. Round ceiling to make you pay in enough.
|
||||
const from_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Expected cost of ${to_amount.value} ${to_amount.currency}: ` +
|
||||
`${xrpl.dropsToXrp(from_amount)} XRP`)
|
||||
|
||||
// Same calculation, but assume we have access to the discounted trading
|
||||
// fee from the auction slot.
|
||||
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn,
|
||||
discounted_fee)
|
||||
const discounted_from_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Expected cost with auction slot discount: `+
|
||||
`${xrpl.dropsToXrp(discounted_from_amount)} XRP`)
|
||||
|
||||
// The potential savings is the difference between the necessary input
|
||||
// amounts with the full vs discounted fee.
|
||||
const potential_savings = from_amount.minus(discounted_from_amount)
|
||||
console.log(`Potential savings: ${xrpl.dropsToXrp(potential_savings)} XRP`)
|
||||
|
||||
// Calculate the cost of winning the auction slot, in LP Tokens -----------
|
||||
const auction_price = auctionDeposit(old_bid, time_interval,
|
||||
full_trading_fee, lpt.value
|
||||
).precision(15)
|
||||
console.log(`Auction price after deposit: ${auction_price} LP Tokens`)
|
||||
|
||||
// Calculate how much XRP to deposit to receive that many LP Tokens -------
|
||||
const deposit_for_bid = ammAssetIn(pool_in_bn, lpt.value, auction_price,
|
||||
full_trading_fee
|
||||
).dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Auction price as XRP single-asset deposit amount: `+
|
||||
`${xrpl.dropsToXrp(deposit_for_bid)} XRP`)
|
||||
|
||||
// Optional. Allow for costs to be 1% greater than estimated, in case other
|
||||
// transactions affect the same AMM during this time.
|
||||
const SLIPPAGE_MULT = BigNumber(1.01)
|
||||
const deposit_max = deposit_for_bid.multipliedBy(SLIPPAGE_MULT).dp(0)
|
||||
|
||||
// Compare price of deposit+bid with potential savings. -------------------
|
||||
// Don't forget XRP burned as transaction costs.
|
||||
const fee_response = (await client.request({"command":"fee"}))
|
||||
const tx_cost_drops = BigNumber(fee_response.result.drops.minimum_fee
|
||||
).multipliedBy(client.feeCushion).dp(0)
|
||||
const net_savings = potential_savings.minus(
|
||||
tx_cost_drops.multipliedBy(2).plus(deposit_max)
|
||||
)
|
||||
if (net_savings > 0) {
|
||||
console.log(`Estimated net savings from the auction slot: ` +
|
||||
`${xrpl.dropsToXrp(net_savings)} XRP`)
|
||||
} else {
|
||||
console.log(`Estimated the auction slot to be MORE EXPENSIVE by `+
|
||||
`${xrpl.dropsToXrp(net_savings.negated())} XRP. Quitting.`)
|
||||
client.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Do a single-asset deposit to get LP Tokens to bid on the auction slot --
|
||||
const auction_bid = {
|
||||
"currency": lpt.currency,
|
||||
"issuer": lpt.issuer,
|
||||
"value": auction_price.toString()
|
||||
}
|
||||
const deposit_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMDeposit",
|
||||
"Account": wallet.address,
|
||||
"Asset": from_asset,
|
||||
"Asset2": to_asset,
|
||||
"Amount": deposit_max.toString(),
|
||||
"LPTokenOut": auction_bid,
|
||||
"Flags": xrpl.AMMDepositFlags.tfOneAssetLPToken
|
||||
}, {autofill: true, wallet: wallet}
|
||||
)
|
||||
console.log("Deposit result:")
|
||||
console.dir(deposit_result, {depth: null})
|
||||
|
||||
// Actually bid on the auction slot ---------------------------------------
|
||||
const bid_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMBid",
|
||||
"Account": wallet.address,
|
||||
"Asset": from_asset,
|
||||
"Asset2": to_asset,
|
||||
"BidMax": auction_bid,
|
||||
"BidMin": auction_bid, // So rounding doesn't leave dust amounts of LPT
|
||||
}, {autofill: true, wallet: wallet}
|
||||
)
|
||||
console.log("Bid result:")
|
||||
console.dir(bid_result, {depth: null})
|
||||
|
||||
// Trade using the discount -----------------------------------------------
|
||||
const spend_drops = discounted_from_amount.multipliedBy(SLIPPAGE_MULT
|
||||
).dp(0).toString()
|
||||
const offer_result = await client.submitAndWait({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": wallet.address,
|
||||
"TakerPays": to_amount,
|
||||
"TakerGets": spend_drops
|
||||
}, {autofill: true, wallet: wallet})
|
||||
console.log("Offer result:")
|
||||
console.dir(offer_result, {depth: null})
|
||||
console.log("Offer balance changes summary:")
|
||||
console.dir(xrpl.getBalanceChanges(offer_result.result.meta), {depth:null})
|
||||
|
||||
// Done.
|
||||
if (to_amount.value > pool_out_bn) {
|
||||
console.log(`Requested ${to_amount.value} ${to_amount.currency} ` +
|
||||
`but AMM only holds ${pool_tst.value}. Quitting.`)
|
||||
client.disconnect()
|
||||
} // End of main()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
main()
|
||||
// Use AMM's SwapOut formula to figure out how much XRP we have to pay
|
||||
// to receive the target amount of TST, under the current trading fee.
|
||||
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn,
|
||||
pool_out_bn, full_trading_fee)
|
||||
// Round XRP to integer drops. Round ceiling to make you pay in enough.
|
||||
const from_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Expected cost of ${to_amount.value} ${to_amount.currency}: ` +
|
||||
`${xrpl.dropsToXrp(from_amount)} XRP`)
|
||||
|
||||
// Same calculation, but assume we have access to the discounted trading
|
||||
// fee from the auction slot.
|
||||
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn,
|
||||
discounted_fee)
|
||||
const discounted_from_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Expected cost with auction slot discount: `+
|
||||
`${xrpl.dropsToXrp(discounted_from_amount)} XRP`)
|
||||
|
||||
// The potential savings is the difference between the necessary input
|
||||
// amounts with the full vs discounted fee.
|
||||
const potential_savings = from_amount.minus(discounted_from_amount)
|
||||
console.log(`Potential savings: ${xrpl.dropsToXrp(potential_savings)} XRP`)
|
||||
|
||||
// Calculate the cost of winning the auction slot, in LP Tokens -----------
|
||||
const auction_price = auctionDeposit(old_bid, time_interval,
|
||||
full_trading_fee, lpt.value
|
||||
).precision(15)
|
||||
console.log(`Auction price after deposit: ${auction_price} LP Tokens`)
|
||||
|
||||
// Calculate how much XRP to deposit to receive that many LP Tokens -------
|
||||
const deposit_for_bid = ammAssetIn(pool_in_bn, lpt.value, auction_price,
|
||||
full_trading_fee
|
||||
).dp(0, BigNumber.ROUND_CEIL)
|
||||
console.log(`Auction price as XRP single-asset deposit amount: `+
|
||||
`${xrpl.dropsToXrp(deposit_for_bid)} XRP`)
|
||||
|
||||
// Optional. Allow for costs to be 1% greater than estimated, in case other
|
||||
// transactions affect the same AMM during this time.
|
||||
const SLIPPAGE_MULT = BigNumber(1.01)
|
||||
const deposit_max = deposit_for_bid.multipliedBy(SLIPPAGE_MULT).dp(0)
|
||||
|
||||
// Compare price of deposit+bid with potential savings. -------------------
|
||||
// Don't forget XRP burned as transaction costs.
|
||||
const fee_response = (await client.request({"command":"fee"}))
|
||||
const tx_cost_drops = BigNumber(fee_response.result.drops.minimum_fee
|
||||
).multipliedBy(client.feeCushion).dp(0)
|
||||
const net_savings = potential_savings.minus(
|
||||
tx_cost_drops.multipliedBy(2).plus(deposit_max)
|
||||
)
|
||||
if (net_savings > 0) {
|
||||
console.log(`Estimated net savings from the auction slot: ` +
|
||||
`${xrpl.dropsToXrp(net_savings)} XRP`)
|
||||
} else {
|
||||
console.log(`Estimated the auction slot to be MORE EXPENSIVE by `+
|
||||
`${xrpl.dropsToXrp(net_savings.negated())} XRP. Quitting.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Do a single-asset deposit to get LP Tokens to bid on the auction slot --
|
||||
const auction_bid = {
|
||||
"currency": lpt.currency,
|
||||
"issuer": lpt.issuer,
|
||||
"value": auction_price.toString()
|
||||
}
|
||||
const deposit_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMDeposit",
|
||||
"Account": wallet.address,
|
||||
"Asset": from_asset,
|
||||
"Asset2": to_asset,
|
||||
"Amount": deposit_max.toString(),
|
||||
"LPTokenOut": auction_bid,
|
||||
"Flags": xrpl.AMMDepositFlags.tfOneAssetLPToken
|
||||
}, {autofill: true, wallet: wallet}
|
||||
)
|
||||
console.log("Deposit result:")
|
||||
console.dir(deposit_result, {depth: null})
|
||||
|
||||
// Actually bid on the auction slot ---------------------------------------
|
||||
const bid_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMBid",
|
||||
"Account": wallet.address,
|
||||
"Asset": from_asset,
|
||||
"Asset2": to_asset,
|
||||
"BidMax": auction_bid,
|
||||
"BidMin": auction_bid, // So rounding doesn't leave dust amounts of LPT
|
||||
}, {autofill: true, wallet: wallet}
|
||||
)
|
||||
console.log("Bid result:")
|
||||
console.dir(bid_result, {depth: null})
|
||||
|
||||
// Trade using the discount -----------------------------------------------
|
||||
const spend_drops = discounted_from_amount.multipliedBy(SLIPPAGE_MULT
|
||||
).dp(0).toString()
|
||||
const offer_result = await client.submitAndWait({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": wallet.address,
|
||||
"TakerPays": to_amount,
|
||||
"TakerGets": spend_drops
|
||||
}, {autofill: true, wallet: wallet})
|
||||
console.log("Offer result:")
|
||||
console.dir(offer_result, {depth: null})
|
||||
console.log("Offer balance changes summary:")
|
||||
console.dir(xrpl.getBalanceChanges(offer_result.result.meta), {depth:null})
|
||||
|
||||
// Done.
|
||||
client.disconnect()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^4.0.0",
|
||||
"bignumber.js": "^9.0.0"
|
||||
}
|
||||
"xrpl": "^4.6.0",
|
||||
"bignumber.js": "^10.0.2"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
xrpl-py>=3.0.0
|
||||
wxPython==4.2.1
|
||||
toml==0.10.2
|
||||
requests==2.32.4
|
||||
requests==2.33.0
|
||||
|
||||
5
_code-samples/calculate-reserves/README.md
Normal file
5
_code-samples/calculate-reserves/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Calculate Reserves
|
||||
|
||||
Look up and calculate an account's reserve requirements.
|
||||
|
||||
See the [Calculate Account Reserves tutorial](https://xrpl.org/docs/tutorials/best-practices/account-management/calculate-reserves) for a detailed walkthrough.
|
||||
10
_code-samples/calculate-reserves/go/README.md
Normal file
10
_code-samples/calculate-reserves/go/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Calculate Reserves (Go)
|
||||
|
||||
This code sample uses [xrpl-go](https://github.com/Peersyst/xrpl-go) to look up and calculate an XRP Ledger account's reserve requirements.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
go run calculate_reserves.go
|
||||
```
|
||||
74
_code-samples/calculate-reserves/go/calculate_reserves.go
Normal file
74
_code-samples/calculate-reserves/go/calculate_reserves.go
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
// Set up client ----------------------
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/currency"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/server"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://xrplcluster.com"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Look up reserve values ----------------------
|
||||
|
||||
res, err := client.Request(&server.StateRequest{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var serverState server.StateResponse
|
||||
if err := res.GetResult(&serverState); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
baseReserve := serverState.State.ValidatedLedger.ReserveBase
|
||||
reserveInc := serverState.State.ValidatedLedger.ReserveInc
|
||||
|
||||
baseReserveXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(baseReserve), 10))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
reserveIncXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(reserveInc), 10))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Base reserve: %v XRP\n", baseReserveXrp)
|
||||
fmt.Printf("Incremental reserve: %v XRP\n", reserveIncXrp)
|
||||
|
||||
// Look up owner count ----------------------
|
||||
|
||||
address := types.Address("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn") // replace with any address
|
||||
accountInfo, err := client.GetAccountInfo(&account.InfoRequest{Account: address})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ownerCount := accountInfo.AccountData.OwnerCount
|
||||
|
||||
// Calculate total reserve ----------------------
|
||||
|
||||
totalReserve := baseReserve + (uint(ownerCount) * reserveInc)
|
||||
|
||||
totalReserveXrp, err := currency.DropsToXrp(strconv.FormatUint(uint64(totalReserve), 10))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Owner count: %v\n", ownerCount)
|
||||
fmt.Printf("Total reserve: %v XRP\n", totalReserveXrp)
|
||||
}
|
||||
16
_code-samples/calculate-reserves/go/go.mod
Normal file
16
_code-samples/calculate-reserves/go/go.mod
Normal file
@@ -0,0 +1,16 @@
|
||||
module calculate-reserves
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require github.com/Peersyst/xrpl-go v0.1.17
|
||||
|
||||
require (
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
)
|
||||
30
_code-samples/calculate-reserves/go/go.sum
Normal file
30
_code-samples/calculate-reserves/go/go.sum
Normal file
@@ -0,0 +1,30 @@
|
||||
github.com/Peersyst/xrpl-go v0.1.17 h1:jOI2es/dzzRm6/WMVCjr7H3fci47XB2kRkezfbddDB0=
|
||||
github.com/Peersyst/xrpl-go v0.1.17/go.mod h1:38j60Mr65poIHdhmjvNXnwbcUFNo8J7CBDot7ZWgrb8=
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 h1:LwFzuts+J5X7A+ECx0LNowtUgIahCkNNlXckdiEMSDk=
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9/go.mod h1:KiHWa/hblo3Bzr+IsX11v0sn1E6elGbNX0VXl5mOq6E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 h1:TvGTmUBHDU75OHro9ojPLK+Yv7gDl2hnUvRocRCjsys=
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2/go.mod h1:uGfjDyePSpa75cSQLzNdVmWlbQMBuiJkvXw/MNKRY4M=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
9
_code-samples/calculate-reserves/js/README.md
Normal file
9
_code-samples/calculate-reserves/js/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Calculate Reserves (JavaScript)
|
||||
|
||||
This code sample uses [xrpl.js](https://js.xrpl.org/) to look up and calculate an XRP Ledger account's reserve requirements.
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
npm install
|
||||
node calculate_reserves.js
|
||||
```
|
||||
32
_code-samples/calculate-reserves/js/calculate_reserves.js
Normal file
32
_code-samples/calculate-reserves/js/calculate_reserves.js
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
// Set up client ----------------------
|
||||
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
const client = new xrpl.Client('wss://xrplcluster.com')
|
||||
await client.connect()
|
||||
|
||||
// Look up reserve values ----------------------
|
||||
|
||||
const serverState = await client.request({ command: 'server_state' })
|
||||
const validatedLedger = serverState.result.state.validated_ledger
|
||||
|
||||
const baseReserveDrops = validatedLedger.reserve_base
|
||||
const reserveIncDrops = validatedLedger.reserve_inc
|
||||
|
||||
console.log(`Base reserve: ${xrpl.dropsToXrp(baseReserveDrops)} XRP`)
|
||||
console.log(`Incremental reserve: ${xrpl.dropsToXrp(reserveIncDrops)} XRP`)
|
||||
|
||||
// Look up owner count ----------------------
|
||||
|
||||
const address = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn' // replace with any address
|
||||
const accountInfo = await client.request({ command: 'account_info', account: address })
|
||||
const ownerCount = accountInfo.result.account_data.OwnerCount
|
||||
|
||||
// Calculate total reserve ----------------------
|
||||
|
||||
const totalReserveDrops = baseReserveDrops + (ownerCount * reserveIncDrops)
|
||||
console.log(`Owner count: ${ownerCount}`)
|
||||
console.log(`Total reserve: ${xrpl.dropsToXrp(totalReserveDrops)} XRP`)
|
||||
|
||||
await client.disconnect()
|
||||
9
_code-samples/calculate-reserves/js/package.json
Normal file
9
_code-samples/calculate-reserves/js/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "calculate-reserves",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"xrpl": "^4.4.0"
|
||||
}
|
||||
}
|
||||
9
_code-samples/calculate-reserves/py/README.md
Normal file
9
_code-samples/calculate-reserves/py/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Calculate Reserves (Python)
|
||||
|
||||
This code sample uses [xrpl-py](https://xrpl-py.readthedocs.io/) to look up and calculate an XRP Ledger account's reserve requirements.
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python calculate_reserves.py
|
||||
```
|
||||
30
_code-samples/calculate-reserves/py/calculate_reserves.py
Normal file
30
_code-samples/calculate-reserves/py/calculate_reserves.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models.requests import ServerState, AccountInfo
|
||||
from xrpl.utils import drops_to_xrp
|
||||
|
||||
# Set up client ----------------------
|
||||
|
||||
client = JsonRpcClient("https://xrplcluster.com")
|
||||
|
||||
# Look up reserve values ----------------------
|
||||
|
||||
response = client.request(ServerState())
|
||||
validated_ledger = response.result["state"]["validated_ledger"]
|
||||
|
||||
base_reserve = validated_ledger["reserve_base"]
|
||||
reserve_inc = validated_ledger["reserve_inc"]
|
||||
|
||||
print(f"Base reserve: {drops_to_xrp(str(base_reserve))} XRP")
|
||||
print(f"Incremental reserve: {drops_to_xrp(str(reserve_inc))} XRP")
|
||||
|
||||
# Look up owner count ----------------------
|
||||
|
||||
address = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" # replace with any address
|
||||
response = client.request(AccountInfo(account=address))
|
||||
owner_count = response.result["account_data"]["OwnerCount"]
|
||||
|
||||
# Calculate total reserve ----------------------
|
||||
|
||||
total_reserve = base_reserve + (owner_count * reserve_inc)
|
||||
print(f"Owner count: {owner_count}")
|
||||
print(f"Total reserve: {drops_to_xrp(str(total_reserve))} XRP")
|
||||
1
_code-samples/calculate-reserves/py/requirements.txt
Normal file
1
_code-samples/calculate-reserves/py/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
xrpl-py>=4.4.0
|
||||
3
_code-samples/delete-account/README.md
Normal file
3
_code-samples/delete-account/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Delete Account
|
||||
|
||||
Delete an account from the XRP Ledger, removing its data and sending its XRP to another account.
|
||||
4
_code-samples/delete-account/js/.env
Normal file
4
_code-samples/delete-account/js/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
45
_code-samples/delete-account/js/README.md
Normal file
45
_code-samples/delete-account/js/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Delete Account (JavaScript)
|
||||
|
||||
JavaScript sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ node delete-account.js
|
||||
Got new account from faucet:
|
||||
Address: rsuTU7xBF1u8jxKMw5UHvbKkLmvix7zQoe
|
||||
Seed: sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226794
|
||||
Validated ledger index: 15226538
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
node delete-account.js
|
||||
```
|
||||
205
_code-samples/delete-account/js/delete-account.js
Normal file
205
_code-samples/delete-account/js/delete-account.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { Client, Wallet, getBalanceChanges, validate } from 'xrpl'
|
||||
import 'dotenv/config'
|
||||
|
||||
const client = new Client('wss://s.altnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Where to send the deleted account's remaining XRP:
|
||||
const DESTINATION_ACCOUNT = 'rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo' // Testnet faucet
|
||||
|
||||
// Load the account to delete from .env file -----------------------------------
|
||||
// If the seed value is still the default, get a new account from the faucet.
|
||||
// It won't be deletable immediately.
|
||||
let wallet
|
||||
if (!process.env.ACCOUNT_SEED || process.env.ACCOUNT_SEED === 's████████████████████████████') {
|
||||
console.log("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = (await client.fundWallet()).wallet
|
||||
console.log(`Got new account from faucet:
|
||||
Address: ${wallet.address}
|
||||
Seed: ${wallet.seed}
|
||||
`)
|
||||
|
||||
console.log('Edit the .env file to add this seed, then wait until the account can be deleted.')
|
||||
} else {
|
||||
wallet = Wallet.fromSeed(process.env.ACCOUNT_SEED, { algorithm: process.env.ACCOUNT_ALGORITHM })
|
||||
console.log(`Loaded account: ${wallet.address}`)
|
||||
}
|
||||
|
||||
// Check account info to see if account can be deleted -------------------------
|
||||
let acctInfoResp
|
||||
try {
|
||||
acctInfoResp = await client.request({
|
||||
command: 'account_info',
|
||||
account: wallet.address,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_info failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let numProblems = 0
|
||||
|
||||
// Check if sequence number is too high
|
||||
const acctSeq = acctInfoResp.result.account_data.Sequence
|
||||
const lastValidatedLedgerIndex = acctInfoResp.result.ledger_index
|
||||
if (acctSeq + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account is too new to be deleted.
|
||||
Account sequence + 255: ${acctSeq + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(Sequence + 255 must be less than or equal to the ledger index)`)
|
||||
|
||||
// Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
const estWaitTimeS = (acctSeq + 255 - lastValidatedLedgerIndex) * 3.5
|
||||
if (estWaitTimeS < 120) {
|
||||
console.log(`Estimate: ${estWaitTimeS} seconds until account can be deleted`)
|
||||
} else {
|
||||
const estWaitTimeM = Math.round(estWaitTimeS / 60, 0)
|
||||
console.log(`Estimate: ${estWaitTimeM} minutes until account can be deleted`)
|
||||
}
|
||||
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account sequence number (${acctSeq}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if owner count is too high
|
||||
const ownerCount = acctInfoResp.result.account_data.OwnerCount
|
||||
if (ownerCount > 1000) {
|
||||
console.error(`Account owns too many objects in the ledger.
|
||||
Owner count: ${ownerCount}
|
||||
(Must be 1000 or less)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account owner count (${ownerCount}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if XRP balance is high enough
|
||||
// Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
// using server_state so that both are in drops
|
||||
let serverStateResp
|
||||
try {
|
||||
serverStateResp = await client.request({
|
||||
command: 'server_state'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('server_state failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
const deletionCost = serverStateResp.result.state.validated_ledger?.reserve_inc
|
||||
if (!deletionCost) {
|
||||
console.error("Couldn't get reserve values from server. " +
|
||||
"Maybe it's not synced to the network?")
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const acctBalance = acctInfoResp.result.account_data.Balance
|
||||
if (acctBalance < deletionCost) {
|
||||
console.error(`Account does not have enough XRP to pay the cost of deletion.
|
||||
Balance: ${acctBalance}
|
||||
Cost of account deletion: ${deletionCost}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account balance (${acctBalance} drops) is high enough.`)
|
||||
}
|
||||
|
||||
// Check if FirstNFTSequence is too high
|
||||
const firstNFTSeq = acctInfoResp.result.account_data.FirstNFTokenSequence || 0
|
||||
const mintedNFTs = acctInfoResp.result.account_data.MintedNFTokens || 0
|
||||
if (firstNFTSeq + mintedNFTs + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: ${firstNFTSeq + mintedNFTs + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the ledger index)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: FirstNFTokenSequence + MintedNFTokens is low enough.')
|
||||
}
|
||||
|
||||
// Check that all issued NFTs have been burned
|
||||
const burnedNFTs = acctInfoResp.result.account_data.BurnedNFTokens || 0
|
||||
if (mintedNFTs > burnedNFTs) {
|
||||
console.error(`Account has issued NFTs outstanding.
|
||||
Number of NFTs minted: ${mintedNFTs}
|
||||
Number of NFTs burned: ${burnedNFTs}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: No outstanding, un-burned NFTs')
|
||||
}
|
||||
|
||||
// Stop if any problems were found
|
||||
if (numProblems) {
|
||||
console.error(`A total of ${numProblems} problem(s) prevent the account from being deleted.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Check for deletion blockers -------------------------------------------------
|
||||
const blockers = []
|
||||
let marker
|
||||
const ledger_index = 'validated'
|
||||
while (true) {
|
||||
let accountObjResp
|
||||
try {
|
||||
accountObjResp = await client.request({
|
||||
command: 'account_objects',
|
||||
account: wallet.address,
|
||||
deletion_blockers_only: true,
|
||||
ledger_index,
|
||||
marker
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_objects failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
for (const obj of accountObjResp.result.account_objects) {
|
||||
blockers.push(obj)
|
||||
}
|
||||
if (accountObjResp.result.marker) {
|
||||
marker = accountObjResp.result.marker
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!blockers.length) {
|
||||
console.log('OK: Account has no deletion blockers.')
|
||||
} else {
|
||||
console.log(`Account cannot be deleted until ${blockers.length} blocker(s) are removed:`)
|
||||
for (const blocker of blockers) {
|
||||
console.log(JSON.stringify(blocker, null, 2))
|
||||
}
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Delete the account ----------------------------------------------------------
|
||||
const accountDeleteTx = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: wallet.address,
|
||||
Destination: DESTINATION_ACCOUNT
|
||||
}
|
||||
validate(accountDeleteTx)
|
||||
|
||||
console.log('Signing and submitting the AccountDelete transaction:',
|
||||
JSON.stringify(accountDeleteTx, null, 2))
|
||||
const deleteTxResponse = await client.submitAndWait(accountDeleteTx, { wallet, autofill: true, failHard: true })
|
||||
|
||||
// Check result of the AccountDelete transaction -------------------------------
|
||||
console.log(JSON.stringify(deleteTxResponse.result, null, 2))
|
||||
const resultCode = deleteTxResponse.result.meta.TransactionResult
|
||||
if (resultCode !== 'tesSUCCESS') {
|
||||
console.error(`AccountDelete failed with code ${resultCode}.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('Account deleted successfully.')
|
||||
const balanceChanges = getBalanceChanges(deleteTxResponse.result.meta)
|
||||
console.log('Balance changes:', JSON.stringify(balanceChanges, null, 2))
|
||||
|
||||
client.disconnect()
|
||||
10
_code-samples/delete-account/js/package.json
Normal file
10
_code-samples/delete-account/js/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "delete-account",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1",
|
||||
"xrpl": "^4.6.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
4
_code-samples/delete-account/py/.env
Normal file
4
_code-samples/delete-account/py/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
47
_code-samples/delete-account/py/README.md
Normal file
47
_code-samples/delete-account/py/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Delete Account (Python)
|
||||
|
||||
Python sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ python delete-account.py
|
||||
Got new account from faucet:
|
||||
Address: rNqLzC9pVbphwwpTBNPjpx14QSauHH3kzv
|
||||
Seed: sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226905
|
||||
Validated ledger index: 15226649
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
python delete-account.py
|
||||
```
|
||||
199
_code-samples/delete-account/py/delete-account.py
Normal file
199
_code-samples/delete-account/py/delete-account.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import os
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet, generate_faucet_wallet
|
||||
from xrpl.models.requests import AccountInfo, ServerState, AccountObjects
|
||||
from xrpl.models.transactions import AccountDelete
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import get_balance_changes
|
||||
|
||||
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
|
||||
|
||||
# Where to send the deleted account's remaining XRP:
|
||||
DESTINATION_ACCOUNT = "rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo" # Testnet faucet
|
||||
|
||||
# Load the account to delete from .env file -----------------------------------
|
||||
# If the seed value is still the default, get a new account from the faucet.
|
||||
# It won't be deletable immediately.
|
||||
load_dotenv()
|
||||
account_seed = os.getenv("ACCOUNT_SEED")
|
||||
account_algorithm = os.getenv("ACCOUNT_ALGORITHM", "ed25519")
|
||||
|
||||
if account_seed == "s████████████████████████████" or not account_seed:
|
||||
print("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = generate_faucet_wallet(client)
|
||||
print(
|
||||
f"Got new account from faucet:\n"
|
||||
f" Address: {wallet.address}\n"
|
||||
f" Seed: {wallet.seed}\n"
|
||||
)
|
||||
|
||||
print(
|
||||
"Edit the .env file to add this seed, then wait until the account can be deleted."
|
||||
)
|
||||
else:
|
||||
wallet = Wallet.from_seed(account_seed, algorithm=account_algorithm)
|
||||
print(f"Loaded account: {wallet.address}")
|
||||
|
||||
# Check account info to see if account can be deleted -------------------------
|
||||
try:
|
||||
acct_info_resp = client.request(
|
||||
AccountInfo(account=wallet.address, ledger_index="validated")
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_info failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
acct_info_result = acct_info_resp.result
|
||||
num_problems = 0
|
||||
|
||||
# Check if sequence number is too high
|
||||
acct_seq = acct_info_result["account_data"]["Sequence"]
|
||||
last_validated_ledger_index = acct_info_result["ledger_index"]
|
||||
|
||||
if acct_seq + 255 > last_validated_ledger_index:
|
||||
print(
|
||||
f"Account is too new to be deleted.\n"
|
||||
f" Account sequence + 255: {acct_seq + 255}\n"
|
||||
f" Validated ledger index: {last_validated_ledger_index}\n"
|
||||
f" (Sequence + 255 must be less than or equal to the ledger index)"
|
||||
)
|
||||
|
||||
# Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
est_wait_time_s = (acct_seq + 255 - last_validated_ledger_index) * 3.5
|
||||
if est_wait_time_s < 120:
|
||||
print(f"Estimate: {est_wait_time_s} seconds until account can be deleted")
|
||||
else:
|
||||
est_wait_time_m = round(est_wait_time_s / 60)
|
||||
print(f"Estimate: {est_wait_time_m} minutes until account can be deleted")
|
||||
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account sequence number ({acct_seq}) is low enough.")
|
||||
|
||||
# Check if owner count is too high
|
||||
owner_count = acct_info_result["account_data"]["OwnerCount"]
|
||||
if owner_count > 1000:
|
||||
print(
|
||||
f"Account owns too many objects in the ledger.\n"
|
||||
f" Owner count: {owner_count}\n"
|
||||
f" (Must be 1000 or less)"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account owner count ({owner_count}) is low enough.")
|
||||
|
||||
# Check if XRP balance is high enough
|
||||
# Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
# using server_state so that both are in drops
|
||||
try:
|
||||
server_state_resp = client.request(ServerState())
|
||||
except Exception as err:
|
||||
print("server_state failed with error:", err)
|
||||
exit(1)
|
||||
|
||||
validated_ledger = server_state_resp.result["state"].get("validated_ledger", {})
|
||||
deletion_cost = validated_ledger.get("reserve_inc")
|
||||
|
||||
if not deletion_cost:
|
||||
print(
|
||||
"Couldn't get reserve values from server. Maybe it's not synced to the network?"
|
||||
)
|
||||
print(json.dumps(server_state_resp.result, indent=2))
|
||||
exit(1)
|
||||
|
||||
acct_balance = int(acct_info_result["account_data"]["Balance"])
|
||||
if acct_balance < deletion_cost:
|
||||
print(
|
||||
f"Account does not have enough XRP to pay the cost of deletion.\n"
|
||||
f" Balance: {acct_balance}\n"
|
||||
f" Cost of account deletion: {deletion_cost}"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account balance ({acct_balance} drops) is high enough.")
|
||||
|
||||
# Check if FirstNFTSequence is too high
|
||||
first_nfq_seq = acct_info_result["account_data"].get("FirstNFTokenSequence", 0)
|
||||
minted_nfts = acct_info_result["account_data"].get("MintedNFTokens", 0)
|
||||
if first_nfq_seq + minted_nfts + 255 > last_validated_ledger_index:
|
||||
print(f"""Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: {first_nfq_seq + minted_nfts + 255}
|
||||
Validated ledger index: {last_validated_ledger_index}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the the ledger index)""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: FirstNFTokenSequence + MintedNFTokens is low enough.")
|
||||
|
||||
# Check that all issued NFTs have been burned
|
||||
burned_nfts = acct_info_result["account_data"].get("BurnedNFTokens", 0)
|
||||
if minted_nfts > burned_nfts:
|
||||
print(f"""Account has NFTs outstanding.
|
||||
Number of NFTs minted: {minted_nfts}
|
||||
Number of NFTs burned: {burned_nfts}""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: No outstanding, un-burned NFTs")
|
||||
|
||||
# Stop if any problems were found
|
||||
if num_problems:
|
||||
print(
|
||||
f"A total of {num_problems} problem(s) prevent the account from being deleted."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
# Check for deletion blockers -------------------------------------------------
|
||||
blockers = []
|
||||
marker = None
|
||||
ledger_index = "validated"
|
||||
|
||||
while True:
|
||||
try:
|
||||
account_obj_resp = client.request(
|
||||
AccountObjects(
|
||||
account=wallet.address,
|
||||
deletion_blockers_only=True,
|
||||
ledger_index=ledger_index,
|
||||
marker=marker,
|
||||
)
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_objects failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
blockers.extend(account_obj_resp.result["account_objects"])
|
||||
|
||||
marker = account_obj_resp.result.get("marker")
|
||||
if not marker:
|
||||
break
|
||||
|
||||
if not blockers:
|
||||
print("OK: Account has no deletion blockers.")
|
||||
else:
|
||||
print(f"Account cannot be deleted until {len(blockers)} blocker(s) are removed:")
|
||||
for blocker in blockers:
|
||||
print(json.dumps(blocker, indent=2))
|
||||
exit(1)
|
||||
|
||||
# Delete the account ----------------------------------------------------------
|
||||
account_delete_tx = AccountDelete(
|
||||
account=wallet.address, destination=DESTINATION_ACCOUNT
|
||||
)
|
||||
|
||||
print("Signing and submitting the AccountDelete transaction:")
|
||||
print(json.dumps(account_delete_tx.to_xrpl(), indent=2))
|
||||
delete_tx_response = submit_and_wait(account_delete_tx, client, wallet, fail_hard=True)
|
||||
|
||||
# Check result of the AccountDelete transaction -------------------------------
|
||||
print(json.dumps(delete_tx_response.result, indent=2))
|
||||
result_code = delete_tx_response.result["meta"]["TransactionResult"]
|
||||
|
||||
if result_code != "tesSUCCESS":
|
||||
print(f"AccountDelete failed with code {result_code}.")
|
||||
exit(1)
|
||||
|
||||
print("Account deleted successfully.")
|
||||
balance_changes = get_balance_changes(delete_tx_response.result["meta"])
|
||||
print("Balance changes:", json.dumps(balance_changes, indent=2))
|
||||
2
_code-samples/delete-account/py/requirements.txt
Normal file
2
_code-samples/delete-account/py/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
xrpl-py==4.5.0
|
||||
python-dotenv==1.2.1
|
||||
184
_code-samples/escrow/go/README.md
Normal file
184
_code-samples/escrow/go/README.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Escrow (Go)
|
||||
|
||||
This directory contains Go examples demonstrating how to create, finish, and cancel escrows on the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
All commands should be run from this `go/` directory.
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Send Fungible Token Escrow
|
||||
|
||||
```sh
|
||||
go run ./send-fungible-token-escrow
|
||||
```
|
||||
|
||||
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
|
||||
|
||||
```sh
|
||||
=== Funding Accounts ===
|
||||
|
||||
Funding Issuer account...
|
||||
Funding Escrow Creator account...
|
||||
Issuer: rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD
|
||||
Escrow Creator: rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk
|
||||
|
||||
=== Creating MPT ===
|
||||
|
||||
{
|
||||
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"Flags": 8,
|
||||
"MaximumAmount": "1000000",
|
||||
"TransactionType": "MPTokenIssuanceCreate"
|
||||
}
|
||||
|
||||
Submitting MPTokenIssuanceCreate transaction...
|
||||
MPT created: 00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B
|
||||
|
||||
=== Escrow Creator Authorizing MPT ===
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"MPTokenIssuanceID": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
|
||||
"TransactionType": "MPTokenAuthorize"
|
||||
}
|
||||
|
||||
Submitting MPTokenAuthorize transaction...
|
||||
Escrow Creator authorized for MPT.
|
||||
|
||||
=== Issuer Sending MPTs to Escrow Creator ===
|
||||
|
||||
{
|
||||
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
|
||||
"value": "5000"
|
||||
},
|
||||
"Destination": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
|
||||
Submitting MPT Payment transaction...
|
||||
Successfully sent 5000 MPTs to Escrow Creator.
|
||||
|
||||
=== Creating Conditional MPT Escrow ===
|
||||
|
||||
Condition: A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120
|
||||
Fulfillment: A0228020F16E8A8697ABAE14C60A5D812A2D228F9E6F67B8CA4818DC80BBF539004490DB
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F7A9BD191FD9BB1D11E217CA5643AED429859BDD40EF8B",
|
||||
"value": "1000"
|
||||
},
|
||||
"CancelAfter": 828559916,
|
||||
"Condition": "A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120",
|
||||
"Destination": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"TransactionType": "EscrowCreate"
|
||||
}
|
||||
|
||||
Submitting MPT EscrowCreate transaction...
|
||||
Conditional MPT escrow created. Sequence: 16230848
|
||||
|
||||
=== Finishing Conditional MPT Escrow ===
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"Condition": "A025802057FDC219A423C4F0DA150941EB529B1D927816FAB394617A0430D1DDB39A3EDB810120",
|
||||
"Fulfillment": "A0228020F16E8A8697ABAE14C60A5D812A2D228F9E6F67B8CA4818DC80BBF539004490DB",
|
||||
"OfferSequence": 16230848,
|
||||
"Owner": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"TransactionType": "EscrowFinish"
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/37CD7FECDC71CE70C24927969AD0FDAD55F57F2905A6F62867CB4F5AB2EE27BB
|
||||
|
||||
=== Enabling Trust Line Token Escrows on Issuer ===
|
||||
|
||||
{
|
||||
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"SetFlag": 17,
|
||||
"TransactionType": "AccountSet"
|
||||
}
|
||||
|
||||
Submitting AccountSet transaction...
|
||||
Trust line token escrows enabled by issuer.
|
||||
|
||||
=== Setting Up Trust Line ===
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"LimitAmount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"value": "10000000"
|
||||
},
|
||||
"TransactionType": "TrustSet"
|
||||
}
|
||||
|
||||
Submitting TrustSet transaction...
|
||||
Trust line successfully created for "IOU" tokens.
|
||||
|
||||
=== Issuer Sending IOU Tokens to Escrow Creator ===
|
||||
|
||||
{
|
||||
"Account": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"value": "5000"
|
||||
},
|
||||
"Destination": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"TransactionType": "Payment"
|
||||
}
|
||||
|
||||
Submitting Trust Line Token payment transaction...
|
||||
Successfully sent 5000 IOU tokens.
|
||||
|
||||
=== Creating Timed Trust Line Token Escrow ===
|
||||
|
||||
Escrow will mature after: 04/03/2026, 12:27:38 PM
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"value": "1000"
|
||||
},
|
||||
"CancelAfter": 828559948,
|
||||
"Destination": "rsHiso1Qb4vb9GfuWSvmPeBuk6BT1479uD",
|
||||
"FinishAfter": 828559658,
|
||||
"TransactionType": "EscrowCreate"
|
||||
}
|
||||
|
||||
Submitting Trust Line Token EscrowCreate transaction...
|
||||
Trust Line Token escrow created. Sequence: 16230851
|
||||
|
||||
=== Waiting For Timed Trust Line Token Escrow to Mature ===
|
||||
|
||||
Waiting for escrow to mature... done.
|
||||
Latest validated ledger closed at: 04/03/2026, 12:27:41 PM
|
||||
Escrow confirmed ready to finish.
|
||||
|
||||
=== Finishing Timed Trust Line Token Escrow ===
|
||||
|
||||
{
|
||||
"Account": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"OfferSequence": 16230851,
|
||||
"Owner": "rfQVjJ9sRYcLqwxRV2rqSSFm9jusXXo9Sk",
|
||||
"TransactionType": "EscrowFinish"
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/9D1937BE3ADFC42078F222B1DBAE8571BBC096DDA7A47911C4715221C83EC22D
|
||||
```
|
||||
23
_code-samples/escrow/go/go.mod
Normal file
23
_code-samples/escrow/go/go.mod
Normal file
@@ -0,0 +1,23 @@
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/Peersyst/xrpl-go v0.1.17
|
||||
github.com/go-interledger/cryptoconditions v0.0.0-20180612102545-aba58e59cef1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PromonLogicalis/asn1 v0.0.0-20190312173541-d60463189a56 // indirect
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/kalaspuffar/base64url v0.0.0-20171121144659-483af17b794c // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/stevenroose/asn1 v0.0.0-20170613173945-a0d410e3f79f // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
)
|
||||
461
_code-samples/escrow/go/send-fungible-token-escrow/main.go
Normal file
461
_code-samples/escrow/go/send-fungible-token-escrow/main.go
Normal file
@@ -0,0 +1,461 @@
|
||||
// This example demonstrates how to create escrows that hold fungible tokens.
|
||||
// It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/pkg/crypto"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/faucet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
ledgerreq "github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
"github.com/go-interledger/cryptoconditions"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.altnet.rippletest.net:51233").
|
||||
WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Fund an issuer account and an escrow creator account ----------------------
|
||||
fmt.Printf("\n=== Funding Accounts ===\n\n")
|
||||
|
||||
createAndFund := func(label string) wallet.Wallet {
|
||||
fmt.Printf("Funding %s account...\n", label)
|
||||
w, err := wallet.New(crypto.ED25519())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := client.FundWallet(&w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Poll until account is validated on ledger
|
||||
funded := false
|
||||
for range 20 {
|
||||
_, err := client.Request(&account.InfoRequest{
|
||||
Account: w.GetAddress(),
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err == nil {
|
||||
funded = true
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !funded {
|
||||
panic("Issue funding account: " + w.GetAddress().String())
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
issuer := createAndFund("Issuer")
|
||||
creator := createAndFund("Escrow Creator")
|
||||
fmt.Printf("Issuer: %s\n", issuer.ClassicAddress)
|
||||
fmt.Printf("Escrow Creator: %s\n", creator.ClassicAddress)
|
||||
|
||||
// ====== Conditional MPT Escrow ======
|
||||
|
||||
// Issuer creates an MPT ----------------------
|
||||
fmt.Printf("\n=== Creating MPT ===\n\n")
|
||||
maxAmount := types.XRPCurrencyAmount(1000000)
|
||||
mptCreateTx := transaction.MPTokenIssuanceCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: issuer.ClassicAddress,
|
||||
Flags: transaction.TfMPTCanEscrow,
|
||||
},
|
||||
MaximumAmount: &maxAmount,
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatMptCreateTx := mptCreateTx.Flatten()
|
||||
mptCreateTxJSON, _ := json.MarshalIndent(flatMptCreateTx, "", " ")
|
||||
fmt.Printf("%s\n", string(mptCreateTxJSON))
|
||||
|
||||
// Submit, sign, and wait for validation
|
||||
fmt.Printf("\nSubmitting MPTokenIssuanceCreate transaction...\n")
|
||||
mptCreateResponse, err := client.SubmitTxAndWait(flatMptCreateTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &issuer,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mptCreateResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("MPTokenIssuanceCreate failed: %s\n", mptCreateResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Extract the MPT issuance ID from the transaction result
|
||||
mptIssuanceID := string(*mptCreateResponse.Meta.MPTIssuanceID)
|
||||
fmt.Printf("MPT created: %s\n", mptIssuanceID)
|
||||
|
||||
// Escrow Creator authorizes the MPT ----------------------
|
||||
fmt.Printf("\n=== Escrow Creator Authorizing MPT ===\n\n")
|
||||
mptAuthTx := transaction.MPTokenAuthorize{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
MPTokenIssuanceID: mptIssuanceID,
|
||||
}
|
||||
|
||||
flatMptAuthTx := mptAuthTx.Flatten()
|
||||
mptAuthTxJSON, _ := json.MarshalIndent(flatMptAuthTx, "", " ")
|
||||
fmt.Printf("%s\n", string(mptAuthTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting MPTokenAuthorize transaction...\n")
|
||||
mptAuthResponse, err := client.SubmitTxAndWait(flatMptAuthTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mptAuthResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("MPTokenAuthorize failed: %s\n", mptAuthResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Escrow Creator authorized for MPT.\n")
|
||||
|
||||
// Issuer sends MPTs to escrow creator ----------------------
|
||||
fmt.Printf("\n=== Issuer Sending MPTs to Escrow Creator ===\n\n")
|
||||
mptPaymentTx := transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: issuer.ClassicAddress,
|
||||
},
|
||||
Destination: creator.ClassicAddress,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptIssuanceID,
|
||||
Value: "5000",
|
||||
},
|
||||
}
|
||||
|
||||
flatMptPaymentTx := mptPaymentTx.Flatten()
|
||||
mptPaymentTxJSON, _ := json.MarshalIndent(flatMptPaymentTx, "", " ")
|
||||
fmt.Printf("%s\n", string(mptPaymentTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting MPT Payment transaction...\n")
|
||||
mptPaymentResponse, err := client.SubmitTxAndWait(flatMptPaymentTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &issuer,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mptPaymentResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("MPT Payment failed: %s\n", mptPaymentResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully sent 5000 MPTs to Escrow Creator.\n")
|
||||
|
||||
// Escrow Creator creates a conditional MPT escrow ----------------------
|
||||
fmt.Printf("\n=== Creating Conditional MPT Escrow ===\n\n")
|
||||
|
||||
// Generate crypto-condition
|
||||
preimage := make([]byte, 32)
|
||||
if _, err := rand.Read(preimage); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fulfillment := cryptoconditions.NewPreimageSha256(preimage)
|
||||
fulfillmentBinary, err := fulfillment.Encode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conditionBinary, err := fulfillment.Condition().Encode()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fulfillmentHex := strings.ToUpper(hex.EncodeToString(fulfillmentBinary))
|
||||
conditionHex := strings.ToUpper(hex.EncodeToString(conditionBinary))
|
||||
fmt.Printf("Condition: %s\n", conditionHex)
|
||||
fmt.Printf("Fulfillment: %s\n\n", fulfillmentHex)
|
||||
|
||||
// Set expiration (300 seconds from now)
|
||||
cancelAfterRippleTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix()) + 300
|
||||
|
||||
mptEscrowCreateTx := transaction.EscrowCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
Destination: issuer.ClassicAddress,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptIssuanceID,
|
||||
Value: "1000",
|
||||
},
|
||||
Condition: conditionHex,
|
||||
CancelAfter: uint32(cancelAfterRippleTime), // Fungible token escrows require a CancelAfter time
|
||||
}
|
||||
|
||||
flatMptEscrowCreateTx := mptEscrowCreateTx.Flatten()
|
||||
mptEscrowCreateTxJSON, _ := json.MarshalIndent(flatMptEscrowCreateTx, "", " ")
|
||||
fmt.Printf("%s\n", string(mptEscrowCreateTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting MPT EscrowCreate transaction...\n")
|
||||
mptEscrowResponse, err := client.SubmitTxAndWait(flatMptEscrowCreateTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mptEscrowResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("MPT EscrowCreate failed: %s\n", mptEscrowResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save the sequence number to identify the escrow later
|
||||
mptEscrowSeq := mptEscrowResponse.TxJSON.Sequence()
|
||||
fmt.Printf("Conditional MPT escrow created. Sequence: %d\n", mptEscrowSeq)
|
||||
|
||||
// Finish the conditional MPT escrow with the fulfillment ----------------------
|
||||
fmt.Printf("\n=== Finishing Conditional MPT Escrow ===\n\n")
|
||||
mptEscrowFinishTx := transaction.EscrowFinish{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
Owner: creator.ClassicAddress,
|
||||
OfferSequence: mptEscrowSeq,
|
||||
Condition: conditionHex,
|
||||
Fulfillment: fulfillmentHex,
|
||||
}
|
||||
|
||||
flatMptEscrowFinishTx := mptEscrowFinishTx.Flatten()
|
||||
mptEscrowFinishTxJSON, _ := json.MarshalIndent(flatMptEscrowFinishTx, "", " ")
|
||||
fmt.Printf("%s\n", string(mptEscrowFinishTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting EscrowFinish transaction...\n")
|
||||
mptFinishResponse, err := client.SubmitTxAndWait(flatMptEscrowFinishTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if mptFinishResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("MPT EscrowFinish failed: %s\n", mptFinishResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/%s\n", mptFinishResponse.Hash)
|
||||
|
||||
// ====== Timed Trust Line Token Escrow ======
|
||||
|
||||
// Enable trust line token escrows on the issuer ----------------------
|
||||
fmt.Printf("\n=== Enabling Trust Line Token Escrows on Issuer ===\n\n")
|
||||
accountSetTx := transaction.AccountSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: issuer.ClassicAddress,
|
||||
},
|
||||
SetFlag: transaction.AsfAllowTrustLineLocking,
|
||||
}
|
||||
|
||||
flatAccountSetTx := accountSetTx.Flatten()
|
||||
accountSetTxJSON, _ := json.MarshalIndent(flatAccountSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(accountSetTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting AccountSet transaction...\n")
|
||||
accountSetResponse, err := client.SubmitTxAndWait(flatAccountSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &issuer,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if accountSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("AccountSet failed: %s\n", accountSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Trust line token escrows enabled by issuer.\n")
|
||||
|
||||
// Escrow Creator sets up a trust line to the issuer ----------------------
|
||||
fmt.Printf("\n=== Setting Up Trust Line ===\n\n")
|
||||
currencyCode := "IOU"
|
||||
|
||||
trustSetTx := transaction.TrustSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
LimitAmount: types.IssuedCurrencyAmount{
|
||||
Currency: currencyCode,
|
||||
Issuer: issuer.ClassicAddress,
|
||||
Value: "10000000",
|
||||
},
|
||||
}
|
||||
|
||||
flatTrustSetTx := trustSetTx.Flatten()
|
||||
trustSetTxJSON, _ := json.MarshalIndent(flatTrustSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(trustSetTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting TrustSet transaction...\n")
|
||||
trustResponse, err := client.SubmitTxAndWait(flatTrustSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if trustResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("TrustSet failed: %s\n", trustResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Trust line successfully created for \"%s\" tokens.\n", currencyCode)
|
||||
|
||||
// Issuer sends IOU tokens to creator ----------------------
|
||||
fmt.Printf("\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n\n")
|
||||
iouPaymentTx := transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: issuer.ClassicAddress,
|
||||
},
|
||||
Destination: creator.ClassicAddress,
|
||||
Amount: types.IssuedCurrencyAmount{
|
||||
Currency: currencyCode,
|
||||
Value: "5000",
|
||||
Issuer: issuer.ClassicAddress,
|
||||
},
|
||||
}
|
||||
|
||||
flatIouPaymentTx := iouPaymentTx.Flatten()
|
||||
iouPaymentTxJSON, _ := json.MarshalIndent(flatIouPaymentTx, "", " ")
|
||||
fmt.Printf("%s\n", string(iouPaymentTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting Trust Line Token payment transaction...\n")
|
||||
iouPayResponse, err := client.SubmitTxAndWait(flatIouPaymentTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &issuer,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if iouPayResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Trust Line Token payment failed: %s\n", iouPayResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully sent 5000 %s tokens.\n", currencyCode)
|
||||
|
||||
// Escrow Creator creates a timed trust line token escrow ----------------------
|
||||
fmt.Printf("\n=== Creating Timed Trust Line Token Escrow ===\n\n")
|
||||
delay := 10 // seconds
|
||||
now := time.Now()
|
||||
finishAfterRippleTime := xrpltime.UnixTimeToRippleTime(now.Unix()) + int64(delay)
|
||||
matureTime := now.Add(time.Duration(delay) * time.Second).Format("01/02/2006, 03:04:05 PM")
|
||||
fmt.Printf("Escrow will mature after: %s\n\n", matureTime)
|
||||
|
||||
iouCancelAfterRippleTime := xrpltime.UnixTimeToRippleTime(now.Unix()) + 300
|
||||
|
||||
iouEscrowCreateTx := transaction.EscrowCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
Destination: issuer.ClassicAddress,
|
||||
Amount: types.IssuedCurrencyAmount{
|
||||
Currency: currencyCode,
|
||||
Value: "1000",
|
||||
Issuer: issuer.ClassicAddress,
|
||||
},
|
||||
FinishAfter: uint32(finishAfterRippleTime),
|
||||
CancelAfter: uint32(iouCancelAfterRippleTime),
|
||||
}
|
||||
|
||||
flatIouEscrowCreateTx := iouEscrowCreateTx.Flatten()
|
||||
iouEscrowCreateTxJSON, _ := json.MarshalIndent(flatIouEscrowCreateTx, "", " ")
|
||||
fmt.Printf("%s\n", string(iouEscrowCreateTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting Trust Line Token EscrowCreate transaction...\n")
|
||||
iouEscrowResponse, err := client.SubmitTxAndWait(flatIouEscrowCreateTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if iouEscrowResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Trust Line Token EscrowCreate failed: %s\n", iouEscrowResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Save the sequence number to identify the escrow later
|
||||
iouEscrowSeq := iouEscrowResponse.TxJSON.Sequence()
|
||||
fmt.Printf("Trust Line Token escrow created. Sequence: %d\n", iouEscrowSeq)
|
||||
|
||||
// Wait for the escrow to mature, then finish it --------------------
|
||||
fmt.Printf("\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n\n")
|
||||
|
||||
// Countdown delay until escrow matures
|
||||
for i := delay; i >= 0; i-- {
|
||||
fmt.Printf("Waiting for escrow to mature... %ds remaining...\r", i)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
fmt.Printf("Waiting for escrow to mature... done. \n")
|
||||
|
||||
// Confirm latest validated ledger close time is after the FinishAfter time
|
||||
escrowReady := false
|
||||
for !escrowReady {
|
||||
ledgerResp, err := client.GetLedger(&ledgerreq.Request{
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ledgerCloseTime := int64(ledgerResp.Ledger.CloseTime)
|
||||
ledgerCloseLocal := time.Unix(xrpltime.RippleTimeToUnixTime(ledgerCloseTime)/1000, 0).Format("01/02/2006, 03:04:05 PM")
|
||||
fmt.Printf("Latest validated ledger closed at: %s\n", ledgerCloseLocal)
|
||||
if ledgerCloseTime > finishAfterRippleTime {
|
||||
escrowReady = true
|
||||
fmt.Printf("Escrow confirmed ready to finish.\n")
|
||||
} else {
|
||||
timeDifference := finishAfterRippleTime - ledgerCloseTime
|
||||
if timeDifference == 0 {
|
||||
timeDifference = 1
|
||||
}
|
||||
fmt.Printf("Escrow needs to wait another %ds.\n", timeDifference)
|
||||
time.Sleep(time.Duration(timeDifference) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the timed trust line token escrow --------------------
|
||||
fmt.Printf("\n=== Finishing Timed Trust Line Token Escrow ===\n\n")
|
||||
iouEscrowFinishTx := transaction.EscrowFinish{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: creator.ClassicAddress,
|
||||
},
|
||||
Owner: creator.ClassicAddress,
|
||||
OfferSequence: iouEscrowSeq,
|
||||
}
|
||||
|
||||
flatIouEscrowFinishTx := iouEscrowFinishTx.Flatten()
|
||||
iouEscrowFinishTxJSON, _ := json.MarshalIndent(flatIouEscrowFinishTx, "", " ")
|
||||
fmt.Printf("%s\n", string(iouEscrowFinishTxJSON))
|
||||
|
||||
fmt.Printf("\nSubmitting EscrowFinish transaction...\n")
|
||||
iouFinishResponse, err := client.SubmitTxAndWait(flatIouEscrowFinishTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &creator,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if iouFinishResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Trust Line Token EscrowFinish failed: %s\n", iouFinishResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/%s\n", iouFinishResponse.Hash)
|
||||
}
|
||||
@@ -20,6 +20,173 @@ node send-timed-escrow.js
|
||||
node send-conditional-escrow.js
|
||||
```
|
||||
|
||||
## Send Fungible Token Escrow
|
||||
|
||||
```sh
|
||||
node sendFungibleTokenEscrow.js
|
||||
```
|
||||
|
||||
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
|
||||
|
||||
```sh
|
||||
=== Funding Accounts ===
|
||||
|
||||
Issuer: rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP
|
||||
Escrow Creator: rGRvH4FanVixca934o3ui4MbcrU56x9Qj4
|
||||
|
||||
=== Creating MPT ===
|
||||
|
||||
{
|
||||
"TransactionType": "MPTokenIssuanceCreate",
|
||||
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"MaximumAmount": "1000000",
|
||||
"Flags": 8
|
||||
}
|
||||
|
||||
Submitting MPTokenIssuanceCreate transaction...
|
||||
MPT created: 00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC
|
||||
|
||||
=== Escrow Creator Authorizing MPT ===
|
||||
|
||||
{
|
||||
"TransactionType": "MPTokenAuthorize",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"MPTokenIssuanceID": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC"
|
||||
}
|
||||
|
||||
Submitting MPTokenAuthorize transaction...
|
||||
Escrow Creator authorized for MPT.
|
||||
|
||||
=== Issuer Sending MPTs to Escrow Creator ===
|
||||
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"Destination": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC",
|
||||
"value": "5000"
|
||||
}
|
||||
}
|
||||
|
||||
Submitting MPT Payment transaction...
|
||||
Successfully sent 5000 MPTs to Escrow Creator.
|
||||
|
||||
=== Creating Conditional MPT Escrow ===
|
||||
|
||||
Condition: A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120
|
||||
Fulfillment: A0228020CA07971CB0C63ED20C69931B41EEA7C4C8CC6F214183FDE031CDC7413856977F
|
||||
|
||||
{
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Destination": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F763A2D998FA5E720228B31E1162AC55E6311C7D31F3FC",
|
||||
"value": "1000"
|
||||
},
|
||||
"Condition": "A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120",
|
||||
"CancelAfter": 828504579
|
||||
}
|
||||
|
||||
Submitting MPT EscrowCreate transaction...
|
||||
Conditional MPT escrow created. Sequence: 16212899
|
||||
|
||||
=== Finishing Conditional MPT Escrow ===
|
||||
|
||||
{
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Owner": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"OfferSequence": 16212899,
|
||||
"Condition": "A0258020AA2B8450898500A9E6332B7AD107264982CB09C63E3D16D139D63E997597E6F6810120",
|
||||
"Fulfillment": "A0228020CA07971CB0C63ED20C69931B41EEA7C4C8CC6F214183FDE031CDC7413856977F"
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/BB6E8BF8A7F28D15C12C24FFDB215180262ABFAEAD43FB020DCB39E826027078
|
||||
|
||||
=== Enabling Trust Line Token Escrows on Issuer ===
|
||||
|
||||
{
|
||||
"TransactionType": "AccountSet",
|
||||
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"SetFlag": 17
|
||||
}
|
||||
|
||||
Submitting AccountSet transaction...
|
||||
Trust line token escrows enabled by issuer.
|
||||
|
||||
=== Setting Up Trust Line ===
|
||||
|
||||
{
|
||||
"TransactionType": "TrustSet",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"LimitAmount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"value": "10000000"
|
||||
}
|
||||
}
|
||||
|
||||
Submitting TrustSet transaction...
|
||||
Trust line successfully created for "IOU" tokens.
|
||||
|
||||
=== Issuer Sending IOU Tokens to Escrow Creator ===
|
||||
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"Destination": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"value": "5000",
|
||||
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP"
|
||||
}
|
||||
}
|
||||
|
||||
Submitting Trust Line Token payment transaction...
|
||||
Successfully sent 5000 IOU tokens.
|
||||
|
||||
=== Creating Timed Trust Line Token Escrow ===
|
||||
|
||||
Escrow will mature after: 4/2/2026, 9:05:12 PM
|
||||
|
||||
{
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Destination": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"value": "1000",
|
||||
"issuer": "rLqYtjhg56pVNJFKueVVKkiA8z5UtznxQP"
|
||||
},
|
||||
"FinishAfter": 828504312,
|
||||
"CancelAfter": 828504602
|
||||
}
|
||||
|
||||
Submitting Trust Line Token EscrowCreate transaction...
|
||||
Trust Line Token escrow created. Sequence: 16212902
|
||||
|
||||
=== Waiting For Timed Trust Line Token Escrow to Mature ===
|
||||
|
||||
Waiting for escrow to mature... done.
|
||||
Latest validated ledger closed at: 4/2/2026, 9:05:13 PM
|
||||
Escrow confirmed ready to finish.
|
||||
|
||||
=== Finishing Timed Trust Line Token Escrow ===
|
||||
|
||||
{
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"Owner": "rGRvH4FanVixca934o3ui4MbcrU56x9Qj4",
|
||||
"OfferSequence": 16212902
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/136402974863BF553706B0A4A341F24DDA5385BB6F93B905038D8FD9863B6D91
|
||||
```
|
||||
|
||||
## List Escrows
|
||||
|
||||
```sh
|
||||
|
||||
358
_code-samples/escrow/js/sendFungibleTokenEscrow.js
Normal file
358
_code-samples/escrow/js/sendFungibleTokenEscrow.js
Normal file
@@ -0,0 +1,358 @@
|
||||
// This example demonstrates how to create escrows that hold fungible tokens.
|
||||
// It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
|
||||
|
||||
import xrpl from 'xrpl'
|
||||
import { PreimageSha256 } from 'five-bells-condition'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Fund an issuer account and an escrow creator account ----------------------
|
||||
console.log(`\n=== Funding Accounts ===\n`)
|
||||
const [
|
||||
{ wallet: issuer },
|
||||
{ wallet: creator }
|
||||
] = await Promise.all([
|
||||
client.fundWallet(),
|
||||
client.fundWallet()
|
||||
])
|
||||
console.log(`Issuer: ${issuer.address}`)
|
||||
console.log(`Escrow Creator: ${creator.address}`)
|
||||
|
||||
// ====== Conditional MPT Escrow ======
|
||||
|
||||
// Issuer creates an MPT ----------------------
|
||||
console.log('\n=== Creating MPT ===\n')
|
||||
const mptCreateTx = {
|
||||
TransactionType: 'MPTokenIssuanceCreate',
|
||||
Account: issuer.address,
|
||||
MaximumAmount: '1000000',
|
||||
Flags: xrpl.MPTokenIssuanceCreateFlags.tfMPTCanEscrow
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(mptCreateTx)
|
||||
console.log(JSON.stringify(mptCreateTx, null, 2))
|
||||
|
||||
// Submit, sign, and wait for validation
|
||||
console.log(`\nSubmitting MPTokenIssuanceCreate transaction...`)
|
||||
const mptCreateResponse = await client.submitAndWait(mptCreateTx, {
|
||||
wallet: issuer,
|
||||
autofill: true
|
||||
})
|
||||
if (mptCreateResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`MPTokenIssuanceCreate failed: ${mptCreateResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Extract the MPT issuance ID from the transaction result
|
||||
const mptIssuanceId = mptCreateResponse.result.meta.mpt_issuance_id
|
||||
console.log(`MPT created: ${mptIssuanceId}`)
|
||||
|
||||
// Escrow Creator authorizes the MPT ----------------------
|
||||
console.log('\n=== Escrow Creator Authorizing MPT ===\n')
|
||||
const mptAuthTx = {
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: creator.address,
|
||||
MPTokenIssuanceID: mptIssuanceId
|
||||
}
|
||||
|
||||
xrpl.validate(mptAuthTx)
|
||||
console.log(JSON.stringify(mptAuthTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting MPTokenAuthorize transaction...`)
|
||||
const mptAuthResponse = await client.submitAndWait(mptAuthTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (mptAuthResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`MPTokenAuthorize failed: ${mptAuthResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Escrow Creator authorized for MPT.')
|
||||
|
||||
// Issuer sends MPTs to escrow creator ----------------------
|
||||
console.log('\n=== Issuer Sending MPTs to Escrow Creator ===\n')
|
||||
const mptPaymentTx = {
|
||||
TransactionType: 'Payment',
|
||||
Account: issuer.address,
|
||||
Destination: creator.address,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptIssuanceId,
|
||||
value: '5000'
|
||||
}
|
||||
}
|
||||
|
||||
xrpl.validate(mptPaymentTx)
|
||||
console.log(JSON.stringify(mptPaymentTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting MPT Payment transaction...`)
|
||||
const mptPaymentResponse = await client.submitAndWait(mptPaymentTx, {
|
||||
wallet: issuer,
|
||||
autofill: true
|
||||
})
|
||||
if (mptPaymentResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`MPT Payment failed: ${mptPaymentResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Successfully sent 5000 MPTs to Escrow Creator.')
|
||||
|
||||
// Escrow Creator creates a conditional MPT escrow ----------------------
|
||||
console.log('\n=== Creating Conditional MPT Escrow ===\n')
|
||||
|
||||
// Generate crypto-condition
|
||||
const preimage = randomBytes(32)
|
||||
const fulfillment = new PreimageSha256()
|
||||
fulfillment.setPreimage(preimage)
|
||||
const fulfillmentHex = fulfillment.serializeBinary().toString('hex').toUpperCase()
|
||||
const conditionHex = fulfillment.getConditionBinary().toString('hex').toUpperCase()
|
||||
console.log(`Condition: ${conditionHex}`)
|
||||
console.log(`Fulfillment: ${fulfillmentHex}\n`)
|
||||
|
||||
// Set expiration (300 seconds from now)
|
||||
const cancelAfter = new Date()
|
||||
cancelAfter.setSeconds(cancelAfter.getSeconds() + 300)
|
||||
const cancelAfterRippleTime = xrpl.isoTimeToRippleTime(cancelAfter.toISOString())
|
||||
|
||||
const mptEscrowCreateTx = {
|
||||
TransactionType: 'EscrowCreate',
|
||||
Account: creator.address,
|
||||
Destination: issuer.address,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptIssuanceId,
|
||||
value: '1000'
|
||||
},
|
||||
Condition: conditionHex,
|
||||
CancelAfter: cancelAfterRippleTime // Fungible token escrows require a CancelAfter time
|
||||
}
|
||||
|
||||
xrpl.validate(mptEscrowCreateTx)
|
||||
console.log(JSON.stringify(mptEscrowCreateTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting MPT EscrowCreate transaction...`)
|
||||
const mptEscrowResponse = await client.submitAndWait(mptEscrowCreateTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (mptEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`MPT EscrowCreate failed: ${mptEscrowResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Save the sequence number to identify the escrow later
|
||||
const mptEscrowSeq = mptEscrowResponse.result.tx_json.Sequence
|
||||
console.log(`Conditional MPT escrow created. Sequence: ${mptEscrowSeq}`)
|
||||
|
||||
// Finish the conditional MPT escrow with the fulfillment ----------------------
|
||||
console.log('\n=== Finishing Conditional MPT Escrow ===\n')
|
||||
const mptEscrowFinishTx = {
|
||||
TransactionType: 'EscrowFinish',
|
||||
Account: creator.address,
|
||||
Owner: creator.address,
|
||||
OfferSequence: mptEscrowSeq,
|
||||
Condition: conditionHex,
|
||||
Fulfillment: fulfillmentHex
|
||||
}
|
||||
|
||||
xrpl.validate(mptEscrowFinishTx)
|
||||
console.log(JSON.stringify(mptEscrowFinishTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting EscrowFinish transaction...`)
|
||||
const mptFinishResponse = await client.submitAndWait(mptEscrowFinishTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (mptFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`MPT EscrowFinish failed: ${mptFinishResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/${mptFinishResponse.result.hash}`)
|
||||
|
||||
// ====== Timed Trust Line Token Escrow ======
|
||||
|
||||
// Enable trust line token escrows on the issuer ----------------------
|
||||
console.log('\n=== Enabling Trust Line Token Escrows on Issuer ===\n')
|
||||
const accountSetTx = {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: issuer.address,
|
||||
SetFlag: xrpl.AccountSetAsfFlags.asfAllowTrustLineLocking
|
||||
}
|
||||
|
||||
xrpl.validate(accountSetTx)
|
||||
console.log(JSON.stringify(accountSetTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting AccountSet transaction...`)
|
||||
const accountSetResponse = await client.submitAndWait(accountSetTx, {
|
||||
wallet: issuer,
|
||||
autofill: true
|
||||
})
|
||||
if (accountSetResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`AccountSet failed: ${accountSetResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Trust line token escrows enabled by issuer.')
|
||||
|
||||
// Escrow Creator sets up a trust line to the issuer ----------------------
|
||||
console.log('\n=== Setting Up Trust Line ===\n')
|
||||
const currencyCode = 'IOU'
|
||||
|
||||
const trustSetTx = {
|
||||
TransactionType: 'TrustSet',
|
||||
Account: creator.address,
|
||||
LimitAmount: {
|
||||
currency: currencyCode,
|
||||
issuer: issuer.address,
|
||||
value: '10000000'
|
||||
}
|
||||
}
|
||||
|
||||
xrpl.validate(trustSetTx)
|
||||
console.log(JSON.stringify(trustSetTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting TrustSet transaction...`)
|
||||
const trustResponse = await client.submitAndWait(trustSetTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (trustResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`TrustSet failed: ${trustResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`Trust line successfully created for "${currencyCode}" tokens.`)
|
||||
|
||||
// Issuer sends IOU tokens to creator ----------------------
|
||||
console.log('\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n')
|
||||
const iouPaymentTx = {
|
||||
TransactionType: 'Payment',
|
||||
Account: issuer.address,
|
||||
Destination: creator.address,
|
||||
Amount: {
|
||||
currency: currencyCode,
|
||||
value: '5000',
|
||||
issuer: issuer.address
|
||||
}
|
||||
}
|
||||
|
||||
xrpl.validate(iouPaymentTx)
|
||||
console.log(JSON.stringify(iouPaymentTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting Trust Line Token payment transaction...`)
|
||||
const iouPayResponse = await client.submitAndWait(iouPaymentTx, {
|
||||
wallet: issuer,
|
||||
autofill: true
|
||||
})
|
||||
if (iouPayResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`Trust Line Token payment failed: ${iouPayResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`Successfully sent 5000 ${currencyCode} tokens.`)
|
||||
|
||||
// Escrow Creator creates a timed trust line token escrow ----------------------
|
||||
console.log('\n=== Creating Timed Trust Line Token Escrow ===\n')
|
||||
const delay = 10 // seconds
|
||||
const now = new Date()
|
||||
const finishAfter = new Date(now.getTime() + delay * 1000)
|
||||
const finishAfterRippleTime = xrpl.isoTimeToRippleTime(finishAfter.toISOString())
|
||||
console.log(`Escrow will mature after: ${finishAfter.toLocaleString()}\n`)
|
||||
|
||||
const iouCancelAfter = new Date(now.getTime() + 300 * 1000)
|
||||
const iouCancelAfterRippleTime = xrpl.isoTimeToRippleTime(iouCancelAfter.toISOString())
|
||||
|
||||
const iouEscrowCreateTx = {
|
||||
TransactionType: 'EscrowCreate',
|
||||
Account: creator.address,
|
||||
Destination: issuer.address,
|
||||
Amount: {
|
||||
currency: currencyCode,
|
||||
value: '1000',
|
||||
issuer: issuer.address
|
||||
},
|
||||
FinishAfter: finishAfterRippleTime,
|
||||
CancelAfter: iouCancelAfterRippleTime
|
||||
}
|
||||
|
||||
xrpl.validate(iouEscrowCreateTx)
|
||||
console.log(JSON.stringify(iouEscrowCreateTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting Trust Line Token EscrowCreate transaction...`)
|
||||
const iouEscrowResponse = await client.submitAndWait(iouEscrowCreateTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (iouEscrowResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`Trust Line Token EscrowCreate failed: ${iouEscrowResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Save the sequence number to identify the escrow later
|
||||
const iouEscrowSeq = iouEscrowResponse.result.tx_json.Sequence
|
||||
console.log(`Trust Line Token escrow created. Sequence: ${iouEscrowSeq}`)
|
||||
|
||||
// Wait for the escrow to mature, then finish it --------------------
|
||||
console.log(`\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n`)
|
||||
|
||||
// Sleep function to countdown delay until escrow matures
|
||||
function sleep (delayInSeconds) {
|
||||
return new Promise((resolve) => setTimeout(resolve, delayInSeconds * 1000))
|
||||
}
|
||||
for (let i = delay; i >= 0; i--) {
|
||||
process.stdout.write(`\rWaiting for escrow to mature... ${i}s remaining...`)
|
||||
await sleep(1)
|
||||
}
|
||||
console.log('\rWaiting for escrow to mature... done. ')
|
||||
|
||||
// Confirm latest validated ledger close time is after the FinishAfter time
|
||||
let escrowReady = false
|
||||
while (!escrowReady) {
|
||||
const validatedLedger = await client.request({
|
||||
command: 'ledger',
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
const ledgerCloseTime = validatedLedger.result.ledger.close_time
|
||||
console.log(`Latest validated ledger closed at: ${new Date(xrpl.rippleTimeToISOTime(ledgerCloseTime)).toLocaleString()}`)
|
||||
if (ledgerCloseTime > finishAfterRippleTime) {
|
||||
escrowReady = true
|
||||
console.log('Escrow confirmed ready to finish.')
|
||||
} else {
|
||||
let timeDifference = finishAfterRippleTime - ledgerCloseTime
|
||||
if (timeDifference === 0) { timeDifference = 1 }
|
||||
console.log(`Escrow needs to wait another ${timeDifference}s.`)
|
||||
await sleep(timeDifference)
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the timed trust line token escrow --------------------
|
||||
console.log('\n=== Finishing Timed Trust Line Token Escrow ===\n')
|
||||
const iouEscrowFinishTx = {
|
||||
TransactionType: 'EscrowFinish',
|
||||
Account: creator.address,
|
||||
Owner: creator.address,
|
||||
OfferSequence: iouEscrowSeq
|
||||
}
|
||||
|
||||
xrpl.validate(iouEscrowFinishTx)
|
||||
console.log(JSON.stringify(iouEscrowFinishTx, null, 2))
|
||||
|
||||
console.log(`\nSubmitting EscrowFinish transaction...`)
|
||||
const iouFinishResponse = await client.submitAndWait(iouEscrowFinishTx, {
|
||||
wallet: creator,
|
||||
autofill: true
|
||||
})
|
||||
if (iouFinishResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error(`Trust Line Token EscrowFinish failed: ${iouFinishResponse.result.meta.TransactionResult}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/${iouFinishResponse.result.hash}`)
|
||||
|
||||
await client.disconnect()
|
||||
@@ -22,6 +22,187 @@ python send_timed_escrow.py
|
||||
python send_conditional_escrow.py
|
||||
```
|
||||
|
||||
## Send Fungible Token Escrow
|
||||
|
||||
```sh
|
||||
python send_fungible_token_escrow.py
|
||||
```
|
||||
|
||||
The script issues an MPT and Trust Line Token, setting up both to be escrowable. It then creates and finishes a conditional escrow with the MPT and a timed escrow with the Trust Line Token.
|
||||
|
||||
```sh
|
||||
=== Funding Accounts ===
|
||||
|
||||
Attempting to fund address rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2
|
||||
Faucet fund successful.
|
||||
Attempting to fund address rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU
|
||||
Faucet fund successful.
|
||||
Issuer: rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2
|
||||
Escrow Creator: rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU
|
||||
|
||||
=== Creating MPT ===
|
||||
|
||||
{
|
||||
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"TransactionType": "MPTokenIssuanceCreate",
|
||||
"Flags": 8,
|
||||
"SigningPubKey": "",
|
||||
"MaximumAmount": "1000000"
|
||||
}
|
||||
|
||||
Submitting MPTokenIssuanceCreate transaction...
|
||||
MPT created: 00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18
|
||||
|
||||
=== Escrow Creator Authorizing MPT ===
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "MPTokenAuthorize",
|
||||
"SigningPubKey": "",
|
||||
"MPTokenIssuanceID": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18"
|
||||
}
|
||||
|
||||
Submitting MPTokenAuthorize transaction...
|
||||
Escrow Creator authorized for MPT.
|
||||
|
||||
=== Issuer Sending MPTs to Escrow Creator ===
|
||||
|
||||
{
|
||||
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"TransactionType": "Payment",
|
||||
"SigningPubKey": "",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18",
|
||||
"value": "5000"
|
||||
},
|
||||
"Destination": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU"
|
||||
}
|
||||
|
||||
Submitting MPT Payment transaction...
|
||||
Successfully sent 5000 MPTs to Escrow Creator.
|
||||
|
||||
=== Creating Conditional MPT Escrow ===
|
||||
|
||||
Condition: A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120
|
||||
Fulfillment: A022802079204EBCCCF4816441F0D4F7B15E7003A757675FC90691107AB770044B07697B
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "EscrowCreate",
|
||||
"SigningPubKey": "",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00F7705DFE38372A760229755F9E4F5EADE06F2CE36BDA18",
|
||||
"value": "1000"
|
||||
},
|
||||
"Destination": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"CancelAfter": 828514495,
|
||||
"Condition": "A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120"
|
||||
}
|
||||
|
||||
Submitting MPT EscrowCreate transaction...
|
||||
Conditional MPT escrow created. Sequence: 16216160
|
||||
|
||||
=== Finishing Conditional MPT Escrow ===
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "EscrowFinish",
|
||||
"SigningPubKey": "",
|
||||
"Owner": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"OfferSequence": 16216160,
|
||||
"Condition": "A02580202959C2DFA17829F23F8A7F2F3A81FE73F9E964A56810A250CB836DE1AB851E47810120",
|
||||
"Fulfillment": "A022802079204EBCCCF4816441F0D4F7B15E7003A757675FC90691107AB770044B07697B"
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/4FE4B0AD6FD9D8CD968F5AFD43C3E1F2180C40C3A20DE7416B1E16069D2340DD
|
||||
|
||||
=== Enabling Trust Line Token Escrows on Issuer ===
|
||||
|
||||
{
|
||||
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"TransactionType": "AccountSet",
|
||||
"SigningPubKey": "",
|
||||
"SetFlag": 17
|
||||
}
|
||||
|
||||
Submitting AccountSet transaction...
|
||||
Trust line token escrows enabled by issuer.
|
||||
|
||||
=== Setting Up Trust Line ===
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "TrustSet",
|
||||
"SigningPubKey": "",
|
||||
"LimitAmount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"value": "10000000"
|
||||
}
|
||||
}
|
||||
|
||||
Submitting TrustSet transaction...
|
||||
Trust line successfully created for "IOU" tokens.
|
||||
|
||||
=== Issuer Sending IOU Tokens to Escrow Creator ===
|
||||
|
||||
{
|
||||
"Account": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"TransactionType": "Payment",
|
||||
"SigningPubKey": "",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"value": "5000"
|
||||
},
|
||||
"Destination": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU"
|
||||
}
|
||||
|
||||
Submitting Trust Line Token payment transaction...
|
||||
Successfully sent 5000 IOU tokens.
|
||||
|
||||
=== Creating Timed Trust Line Token Escrow ===
|
||||
|
||||
Escrow will mature after: 04/02/2026, 11:50:39 PM
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "EscrowCreate",
|
||||
"SigningPubKey": "",
|
||||
"Amount": {
|
||||
"currency": "IOU",
|
||||
"issuer": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"value": "1000"
|
||||
},
|
||||
"Destination": "rQBByeTLvRVQy9GLsqvsnczMB3QZ7b7gs2",
|
||||
"CancelAfter": 828514529,
|
||||
"FinishAfter": 828514239
|
||||
}
|
||||
|
||||
Submitting Trust Line Token EscrowCreate transaction...
|
||||
Trust Line Token escrow created. Sequence: 16216163
|
||||
|
||||
=== Waiting For Timed Trust Line Token Escrow to Mature ===
|
||||
|
||||
Waiting for escrow to mature... done.
|
||||
Latest validated ledger closed at: 04/02/2026, 11:50:42 PM
|
||||
Escrow confirmed ready to finish.
|
||||
|
||||
=== Finishing Timed Trust Line Token Escrow ===
|
||||
|
||||
{
|
||||
"Account": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"TransactionType": "EscrowFinish",
|
||||
"SigningPubKey": "",
|
||||
"Owner": "rBkkT1Q3E3bm5tM5D5YazgX8cpq3a2V6zU",
|
||||
"OfferSequence": 16216163
|
||||
}
|
||||
|
||||
Submitting EscrowFinish transaction...
|
||||
Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/F3FCFE9D0E9A0A7CC6FA804526A509532833AEAD4C7A7BF1978194F4F1CCDED4
|
||||
```
|
||||
|
||||
## List Escrows
|
||||
|
||||
```sh
|
||||
|
||||
301
_code-samples/escrow/py/send_fungible_token_escrow.py
Normal file
301
_code-samples/escrow/py/send_fungible_token_escrow.py
Normal file
@@ -0,0 +1,301 @@
|
||||
# This example demonstrates how to create escrows that hold fungible tokens.
|
||||
# It covers MPTs and Trust Line Tokens, and uses conditional and timed escrows.
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from os import urandom
|
||||
from time import sleep
|
||||
|
||||
from cryptoconditions import PreimageSha256
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import (
|
||||
AccountSet,
|
||||
EscrowCreate,
|
||||
EscrowFinish,
|
||||
MPTokenAuthorize,
|
||||
MPTokenIssuanceCreate,
|
||||
Payment,
|
||||
TrustSet,
|
||||
)
|
||||
from xrpl.models.amounts import IssuedCurrencyAmount, MPTAmount
|
||||
from xrpl.models.requests import Ledger
|
||||
from xrpl.models.transactions.account_set import AccountSetAsfFlag
|
||||
from xrpl.models.transactions.mptoken_issuance_create import MPTokenIssuanceCreateFlag
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import datetime_to_ripple_time, ripple_time_to_datetime
|
||||
from xrpl.wallet import generate_faucet_wallet
|
||||
|
||||
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
|
||||
|
||||
# Fund an issuer account and an escrow creator account ----------------------
|
||||
print("\n=== Funding Accounts ===\n")
|
||||
issuer = generate_faucet_wallet(client, debug=True)
|
||||
creator = generate_faucet_wallet(client, debug=True)
|
||||
print(f"Issuer: {issuer.address}")
|
||||
print(f"Escrow Creator: {creator.address}")
|
||||
|
||||
# ====== Conditional MPT Escrow ======
|
||||
|
||||
# Issuer creates an MPT ----------------------
|
||||
print("\n=== Creating MPT ===\n")
|
||||
mpt_create_tx = MPTokenIssuanceCreate(
|
||||
account=issuer.address,
|
||||
maximum_amount="1000000",
|
||||
flags=MPTokenIssuanceCreateFlag.TF_MPT_CAN_ESCROW,
|
||||
)
|
||||
|
||||
print(json.dumps(mpt_create_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit, sign, and wait for validation
|
||||
print("\nSubmitting MPTokenIssuanceCreate transaction...")
|
||||
mpt_create_response = submit_and_wait(mpt_create_tx, client, issuer)
|
||||
|
||||
if mpt_create_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"MPTokenIssuanceCreate failed: {mpt_create_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
|
||||
# Extract the MPT issuance ID from the transaction result
|
||||
mpt_issuance_id = mpt_create_response.result["meta"]["mpt_issuance_id"]
|
||||
print(f"MPT created: {mpt_issuance_id}")
|
||||
|
||||
# Escrow Creator authorizes the MPT ----------------------
|
||||
print("\n=== Escrow Creator Authorizing MPT ===\n")
|
||||
mpt_auth_tx = MPTokenAuthorize(
|
||||
account=creator.address,
|
||||
mptoken_issuance_id=mpt_issuance_id,
|
||||
)
|
||||
|
||||
print(json.dumps(mpt_auth_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting MPTokenAuthorize transaction...")
|
||||
mpt_auth_response = submit_and_wait(mpt_auth_tx, client, creator)
|
||||
|
||||
if mpt_auth_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"MPTokenAuthorize failed: {mpt_auth_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print("Escrow Creator authorized for MPT.")
|
||||
|
||||
# Issuer sends MPTs to escrow creator ----------------------
|
||||
print("\n=== Issuer Sending MPTs to Escrow Creator ===\n")
|
||||
mpt_payment_tx = Payment(
|
||||
account=issuer.address,
|
||||
destination=creator.address,
|
||||
amount=MPTAmount(
|
||||
mpt_issuance_id=mpt_issuance_id,
|
||||
value="5000",
|
||||
),
|
||||
)
|
||||
|
||||
print(json.dumps(mpt_payment_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting MPT Payment transaction...")
|
||||
mpt_payment_response = submit_and_wait(mpt_payment_tx, client, issuer)
|
||||
|
||||
if mpt_payment_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"MPT Payment failed: {mpt_payment_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print("Successfully sent 5000 MPTs to Escrow Creator.")
|
||||
|
||||
# Escrow Creator creates a conditional MPT escrow ----------------------
|
||||
print("\n=== Creating Conditional MPT Escrow ===\n")
|
||||
|
||||
# Generate crypto-condition
|
||||
preimage = urandom(32)
|
||||
fulfillment = PreimageSha256(preimage=preimage)
|
||||
fulfillment_hex = fulfillment.serialize_binary().hex().upper()
|
||||
condition_hex = fulfillment.condition_binary.hex().upper()
|
||||
print(f"Condition: {condition_hex}")
|
||||
print(f"Fulfillment: {fulfillment_hex}\n")
|
||||
|
||||
# Set expiration (300 seconds from now)
|
||||
cancel_after = datetime.now(tz=UTC) + timedelta(seconds=300)
|
||||
cancel_after_ripple_time = datetime_to_ripple_time(cancel_after)
|
||||
|
||||
mpt_escrow_create_tx = EscrowCreate(
|
||||
account=creator.address,
|
||||
destination=issuer.address,
|
||||
amount=MPTAmount(
|
||||
mpt_issuance_id=mpt_issuance_id,
|
||||
value="1000",
|
||||
),
|
||||
condition=condition_hex,
|
||||
cancel_after=cancel_after_ripple_time, # Fungible token escrows require a cancel_after time
|
||||
)
|
||||
|
||||
print(json.dumps(mpt_escrow_create_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting MPT EscrowCreate transaction...")
|
||||
mpt_escrow_response = submit_and_wait(mpt_escrow_create_tx, client, creator)
|
||||
|
||||
if mpt_escrow_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"MPT EscrowCreate failed: {mpt_escrow_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
|
||||
# Save the sequence number to identify the escrow later
|
||||
mpt_escrow_seq = mpt_escrow_response.result["tx_json"]["Sequence"]
|
||||
print(f"Conditional MPT escrow created. Sequence: {mpt_escrow_seq}")
|
||||
|
||||
# Finish the conditional MPT escrow with the fulfillment ----------------------
|
||||
print("\n=== Finishing Conditional MPT Escrow ===\n")
|
||||
mpt_escrow_finish_tx = EscrowFinish(
|
||||
account=creator.address,
|
||||
owner=creator.address,
|
||||
offer_sequence=mpt_escrow_seq,
|
||||
condition=condition_hex,
|
||||
fulfillment=fulfillment_hex,
|
||||
)
|
||||
|
||||
print(json.dumps(mpt_escrow_finish_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting EscrowFinish transaction...")
|
||||
mpt_finish_response = submit_and_wait(mpt_escrow_finish_tx, client, creator)
|
||||
|
||||
if mpt_finish_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"MPT EscrowFinish failed: {mpt_finish_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print(f"Conditional MPT escrow finished successfully: https://testnet.xrpl.org/transactions/{mpt_finish_response.result['hash']}")
|
||||
|
||||
# ====== Timed Trust Line Token Escrow ======
|
||||
|
||||
# Enable trust line token escrows on the issuer ----------------------
|
||||
print("\n=== Enabling Trust Line Token Escrows on Issuer ===\n")
|
||||
account_set_tx = AccountSet(
|
||||
account=issuer.address,
|
||||
set_flag=AccountSetAsfFlag.ASF_ALLOW_TRUSTLINE_LOCKING,
|
||||
)
|
||||
|
||||
print(json.dumps(account_set_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting AccountSet transaction...")
|
||||
account_set_response = submit_and_wait(account_set_tx, client, issuer)
|
||||
|
||||
if account_set_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"AccountSet failed: {account_set_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print("Trust line token escrows enabled by issuer.")
|
||||
|
||||
# Escrow Creator sets up a trust line to the issuer ----------------------
|
||||
print("\n=== Setting Up Trust Line ===\n")
|
||||
currency_code = "IOU"
|
||||
|
||||
trust_set_tx = TrustSet(
|
||||
account=creator.address,
|
||||
limit_amount=IssuedCurrencyAmount(
|
||||
currency=currency_code,
|
||||
issuer=issuer.address,
|
||||
value="10000000",
|
||||
),
|
||||
)
|
||||
|
||||
print(json.dumps(trust_set_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting TrustSet transaction...")
|
||||
trust_response = submit_and_wait(trust_set_tx, client, creator)
|
||||
|
||||
if trust_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"TrustSet failed: {trust_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print(f'Trust line successfully created for "{currency_code}" tokens.')
|
||||
|
||||
# Issuer sends IOU tokens to creator ----------------------
|
||||
print("\n=== Issuer Sending IOU Tokens to Escrow Creator ===\n")
|
||||
iou_payment_tx = Payment(
|
||||
account=issuer.address,
|
||||
destination=creator.address,
|
||||
amount=IssuedCurrencyAmount(
|
||||
currency=currency_code,
|
||||
value="5000",
|
||||
issuer=issuer.address,
|
||||
),
|
||||
)
|
||||
|
||||
print(json.dumps(iou_payment_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting Trust Line Token payment transaction...")
|
||||
iou_pay_response = submit_and_wait(iou_payment_tx, client, issuer)
|
||||
|
||||
if iou_pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"Trust Line Token payment failed: {iou_pay_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print(f"Successfully sent 5000 {currency_code} tokens.")
|
||||
|
||||
# Escrow Creator creates a timed trust line token escrow ----------------------
|
||||
print("\n=== Creating Timed Trust Line Token Escrow ===\n")
|
||||
delay = 10 # seconds
|
||||
now = datetime.now(tz=UTC)
|
||||
finish_after = now + timedelta(seconds=delay)
|
||||
finish_after_ripple_time = datetime_to_ripple_time(finish_after)
|
||||
mature_time = finish_after.astimezone().strftime("%m/%d/%Y, %I:%M:%S %p")
|
||||
print(f"Escrow will mature after: {mature_time}\n")
|
||||
|
||||
iou_cancel_after = now + timedelta(seconds=300)
|
||||
iou_cancel_after_ripple_time = datetime_to_ripple_time(iou_cancel_after)
|
||||
|
||||
iou_escrow_create_tx = EscrowCreate(
|
||||
account=creator.address,
|
||||
destination=issuer.address,
|
||||
amount=IssuedCurrencyAmount(
|
||||
currency=currency_code,
|
||||
value="1000",
|
||||
issuer=issuer.address,
|
||||
),
|
||||
finish_after=finish_after_ripple_time,
|
||||
cancel_after=iou_cancel_after_ripple_time,
|
||||
)
|
||||
|
||||
print(json.dumps(iou_escrow_create_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting Trust Line Token EscrowCreate transaction...")
|
||||
iou_escrow_response = submit_and_wait(iou_escrow_create_tx, client, creator)
|
||||
|
||||
if iou_escrow_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"Trust Line Token EscrowCreate failed: {iou_escrow_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
|
||||
# Save the sequence number to identify the escrow later
|
||||
iou_escrow_seq = iou_escrow_response.result["tx_json"]["Sequence"]
|
||||
print(f"Trust Line Token escrow created. Sequence: {iou_escrow_seq}")
|
||||
|
||||
# Wait for the escrow to mature, then finish it --------------------
|
||||
print("\n=== Waiting For Timed Trust Line Token Escrow to Mature ===\n")
|
||||
|
||||
# Countdown delay until escrow matures
|
||||
for i in range(delay, -1, -1):
|
||||
print(f"Waiting for escrow to mature... {i}s remaining...", end="\r", flush=True)
|
||||
sleep(1)
|
||||
print("Waiting for escrow to mature... done. ")
|
||||
|
||||
# Confirm latest validated ledger close time is after the finish_after time
|
||||
escrow_ready = False
|
||||
while not escrow_ready:
|
||||
validated_ledger = client.request(Ledger(ledger_index="validated"))
|
||||
ledger_close_time = validated_ledger.result["ledger"]["close_time"]
|
||||
ledger_close_local = ripple_time_to_datetime(ledger_close_time).astimezone().strftime("%m/%d/%Y, %I:%M:%S %p")
|
||||
print(f"Latest validated ledger closed at: {ledger_close_local}")
|
||||
if ledger_close_time > finish_after_ripple_time:
|
||||
escrow_ready = True
|
||||
print("Escrow confirmed ready to finish.")
|
||||
else:
|
||||
time_difference = finish_after_ripple_time - ledger_close_time
|
||||
if time_difference == 0:
|
||||
time_difference = 1
|
||||
print(f"Escrow needs to wait another {time_difference}s.")
|
||||
sleep(time_difference)
|
||||
|
||||
# Finish the timed trust line token escrow --------------------
|
||||
print("\n=== Finishing Timed Trust Line Token Escrow ===\n")
|
||||
iou_escrow_finish_tx = EscrowFinish(
|
||||
account=creator.address,
|
||||
owner=creator.address,
|
||||
offer_sequence=iou_escrow_seq,
|
||||
)
|
||||
|
||||
print(json.dumps(iou_escrow_finish_tx.to_xrpl(), indent=2))
|
||||
|
||||
print("\nSubmitting EscrowFinish transaction...")
|
||||
iou_finish_response = submit_and_wait(iou_escrow_finish_tx, client, creator)
|
||||
|
||||
if iou_finish_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"Trust Line Token EscrowFinish failed: {iou_finish_response.result['meta']['TransactionResult']}")
|
||||
exit(1)
|
||||
print(f"Timed Trust Line Token escrow finished successfully: https://testnet.xrpl.org/transactions/{iou_finish_response.result['hash']}")
|
||||
395
_code-samples/lending-protocol/go/README.md
Normal file
395
_code-samples/lending-protocol/go/README.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# Lending Protocol Examples (Go)
|
||||
|
||||
This directory contains Go examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
|
||||
|
||||
## Setup
|
||||
|
||||
All commands should be run from this `go/` directory.
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan Broker
|
||||
|
||||
```sh
|
||||
go run ./create-loan-broker
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account.
|
||||
|
||||
```sh
|
||||
Loan broker/vault owner address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
Vault ID: A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9
|
||||
|
||||
=== Preparing LoanBrokerSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"ManagementFeeRate": 1000,
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"VaultID": "A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerSet transaction ===
|
||||
|
||||
Loan broker created successfully!
|
||||
|
||||
=== Loan Broker Information ===
|
||||
|
||||
LoanBroker ID: E4D9C485E101FAE449C8ACEC7FD039920CC02D2443687F2593DB397CC8EA670B
|
||||
LoanBroker Pseudo-Account Address: rMDsnf9CVRLRJzrL12Ex7nhstbni78y8af
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Claw Back First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-clawback
|
||||
```
|
||||
|
||||
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback.
|
||||
|
||||
```sh
|
||||
Loan broker address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
MPT issuer address: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW
|
||||
LoanBrokerID: 373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D
|
||||
MPT ID: 003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51
|
||||
|
||||
=== Cover Available ===
|
||||
|
||||
0 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Available After Deposit ===
|
||||
|
||||
1000 TSTUSD
|
||||
|
||||
=== Verifying Asset Issuer ===
|
||||
|
||||
MPT issuer account verified: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW. Proceeding to clawback.
|
||||
|
||||
=== Preparing LoanBrokerCoverClawback transaction ===
|
||||
|
||||
{
|
||||
"Account": "rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverClawback"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverClawback transaction ===
|
||||
|
||||
Successfully clawed back 1000 TSTUSD!
|
||||
|
||||
=== Final Cover Available After Clawback ===
|
||||
|
||||
0 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit and Withdraw First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-deposit-and-withdraw
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal.
|
||||
|
||||
```sh
|
||||
Loan broker address: rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV
|
||||
LoanBrokerID: 633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3
|
||||
MPT ID: 003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "2000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after deposit: 2000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverWithdraw"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
Cover withdraw successful!
|
||||
|
||||
=== Updated Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after withdraw: 1000 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan
|
||||
|
||||
```sh
|
||||
go run ./create-loan
|
||||
```
|
||||
|
||||
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information.
|
||||
|
||||
```sh
|
||||
Loan broker address: rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX
|
||||
Borrower address: rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp
|
||||
LoanBrokerID: 490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09
|
||||
|
||||
=== Preparing LoanSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"TransactionType": "LoanSet"
|
||||
}
|
||||
|
||||
=== Adding loan broker signature ===
|
||||
|
||||
TxnSignature: 9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F
|
||||
SigningPubKey: ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1
|
||||
|
||||
Signed loanSetTx for borrower to sign over:
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
|
||||
}
|
||||
|
||||
=== Adding borrower signature ===
|
||||
|
||||
Borrower TxnSignature: 5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005
|
||||
Borrower SigningPubKey: ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E
|
||||
|
||||
Fully signed LoanSet transaction:
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E",
|
||||
"TxnSignature": "5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005"
|
||||
},
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
|
||||
}
|
||||
|
||||
=== Submitting signed LoanSet transaction ===
|
||||
|
||||
Loan created successfully!
|
||||
|
||||
=== Loan Information ===
|
||||
|
||||
{
|
||||
"Borrower": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanSequence": 4,
|
||||
"LoanServiceFee": "10",
|
||||
"NextPaymentDueDate": 829803181,
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentRemaining": 12,
|
||||
"PeriodicPayment": "83.55610375293148956",
|
||||
"PrincipalOutstanding": "1000",
|
||||
"StartDate": 827211181,
|
||||
"TotalValueOutstanding": "1003"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-manage
|
||||
```
|
||||
|
||||
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
|
||||
|
||||
```sh
|
||||
Loan broker address: rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX
|
||||
LoanID: 2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Total Amount Owed: 1001 TSTUSD.
|
||||
Payment Due Date: 2026-03-23 01:23:40
|
||||
|
||||
=== Preparing LoanManage transaction to impair loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 131072,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage impairment transaction ===
|
||||
|
||||
Loan impaired successfully!
|
||||
New Payment Due Date: 2026-02-21 00:24:10
|
||||
Grace Period: 60 seconds
|
||||
|
||||
=== Countdown until loan can be defaulted ===
|
||||
|
||||
Grace period expired. Loan can now be defaulted.
|
||||
|
||||
=== Preparing LoanManage transaction to default loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 65536,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage default transaction ===
|
||||
|
||||
Loan defaulted successfully!
|
||||
|
||||
=== Checking final loan status ===
|
||||
|
||||
Final loan flags: [tfLoanDefault tfLoanImpair]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pay a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-pay
|
||||
```
|
||||
|
||||
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry.
|
||||
|
||||
```sh
|
||||
Borrower address: rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2
|
||||
LoanID: D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A
|
||||
MPT ID: 003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Amount Owed: 1001 TSTUSD
|
||||
Loan Service Fee: 10 TSTUSD
|
||||
Total Payment Due (including fees): 1011 TSTUSD
|
||||
|
||||
=== Preparing LoanPay transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B",
|
||||
"value": "1011"
|
||||
},
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanPay"
|
||||
}
|
||||
|
||||
=== Submitting LoanPay transaction ===
|
||||
|
||||
Loan paid successfully!
|
||||
|
||||
=== Loan Status After Payment ===
|
||||
|
||||
Outstanding Loan Balance: Loan fully paid off!
|
||||
|
||||
=== Preparing LoanDelete transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanDelete"
|
||||
}
|
||||
|
||||
=== Submitting LoanDelete transaction ===
|
||||
|
||||
Loan deleted successfully!
|
||||
|
||||
=== Verifying Loan Deletion ===
|
||||
|
||||
Loan has been successfully removed from the XRP Ledger!
|
||||
```
|
||||
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// IMPORTANT: This example deposits and claws back first-loss capital from a
|
||||
// preconfigured LoanBroker entry. The first-loss capital is an MPT
|
||||
// with clawback enabled.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
// mptIssuanceEntryRequest looks up an MPTIssuance ledger entry by its MPT ID.
|
||||
// The library's GetLedgerEntry() method only supports lookup by ledger entry ID,
|
||||
// so this custom type is used with the generic Request() method.
|
||||
type mptIssuanceEntryRequest struct {
|
||||
common.BaseRequest
|
||||
MPTIssuance string `json:"mpt_issuance"`
|
||||
LedgerIndex common.LedgerSpecifier `json:"ledger_index,omitempty"`
|
||||
}
|
||||
|
||||
func (*mptIssuanceEntryRequest) Method() string { return "ledger_entry" }
|
||||
func (*mptIssuanceEntryRequest) Validate() error { return nil }
|
||||
func (*mptIssuanceEntryRequest) APIVersion() int { return 2 }
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mptIssuerWallet, err := wallet.FromSecret(setup["depositor"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("MPT issuer address: %s\n", mptIssuerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check cover available ----------------------
|
||||
fmt.Printf("\n=== Cover Available ===\n\n")
|
||||
coverInfo, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanBrokerID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentCoverAvailable := "0"
|
||||
if ca, ok := coverInfo.Node["CoverAvailable"].(string); ok {
|
||||
currentCoverAvailable = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract updated cover available after deposit ----------------------
|
||||
fmt.Printf("\n=== Cover Available After Deposit ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
currentCoverAvailable = node.ModifiedNode.FinalFields["CoverAvailable"].(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Verify issuer of cover asset matches ----------------------
|
||||
// Only the issuer of the asset can submit clawback transactions.
|
||||
// The asset must also have clawback enabled.
|
||||
fmt.Printf("\n=== Verifying Asset Issuer ===\n\n")
|
||||
assetIssuerInfo, err := client.Request(&mptIssuanceEntryRequest{
|
||||
MPTIssuance: mptID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
issuer := assetIssuerInfo.Result["node"].(map[string]any)["Issuer"].(string)
|
||||
if issuer != string(mptIssuerWallet.ClassicAddress) {
|
||||
fmt.Printf("Error: %s does not match account (%s) attempting clawback!\n", issuer, mptIssuerWallet.ClassicAddress)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("MPT issuer account verified: %s. Proceeding to clawback.\n", mptIssuerWallet.ClassicAddress)
|
||||
|
||||
// Prepare LoanBrokerCoverClawback transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverClawback transaction ===\n\n")
|
||||
lbID := types.LoanBrokerID(loanBrokerID)
|
||||
coverClawbackTx := transaction.LoanBrokerCoverClawback{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: mptIssuerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: &lbID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: currentCoverAvailable,
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverClawbackTx := coverClawbackTx.Flatten()
|
||||
coverClawbackTxJSON, _ := json.MarshalIndent(flatCoverClawbackTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverClawbackTxJSON))
|
||||
|
||||
// Sign, submit, and wait for clawback validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverClawback transaction ===\n\n")
|
||||
clawbackResponse, err := client.SubmitTxAndWait(flatCoverClawbackTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &mptIssuerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if clawbackResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to clawback cover: %s\n", clawbackResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully clawed back %s TSTUSD!\n", currentCoverAvailable)
|
||||
|
||||
// Extract final cover available ----------------------
|
||||
fmt.Printf("\n=== Final Cover Available After Clawback ===\n\n")
|
||||
for _, node := range clawbackResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
finalCover := "0"
|
||||
if ca, ok := node.ModifiedNode.FinalFields["CoverAvailable"].(string); ok {
|
||||
finalCover = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", finalCover)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// IMPORTANT: This example deposits and withdraws first-loss capital from a
|
||||
// preconfigured LoanBroker entry.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "2000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract cover balance from the transaction result
|
||||
fmt.Printf("\n=== Cover Balance ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
// First-loss capital is stored in the LoanBroker's pseudo-account.
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after deposit: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
coverWithdrawTx := transaction.LoanBrokerCoverWithdraw{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverWithdrawTx := coverWithdrawTx.Flatten()
|
||||
coverWithdrawTxJSON, _ := json.MarshalIndent(flatCoverWithdrawTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverWithdrawTxJSON))
|
||||
|
||||
// Sign, submit, and wait for withdraw validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
withdrawResponse, err := client.SubmitTxAndWait(flatCoverWithdrawTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if withdrawResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to withdraw cover: %s\n", withdrawResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover withdraw successful!\n")
|
||||
|
||||
// Extract updated cover balance from the transaction result
|
||||
fmt.Printf("\n=== Updated Cover Balance ===\n\n")
|
||||
for _, node := range withdrawResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after withdraw: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// IMPORTANT: This example creates a loan broker using an existing account
|
||||
// that has already created a PRIVATE vault.
|
||||
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
|
||||
// and loanBroker values with your own.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and VaultID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultID := setup["vaultID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker/vault owner address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("Vault ID: %s\n", vaultID)
|
||||
|
||||
// Prepare LoanBrokerSet transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerSet transaction ===\n\n")
|
||||
mgmtFeeRate := types.InterestRate(1000)
|
||||
loanBrokerSetTx := transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
VaultID: vaultID,
|
||||
ManagementFeeRate: &mgmtFeeRate,
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanBrokerSetTx := loanBrokerSetTx.Flatten()
|
||||
loanBrokerSetTxJSON, _ := json.MarshalIndent(flatLoanBrokerSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanBrokerSetTxJSON))
|
||||
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerSet transaction ===\n\n")
|
||||
loanBrokerSetResponse, err := client.SubmitTxAndWait(flatLoanBrokerSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanBrokerSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan broker: %s\n", loanBrokerSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan broker created successfully!\n")
|
||||
|
||||
// Extract loan broker information from the transaction result
|
||||
fmt.Printf("\n=== Loan Broker Information ===\n\n")
|
||||
for _, node := range loanBrokerSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker ID: %s\n", node.CreatedNode.LedgerIndex)
|
||||
fmt.Printf("LoanBroker Pseudo-Account Address: %s\n", node.CreatedNode.NewFields["Account"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
153
_code-samples/lending-protocol/go/create-loan/main.go
Normal file
153
_code-samples/lending-protocol/go/create-loan/main.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// IMPORTANT: This example creates a loan using a preconfigured
|
||||
// loan broker, borrower, and private vault.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
)
|
||||
|
||||
// ptr is a helper that returns a pointer to the given value,
|
||||
// used for setting optional transaction fields in Go.
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("Borrower address: %s\n", borrowerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
|
||||
// Prepare LoanSet transaction ----------------------
|
||||
// Account and Counterparty accounts can be swapped, but determines signing order.
|
||||
// Account signs first, Counterparty signs second.
|
||||
fmt.Printf("\n=== Preparing LoanSet transaction ===\n\n")
|
||||
|
||||
counterparty := borrowerWallet.ClassicAddress
|
||||
loanSetTx := transaction.LoanSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: "1000",
|
||||
Counterparty: &counterparty,
|
||||
InterestRate: ptr(types.InterestRate(500)),
|
||||
PaymentTotal: ptr(types.PaymentTotal(12)),
|
||||
PaymentInterval: ptr(types.PaymentInterval(2592000)),
|
||||
GracePeriod: ptr(types.GracePeriod(604800)),
|
||||
LoanOriginationFee: ptr(types.XRPLNumber("100")),
|
||||
LoanServiceFee: ptr(types.XRPLNumber("10")),
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field.
|
||||
// The result is cast to FlatTransaction, which is required by Autofill and signing methods.
|
||||
flatLoanSetTx := transaction.FlatTransaction(loanSetTx.Flatten())
|
||||
|
||||
// Autofill the transaction
|
||||
if err := client.Autofill(&flatLoanSetTx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
loanSetTxJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanSetTxJSON))
|
||||
|
||||
// Loan broker signs first
|
||||
fmt.Printf("\n=== Adding loan broker signature ===\n\n")
|
||||
_, _, err = loanBrokerWallet.Sign(flatLoanSetTx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("TxnSignature: %s\n", flatLoanSetTx["TxnSignature"])
|
||||
fmt.Printf("SigningPubKey: %s\n\n", flatLoanSetTx["SigningPubKey"])
|
||||
|
||||
loanBrokerSignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("Signed loanSetTx for borrower to sign over:\n%s\n", string(loanBrokerSignedJSON))
|
||||
|
||||
// Borrower signs second
|
||||
fmt.Printf("\n=== Adding borrower signature ===\n\n")
|
||||
fullySignedBlob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatLoanSetTx, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
borrowerSignatures := flatLoanSetTx["CounterpartySignature"].(map[string]any)
|
||||
fmt.Printf("Borrower TxnSignature: %s\n", borrowerSignatures["TxnSignature"])
|
||||
fmt.Printf("Borrower SigningPubKey: %s\n", borrowerSignatures["SigningPubKey"])
|
||||
|
||||
fullySignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("\nFully signed LoanSet transaction:\n%s\n", string(fullySignedJSON))
|
||||
|
||||
// Submit and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting signed LoanSet transaction ===\n\n")
|
||||
|
||||
loanSetResponse, err := client.SubmitTxBlobAndWait(fullySignedBlob, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan: %s\n", loanSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan created successfully!\n")
|
||||
|
||||
// Extract loan information from the transaction result
|
||||
fmt.Printf("\n=== Loan Information ===\n\n")
|
||||
for _, node := range loanSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanJSON, _ := json.MarshalIndent(node.CreatedNode.NewFields, "", " ")
|
||||
fmt.Printf("%s\n", string(loanJSON))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
19
_code-samples/lending-protocol/go/go.mod
Normal file
19
_code-samples/lending-protocol/go/go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/Peersyst/xrpl-go v0.1.17
|
||||
|
||||
require (
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
)
|
||||
656
_code-samples/lending-protocol/go/lending-setup/main.go
Normal file
656
_code-samples/lending-protocol/go/lending-setup/main.go
Normal file
@@ -0,0 +1,656 @@
|
||||
// Setup script for lending protocol tutorials
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/pkg/crypto"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/faucet"
|
||||
ledger "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
requests "github.com/Peersyst/xrpl-go/xrpl/queries/transactions"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/rpc"
|
||||
rpctypes "github.com/Peersyst/xrpl-go/xrpl/rpc/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
)
|
||||
|
||||
// ptr is a helper that returns a pointer to the given value,
|
||||
// used for setting optional transaction fields in Go.
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func main() {
|
||||
fmt.Print("Setting up tutorial: 0/7\r")
|
||||
|
||||
// Connect to devnet
|
||||
cfg, err := rpc.NewClientConfig(
|
||||
"https://s.devnet.rippletest.net:51234",
|
||||
rpc.WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := rpc.NewClient(cfg)
|
||||
|
||||
submitOpts := func(w *wallet.Wallet) *rpctypes.SubmitOptions {
|
||||
return &rpctypes.SubmitOptions{Autofill: true, Wallet: w}
|
||||
}
|
||||
|
||||
// Create and fund wallets concurrently
|
||||
createAndFund := func(ch chan<- wallet.Wallet) {
|
||||
w, err := wallet.New(crypto.ED25519())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := client.FundWallet(&w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Poll until account is validated on ledger
|
||||
funded := false
|
||||
for range 20 {
|
||||
_, err := client.Request(&account.InfoRequest{
|
||||
Account: w.GetAddress(),
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err == nil {
|
||||
funded = true
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !funded {
|
||||
panic("Issue funding account: " + w.GetAddress().String())
|
||||
}
|
||||
ch <- w
|
||||
}
|
||||
|
||||
lbWalletCh := make(chan wallet.Wallet, 1)
|
||||
brWalletCh := make(chan wallet.Wallet, 1)
|
||||
depWalletCh := make(chan wallet.Wallet, 1)
|
||||
credWalletCh := make(chan wallet.Wallet, 1)
|
||||
|
||||
go createAndFund(lbWalletCh)
|
||||
go createAndFund(brWalletCh)
|
||||
go createAndFund(depWalletCh)
|
||||
go createAndFund(credWalletCh)
|
||||
|
||||
loanBrokerWallet := <-lbWalletCh
|
||||
borrowerWallet := <-brWalletCh
|
||||
depositorWallet := <-depWalletCh
|
||||
credIssuerWallet := <-credWalletCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 1/7\r")
|
||||
|
||||
// Create tickets for parallel transactions
|
||||
extractTickets := func(resp *requests.TxResponse) []uint32 {
|
||||
var tickets []uint32
|
||||
for _, node := range resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Ticket" {
|
||||
ticketSeq, _ := node.CreatedNode.NewFields["TicketSequence"].(json.Number).Int64()
|
||||
tickets = append(tickets, uint32(ticketSeq))
|
||||
}
|
||||
}
|
||||
return tickets
|
||||
}
|
||||
|
||||
ciTicketCh := make(chan []uint32, 1)
|
||||
lbTicketCh := make(chan []uint32, 1)
|
||||
brTicketCh := make(chan []uint32, 1)
|
||||
dpTicketCh := make(chan []uint32, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 4,
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ciTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 4,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 2,
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 2,
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dpTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
ciTickets := <-ciTicketCh
|
||||
lbTickets := <-lbTicketCh
|
||||
brTickets := <-brTicketCh
|
||||
dpTickets := <-dpTicketCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 2/7\r")
|
||||
|
||||
// Issue MPT with depositor
|
||||
// Set up credentials and domain with credentialIssuer
|
||||
credentialType := hex.EncodeToString([]byte("KYC-Verified"))
|
||||
|
||||
mptData := types.ParsedMPTokenMetadata{
|
||||
Ticker: "TSTUSD",
|
||||
Name: "Test USD MPT",
|
||||
Desc: ptr("A sample non-yield-bearing stablecoin backed by U.S. Treasuries."),
|
||||
Icon: "https://example.org/tstusd-icon.png",
|
||||
AssetClass: "rwa",
|
||||
AssetSubclass: ptr("stablecoin"),
|
||||
IssuerName: "Example Treasury Reserve Co.",
|
||||
URIs: []types.ParsedMPTokenMetadataURI{
|
||||
{
|
||||
URI: "https://exampletreasury.com/tstusd",
|
||||
Category: "website",
|
||||
Title: "Product Page",
|
||||
},
|
||||
{
|
||||
URI: "https://exampletreasury.com/tstusd/reserve",
|
||||
Category: "docs",
|
||||
Title: "Reserve Attestation",
|
||||
},
|
||||
},
|
||||
AdditionalInfo: map[string]any{
|
||||
"reserve_type": "U.S. Treasury Bills",
|
||||
"custody_provider": "Example Custodian Bank",
|
||||
"audit_frequency": "Monthly",
|
||||
"last_audit_date": "2026-01-15",
|
||||
"pegged_currency": "USD",
|
||||
},
|
||||
}
|
||||
mptMetadataHex, err := types.EncodeMPTokenMetadata(mptData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mptCh := make(chan *requests.TxResponse, 1)
|
||||
domainCh := make(chan *requests.TxResponse, 1)
|
||||
credLbCh := make(chan struct{}, 1)
|
||||
credBrCh := make(chan struct{}, 1)
|
||||
credDpCh := make(chan struct{}, 1)
|
||||
|
||||
// MPT issuance
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.MPTokenIssuanceCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Flags: transaction.TfMPTCanTransfer | transaction.TfMPTCanClawback | transaction.TfMPTCanTrade,
|
||||
},
|
||||
MaximumAmount: ptr(types.XRPCurrencyAmount(100000000)),
|
||||
TransferFee: ptr(uint16(0)),
|
||||
MPTokenMetadata: &mptMetadataHex,
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mptCh <- resp
|
||||
}()
|
||||
|
||||
// PermissionedDomainSet
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.PermissionedDomainSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[0],
|
||||
},
|
||||
AcceptedCredentials: types.AuthorizeCredentialList{
|
||||
{
|
||||
Credential: types.Credential{
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
domainCh <- resp
|
||||
}()
|
||||
|
||||
// CredentialCreate for loan broker
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[1],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: loanBrokerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credLbCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// CredentialCreate for borrower
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[2],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: borrowerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credBrCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// CredentialCreate for depositor
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[3],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: depositorWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credDpCh <- struct{}{}
|
||||
}()
|
||||
|
||||
mptResp := <-mptCh
|
||||
domainResp := <-domainCh
|
||||
<-credLbCh
|
||||
<-credBrCh
|
||||
<-credDpCh
|
||||
|
||||
// Extract MPT issuance ID
|
||||
mptID := string(*mptResp.Meta.MPTIssuanceID)
|
||||
|
||||
// Extract domain ID from transaction meta
|
||||
var domainID string
|
||||
for _, node := range domainResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "PermissionedDomain" {
|
||||
domainID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 3/7\r")
|
||||
|
||||
// Accept credentials and authorize MPT for each account
|
||||
lbCredCh := make(chan struct{}, 1)
|
||||
lbMptCh := make(chan struct{}, 1)
|
||||
brCredCh := make(chan struct{}, 1)
|
||||
brMptCh := make(chan struct{}, 1)
|
||||
depCredCh := make(chan struct{}, 1)
|
||||
|
||||
// Loan broker: accept credential
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[0],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Loan broker: authorize MPT
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[1],
|
||||
},
|
||||
MPTokenIssuanceID: mptID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbMptCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Borrower: accept credential
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[0],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Borrower: authorize MPT
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[1],
|
||||
},
|
||||
MPTokenIssuanceID: mptID,
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brMptCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Depositor: accept credential only
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
depCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
<-lbCredCh
|
||||
<-lbMptCh
|
||||
<-brCredCh
|
||||
<-brMptCh
|
||||
<-depCredCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 4/7\r")
|
||||
|
||||
// Create private vault and distribute MPT to accounts concurrently
|
||||
vaultCh := make(chan *requests.TxResponse, 1)
|
||||
distLbCh := make(chan struct{}, 1)
|
||||
distBrCh := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.VaultCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Flags: transaction.TfVaultPrivate,
|
||||
},
|
||||
Asset: ledger.Asset{MPTIssuanceID: mptID},
|
||||
DomainID: &domainID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultCh <- resp
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[0],
|
||||
},
|
||||
Destination: loanBrokerWallet.GetAddress(),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "5000",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
distLbCh <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[1],
|
||||
},
|
||||
Destination: borrowerWallet.GetAddress(),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "2500",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
distBrCh <- struct{}{}
|
||||
}()
|
||||
|
||||
vaultResp := <-vaultCh
|
||||
<-distLbCh
|
||||
<-distBrCh
|
||||
|
||||
var vaultID string
|
||||
for _, node := range vaultResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Vault" {
|
||||
vaultID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 5/7\r")
|
||||
|
||||
// Create LoanBroker and deposit MPT into vault
|
||||
lbSetCh := make(chan *requests.TxResponse, 1)
|
||||
vaultDepCh := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
},
|
||||
VaultID: vaultID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbSetCh <- resp
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.VaultDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
VaultID: types.Hash256(vaultID),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "50000000",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultDepCh <- struct{}{}
|
||||
}()
|
||||
|
||||
lbSetResp := <-lbSetCh
|
||||
<-vaultDepCh
|
||||
|
||||
var loanBrokerID string
|
||||
for _, node := range lbSetResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
loanBrokerID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 6/7\r")
|
||||
|
||||
// Create 2 identical loans with complete repayment due in 30 days
|
||||
|
||||
// Helper function to create, sign, and submit a LoanSet transaction
|
||||
createLoan := func(ticketSequence uint32) *requests.TxResponse {
|
||||
counterparty := borrowerWallet.GetAddress()
|
||||
loanSetTx := &transaction.LoanSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ticketSequence,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: "1000",
|
||||
Counterparty: &counterparty,
|
||||
InterestRate: ptr(types.InterestRate(500)),
|
||||
PaymentTotal: ptr(types.PaymentTotal(1)),
|
||||
PaymentInterval: ptr(types.PaymentInterval(2592000)),
|
||||
LoanOriginationFee: ptr(types.XRPLNumber("100")),
|
||||
LoanServiceFee: ptr(types.XRPLNumber("10")),
|
||||
}
|
||||
|
||||
flatTx := transaction.FlatTransaction(loanSetTx.Flatten())
|
||||
if err := client.Autofill(&flatTx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Loan broker signs first
|
||||
_, _, err := loanBrokerWallet.Sign(flatTx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Borrower signs second
|
||||
blob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatTx, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Submit and wait for validation
|
||||
resp, err := client.SubmitTxBlobAndWait(blob, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
loan1Ch := make(chan *requests.TxResponse, 1)
|
||||
loan2Ch := make(chan *requests.TxResponse, 1)
|
||||
|
||||
go func() { loan1Ch <- createLoan(lbTickets[2]) }()
|
||||
go func() { loan2Ch <- createLoan(lbTickets[3]) }()
|
||||
|
||||
loan1Resp := <-loan1Ch
|
||||
loan2Resp := <-loan2Ch
|
||||
|
||||
var loanID1, loanID2 string
|
||||
for _, node := range loan1Resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanID1 = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, node := range loan2Resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanID2 = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 7/7\r")
|
||||
|
||||
// Write setup data to JSON file.
|
||||
// Using a struct to preserve field order.
|
||||
setupData := struct {
|
||||
Description string `json:"description"`
|
||||
LoanBroker any `json:"loanBroker"`
|
||||
Borrower any `json:"borrower"`
|
||||
Depositor any `json:"depositor"`
|
||||
CredentialIssuer any `json:"credentialIssuer"`
|
||||
DomainID string `json:"domainID"`
|
||||
MptID string `json:"mptID"`
|
||||
VaultID string `json:"vaultID"`
|
||||
LoanBrokerID string `json:"loanBrokerID"`
|
||||
LoanID1 string `json:"loanID1"`
|
||||
LoanID2 string `json:"loanID2"`
|
||||
}{
|
||||
Description: "This file is auto-generated by lending-setup. It stores XRPL account info for use in lending protocol tutorials.",
|
||||
LoanBroker: map[string]string{
|
||||
"address": string(loanBrokerWallet.ClassicAddress),
|
||||
"seed": loanBrokerWallet.Seed,
|
||||
},
|
||||
Borrower: map[string]string{
|
||||
"address": string(borrowerWallet.ClassicAddress),
|
||||
"seed": borrowerWallet.Seed,
|
||||
},
|
||||
Depositor: map[string]string{
|
||||
"address": string(depositorWallet.ClassicAddress),
|
||||
"seed": depositorWallet.Seed,
|
||||
},
|
||||
CredentialIssuer: map[string]string{
|
||||
"address": string(credIssuerWallet.ClassicAddress),
|
||||
"seed": credIssuerWallet.Seed,
|
||||
},
|
||||
DomainID: domainID,
|
||||
MptID: mptID,
|
||||
VaultID: vaultID,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
LoanID1: loanID1,
|
||||
LoanID2: loanID2,
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(setupData, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile("lending-setup.json", jsonData, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Setting up tutorial: Complete!")
|
||||
}
|
||||
195
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
195
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
|
||||
// After the 60 seconds pass, this example defaults the loan.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/flag"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID1"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
|
||||
// Check loan status before impairment ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Total Amount Owed: %s TSTUSD.\n", loanStatus.Node["TotalValueOutstanding"])
|
||||
|
||||
// Convert Ripple Epoch timestamp to local date and time
|
||||
nextPaymentDueDate := int64(loanStatus.Node["NextPaymentDueDate"].(float64))
|
||||
paymentDue := time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
fmt.Printf("Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
|
||||
// Prepare LoanManage transaction to impair the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to impair loan ===\n\n")
|
||||
loanManageImpair := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageImpair.SetLoanImpairFlag()
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanManageImpair := loanManageImpair.Flatten()
|
||||
loanManageImpairJSON, _ := json.MarshalIndent(flatLoanManageImpair, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageImpairJSON))
|
||||
|
||||
// Sign, submit, and wait for impairment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage impairment transaction ===\n\n")
|
||||
impairResponse, err := client.SubmitTxAndWait(flatLoanManageImpair, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if impairResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to impair loan: %s\n", impairResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan impaired successfully!\n")
|
||||
|
||||
// Extract loan impairment info from transaction results ----------------------
|
||||
var loanNode map[string]any
|
||||
for _, node := range impairResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check grace period and next payment due date
|
||||
gracePeriod := int64(loanNode["GracePeriod"].(float64))
|
||||
nextPaymentDueDate = int64(loanNode["NextPaymentDueDate"].(float64))
|
||||
defaultTime := nextPaymentDueDate + gracePeriod
|
||||
paymentDue = time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
|
||||
fmt.Printf("New Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
fmt.Printf("Grace Period: %d seconds\n", gracePeriod)
|
||||
|
||||
// Convert current time to Ripple Epoch timestamp
|
||||
currentTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix())
|
||||
// Add a small buffer (5 seconds) to account for ledger close time
|
||||
secondsUntilDefault := defaultTime - currentTime + 5
|
||||
|
||||
// Countdown until loan can be defaulted ----------------------
|
||||
fmt.Printf("\n=== Countdown until loan can be defaulted ===\n\n")
|
||||
for secondsUntilDefault >= 0 {
|
||||
fmt.Printf("\r%d seconds...", secondsUntilDefault)
|
||||
time.Sleep(time.Second)
|
||||
secondsUntilDefault--
|
||||
}
|
||||
fmt.Print("\rGrace period expired. Loan can now be defaulted.\n")
|
||||
|
||||
// Prepare LoanManage transaction to default the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to default loan ===\n\n")
|
||||
loanManageDefault := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageDefault.SetLoanDefaultFlag()
|
||||
|
||||
flatLoanManageDefault := loanManageDefault.Flatten()
|
||||
loanManageDefaultJSON, _ := json.MarshalIndent(flatLoanManageDefault, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageDefaultJSON))
|
||||
|
||||
// Sign, submit, and wait for default validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage default transaction ===\n\n")
|
||||
defaultResponse, err := client.SubmitTxAndWait(flatLoanManageDefault, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if defaultResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to default loan: %s\n", defaultResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan defaulted successfully!\n")
|
||||
|
||||
// Verify loan default status from transaction results ----------------------
|
||||
fmt.Printf("\n=== Checking final loan status ===\n\n")
|
||||
for _, node := range defaultResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
loanFlags := uint32(loanNode["Flags"].(float64))
|
||||
|
||||
// Check which loan flags are set
|
||||
activeFlags := []string{}
|
||||
if flag.Contains(loanFlags, transaction.TfLoanDefault) {
|
||||
activeFlags = append(activeFlags, "tfLoanDefault")
|
||||
}
|
||||
if flag.Contains(loanFlags, transaction.TfLoanImpair) {
|
||||
activeFlags = append(activeFlags, "tfLoanImpair")
|
||||
}
|
||||
fmt.Printf("Final loan flags: %v\n", activeFlags)
|
||||
}
|
||||
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// IMPORTANT: This example pays off an existing loan and then deletes it.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID2"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nBorrower address: %s\n", borrowerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check initial loan status ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
totalValueOutstanding := loanStatus.Node["TotalValueOutstanding"].(string)
|
||||
loanServiceFee := loanStatus.Node["LoanServiceFee"].(string)
|
||||
|
||||
outstanding, _ := new(big.Int).SetString(totalValueOutstanding, 10)
|
||||
serviceFee, _ := new(big.Int).SetString(loanServiceFee, 10)
|
||||
totalPayment := new(big.Int).Add(outstanding, serviceFee).String()
|
||||
|
||||
fmt.Printf("Amount Owed: %s TSTUSD\n", totalValueOutstanding)
|
||||
fmt.Printf("Loan Service Fee: %s TSTUSD\n", loanServiceFee)
|
||||
fmt.Printf("Total Payment Due (including fees): %s TSTUSD\n", totalPayment)
|
||||
|
||||
// Prepare LoanPay transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanPay transaction ===\n\n")
|
||||
loanPayTx := transaction.LoanPay{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: totalPayment,
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanPayTx := loanPayTx.Flatten()
|
||||
loanPayTxJSON, _ := json.MarshalIndent(flatLoanPayTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanPayTxJSON))
|
||||
|
||||
// Sign, submit, and wait for payment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanPay transaction ===\n\n")
|
||||
payResponse, err := client.SubmitTxAndWait(flatLoanPayTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if payResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to pay loan: %s\n", payResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan paid successfully!\n")
|
||||
|
||||
// Extract updated loan info from transaction results ----------------------
|
||||
fmt.Printf("\n=== Loan Status After Payment ===\n\n")
|
||||
for _, node := range payResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
if balance, ok := node.ModifiedNode.FinalFields["TotalValueOutstanding"].(string); ok {
|
||||
fmt.Printf("Outstanding Loan Balance: %s TSTUSD\n", balance)
|
||||
} else {
|
||||
fmt.Printf("Outstanding Loan Balance: Loan fully paid off!\n")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanDelete transaction ----------------------
|
||||
// Either the loan broker or borrower can submit this transaction.
|
||||
fmt.Printf("\n=== Preparing LoanDelete transaction ===\n\n")
|
||||
loanDeleteTx := transaction.LoanDelete{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
|
||||
flatLoanDeleteTx := loanDeleteTx.Flatten()
|
||||
loanDeleteTxJSON, _ := json.MarshalIndent(flatLoanDeleteTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanDeleteTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deletion validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanDelete transaction ===\n\n")
|
||||
deleteResponse, err := client.SubmitTxAndWait(flatLoanDeleteTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if deleteResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to delete loan: %s\n", deleteResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan deleted successfully!\n")
|
||||
|
||||
// Verify loan deletion ----------------------
|
||||
fmt.Printf("\n=== Verifying Loan Deletion ===\n\n")
|
||||
_, err = client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
if err.Error() == "entryNotFound" {
|
||||
fmt.Printf("Loan has been successfully removed from the XRP Ledger!\n")
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Warning: Loan still exists in the ledger.\n")
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Create a Conditional Escrow</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<link href="modular-tutorials.css" rel="stylesheet">
|
||||
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
|
||||
<script src="account-support.js"></script>
|
||||
<script src="create-time-escrow.js"></script>
|
||||
<script src='create-conditional-escrow.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Create a Conditional Escrow</h1>
|
||||
<form id="theForm">
|
||||
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
|
||||
Choose your ledger instance:
|
||||
</span>
|
||||
|
||||
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
|
||||
<label for="dn">Devnet</label>
|
||||
|
||||
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
|
||||
<label for="tn">Testnet</label>
|
||||
<br /><br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1name" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
|
||||
<label for="account2name">Account 2 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2name" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account1address">Account 1 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1address" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account2address">Account 2 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2address" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account1seed">Account 1 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1seed" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account2seed">Account 2 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2seed" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Name of the currently selected account.">
|
||||
<label for="accountNameField">Account Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountNameField" size="40" readonly></input>
|
||||
<input type="radio" id="account1" name="accounts" value="account1">
|
||||
<label for="account1">Account 1</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Address of the currently selected account.">
|
||||
<label for="accountAddressField">Account Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountAddressField" size="40" readonly></input>
|
||||
<input type="radio" id="account2" name="accounts" value="account2">
|
||||
<label for="account2">Account 2</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
|
||||
<label for="accountSeedField">Account Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountSeedField" size="40" readonly></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
|
||||
<label for="xrpBalanceField">XRP Balance</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="xrpBalanceField" size="40" readonly></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Amount of XRP to send.">
|
||||
<label for="amountField">Amount</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="amountField" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Destination account address where the escrow is sent.">
|
||||
<lable for="destinationField">Destination</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="destinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="createConditionalEscrow()">Create Escrow</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Condition code used to begin the escrow transaction.">
|
||||
<lable for="escrowConditionField">Escrow Condition</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowConditionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="getEscrows()">Get Escrows</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Fullfillment code to complete the escrow transaction.">
|
||||
<lable for="escrowFulfillmentField">Escrow Fulfillment</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowFulfillmentField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="finishConditionalEscrow()">Finish Escrow</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow cancel time, in seconds.">
|
||||
<lable for="escrowCancelDateField">Escrow Cancel Time</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowCancelDateField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow sequence number, used when finishing the escrow.">
|
||||
<lable for="escrowSequenceNumberField">Escrow Sequence Number</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowSequenceNumberField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getTransaction()">Get Transaction</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow owner, the account that created the escrow.">
|
||||
<lable for="escrowOwnerField">Escrow Owner</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowOwnerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Transaction number, used with the Get Transaction button.">
|
||||
<lable for="transactionField">Transaction</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="transactionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<p align="right">
|
||||
<textarea id="resultField" cols="80" rows="20"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
|
||||
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.value === 'account1') {
|
||||
populate1()
|
||||
} else if (this.value === 'account2') {
|
||||
populate2()
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,86 +0,0 @@
|
||||
// *******************************************************
|
||||
// ************* Create Conditional Escrow ***************
|
||||
// *******************************************************
|
||||
|
||||
async function createConditionalEscrow() {
|
||||
|
||||
//------------------------------------------------------Connect to the Ledger
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
const sendAmount = amountField.value
|
||||
let results = `===Connected to ${net}===\n===Creating conditional escrow.===\n\n`
|
||||
resultField.value = results
|
||||
let escrow_cancel_date = new Date()
|
||||
escrow_cancel_date = addSeconds(parseInt(escrowCancelDateField.value))
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
try {
|
||||
const escrowTx = await client.autofill({
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": wallet.address,
|
||||
"Amount": xrpl.xrpToDrops(sendAmount),
|
||||
"Destination": destinationField.value,
|
||||
"CancelAfter": escrow_cancel_date,
|
||||
"Condition": escrowConditionField.value
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = wallet.sign(escrowTx)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results = "\n=== *** Sequence Number (Save!): " + tx.result.tx_json.Sequence
|
||||
results += "\n\n===Balance changes===\n" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
resultField.value += results
|
||||
}
|
||||
catch (error) {
|
||||
results += "\n===Error: " + error.message
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
// -------------------------------------------------------- Disconnect
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of createTimeEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ************** Finish Conditional Escrow **************
|
||||
// *******************************************************
|
||||
|
||||
async function finishConditionalEscrow() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ${net}===\n===Fulfilling conditional escrow.===\n`
|
||||
resultField.value = results
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
try {
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": accountAddressField.value,
|
||||
"Owner": escrowOwnerField.value,
|
||||
"OfferSequence": parseInt(escrowSequenceNumberField.value),
|
||||
"Condition": escrowConditionField.value,
|
||||
"Fulfillment": escrowFulfillmentField.value
|
||||
})
|
||||
const signed = wallet.sign(prepared)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\n===Balance changes===" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
resultField.value = results
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
}
|
||||
catch (error) {
|
||||
results += "\n===Error: " + error.message + ".===\n"
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
// -------------------------------------------------------- Disconnect
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of finisConditionalEscrow()
|
||||
@@ -1,249 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Create Offers</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<link href="modular-tutorials.css" rel="stylesheet">
|
||||
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
|
||||
<script src="account-support.js"></script>
|
||||
<script src='send-xrp.js'></script>
|
||||
<script src='create-offer.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Create Offers</h1>
|
||||
<form id="theForm">
|
||||
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
|
||||
Choose your ledger instance:
|
||||
</span>
|
||||
|
||||
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
|
||||
<label for="dn">Devnet</label>
|
||||
|
||||
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
|
||||
<label for="tn">Testnet</label>
|
||||
<br /><br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1name" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
|
||||
<label for="account2name">Account 2 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2name" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account1address">Account 1 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1address" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account2address">Account 2 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2address" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account1seed">Account 1 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1seed" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account2seed">Account 2 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2seed" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Name of the currently selected account.">
|
||||
<label for="accountNameField">Account Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountNameField" size="40" readonly></input>
|
||||
<input type="radio" id="account1" name="accounts" value="account1">
|
||||
<label for="account1">Account 1</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Address of the currently selected account.">
|
||||
<label for="accountAddressField">Account Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountAddressField" size="40" readonly></input>
|
||||
<input type="radio" id="account2" name="accounts" value="account2">
|
||||
<label for="account2">Account 2</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
|
||||
<label for="accountSeedField">Account Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountSeedField" size="40" readonly></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
|
||||
<label for="xrpBalanceField">XRP Balance</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="xrpBalanceField" size="40" readonly></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<h4 align="center">Taker Pays</h4>
|
||||
</td>
|
||||
<td>
|
||||
<h4 align="center">Taker Gets</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Currency codes for the Pay and Get offers.">
|
||||
<lable for="payCurrencyField">Currency Code</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="payCurrencyField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="getCurrencyField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="createOffer()">Create Offer</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Issuers of the offered currencies.">
|
||||
<lable for="payIssuerField">Issuer</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="payIssuerField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="getIssuerField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getOffers()">Get Offers</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Amounts of offered currencies.">
|
||||
<lable for="amountField">Amount</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="payAmountField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="getAmountField" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="cancelOffer()">Cancel Offer</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Sequence number of the offer.">
|
||||
<lable for="offerSequenceField">Offer Sequence</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="offerSequenceField" size="40"></input>
|
||||
</td>
|
||||
<td></td>
|
||||
<td>
|
||||
<button type="button" onClick="getTokenBalance()">Get Token Balance</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3">
|
||||
<p align="right">
|
||||
<textarea id="resultField" cols="80" rows="20"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
|
||||
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.value === 'account1') {
|
||||
populate1()
|
||||
} else if (this.value === 'account2') {
|
||||
populate2()
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,121 +0,0 @@
|
||||
/***********************************
|
||||
*********** Create Offer **********
|
||||
**********************************/
|
||||
|
||||
async function createOffer() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ${net}, getting wallet....===\n`
|
||||
resultField.value = results
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
try {
|
||||
if (getCurrencyField.value == 'XRP') {
|
||||
takerGets = xrpl.xrpToDrops(getAmountField.value)
|
||||
}
|
||||
else {
|
||||
takerGetsString = '{"currency": "' + getCurrencyField.value + '",\n' +
|
||||
'"issuer": "' + getIssuerField.value + '",\n' +
|
||||
'"value": "' + getAmountField.value + '"}'
|
||||
takerGets = JSON.parse(takerGetsString)
|
||||
}
|
||||
|
||||
if (payCurrencyField.value == 'XRP') {
|
||||
takerPays = xrpl.xrpToDrops(payAmountField.value)
|
||||
} else {
|
||||
takerPaysString = '{"currency": "' + payCurrencyField.value + '",\n' +
|
||||
'"issuer": "' + payIssuerField.value + '",\n' +
|
||||
'"value": "' + payAmountField.value + '"}'
|
||||
takerPays = JSON.parse(takerPaysString)
|
||||
}
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": wallet.address,
|
||||
"TakerGets": takerGets,
|
||||
"TakerPays": takerPays
|
||||
})
|
||||
const signed = wallet.sign(prepared)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results = '\n\n===Offer created===\n\n' +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
resultField.value += results
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
} catch (err) {
|
||||
console.error('Error creating offer:', err);
|
||||
results = `\nError: ${err.message}\n`
|
||||
resultField.value += results
|
||||
throw err; // Re-throw the error to be handled by the caller
|
||||
}
|
||||
finally {
|
||||
// Disconnect from the client
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of createOffer()
|
||||
|
||||
/***********************************
|
||||
************ Get Offers ***********
|
||||
**********************************/
|
||||
|
||||
async function getOffers() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ' + ${net}, getting offers....===\n`
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
resultField.value = results
|
||||
results += '\n\n=== Offers ===\n'
|
||||
let offers
|
||||
try {
|
||||
offers = await client.request({
|
||||
method: "account_offers",
|
||||
account: wallet.address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
results = JSON.stringify(offers, null, 2)
|
||||
resultField.value += results
|
||||
} catch (err) {
|
||||
console.error('Error getting offers:', err);
|
||||
results = `\nError: ${err.message}\n`
|
||||
resultField.value += results
|
||||
throw err; // Re-throw the error to be handled by the caller
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
}// End of getOffers()
|
||||
|
||||
/***********************************
|
||||
*********** Cancel Offer **********
|
||||
**********************************/
|
||||
async function cancelOffer() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ${net}, canceling offer.===\n`
|
||||
resultField.value = results
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
try {
|
||||
// OfferSequence is the _seq_ value from getOffers.
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCancel",
|
||||
"Account": wallet.address,
|
||||
"OfferSequence": parseInt(offerSequenceField.value)
|
||||
})
|
||||
const signed = wallet.sign(prepared)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nOffer canceled. Balance changes: \n" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
resultField.value = results
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
}
|
||||
catch (err) {
|
||||
console.error('Error canceling offer:', err);
|
||||
results = `\nError: ${err.message}\n`
|
||||
resultField.value += results
|
||||
throw err; // Re-throw the error to be handled by the caller
|
||||
}
|
||||
finally {
|
||||
// Disconnect from the client
|
||||
client.disconnect()
|
||||
}
|
||||
}// End of cancelOffer()
|
||||
@@ -1,269 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Create a Time-based Escrow</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<link href="modular-tutorials.css" rel="stylesheet">
|
||||
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
|
||||
<script src="account-support.js"></script>
|
||||
<script src='create-time-escrow.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Create a Time-based Escrow</h1>
|
||||
<form id="theForm">
|
||||
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
|
||||
Choose your ledger instance:
|
||||
</span>
|
||||
|
||||
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
|
||||
<label for="dn">Devnet</label>
|
||||
|
||||
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
|
||||
<label for="tn">Testnet</label>
|
||||
<br /><br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1name" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
|
||||
<label for="account2name">Account 2 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2name" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account1address">Account 1 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1address" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account2address">Account 2 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2address" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account1seed">Account 1 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1seed" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account2seed">Account 2 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2seed" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Name of the currently selected account.">
|
||||
<label for="accountNameField">Account Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountNameField" size="40" readonly></input>
|
||||
<input type="radio" id="account1" name="accounts" value="account1">
|
||||
<label for="account1">Account 1</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Address of the currently selected account.">
|
||||
<label for="accountAddressField">Account Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountAddressField" size="40" readonly></input>
|
||||
<input type="radio" id="account2" name="accounts" value="account2">
|
||||
<label for="account2">Account 2</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
|
||||
<label for="accountSeedField">Account Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountSeedField" size="40" readonly></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
|
||||
<label for="xrpBalanceField">XRP Balance</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="xrpBalanceField" size="40" readonly></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Amount of XRP to send.">
|
||||
<label for="amountField">Amount</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="amountField" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Destination account address where the escrow is sent.">
|
||||
<lable for="destinationField">Destination</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="destinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="createTimeBasedEscrow()">Create Time-based Escrow</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow finish time, in seconds.">
|
||||
<lable for="escrowFinishTimeField">Escrow Finish Time</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowFinishTimeField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="getEscrows()">Get Escrows</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow cancel time, in seconds.">
|
||||
<lable for="escrowCancelTimeField">Escrow Cancel Time</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowCancelTimeField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="finishTimeBasedEscrow()">Finish Time-based Escrow</button>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow sequence number, used when finishing the escrow.">
|
||||
<lable for="escrowSequenceNumberField">Escrow Sequence Number</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowSequenceNumberField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Escrow owner, the account that created the escrow.">
|
||||
<lable for="escrowOwnerField">Escrow Owner</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="escrowOwnerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getTransaction()">Get Transaction</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Transaction number, used with the Get Transaction button.">
|
||||
<lable for="transactionField">Transaction</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="transactionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<p align="right">
|
||||
<textarea id="resultField" cols="80" rows="20"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
|
||||
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.value === 'account1') {
|
||||
populate1()
|
||||
} else if (this.value === 'account2') {
|
||||
populate2()
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,177 +0,0 @@
|
||||
// *******************************************************
|
||||
// ************* Add Seconds to Current Date *************
|
||||
// *******************************************************
|
||||
|
||||
function addSeconds(numOfSeconds, date = new Date()) {
|
||||
date.setSeconds(date.getSeconds() + numOfSeconds);
|
||||
date = Math.floor(date / 1000)
|
||||
date = date - 946684800
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
// *******************************************************
|
||||
// ************* Create Time-based Escrow ****************
|
||||
// *******************************************************
|
||||
|
||||
async function createTimeBasedEscrow() {
|
||||
//-------------------------------------------- Prepare Finish and Cancel Dates
|
||||
let escrow_finish_date = new Date()
|
||||
let escrow_cancel_date = new Date()
|
||||
escrow_finish_date = addSeconds(parseInt(escrowFinishTimeField.value))
|
||||
escrow_cancel_date = addSeconds(parseInt(escrowCancelTimeField.value))
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ${net}.===\n\n===Creating time-based escrow.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
const sendAmount = amountField.value
|
||||
const escrowTx = await client.autofill({
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": wallet.address,
|
||||
"Amount": xrpl.xrpToDrops(sendAmount),
|
||||
"Destination": destinationField.value,
|
||||
"FinishAfter": escrow_finish_date,
|
||||
"CancelAfter": escrow_cancel_date
|
||||
})
|
||||
const signed = wallet.sign(escrowTx)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\n===Success! === *** Save this sequence number: " + tx.result.tx_json.Sequence
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
resultField.value = results
|
||||
}
|
||||
catch (error) {
|
||||
results += "\n===Error: " + error.message
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of createTimeEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ***************** Finish Time- Based Escrow ***********
|
||||
// *******************************************************
|
||||
|
||||
async function finishTimeBasedEscrow() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `===Connected to ${net}. Finishing escrow.===\n`
|
||||
resultField.value = results
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
try {
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": accountAddressField.value,
|
||||
"Owner": escrowOwnerField.value,
|
||||
"OfferSequence": parseInt(escrowSequenceNumberField.value)
|
||||
})
|
||||
const signed = wallet.sign(prepared)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\n===Balance changes===" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
resultField.value = results
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
}
|
||||
catch (error) {
|
||||
results += "\n===Error: " + error.message + "==="
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of finishTimeBasedEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ******************* Get Escrows ***********************
|
||||
// *******************************************************
|
||||
|
||||
async function getEscrows() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `\n===Connected to ${net}.\nGetting account escrows.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const escrow_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": accountAddressField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "escrow"
|
||||
})
|
||||
results += JSON.stringify(escrow_objects.result, null, 2)
|
||||
resultField.value = results
|
||||
}
|
||||
catch (error) {
|
||||
results += "\nError: " + error.message
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// *******************************************************
|
||||
// ************** Get Transaction Info *******************
|
||||
// *******************************************************
|
||||
|
||||
async function getTransaction() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `\n===Connected to ${net}.===\n===Getting transaction information.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const tx_info = await client.request({
|
||||
"id": 1,
|
||||
"command": "tx",
|
||||
"transaction": transactionField.value,
|
||||
})
|
||||
results += JSON.stringify(tx_info.result, null, 2)
|
||||
resultField.value = results
|
||||
}
|
||||
catch (error) {
|
||||
results += "\nError: " + error.message
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of getTransaction()
|
||||
|
||||
// *******************************************************
|
||||
// ****************** Cancel Escrow **********************
|
||||
// *******************************************************
|
||||
|
||||
async function cancelEscrow() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `\n===Connected to ${net}. Cancelling escrow.===`
|
||||
resultField.value = results
|
||||
try {
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowCancel",
|
||||
"Account": accountAddressField.value,
|
||||
"Owner": escrowOwnerField.value,
|
||||
"OfferSequence": parseInt(escrowSequenceNumberField.value)
|
||||
})
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
const signed = wallet.sign(prepared)
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\n===Balance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
resultField.value = results
|
||||
}
|
||||
catch (error) {
|
||||
results += "\n===Error: " + error.message
|
||||
resultField.value = results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
const cc = require('five-bells-condition');
|
||||
const crypto = require('crypto');
|
||||
|
||||
// 1. Generate a random 32-byte seed
|
||||
const preimageData = crypto.randomBytes(32);
|
||||
|
||||
// 2. Create a PreimageSha256 fulfillment object
|
||||
const fulfillment = new cc.PreimageSha256();
|
||||
|
||||
// 3. Set the preimage
|
||||
fulfillment.setPreimage(preimageData);
|
||||
|
||||
// 4. Generate the condition (binary)
|
||||
const conditionBinary = fulfillment.getConditionBinary();
|
||||
|
||||
// 5. Generate the fulfillment (binary)
|
||||
const fulfillmentBinary = fulfillment.serializeBinary();
|
||||
|
||||
// Convert to hex for easier use
|
||||
const conditionHex = conditionBinary.toString('hex').toUpperCase();
|
||||
const fulfillmentHex = fulfillmentBinary.toString('hex').toUpperCase();
|
||||
|
||||
console.log('Condition (hex):', conditionHex);
|
||||
console.log('Fulfillment (hex):', fulfillmentHex);
|
||||
@@ -1,247 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Send Checks</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<link href="modular-tutorials.css" rel="stylesheet">
|
||||
<script src='https://unpkg.com/xrpl@4.1.0/build/xrpl-latest.js'></script>
|
||||
<script src="account-support.js"></script>
|
||||
<script src='send-xrp.js'></script>
|
||||
<script src='send-currency.js'></script>
|
||||
<script src='send-checks.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Send Checks</h1>
|
||||
<form id="theForm">
|
||||
<span class="tooltip" tooltip-data="Choose the XRPL host server for your account.">
|
||||
Choose your ledger instance:
|
||||
</span>
|
||||
|
||||
<input type="radio" id="dn" name="server" value="wss://s.devnet.rippletest.net:51233" checked>
|
||||
<label for="dn">Devnet</label>
|
||||
|
||||
<input type="radio" id="tn" name="server" value="wss://s.altnet.rippletest.net:51233">
|
||||
<label for="tn">Testnet</label>
|
||||
<br /><br />
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount1()">Get New Account 1</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed1()">Get Account 1 From Seed</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getNewAccount2()">Get New Account 2</button>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccountFromSeed2()">Get Account 2 From Seed</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account."><label for="account1name">Account 1 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1name" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Arbitrary human-readable name for the account.">
|
||||
<label for="account2name">Account 2 Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2name" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account1address">Account 1 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1address" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Identifying address for the account.">
|
||||
<label for="account2address">Account 2 Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2address" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account1seed">Account 1 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account1seed" size="40"></input>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tooltip" tooltip-data="Seed for deriving public and private keys for the account.">
|
||||
<label for="account2seed">Account 2 Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="account2seed" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Name of the currently selected account.">
|
||||
<label for="accountNameField">Account Name</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountNameField" size="40" readonly></input>
|
||||
<input type="radio" id="account1" name="accounts" value="account1">
|
||||
<label for="account1">Account 1</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Address of the currently selected account.">
|
||||
<label for="accountAddressField">Account Address</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountAddressField" size="40" readonly></input>
|
||||
<input type="radio" id="account2" name="accounts" value="account2">
|
||||
<label for="account2">Account 2</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Seed of the currently selected account.">
|
||||
<label for="accountSeedField">Account Seed</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="accountSeedField" size="40" readonly></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="XRP balance for the currently selected account.">
|
||||
<label for="xrpBalanceField">XRP Balance</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="xrpBalanceField" size="40" readonly></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Currency code for the check.">
|
||||
<lable for="currencyField">Currency Code</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="currencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="sendCheck()">Send Check</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Issuing account for the currency.">
|
||||
<lable for="issuerField">Issuer</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="issuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" onClick="cashCheck()">Cash Check</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Amount of XRP to send.">
|
||||
<label for="amountField">Amount</label>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="amountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="getChecks()">Get Checks</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Destination account address where XRP is sent.">
|
||||
<lable for="destinationField">Destination</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="destinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="cancelCheck()">Cancel Check</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<span class="tooltip" tooltip-data="Check ID.">
|
||||
<lable for="checkIdField">Check ID</lable>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="checkIdField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="getTokenBalance()">Get Token Balance</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<p align="right">
|
||||
<textarea id="resultField" cols="80" rows="20"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
<td align="left" valign="top">
|
||||
<button type="button" onClick="gatherAccountInfo()">Gather Account Info</button><br/>
|
||||
<button type="button" onClick="distributeAccountInfo()">Distribute Account Info</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
<script>
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
radioButtons.forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (this.value === 'account1') {
|
||||
populate1()
|
||||
} else if (this.value === 'account2') {
|
||||
populate2()
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -1,152 +0,0 @@
|
||||
// *******************************************************
|
||||
// ***************** Send Check **************************
|
||||
// *******************************************************
|
||||
async function sendCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
results = `\n===Connected to ${net}.===\n===Sending check.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
let check_amount = amountField.value
|
||||
if (currencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"currency": currencyField.value,
|
||||
"value": amountField.value,
|
||||
"issuer": wallet.address
|
||||
}
|
||||
}
|
||||
const send_check_tx = {
|
||||
"TransactionType": "CheckCreate",
|
||||
"Account": wallet.address,
|
||||
"SendMax": check_amount,
|
||||
"Destination": destinationField.value
|
||||
}
|
||||
const check_prepared = await client.autofill(send_check_tx)
|
||||
const check_signed = wallet.sign(check_prepared)
|
||||
results = '\n===Sending ' + amountField.value + ' ' + currencyField.
|
||||
value + ' to ' + destinationField.value + '.===\n'
|
||||
resultField.value += results
|
||||
const check_result = await client.submitAndWait(check_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += '===Transaction succeeded===\n\n'
|
||||
resultField.value += JSON.stringify(check_result.result, null, 2)
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
results = `Error sending transaction: ${error}`
|
||||
resultField.value += results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // end of sendCheck()
|
||||
|
||||
// *******************************************************
|
||||
// ********************* Get Checks **********************
|
||||
// *******************************************************
|
||||
|
||||
async function getChecks() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
let results = `\n===Connected to ${net}.===\n===Getting account checks.===\n\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const check_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": accountAddressField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "check"
|
||||
})
|
||||
resultField.value += JSON.stringify(check_objects.result, null, 2)
|
||||
} catch (error) {
|
||||
results = `Error getting checks: ${error}`
|
||||
resultField.value += results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // End of getChecks()
|
||||
|
||||
// *******************************************************
|
||||
// ******************** Cash Check **********************
|
||||
// *******************************************************
|
||||
|
||||
async function cashCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
results = `\n===Connected to ${net}.===\n===Cashing check.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
let check_amount = amountField.value
|
||||
if (currencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"value": amountField.value,
|
||||
"currency": currencyField.value,
|
||||
"issuer": issuerField.value
|
||||
}
|
||||
}
|
||||
const cash_check_tx = {
|
||||
"TransactionType": "CheckCash",
|
||||
"Account": wallet.address,
|
||||
"Amount": check_amount,
|
||||
"CheckID": checkIdField.value
|
||||
}
|
||||
const cash_prepared = await client.autofill(cash_check_tx)
|
||||
const cash_signed = wallet.sign(cash_prepared)
|
||||
results = ' Receiving ' + amountField.value + ' ' + currencyField.value + '.\n'
|
||||
resultField.value += results
|
||||
const check_result = await client.submitAndWait(cash_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results = '===Transaction succeeded===\n' + JSON.stringify(check_result.result, null, 2)
|
||||
resultField.value += results
|
||||
}
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
} catch (error) {
|
||||
results = `Error sending transaction: ${error}`
|
||||
resultField.value += results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // end of cashCheck()
|
||||
|
||||
// *******************************************************
|
||||
// **************** Cancel Check *************************
|
||||
// *******************************************************
|
||||
|
||||
async function cancelCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
results = `\n===Connected to ${net}.===\n===Cancelling check.===\n`
|
||||
resultField.value = results
|
||||
try {
|
||||
const wallet = xrpl.Wallet.fromSeed(accountSeedField.value)
|
||||
const cancel_check_tx = {
|
||||
"TransactionType": "CheckCancel",
|
||||
"Account": wallet.address,
|
||||
"CheckID": checkIdField.value
|
||||
}
|
||||
const cancel_prepared = await client.autofill(cancel_check_tx)
|
||||
const cancel_signed = wallet.sign(cancel_prepared)
|
||||
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += `===Transaction succeeded===\n${check_result.result.meta.TransactionResult}`
|
||||
resultField.value = results
|
||||
}
|
||||
xrpBalanceField.value = (await client.getXrpBalance(wallet.address))
|
||||
} catch (error) {
|
||||
results = `Error sending transaction: ${error}`
|
||||
resultField.value += results
|
||||
}
|
||||
finally {
|
||||
client.disconnect()
|
||||
}
|
||||
} // end of cancelCheck()
|
||||
@@ -1,192 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Token Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Token Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="testnet">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="devnet">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="sendXRP()">Send XRP></button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="oPsendXRP()"><Send XRP</button>
|
||||
</td>
|
||||
<td align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,278 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Token Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex10-check.js'></script>
|
||||
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Token Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="testnet">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="devnet">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyIssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Check ID
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyCheckID" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="true"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyCurrencyField" size="40" value="USD"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="sendXRP()">Send XRP></button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="sendCheck()">Send Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getChecks()">Get Checks</button>
|
||||
<br/>
|
||||
<button type="button" onClick="cashCheck()">Cash Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="cancelCheck()">Cancel Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="opSendCheck()">Send Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="opGetChecks()">Get Checks</button>
|
||||
<br/>
|
||||
<button type="button" onClick="opCashCheck()">Cash Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="opCancelCheck()">Cancel Check</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
</td>
|
||||
<td valign="top" align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalIssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Check ID
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalCheckIDField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="true"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalCurrencyField" size="40" value="USD"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,302 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Create AMM Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex11-create-amm.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Create AMM Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="testnet">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="devnet">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table style="padding-bottom: 400px;">
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="true"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyCurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
</p>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Asset 1 Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1CurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 1 Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1IssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 1 Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1AmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2CurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2IssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2AmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top" style="padding-top: 165px;">
|
||||
<br>
|
||||
<button type="button" onClick="sendXRP()">Send XRP></button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="createTrustline()">Create TrustLine</button>
|
||||
<br/>
|
||||
<button type="button" onClick="sendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()" style="margin-bottom: 160px;">Get Balances</button>
|
||||
<br/>
|
||||
<button type="button" onClick="checkAMM()">Check AMM</button>
|
||||
<br/>
|
||||
<button type="button" onClick="createAMM()">Create AMM</button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table style="padding-bottom: 350px;">
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="top" style="padding-bottom: 100px;">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="oPcreateTrustline()">Create TrustLine</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
</td>
|
||||
<td valign="top" align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="true"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalCurrencyField" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
<br><br>
|
||||
<textarea id="ammInfoField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,354 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Trade with Auction Slot Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='https://unpkg.com/bignumber.js@9.1.2/bignumber.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex11-create-amm.js'></script>
|
||||
<script src='ripplex12-add-to-amm.js'></script>
|
||||
<script src='ripplex13a-trade-with-auction-slot.js'></script>
|
||||
<script src='ripplex13b-amm-formulas.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Trade with Auction Slot Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="testnet">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="devnet">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table style="padding-bottom: 400px;">
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="true"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyCurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Taker Pays<br/><br/>
|
||||
Currency <input type="text" id="standbyTakerPaysCurrencyField" size="15"></input><br/>
|
||||
Issuer <input type="text" id="standbyTakerPaysIssuerField" size="15"></input><br/>
|
||||
Amount <input type="text" id="standbyTakerPaysAmountField" size="15"></input>
|
||||
</td>
|
||||
<td align="right">
|
||||
Taker Gets<br/><br/>
|
||||
Currency <input type="text" id="standbyTakerGetsCurrencyField" size="15"></input><br/>
|
||||
Issuer <input type="text" id="standbyTakerGetsIssuerField" size="15"></input><br/>
|
||||
Amount <input type="text" id="standbyTakerGetsAmountField" size="15"></input><br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
</p>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Asset 1 Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1CurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 1 Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1IssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 1 Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset1AmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2CurrencyField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Issuer
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2IssuerField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Asset 2 Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="asset2AmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Trading Fee
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyFeeField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
LP Tokens
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyLPField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top" style="padding-top: 450px;">
|
||||
<br>
|
||||
<button type="button" onClick="sendXRP()">Send XRP></button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="createTrustline()">Create TrustLine</button>
|
||||
<br/>
|
||||
<button type="button" onClick="sendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="estimateCost()">Estimate Cost</button>
|
||||
<br/>
|
||||
<button type="button" onClick="swapTokens()" style="margin-bottom: 40px;">Swap Tokens</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="checkAMM()">Check AMM</button>
|
||||
<br/>
|
||||
<button type="button" onClick="createAMM()">Create AMM</button>
|
||||
<br/>
|
||||
<button type="button" onClick="addAssets()">Add to AMM</button>
|
||||
<br/>
|
||||
<button type="button" onClick="voteFees()">Vote on Fee</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="calculateLP()">Get LP Value</button>
|
||||
<br/>
|
||||
<button type="button" onClick="redeemLP()">Redeem LP</button>
|
||||
<br/>
|
||||
<button type="button" onClick="bidAuction()">Bid Auction Slot</button>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table style="padding-bottom: 430px;">
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" valign="top" style="padding-top: 60px;">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="oPcreateTrustline()">Create TrustLine</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
</td>
|
||||
<td valign="top" align="right" style="padding-bottom: 15px;">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="true"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalCurrencyField" size="40"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right" style="padding-top: 170px;">
|
||||
<textarea id="operationalResultField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
<br><br>
|
||||
<textarea id="ammInfoField" cols="60" rows="20" style="resize: none;"></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,313 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Token Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex3a-create-offers.js'></script>
|
||||
<script src='ripplex3b-NameFieldSupport.js'></script>
|
||||
<script>
|
||||
if (typeof module !== "undefined") {
|
||||
const xrpl = require('xrpl')
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
<body>
|
||||
<h1>Token Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="testnet">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="devnet">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="55" rows= "4"></textarea>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Account Name
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyNameField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="false"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyCurrencyField" size="40" value="USD"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Offer Sequence
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyOfferSequenceField" size="10"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Taker Pays:<br/>
|
||||
Currency: <input type="text" id="standbyTakerPaysCurrencyField" size="10"></input><br/>
|
||||
Issuer: <input type="text" id="standbyTakerPaysIssuerField" size="35"></input><br/>
|
||||
Value: <input type="text" id="standbyTakerPaysValueField" size="10"></input>
|
||||
</td>
|
||||
<td>
|
||||
Taker Gets:<br/>
|
||||
Currency: <input type="text" id="standbyTakerGetsCurrencyField" size="10"></input><br/>
|
||||
Issuer: <input type="text" id="standbyTakerGetsIssuerField" size="35"></input><br/>
|
||||
Value: <input type="text" id="standbyTakerGetsValueField" size="10"></input><br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<!-- Standby Buttons, Column 2 -->
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="sendXRP()">Send XRP></button>
|
||||
<br/>
|
||||
<button type="button" onClick="createTrustline()">Create Trust Line</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getTrustLines()">Get Trust Lines</button>
|
||||
<br/>
|
||||
<button type="button" onClick="sendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="createOffer()">Create Offer</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getOffers()">Get Offers</button>
|
||||
<br/>
|
||||
<button type="button" onClick="cancelOffer()">Cancel Offer</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<!-- Operational Buttons, Column 3 -->
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="bottom">
|
||||
<td align="center" valign="middle">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPcreateTrustline()">Create Trust Line</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPgetTrustLines()">Get Trust Lines</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPsendCurrency()">Send Currency</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="oPcreateOffer()">Create Offer</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPgetOffers()">Get Offers</button>
|
||||
<br/>
|
||||
<button type="button" onClick="oPcancelOffer()">Cancel Offer</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
|
||||
<!-- Operational fields, Column 4 -->
|
||||
|
||||
<td valign="top" align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="left">
|
||||
Account Name
|
||||
</td>
|
||||
<td align="left">
|
||||
<input type="text" id="operationalNameField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="false"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Currency
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalCurrencyField" size="40" value="USD"></input>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Offer Sequence
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalOfferSequenceField" size="10"></input>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Taker Pays:<br/>
|
||||
Currency: <input type="text" id="operationalTakerPaysCurrencyField" size="10"></input><br/>
|
||||
Issuer: <input type="text" id="operationalTakerPaysIssuerField" size="35"></input><br/>
|
||||
Value: <input type="text" id="operationalTakerPaysValueField" size="10"></input>
|
||||
</td>
|
||||
<td>
|
||||
Taker Gets:<br/>
|
||||
Currency: <input type="text" id="operationalTakerGetsCurrencyField" size="10"></input><br/>
|
||||
Issuer: <input type="text" id="operationalTakerGetsIssuerField" size="35"></input><br/>
|
||||
Value: <input type="text" id="operationalTakerGetsValueField" size="10"></input><br/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,269 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Time-based Escrow Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex8-escrow.js'></script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Time-based Escrow Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="tn">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="dn">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Finish (seconds)
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowFinishDateField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Cancel (seconds)
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowCancelDateField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Sequence Number
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowSequenceNumberField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="true"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="sendXRP()">Send XRP ></button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/>
|
||||
<button type="button" onClick="createTimeEscrow()">Create Time-based Escrow</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getStandbyEscrows()">Get Escrows</button>
|
||||
<br/>
|
||||
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/>
|
||||
<button type="button" onClick="finishEscrow()">Finish Time-based Escrow</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getOperationalEscrows()">Get Escrows</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getTransaction()">Get Transaction</button>
|
||||
</td>
|
||||
<td valign="top" align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Sequence Number
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalEscrowSequenceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr> <td align="right">
|
||||
Transaction to Look Up
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalTransactionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="true"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,270 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Conditional Escrow Test Harness</title>
|
||||
<link href='https://fonts.googleapis.com/css?family=Work Sans' rel='stylesheet'>
|
||||
<style>
|
||||
body{font-family: "Work Sans", sans-serif;padding: 20px;background: #fafafa;}
|
||||
h1{font-weight: bold;}
|
||||
input, button {padding: 6px;margin-bottom: 8px;}
|
||||
button{font-weight: bold;font-family: "Work Sans", sans-serif;}
|
||||
td{vertical-align: middle;}
|
||||
</style>
|
||||
<script src='https://unpkg.com/xrpl@4.0.0/build/xrpl-latest.js'></script>
|
||||
<script src='ripplex1-send-xrp.js'></script>
|
||||
<script src='ripplex2-send-currency.js'></script>
|
||||
<script src='ripplex8-escrow.js'></script>
|
||||
<script src='ripplex9-escrow-condition.js'></script>
|
||||
</head>
|
||||
|
||||
<!-- ************************************************************** -->
|
||||
<!-- ********************** The Form ****************************** -->
|
||||
<!-- ************************************************************** -->
|
||||
|
||||
<body>
|
||||
<h1>Conditional Escrow Test Harness</h1>
|
||||
<form id="theForm">
|
||||
Choose your ledger instance:
|
||||
|
||||
<input type="radio" id="tn" name="server"
|
||||
value="wss://s.altnet.rippletest.net:51233" checked>
|
||||
<label for="tn">Testnet</label>
|
||||
|
||||
<input type="radio" id="dn" name="server"
|
||||
value="wss://s.devnet.rippletest.net:51233">
|
||||
<label for="dn">Devnet</label>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getAccountsFromSeeds()">Get Accounts From Seeds</button>
|
||||
<br/>
|
||||
<textarea id="seeds" cols="40" rows= "2"></textarea>
|
||||
<br/><br/>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td>
|
||||
<td>
|
||||
<button type="button" onClick="getAccount('standby')">Get New Standby Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Standby Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Destination Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyDestinationField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Condition
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowConditionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Cancel (seconds)
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowCancelDateField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Sequence Number
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbyEscrowSequenceNumberField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<td><button type="button" onClick="configureAccount('standby',document.querySelector('#standbyDefault').checked)">Configure Account</button></td>
|
||||
<td>
|
||||
<input type="checkbox" id="standbyDefault" checked="true"/>
|
||||
<label for="standbyDefault">Allow Rippling</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="standbySeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="left">
|
||||
<textarea id="standbyResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="sendXRP()">Send XRP ></button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/>
|
||||
<button type="button" onClick="createConditionalEscrow()">Create Conditional Escrow</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getStandbyEscrows()">Get Escrows</button>
|
||||
<br/>
|
||||
<button type="button" onClick="cancelEscrow()">Cancel Escrow</button>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<td>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="center" valign="top">
|
||||
<button type="button" onClick="oPsendXRP()">< Send XRP</button>
|
||||
<br/><br/>
|
||||
<button type="button" onClick="getBalances()">Get Balances</button>
|
||||
<br/>
|
||||
<button type="button" onClick="finishConditionalEscrow()">Finish Conditional Escrow</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getOperationalEscrows()">Get Escrows</button>
|
||||
<br/>
|
||||
<button type="button" onClick="getTransaction()">Get Transaction</button>
|
||||
</td>
|
||||
<td valign="top" align="right">
|
||||
<button type="button" onClick="getAccount('operational')">Get New Operational Account</button>
|
||||
<table>
|
||||
<tr valign="top">
|
||||
<td align="right">
|
||||
Operational Account
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAccountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td align="right">
|
||||
XRP Balance
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalBalanceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Amount
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalAmountField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Fulfillment Code
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalFulfillmentField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Escrow Sequence Number
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalEscrowSequenceField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Transaction to Look Up
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalTransactionField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
</td>
|
||||
<td align="right">
|
||||
<input type="checkbox" id="operationalDefault" checked="true"/>
|
||||
<label for="operationalDefault">Allow Rippling</label>
|
||||
<button type="button" onClick="configureAccount('operational',document.querySelector('#operationalDefault').checked)">Configure Account</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
Seed
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" id="operationalSeedField" size="40"></input>
|
||||
<br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p align="right">
|
||||
<textarea id="operationalResultField" cols="80" rows="20" ></textarea>
|
||||
</p>
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,329 +0,0 @@
|
||||
// *******************************************************
|
||||
// ************* Standby Send Check **********************
|
||||
// *******************************************************
|
||||
async function sendCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
var check_amount = standbyAmountField.value
|
||||
|
||||
if (standbyCurrencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"currency": standbyCurrencyField.value,
|
||||
"value": standbyAmountField.value,
|
||||
"issuer": standby_wallet.address
|
||||
}
|
||||
}
|
||||
|
||||
const send_check_tx = {
|
||||
"TransactionType": "CheckCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"SendMax": check_amount,
|
||||
"Destination": standbyDestinationField.value
|
||||
}
|
||||
const check_prepared = await client.autofill(send_check_tx)
|
||||
const check_signed = standby_wallet.sign(check_prepared)
|
||||
results += 'Sending ' + check_amount + ' ' + standbyCurrencyField + ' to ' +
|
||||
standbyDestinationField.value + '...'
|
||||
standbyResultField.value = results
|
||||
const check_result = await client.submitAndWait(check_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${check_signed.hash}'
|
||||
standbyResultField.value = JSON.stringify(check_result.result, null, 2)
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
standbyResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
|
||||
client.disconnect()
|
||||
} // end of sendCheck()
|
||||
|
||||
// *******************************************************
|
||||
// *************** Standby Get Checks ********************
|
||||
// *******************************************************
|
||||
|
||||
async function getChecks() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
results= "\nGetting standby account checks...\n"
|
||||
const check_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": standbyAccountField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "check"
|
||||
})
|
||||
standbyResultField.value = JSON.stringify(check_objects.result, null, 2)
|
||||
client.disconnect()
|
||||
} // End of getChecks()
|
||||
|
||||
// *******************************************************
|
||||
// ************* Standby Cash Check **********************
|
||||
// *******************************************************
|
||||
|
||||
async function cashCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
var check_amount = standbyAmountField.value
|
||||
|
||||
if (standbyCurrencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"value": standbyAmountField.value,
|
||||
"currency": standbyCurrencyField.value,
|
||||
"issuer": standbyIssuerField.value
|
||||
}
|
||||
}
|
||||
const cash_check_tx = {
|
||||
"TransactionType": "CheckCash",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": check_amount,
|
||||
"CheckID": standbyCheckID.value
|
||||
}
|
||||
const cash_prepared = await client.autofill(cash_check_tx)
|
||||
const cash_signed = standby_wallet.sign(cash_prepared)
|
||||
results += ' Receiving ' + standbyAmountField.value + ' ' + standbyCurrencyField.value + '.\n'
|
||||
standbyResultField.value = results
|
||||
const check_result = await client.submitAndWait(cash_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
|
||||
standbyResultField.value = results
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
standbyResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
|
||||
client.disconnect()
|
||||
} // end of cashCheck()
|
||||
|
||||
// *******************************************************
|
||||
// *************** Standby Cancel Check ******************
|
||||
// *******************************************************
|
||||
|
||||
async function cancelCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
const cancel_check_tx = {
|
||||
"TransactionType": "CheckCancel",
|
||||
"Account": standby_wallet.address,
|
||||
"CheckID": standbyCheckID.value
|
||||
}
|
||||
const cancel_prepared = await client.autofill(cancel_check_tx)
|
||||
const cancel_signed = standby_wallet.sign(cancel_prepared)
|
||||
results += ' Cancelling check.\n'
|
||||
standbyResultField.value = results
|
||||
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
|
||||
standbyResultField.value = results
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
standbyResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
client.disconnect()
|
||||
} // end of cancelCheck()
|
||||
|
||||
// *******************************************************
|
||||
// ************ Operational Send Check *******************
|
||||
// *******************************************************
|
||||
async function opSendCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
const issue_quantity = operationalAmountField.value
|
||||
var check_amount = operationalAmountField.value
|
||||
|
||||
if (operationalCurrencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"currency": operationalCurrencyField.value,
|
||||
"value": operationalAmountField.value,
|
||||
"issuer": operational_wallet.address
|
||||
}
|
||||
}
|
||||
const send_check_tx = {
|
||||
"TransactionType": "CheckCreate",
|
||||
"Account": operational_wallet.address,
|
||||
"SendMax": check_amount,
|
||||
"Destination": operationalDestinationField.value
|
||||
}
|
||||
const check_prepared = await client.autofill(send_check_tx)
|
||||
const check_signed = operational_wallet.sign(check_prepared)
|
||||
results += '\nSending check to ' +
|
||||
operationalDestinationField.value + '...'
|
||||
operationalResultField.value = results
|
||||
const check_result = await client.submitAndWait(check_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${check_signed.hash}'
|
||||
operationalResultField.value = JSON.stringify(check_result.result, null, 2)
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
operationalResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
client.disconnect()
|
||||
} // end of opSendCheck()
|
||||
|
||||
// *******************************************************
|
||||
// ************ Operational Get Checks *******************
|
||||
// *******************************************************
|
||||
|
||||
async function opGetChecks() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
results= "\nGetting standby account checks...\n"
|
||||
const check_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": operationalAccountField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "check"
|
||||
})
|
||||
operationalResultField.value = JSON.stringify(check_objects.result, null, 2)
|
||||
client.disconnect()
|
||||
} // End of opGetChecks()
|
||||
|
||||
|
||||
// *******************************************************
|
||||
// ************* Operational Cash Check ******************
|
||||
// *******************************************************
|
||||
|
||||
async function opCashCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
var check_amount = operationalAmountField.value
|
||||
|
||||
if (operationalCurrencyField.value != "XRP") {
|
||||
check_amount = {
|
||||
"value": operationalAmountField.value,
|
||||
"currency": operationalCurrencyField.value,
|
||||
"issuer": operationalIssuerField.value
|
||||
}
|
||||
}
|
||||
const cash_check_tx = {
|
||||
"TransactionType": "CheckCash",
|
||||
"Account": operational_wallet.address,
|
||||
"Amount": check_amount,
|
||||
"CheckID": operationalCheckIDField.value
|
||||
}
|
||||
const cash_prepared = await client.autofill(cash_check_tx)
|
||||
const cash_signed = operational_wallet.sign(cash_prepared)
|
||||
results += ' Receiving ' + operationalAmountField.value + ' ' + operationalCurrencyField.value + '.\n'
|
||||
operationalResultField.value = results
|
||||
const check_result = await client.submitAndWait(cash_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
|
||||
operationalResultField.value = results
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
operationalResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
client.disconnect()
|
||||
}
|
||||
// end of opCashCheck()
|
||||
|
||||
// *******************************************************
|
||||
// ************* Operational Cancel Check ****************
|
||||
// *******************************************************
|
||||
|
||||
async function opCancelCheck() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
const cancel_check_tx = {
|
||||
"TransactionType": "CheckCancel",
|
||||
"Account": operational_wallet.address,
|
||||
"CheckID": operationalCheckIDField.value
|
||||
}
|
||||
|
||||
const cancel_prepared = await client.autofill(cancel_check_tx)
|
||||
const cancel_signed = operational_wallet.sign(cancel_prepared)
|
||||
results += ' Cancelling check.\n'
|
||||
operationalResultField.value = results
|
||||
const check_result = await client.submitAndWait(cancel_signed.tx_blob)
|
||||
if (check_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += 'Transaction succeeded: https://testnet.xrpl.org/transactions/${cash_signed.hash}'
|
||||
operationalResultField.value = results
|
||||
} else {
|
||||
results += 'Transaction failed: See JavaScript console for details.'
|
||||
operationalResultField.value = results
|
||||
throw 'Error sending transaction: ${check_result.result.meta.TransactionResult}'
|
||||
}
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
client.disconnect()
|
||||
} // end of cancelCheck()
|
||||
@@ -1,194 +0,0 @@
|
||||
// Create AMM function
|
||||
|
||||
async function createAMM() {
|
||||
|
||||
let net = getNet()
|
||||
|
||||
const client = new xrpl.Client(net)
|
||||
results = `\n\nConnecting to ${getNet()} ...`
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\n\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
|
||||
const asset1_currency = asset1CurrencyField.value
|
||||
const asset1_issuer = asset1IssuerField.value
|
||||
const asset1_amount = asset1AmountField.value
|
||||
|
||||
const asset2_currency = asset2CurrencyField.value
|
||||
const asset2_issuer = asset2IssuerField.value
|
||||
const asset2_amount = asset2AmountField.value
|
||||
|
||||
let ammCreate = null
|
||||
|
||||
results += '\n\nCreating AMM ...'
|
||||
standbyResultField.value = results
|
||||
|
||||
// AMMCreate requires burning one owner reserve. We can look up that amount
|
||||
// (in drops) on the current network using server_state:
|
||||
const ss = await client.request({"command": "server_state"})
|
||||
const amm_fee_drops = ss.result.state.validated_ledger.reserve_inc.toString()
|
||||
|
||||
if (asset1_currency == 'XRP') {
|
||||
|
||||
ammCreate = {
|
||||
"TransactionType": "AMMCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": JSON.stringify(asset1_amount * 1000000), // convert XRP to drops
|
||||
"Amount2": {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer,
|
||||
"value": asset2_amount
|
||||
},
|
||||
"TradingFee": 500, // 500 = 0.5%
|
||||
"Fee": amm_fee_drops
|
||||
}
|
||||
|
||||
} else if (asset2_currency =='XRP') {
|
||||
|
||||
ammCreate = {
|
||||
"TransactionType": "AMMCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer,
|
||||
"value": asset1_amount
|
||||
},
|
||||
"Amount2": JSON.stringify(asset2_amount * 1000000), // convert XRP to drops
|
||||
"TradingFee": 500, // 500 = 0.5%
|
||||
"Fee": amm_fee_drops
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
ammCreate = {
|
||||
"TransactionType": "AMMCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer,
|
||||
"value": asset1_amount
|
||||
},
|
||||
"Amount2": {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer,
|
||||
"value": asset2_amount
|
||||
},
|
||||
"TradingFee": 500, // 500 = 0.5%
|
||||
"Fee": amm_fee_drops
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const prepared_create = await client.autofill(ammCreate)
|
||||
results += `\n\nPrepared transaction:\n${JSON.stringify(prepared_create, null, 2)}`
|
||||
standbyResultField.value = results
|
||||
standbyResultField.scrollTop = standbyResultField.scrollHeight
|
||||
|
||||
const signed_create = standby_wallet.sign(prepared_create)
|
||||
results += `\n\nSending AMMCreate transaction ...`
|
||||
standbyResultField.value = results
|
||||
standbyResultField.scrollTop = standbyResultField.scrollHeight
|
||||
|
||||
const amm_create = await client.submitAndWait(signed_create.tx_blob)
|
||||
|
||||
if (amm_create.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += `\n\nTransaction succeeded.`
|
||||
} else {
|
||||
results += `\n\nError sending transaction: ${JSON.stringify(amm_create.result.meta.TransactionResult, null, 2)}`
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
results += `\n\n${error.message}`
|
||||
}
|
||||
|
||||
standbyResultField.value = results
|
||||
standbyResultField.scrollTop = standbyResultField.scrollHeight
|
||||
|
||||
checkAMM()
|
||||
|
||||
client.disconnect()
|
||||
|
||||
}
|
||||
|
||||
// Check AMM function
|
||||
|
||||
async function checkAMM() {
|
||||
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
// Gets the issuer and currency code
|
||||
const asset1_currency = asset1CurrencyField.value
|
||||
const asset1_issuer = asset1IssuerField.value
|
||||
|
||||
const asset2_currency = asset2CurrencyField.value
|
||||
const asset2_issuer = asset2IssuerField.value
|
||||
|
||||
let amm_info_request = null
|
||||
|
||||
// Get AMM info transaction
|
||||
|
||||
if (asset1_currency == 'XRP') {
|
||||
|
||||
amm_info_request = {
|
||||
"command": "amm_info",
|
||||
"asset": {
|
||||
"currency": "XRP"
|
||||
},
|
||||
"asset2": {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer
|
||||
},
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
} else if (asset2_currency =='XRP') {
|
||||
|
||||
amm_info_request = {
|
||||
"command": "amm_info",
|
||||
"asset": {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer
|
||||
},
|
||||
"asset2": {
|
||||
"currency": "XRP"
|
||||
},
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
amm_info_request = {
|
||||
"command": "amm_info",
|
||||
"asset": {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer
|
||||
},
|
||||
"asset2": {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer
|
||||
},
|
||||
"ledger_index": "validated"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
const amm_info_result = await client.request(amm_info_request)
|
||||
ammInfo = `AMM Info:\n\n${JSON.stringify(amm_info_result.result.amm, null, 2)}`
|
||||
} catch(error) {
|
||||
ammInfo = `AMM Info:\n\n${error}`
|
||||
}
|
||||
|
||||
ammInfoField.value = ammInfo
|
||||
|
||||
client.disconnect()
|
||||
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
// Function to estimate cost to swap for specified token value.
|
||||
|
||||
async function estimateCost() {
|
||||
|
||||
let net = getNet()
|
||||
|
||||
const client = new xrpl.Client(net)
|
||||
results = `\n\nConnecting to ${getNet()} ...`
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\n\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
try {
|
||||
|
||||
const asset1_currency = asset1CurrencyField.value
|
||||
const asset1_issuer = asset1IssuerField.value
|
||||
|
||||
const asset2_currency = asset2CurrencyField.value
|
||||
const asset2_issuer = asset2IssuerField.value
|
||||
|
||||
|
||||
// Look up AMM info
|
||||
|
||||
let asset1_info = null
|
||||
let asset2_info = null
|
||||
|
||||
if ( asset1_currency == 'XRP' ) {
|
||||
asset1_info = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
} else {
|
||||
asset1_info = {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer
|
||||
}
|
||||
}
|
||||
|
||||
if ( asset2_currency == 'XRP' ) {
|
||||
asset2_info = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
} else {
|
||||
asset2_info = {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer
|
||||
}
|
||||
}
|
||||
|
||||
const amm_info = (await client.request({
|
||||
"command": "amm_info",
|
||||
"asset": asset1_info,
|
||||
"asset2": asset2_info
|
||||
}))
|
||||
|
||||
// Save relevant AMM info for calculations
|
||||
|
||||
const lpt = amm_info.result.amm.lp_token
|
||||
const pool_asset1 = amm_info.result.amm.amount
|
||||
const pool_asset2 = amm_info.result.amm.amount2
|
||||
const full_trading_fee = amm_info.result.amm.trading_fee
|
||||
const discounted_fee = amm_info.result.amm.auction_slot.discounted_fee
|
||||
const old_bid = amm_info.result.amm.auction_slot.price.value
|
||||
const time_interval = amm_info.result.amm.auction_slot.time_interval
|
||||
|
||||
results += `\n\nTrading Fee: ${full_trading_fee/1000}%\nDiscounted Fee: ${discounted_fee/1000}%`
|
||||
|
||||
// Save taker pays and gets values.
|
||||
|
||||
const takerPays = {
|
||||
"currency": standbyTakerPaysCurrencyField.value,
|
||||
"issuer": standbyTakerPaysIssuerField.value,
|
||||
"amount": standbyTakerPaysAmountField.value
|
||||
}
|
||||
|
||||
const takerGets = {
|
||||
"currency": standbyTakerGetsCurrencyField.value,
|
||||
"issuer": standbyTakerGetsIssuerField.value,
|
||||
"amount": standbyTakerGetsAmountField.value
|
||||
}
|
||||
|
||||
// Get amount of assets in the pool.
|
||||
// Convert values to BigNumbers with the appropriate precision.
|
||||
// Tokens always have 15 significant digits;
|
||||
// XRP is precise to integer drops, which can be as high as 10^17
|
||||
|
||||
let asset_out_bn = null
|
||||
let pool_in_bn = null
|
||||
let pool_out_bn = null
|
||||
let isAmmAsset1Xrp = false
|
||||
let isAmmAsset2Xrp = false
|
||||
|
||||
if ( takerPays.currency == 'XRP' ) {
|
||||
asset_out_bn = BigNumber(xrpl.xrpToDrops(takerPays.amount)).precision(17)
|
||||
} else {
|
||||
asset_out_bn = BigNumber(takerPays.amount).precision(15)
|
||||
}
|
||||
|
||||
if ( takerGets.currency == 'XRP' && asset1_currency == 'XRP' ) {
|
||||
pool_in_bn = BigNumber(pool_asset1).precision(17)
|
||||
isAmmAsset1Xrp = true
|
||||
} else if ( takerGets.currency == 'XRP' && asset2_currency == 'XRP' ) {
|
||||
pool_in_bn = BigNumber(pool_asset2).precision(17)
|
||||
isAmmAsset2Xrp = true
|
||||
} else if ( takerGets.currency == asset1_currency ) {
|
||||
pool_in_bn = BigNumber(pool_asset1.value).precision(15)
|
||||
} else {
|
||||
pool_in_bn = BigNumber(pool_asset2.value).precision(15)
|
||||
}
|
||||
|
||||
if (takerPays.currency == 'XRP' && asset1_currency == 'XRP' ) {
|
||||
pool_out_bn = BigNumber(pool_asset1).precision(17)
|
||||
} else if ( takerPays.currency == 'XRP' && asset2_currency == 'XRP' ) {
|
||||
pool_out_bn = BigNumber(pool_asset2).precision(17)
|
||||
} else if ( takerPays.currency == asset1_currency ) {
|
||||
pool_out_bn = BigNumber(pool_asset1.value).precision(15)
|
||||
} else {
|
||||
pool_out_bn = BigNumber(pool_asset2.value).precision(15)
|
||||
}
|
||||
|
||||
if ( takerPays.currency == 'XRP' && parseFloat(takerPays.amount) > parseFloat(xrpl.dropsToXrp(pool_out_bn)) ) {
|
||||
results += `\n\nRequested ${takerPays.amount} ${takerPays.currency}, but AMM only holds ${xrpl.dropsToXrp(pool_out_bn)}. Quitting.`
|
||||
standbyResultField.value = results
|
||||
client.disconnect()
|
||||
return
|
||||
} else if ( parseFloat(takerPays.amount) > parseFloat(pool_out_bn) ) {
|
||||
results += `\n\nRequested ${takerPays.amount} ${takerPays.currency}, but AMM only holds ${pool_out_bn}. Quitting.`
|
||||
standbyResultField.value = results
|
||||
client.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// Use AMM's SwapOut formula to figure out how much of the takerGets asset
|
||||
// you have to pay to receive the target amount of takerPays asset
|
||||
const unrounded_amount = swapOut(asset_out_bn, pool_in_bn, pool_out_bn, full_trading_fee)
|
||||
// Drop decimal places and round ceiling to ensure you pay in enough.
|
||||
const swap_amount = unrounded_amount.dp(0, BigNumber.ROUND_CEIL)
|
||||
|
||||
// Helper function to convert drops to XRP in log window
|
||||
function convert(currency, amount) {
|
||||
if ( currency == 'XRP' ) {
|
||||
amount = xrpl.dropsToXrp(amount)
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
results += `\n\nExpected cost for ${takerPays.amount} ${takerPays.currency}: ${convert(takerGets.currency, swap_amount)} ${takerGets.currency}`
|
||||
|
||||
// Use SwapOut to calculate discounted swap amount with auction slot
|
||||
const raw_discounted = swapOut(asset_out_bn, pool_in_bn, pool_out_bn, discounted_fee)
|
||||
const discounted_swap_amount = raw_discounted.dp(0, BigNumber.ROUND_CEIL)
|
||||
results += `\n\nExpected cost with auction slot for ${takerPays.amount} ${takerPays.currency}: ${convert(takerGets.currency, discounted_swap_amount)} ${takerGets.currency}`
|
||||
|
||||
// Calculate savings by using auction slot
|
||||
const potential_savings = swap_amount.minus(discounted_swap_amount)
|
||||
results += `\nPotential savings: ${convert(takerGets.currency, potential_savings)} ${takerGets.currency}`
|
||||
|
||||
// Calculate the cost of winning the auction slot, in LP Tokens.
|
||||
const auction_price = auctionDeposit(old_bid, time_interval, full_trading_fee, lpt.value).dp(3, BigNumber.ROUND_CEIL)
|
||||
results += `\n\nYou can win the current auction slot by bidding ${auction_price} LP Tokens.`
|
||||
|
||||
// Calculate how much to add for a single-asset deposit to receive the target LP Token amount
|
||||
let deposit_for_bid_asset1 = null
|
||||
let deposit_for_bid_asset2 = null
|
||||
|
||||
if ( isAmmAsset1Xrp == true ) {
|
||||
deposit_for_bid_asset1 = xrpl.dropsToXrp(ammAssetIn(pool_asset1, lpt.value, auction_price, full_trading_fee).dp(0, BigNumber.ROUND_CEIL))
|
||||
} else {
|
||||
deposit_for_bid_asset1 = ammAssetIn(pool_asset1.value, lpt.value, auction_price, full_trading_fee).dp(15, BigNumber.ROUND_CEIL)
|
||||
}
|
||||
|
||||
if ( isAmmAsset2Xrp == true ) {
|
||||
deposit_for_bid_asset2 = xrpl.dropsToXrp(ammAssetIn(pool_asset2, lpt.value, auction_price, full_trading_fee).dp(0, BigNumber.ROUND_CEIL))
|
||||
} else {
|
||||
deposit_for_bid_asset2 = ammAssetIn(pool_asset2.value, lpt.value, auction_price, full_trading_fee).dp(15, BigNumber.ROUND_CEIL)
|
||||
}
|
||||
|
||||
if ( isAmmAsset1Xrp == true ) {
|
||||
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} XRP or ${deposit_for_bid_asset2} ${pool_asset2.currency} to get the required LP Tokens.`
|
||||
} else if ( isAmmAsset2Xrp == true ) {
|
||||
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} ${pool_asset1.currency} or ${deposit_for_bid_asset2} XRP to get the required LP Tokens.`
|
||||
} else {
|
||||
results += `\n\nMake a single-asset deposit to the AMM of ${deposit_for_bid_asset1} ${pool_asset1.currency} or ${deposit_for_bid_asset2} ${pool_asset2.currency} to get the required LP Tokens.`
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
results += `\n\n${error.message}`
|
||||
}
|
||||
|
||||
standbyResultField.value = results
|
||||
|
||||
client.disconnect()
|
||||
|
||||
}
|
||||
|
||||
// Bid on the auction slot
|
||||
|
||||
async function bidAuction() {
|
||||
let net = getNet()
|
||||
|
||||
const client = new xrpl.Client(net)
|
||||
results = `\n\nConnecting to ${getNet()} ...`
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\n\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
try {
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
|
||||
const asset1_currency = asset1CurrencyField.value
|
||||
const asset1_issuer = asset1IssuerField.value
|
||||
|
||||
const asset2_currency = asset2CurrencyField.value
|
||||
const asset2_issuer = asset2IssuerField.value
|
||||
const valueLPT = standbyLPField.value
|
||||
|
||||
// Look up AMM info
|
||||
|
||||
let asset1_info = null
|
||||
let asset2_info = null
|
||||
|
||||
if ( asset1_currency == 'XRP' ) {
|
||||
asset1_info = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
} else {
|
||||
asset1_info = {
|
||||
"currency": asset1_currency,
|
||||
"issuer": asset1_issuer
|
||||
}
|
||||
}
|
||||
|
||||
if ( asset2_currency == 'XRP' ) {
|
||||
asset2_info = {
|
||||
"currency": "XRP"
|
||||
}
|
||||
} else {
|
||||
asset2_info = {
|
||||
"currency": asset2_currency,
|
||||
"issuer": asset2_issuer
|
||||
}
|
||||
}
|
||||
|
||||
const amm_info = (await client.request({
|
||||
"command": "amm_info",
|
||||
"asset": asset1_info,
|
||||
"asset2": asset2_info
|
||||
}))
|
||||
|
||||
// Save relevant AMM info for calculations
|
||||
|
||||
const lpt = amm_info.result.amm.lp_token
|
||||
|
||||
results += '\n\nBidding on auction slot ...'
|
||||
standbyResultField.value = results
|
||||
|
||||
const bid_result = await client.submitAndWait({
|
||||
"TransactionType": "AMMBid",
|
||||
"Account": standby_wallet.address,
|
||||
"Asset": asset1_info,
|
||||
"Asset2": asset2_info,
|
||||
"BidMax": {
|
||||
"currency": lpt.currency,
|
||||
"issuer": lpt.issuer,
|
||||
"value": valueLPT
|
||||
},
|
||||
"BidMin": {
|
||||
"currency": lpt.currency,
|
||||
"issuer": lpt.issuer,
|
||||
"value": valueLPT
|
||||
} // So rounding doesn't leave dust amounts of LPT
|
||||
}, {autofill: true, wallet: standby_wallet})
|
||||
|
||||
if (bid_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += `\n\nTransaction succeeded.`
|
||||
checkAMM()
|
||||
} else {
|
||||
results += `\n\nError sending transaction: ${JSON.stringify(bid_result.result.meta.TransactionResult, null, 2)}`
|
||||
}
|
||||
} catch (error) {
|
||||
results += `\n\n${error.message}`
|
||||
}
|
||||
|
||||
standbyResultField.value = results
|
||||
|
||||
client.disconnect()
|
||||
|
||||
}
|
||||
|
||||
// Swap tokens with AMM
|
||||
async function swapTokens() {
|
||||
let net = getNet()
|
||||
|
||||
const client = new xrpl.Client(net)
|
||||
results = `\n\nConnecting to ${getNet()} ...`
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\n\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
try {
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
|
||||
const takerPaysCurrency = standbyTakerPaysCurrencyField.value
|
||||
const takerPaysIssuer = standbyTakerPaysIssuerField.value
|
||||
const takerPaysAmount = standbyTakerPaysAmountField.value
|
||||
|
||||
const takerGetsCurrency = standbyTakerGetsCurrencyField.value
|
||||
const takerGetsIssuer = standbyTakerGetsIssuerField.value
|
||||
const takerGetsAmount = standbyTakerGetsAmountField.value
|
||||
|
||||
let takerPays = null
|
||||
let takerGets = null
|
||||
|
||||
if ( takerPaysCurrency == 'XRP' ) {
|
||||
takerPays = xrpl.xrpToDrops(takerPaysAmount)
|
||||
} else {
|
||||
takerPays = {
|
||||
"currency": takerPaysCurrency,
|
||||
"issuer": takerPaysIssuer,
|
||||
"value": takerPaysAmount
|
||||
}
|
||||
}
|
||||
|
||||
if ( takerGetsCurrency == 'XRP' ) {
|
||||
takerGets = xrpl.xrpToDrops(takerGetsAmount)
|
||||
} else {
|
||||
takerGets = {
|
||||
"currency": takerGetsCurrency,
|
||||
"issuer": takerGetsIssuer,
|
||||
"value": takerGetsAmount
|
||||
}
|
||||
}
|
||||
|
||||
results += '\n\nSwapping tokens ...'
|
||||
standbyResultField.value = results
|
||||
|
||||
const offer_result = await client.submitAndWait({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"TakerPays": takerPays,
|
||||
"TakerGets": takerGets
|
||||
}, {autofill: true, wallet: standby_wallet})
|
||||
|
||||
if (offer_result.result.meta.TransactionResult == "tesSUCCESS") {
|
||||
results += `\n\nTransaction succeeded.`
|
||||
checkAMM()
|
||||
} else {
|
||||
results += `\n\nError sending transaction: ${JSON.stringify(offer_result.result.meta.TransactionResult, null, 2)}`
|
||||
}
|
||||
} catch (error) {
|
||||
results += `\n\n${error.message}`
|
||||
}
|
||||
|
||||
standbyResultField.value = results
|
||||
|
||||
client.disconnect()
|
||||
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/* Convert a trading fee to a value that can be multiplied
|
||||
* by a total to "subtract" the fee from the total.
|
||||
* @param tFee int {0, 1000}
|
||||
* such that 1 = 1/100,000 and 1000 = 1% fee
|
||||
* @returns BigNumber (1 - fee) as a decimal
|
||||
*/
|
||||
function feeMult(tFee) {
|
||||
return BigNumber(1).minus( feeDecimal(tFee) )
|
||||
}
|
||||
|
||||
/* Same as feeMult, but with half the trading fee. Single-asset deposits and
|
||||
* withdrawals use this because half of the deposit is treated as being
|
||||
* "swapped" for the other asset in the AMM's pool.
|
||||
* @param tFee int {0, 1000}
|
||||
* such that 1 = 1/100,000 and 1000 = 1% fee
|
||||
* @returns BigNumber (1 - (fee/2)) as a decimal
|
||||
*/
|
||||
function feeMultHalf(tFee) {
|
||||
return BigNumber(1).minus( feeDecimal(tFee).dividedBy(2) )
|
||||
}
|
||||
|
||||
/* Convert a trading fee to a decimal BigNumber value,
|
||||
* for example 1000 becomes 0.01
|
||||
* @param tFee int {0, 1000}
|
||||
* such that 1 = 1/100,000 and 1000 = 1% fee
|
||||
* @returns BigNumber(fee) as a decimal
|
||||
*/
|
||||
function feeDecimal(tFee) {
|
||||
const AUCTION_SLOT_FEE_SCALE_FACTOR = 100000
|
||||
return BigNumber(tFee).dividedBy(AUCTION_SLOT_FEE_SCALE_FACTOR)
|
||||
}
|
||||
|
||||
/* Implement the AMM SwapOut formula, as defined in XLS-30 section 2.4 AMM
|
||||
* Swap, formula 10. The asset weights WA/WB are currently always 1/1 so
|
||||
* they're canceled out.
|
||||
* C++ source: https://github.com/XRPLF/rippled/blob/2d1854f354ff8bb2b5671fd51252c5acd837c433/src/ripple/app/misc/AMMHelpers.h#L253-L258
|
||||
* @param asset_out_bn BigNumber - The target amount to receive from the AMM.
|
||||
* @param pool_in_bn BigNumber - The amount of the input asset in the AMM's
|
||||
* pool before the swap.
|
||||
* @param pool_out_bn BigNumber - The amount of the output asset in the AMM's
|
||||
* pool before the swap.
|
||||
* @param trading_fee int - The trading fee as an integer {0, 1000} where 1000
|
||||
* represents a 1% fee.
|
||||
* @returns BigNumber - The amount of the input asset that must be swapped in
|
||||
* to receive the target output amount. Unrounded, because
|
||||
* the number of decimals depends on if this is drops of
|
||||
* XRP or a decimal amount of a token; since this is a
|
||||
* theoretical input to the pool, it should be rounded
|
||||
* up (ceiling) to preserve the pool's constant product.
|
||||
*/
|
||||
function swapOut(asset_out_bn, pool_in_bn, pool_out_bn, trading_fee) {
|
||||
return ( ( pool_in_bn.multipliedBy(pool_out_bn) ).dividedBy(
|
||||
pool_out_bn.minus(asset_out_bn)
|
||||
).minus(pool_in_bn)
|
||||
).dividedBy(feeMult(trading_fee))
|
||||
}
|
||||
|
||||
/* Compute the quadratic formula. Helper function for ammAssetIn.
|
||||
* Params and return value are BigNumber instances.
|
||||
*/
|
||||
function solveQuadraticEq(a,b,c) {
|
||||
const b2minus4ac = b.multipliedBy(b).minus(
|
||||
a.multipliedBy(c).multipliedBy(4)
|
||||
)
|
||||
return ( b.negated().plus(b2minus4ac.sqrt()) ).dividedBy(a.multipliedBy(2))
|
||||
}
|
||||
|
||||
/* Implement the AMM single-asset deposit formula to calculate how much to
|
||||
* put in so that you receive a specific number of LP Tokens back.
|
||||
* C++ source: https://github.com/XRPLF/rippled/blob/2d1854f354ff8bb2b5671fd51252c5acd837c433/src/ripple/app/misc/impl/AMMHelpers.cpp#L55-L83
|
||||
* @param pool_in string - Quantity of input asset the pool already has
|
||||
* @param lpt_balance string - Quantity of LP Tokens already issued by the AMM
|
||||
* @param desired_lpt string - Quantity of new LP Tokens you want to receive
|
||||
* @param trading_fee int - The trading fee as an integer {0,1000} where 1000
|
||||
* represents a 1% fee.
|
||||
*/
|
||||
function ammAssetIn(pool_in, lpt_balance, desired_lpt, trading_fee) {
|
||||
// convert inputs to BigNumber
|
||||
const lpTokens = BigNumber(desired_lpt)
|
||||
const lptAMMBalance = BigNumber(lpt_balance)
|
||||
const asset1Balance = BigNumber(pool_in)
|
||||
|
||||
const f1 = feeMult(trading_fee)
|
||||
const f2 = feeMultHalf(trading_fee).dividedBy(f1)
|
||||
const t1 = lpTokens.dividedBy(lptAMMBalance)
|
||||
const t2 = t1.plus(1)
|
||||
const d = f2.minus( t1.dividedBy(t2) )
|
||||
const a = BigNumber(1).dividedBy( t2.multipliedBy(t2))
|
||||
const b = BigNumber(2).multipliedBy(d).dividedBy(t2).minus(
|
||||
BigNumber(1).dividedBy(f1)
|
||||
)
|
||||
const c = d.multipliedBy(d).minus( f2.multipliedBy(f2) )
|
||||
return asset1Balance.multipliedBy(solveQuadraticEq(a,b,c))
|
||||
}
|
||||
|
||||
/* Calculate how much to deposit, in terms of LP Tokens out, to be able to win
|
||||
* the auction slot. This is based on the slot pricing algorithm defined in
|
||||
* XLS-30 section 4.1.1, but factors in the increase in the minimum bid as a
|
||||
* result of having new LP Tokens issued to you from your deposit.
|
||||
*/
|
||||
function auctionDeposit(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
const tfee_decimal = feeDecimal(trading_fee)
|
||||
const lptokens = BigNumber(lpt_balance)
|
||||
const b = BigNumber(old_bid)
|
||||
let outbidAmount = BigNumber(0) // This is the case if time_interval >= 20
|
||||
if (time_interval == 0) {
|
||||
outbidAmount = b.multipliedBy("1.05")
|
||||
} else if (time_interval <= 19) {
|
||||
const t60 = BigNumber(time_interval).multipliedBy("0.05").exponentiatedBy(60)
|
||||
outbidAmount = b.multipliedBy("1.05").multipliedBy(BigNumber(1).minus(t60))
|
||||
}
|
||||
|
||||
const new_bid = lptokens.plus(outbidAmount).dividedBy(
|
||||
BigNumber(25).dividedBy(tfee_decimal).minus(1)
|
||||
).plus(outbidAmount)
|
||||
|
||||
// Significant digits for the deposit are limited by total LPTokens issued
|
||||
// so we calculate lptokens + deposit - lptokens to determine where the
|
||||
// rounding occurs. We use ceiling/floor to make sure the amount we receive
|
||||
// after rounding is still enough to win the auction slot.
|
||||
const rounded_bid = new_bid.plus(lptokens).precision(15, BigNumber.CEILING
|
||||
).minus(lptokens).precision(15, BigNumber.FLOOR)
|
||||
return rounded_bid
|
||||
}
|
||||
|
||||
/* Calculate the necessary bid to win the AMM Auction slot, per the pricing
|
||||
* algorithm defined in XLS-30 section 4.1.1, if you already hold LP Tokens.
|
||||
*
|
||||
* NOT USED in the Auction Slot tutorial, which assumes the user does not hold
|
||||
* any LP Tokens.
|
||||
*
|
||||
* @returns BigNumber - the minimum amount of LP tokens to win the auction slot
|
||||
*/
|
||||
function auctionPrice(old_bid, time_interval, trading_fee, lpt_balance) {
|
||||
const tfee_decimal = feeDecimal(trading_fee)
|
||||
const lptokens = BigNumber(lpt_balance)
|
||||
const min_bid = lptokens.multipliedBy(tfee_decimal).dividedBy(25)
|
||||
const b = BigNumber(old_bid)
|
||||
let new_bid = min_bid
|
||||
|
||||
if (time_interval == 0) {
|
||||
new_bid = b.multipliedBy("1.05").plus(min_bid)
|
||||
} else if (time_interval <= 19) {
|
||||
const t60 = BigNumber(time_interval).multipliedBy("0.05"
|
||||
).exponentiatedBy(60)
|
||||
new_bid = b.multipliedBy("1.05").multipliedBy(
|
||||
BigNumber(1).minus(t60)
|
||||
).plus(min_bid)
|
||||
}
|
||||
|
||||
const rounded_bid = new_bid.plus(lptokens).precision(15, BigNumber.CEILING
|
||||
).minus(lptokens).precision(15, BigNumber.FLOOR)
|
||||
return rounded_bid
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
/***********************************
|
||||
*********** Create Offer **********
|
||||
**********************************/
|
||||
|
||||
async function createOffer() {
|
||||
let takerGets = ''
|
||||
let takerPays = ''
|
||||
let net = getNet()
|
||||
let results = 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected. Getting wallets.\n"
|
||||
standbyResultField.value = results
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
results += standbyNameField.value + " account address: " + standby_wallet.address + "\n"
|
||||
standbyResultField.value = results
|
||||
|
||||
|
||||
if (standbyTakerGetsCurrencyField.value == 'XRP') {
|
||||
takerGets = standbyTakerGetsValueField.value
|
||||
} else {
|
||||
takerGetsString = '{"currency": "' + standbyTakerGetsCurrencyField.value +'",\n' +
|
||||
'"issuer": "' + standbyTakerGetsIssuerField.value + '",\n' +
|
||||
'"value": "' + standbyTakerGetsValueField.value + '"}'
|
||||
takerGets = JSON.parse(takerGetsString)
|
||||
}
|
||||
|
||||
if (standbyTakerPaysCurrencyField.value == 'XRP') {
|
||||
takerPays = standbyTakerPaysValueField.value
|
||||
} else {
|
||||
takerPaysString = '{"currency": "' + standbyTakerPaysCurrencyField.value + '",\n' +
|
||||
'"issuer": "' + standbyTakerPaysIssuerField.value + '",\n' +
|
||||
'"value": "' + standbyTakerPaysValueField.value + '"}'
|
||||
takerPays = JSON.parse(takerPaysString)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------- Prepare transaction
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"TakerGets": takerGets,
|
||||
"TakerPays": takerPays
|
||||
})
|
||||
// ------------------------------------------------- Sign prepared instructions
|
||||
const signed = standby_wallet.sign(prepared)
|
||||
results += "\nSubmitting transaction...."
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
standbyResultField.value = results
|
||||
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
getOffers()
|
||||
client.disconnect()
|
||||
} // End of createOffer()
|
||||
|
||||
/***********************************
|
||||
************ Get Offers ***********
|
||||
**********************************/
|
||||
|
||||
async function getOffers() {
|
||||
let net = getNet()
|
||||
let results = 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
results += "Connected.\n"
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
results += standbyNameField.value + " account: " + standby_wallet.address
|
||||
// -------------------------------------------------------- Prepare request
|
||||
|
||||
results += '\n\n*** Offers ***\n'
|
||||
let offers
|
||||
try {
|
||||
const offers = await client.request({
|
||||
method: "account_offers",
|
||||
account: standby_wallet.address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
results += JSON.stringify(offers,null,2)
|
||||
} catch (err) {
|
||||
results += err
|
||||
}
|
||||
standbyResultField.value = results
|
||||
client.disconnect()
|
||||
}// End of getOffers()
|
||||
|
||||
/***********************************
|
||||
*********** Cancel Offer **********
|
||||
**********************************/
|
||||
|
||||
async function cancelOffer() {
|
||||
let results = "Connecting to the selected ledger.\n"
|
||||
standbyResultField.value = results
|
||||
let net = getNet()
|
||||
results += 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected.\n"
|
||||
standbyResultField.value = results
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
results += "standby_wallet.address: = " + standby_wallet.address
|
||||
standbyResultField.value = results
|
||||
|
||||
// -------------------------------------------------------- Prepare transaction
|
||||
/* OfferSequence is the Seq value when you getOffers. */
|
||||
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCancel",
|
||||
"Account": standby_wallet.address,
|
||||
"OfferSequence": parseInt(standbyOfferSequenceField.value)
|
||||
})
|
||||
|
||||
// ------------------------------------------------- Sign prepared instructions
|
||||
const signed = standby_wallet.sign(prepared)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
|
||||
results += "\nBalance changes: \n" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
standbyResultField.value = results
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
client.disconnect()
|
||||
} // End of cancelOffer()
|
||||
|
||||
/*********************************************
|
||||
************* Reciprocal Functions **********
|
||||
********************************************/
|
||||
|
||||
/***********************************
|
||||
********* OP Create Offer *********
|
||||
**********************************/
|
||||
|
||||
async function oPcreateOffer() {
|
||||
let takerGets = ''
|
||||
let takerPays = ''
|
||||
|
||||
operationalResultField.value = ''
|
||||
let net = getNet()
|
||||
let results = 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected. Getting wallets.\n"
|
||||
operationalResultField.value = results
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
results += operationalNameField.value + " account address: " + operational_wallet.address + "\n"
|
||||
operationalResultField.value = results
|
||||
|
||||
|
||||
if (operationalTakerGetsCurrencyField.value == 'XRP') {
|
||||
takerGets = operationalTakerGetsValueField.value
|
||||
} else {
|
||||
takerGetsString = '{"currency": "' + operationalTakerGetsCurrencyField.value +'",\n' +
|
||||
'"issuer": "' + operationalTakerGetsIssuerField.value + '",\n' +
|
||||
'"value": "' + operationalTakerGetsValueField.value + '"}'
|
||||
takerGets = JSON.parse(takerGetsString)
|
||||
}
|
||||
|
||||
if (operationalTakerPaysCurrencyField.value == 'XRP') {
|
||||
takerPays = operationalTakerPaysValueField.value
|
||||
} else {
|
||||
takerPaysString = '{"currency": "' + operationalTakerPaysCurrencyField.value + '",\n' +
|
||||
'"issuer": "' + operationalTakerPaysIssuerField.value + '",\n' +
|
||||
'"value": "' + operationalTakerPaysValueField.value + '"}'
|
||||
takerPays = JSON.parse(takerPaysString)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------- Prepare transaction
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCreate",
|
||||
"Account": operational_wallet.address,
|
||||
"TakerGets": takerGets,
|
||||
"TakerPays": takerPays
|
||||
})
|
||||
// ------------------------------------------------- Sign prepared instructions
|
||||
const signed = operational_wallet.sign(prepared)
|
||||
results += "\nSubmitting transaction...."
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
|
||||
results += "\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
operationalResultField.value = results
|
||||
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
getOffers()
|
||||
client.disconnect()
|
||||
} // End of oPcreateOffer()
|
||||
|
||||
/***********************************
|
||||
********** OP Get Offers ***********
|
||||
***********************************/
|
||||
|
||||
async function oPgetOffers() {
|
||||
let results = "Connecting to the selected ledger.\n"
|
||||
operationalResultField.value = results
|
||||
let net = getNet()
|
||||
results = 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected.\n"
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
results += operationalNameField.value + " account: " + operational_wallet.address
|
||||
operationalResultField.value = results
|
||||
|
||||
// -------------------------------------------------------- Prepare request
|
||||
|
||||
results += '\n\n*** Offers ***\n'
|
||||
let offers
|
||||
try {
|
||||
const offers = await client.request({
|
||||
method: "account_offers",
|
||||
account: operational_wallet.address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
results += JSON.stringify(offers,null,2)
|
||||
} catch (err) {
|
||||
results += err
|
||||
}
|
||||
operationalResultField.value = results
|
||||
client.disconnect()
|
||||
}// End of oPgetOffers()
|
||||
|
||||
/************************************
|
||||
********** Op Cancel Offer *********
|
||||
***********************************/
|
||||
|
||||
async function oPcancelOffer() {
|
||||
let net = getNet()
|
||||
let results = 'Connecting to ' + net + '....\n'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected.\n"
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
results += "wallet.address: = " + operational_wallet.address
|
||||
operationalResultField.value = results
|
||||
|
||||
// -------------------------------------------------------- Prepare transaction
|
||||
|
||||
/* OfferSequence is the Seq value when you getOffers. */
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "OfferCancel",
|
||||
"Account": operational_wallet.address,
|
||||
"OfferSequence": parseInt(operationalOfferSequenceField.value)
|
||||
})
|
||||
|
||||
// ------------------------------------------------- Sign prepared instructions
|
||||
const signed = operational_wallet.sign(prepared)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
|
||||
results += "\nBalance changes: \n" + tx.result + "\n" +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
operationalResultField.value = results
|
||||
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
client.disconnect()
|
||||
} // End of oPcancelOffer()
|
||||
@@ -1,240 +0,0 @@
|
||||
// *******************************************************
|
||||
// ************* Add Seconds to Current Date *************
|
||||
// *******************************************************
|
||||
|
||||
function addSeconds(numOfSeconds, date = new Date()) {
|
||||
date.setSeconds(date.getSeconds() + numOfSeconds);
|
||||
date = Math.floor(date / 1000)
|
||||
date = date - 946684800
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
// *******************************************************
|
||||
// ***************** Create Time Escrow ******************
|
||||
// *******************************************************
|
||||
|
||||
async function createTimeEscrow() {
|
||||
|
||||
//-------------------------------------------- Prepare Finish and Cancel Dates
|
||||
|
||||
let escrow_finish_date = new Date()
|
||||
let escrow_cancel_date = new Date()
|
||||
escrow_finish_date = addSeconds(parseInt(standbyEscrowFinishDateField.value))
|
||||
escrow_cancel_date = addSeconds(parseInt(standbyEscrowCancelDateField.value))
|
||||
|
||||
//------------------------------------------------------Connect to the Ledger
|
||||
results = "Connecting to the selected ledger.\n"
|
||||
standbyResultField.value = results
|
||||
let net = getNet()
|
||||
results = "Connecting to " + net + "....\n"
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected. Creating time-based escrow.\n"
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
const sendAmount = standbyAmountField.value
|
||||
|
||||
results += "\nstandby_wallet.address: = " + standby_wallet.address
|
||||
standbyResultField.value = results
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
|
||||
const escrowTx = await client.autofill({
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": xrpl.xrpToDrops(sendAmount),
|
||||
"Destination": standbyDestinationField.value,
|
||||
"FinishAfter": escrow_finish_date,
|
||||
"CancelAfter": escrow_cancel_date
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = standby_wallet.sign(escrowTx)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nSequence Number (Save!): " + JSON.stringify(tx.result.Sequence)
|
||||
results += "\n\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
standbyResultField.value = results
|
||||
|
||||
// ----------------------------------------------Disconnect from the XRP Ledger
|
||||
client.disconnect()
|
||||
|
||||
} // End of createTimeEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ***************** Finish Time Escrow ******************
|
||||
// *******************************************************
|
||||
|
||||
async function finishEscrow() {
|
||||
|
||||
results = "Connecting to the selected ledger.\n"
|
||||
operationalResultField.value = results
|
||||
let net = getNet()
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "\nConnected. Finishing escrow.\n"
|
||||
operationalResultField.value = results
|
||||
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const sendAmount = operationalAmountField.value
|
||||
|
||||
results += "\noperational_wallet.address: = " + operational_wallet.address
|
||||
operationalResultField.value = results
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
// Note that the destination is hard coded.
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": operationalAccountField.value,
|
||||
"Owner": standbyAccountField.value,
|
||||
"OfferSequence": parseInt(operationalEscrowSequenceField.value)
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = operational_wallet.sign(prepared)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
operationalResultField.value = results
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
|
||||
client.disconnect()
|
||||
} // End of finishEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ************** Get Standby Escrows ********************
|
||||
// *******************************************************
|
||||
|
||||
async function getStandbyEscrows() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
results= "\nGetting standby account escrows...\n"
|
||||
const escrow_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": standbyAccountField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "escrow"
|
||||
})
|
||||
results += JSON.stringify(escrow_objects.result, null, 2)
|
||||
standbyResultField.value = results
|
||||
|
||||
client.disconnect()
|
||||
} // End of getStandbyEscrows()
|
||||
|
||||
// *******************************************************
|
||||
// ***************** Get Op Escrows **********************
|
||||
// *******************************************************
|
||||
|
||||
async function getOperationalEscrows() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
results= "\nGetting operational account escrows...\n"
|
||||
const escrow_objects = await client.request({
|
||||
"id": 5,
|
||||
"command": "account_objects",
|
||||
"account": operationalAccountField.value,
|
||||
"ledger_index": "validated",
|
||||
"type": "escrow"
|
||||
})
|
||||
results += JSON.stringify(escrow_objects.result, null, 2)
|
||||
operationalResultField.value = results
|
||||
client.disconnect()
|
||||
|
||||
} // End of getOperationalEscrows()
|
||||
|
||||
// *******************************************************
|
||||
// ************** Get Transaction Info *******************
|
||||
// *******************************************************
|
||||
|
||||
async function getTransaction() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
operationalResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
operationalResultField.value = results
|
||||
|
||||
results= "\nGetting transaction information...\n"
|
||||
const tx_info = await client.request({
|
||||
"id": 1,
|
||||
"command": "tx",
|
||||
"transaction": operationalTransactionField.value,
|
||||
})
|
||||
results += JSON.stringify(tx_info.result, null, 2)
|
||||
operationalResultField.value = results
|
||||
client.disconnect()
|
||||
|
||||
} // End of getTransaction()
|
||||
|
||||
// *******************************************************
|
||||
// ****************** Cancel Escrow **********************
|
||||
// *******************************************************
|
||||
|
||||
async function cancelEscrow() {
|
||||
let net = getNet()
|
||||
const client = new xrpl.Client(net)
|
||||
results = 'Connecting to ' + getNet() + '....'
|
||||
standbyResultField.value = results
|
||||
|
||||
await client.connect()
|
||||
results += '\nConnected.'
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowCancel",
|
||||
"Account": standby_wallet.address,
|
||||
"Owner": standbyAccountField.value,
|
||||
"OfferSequence": parseInt(standbyEscrowSequenceNumberField.value)
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = standby_wallet.sign(prepared)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
|
||||
results += "\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
standbyResultField.value = results
|
||||
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
|
||||
client.disconnect()
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// *******************************************************
|
||||
// ************* Create Conditional Escrow ***************
|
||||
// *******************************************************
|
||||
|
||||
async function createConditionalEscrow() {
|
||||
|
||||
//------------------------------------------------------Connect to the Ledger
|
||||
results = "Connecting to the selected ledger.\n"
|
||||
standbyResultField.value = results
|
||||
let net = getNet()
|
||||
results = "Connecting to " + net + "....\n"
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
|
||||
results += "Connected. Creating conditional escrow.\n"
|
||||
standbyResultField.value = results
|
||||
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
const sendAmount = standbyAmountField.value
|
||||
|
||||
results += "\nstandby_wallet.address: = " + standby_wallet.address
|
||||
standbyResultField.value = results
|
||||
|
||||
let escrow_cancel_date = new Date()
|
||||
escrow_cancel_date = addSeconds(parseInt(standbyEscrowCancelDateField.value))
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
|
||||
const escrowTx = await client.autofill({
|
||||
"TransactionType": "EscrowCreate",
|
||||
"Account": standby_wallet.address,
|
||||
"Amount": xrpl.xrpToDrops(sendAmount),
|
||||
"Destination": standbyDestinationField.value,
|
||||
"CancelAfter": escrow_cancel_date,
|
||||
"Condition": standbyEscrowConditionField.value
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = standby_wallet.sign(escrowTx)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nSequence Number (Save!): " + JSON.stringify(tx.result.Sequence)
|
||||
results += "\n\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
standbyResultField.value = results
|
||||
|
||||
// ----------------------------------------------Disconnect from the XRP Ledger
|
||||
client.disconnect()
|
||||
|
||||
} // End of createTimeEscrow()
|
||||
|
||||
// *******************************************************
|
||||
// ************** Finish Conditional Escrow **************
|
||||
// *******************************************************
|
||||
|
||||
async function finishConditionalEscrow() {
|
||||
results = "Connecting to the selected ledger.\n"
|
||||
operationalResultField.value = results
|
||||
let net = getNet()
|
||||
results += 'Connecting to ' + getNet() + '....'
|
||||
const client = new xrpl.Client(net)
|
||||
await client.connect()
|
||||
results += "\nConnected. Finishing escrow.\n"
|
||||
operationalResultField.value = results
|
||||
|
||||
const operational_wallet = xrpl.Wallet.fromSeed(operationalSeedField.value)
|
||||
const standby_wallet = xrpl.Wallet.fromSeed(standbySeedField.value)
|
||||
const sendAmount = operationalAmountField.value
|
||||
|
||||
results += "\noperational_wallet.address: = " + operational_wallet.address
|
||||
operationalResultField.value = results
|
||||
|
||||
// ------------------------------------------------------- Prepare transaction
|
||||
|
||||
const prepared = await client.autofill({
|
||||
"TransactionType": "EscrowFinish",
|
||||
"Account": operationalAccountField.value,
|
||||
"Owner": standbyAccountField.value,
|
||||
"OfferSequence": parseInt(operationalEscrowSequenceField.value),
|
||||
"Condition": standbyEscrowConditionField.value,
|
||||
"Fulfillment": operationalFulfillmentField.value
|
||||
})
|
||||
|
||||
// ------------------------------------------------ Sign prepared instructions
|
||||
const signed = operational_wallet.sign(prepared)
|
||||
|
||||
// -------------------------------------------------------- Submit signed blob
|
||||
const tx = await client.submitAndWait(signed.tx_blob)
|
||||
results += "\nBalance changes: " +
|
||||
JSON.stringify(xrpl.getBalanceChanges(tx.result.meta), null, 2)
|
||||
operationalResultField.value = results
|
||||
standbyBalanceField.value = (await client.getXrpBalance(standby_wallet.address))
|
||||
operationalBalanceField.value = (await client.getXrpBalance(operational_wallet.address))
|
||||
|
||||
client.disconnect()
|
||||
|
||||
} // End of finishEscrow()``
|
||||
@@ -1,11 +0,0 @@
|
||||
from os import urandom
|
||||
from cryptoconditions import PreimageSha256
|
||||
|
||||
secret = urandom(32)
|
||||
|
||||
fulfillment = PreimageSha256(preimage=secret)
|
||||
|
||||
print("Condition", fulfillment.condition_binary.hex().upper())
|
||||
|
||||
# Keep secret until you want to finish the escrow
|
||||
print("Fulfillment", fulfillment.serialize_binary().hex().upper())
|
||||
@@ -1,318 +0,0 @@
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod2 import get_balance
|
||||
from mod10 import send_check, cash_check, cancel_check, get_checks
|
||||
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
|
||||
## Mod 10 Handlers
|
||||
|
||||
def standby_send_check():
|
||||
results=send_check(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_issuer.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cash_check():
|
||||
results=cash_check(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_check_id.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_issuer.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cancel_check():
|
||||
results=cancel_check(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_check_id.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_get_checks():
|
||||
results=get_checks(
|
||||
ent_standby_account.get(),
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_get_balance():
|
||||
results=get_balance(
|
||||
ent_standby_seed.get(),
|
||||
ent_operational_seed.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_send_check():
|
||||
results=send_check(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_issuer.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_cash_check():
|
||||
results=cash_check(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_check_id.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_issuer.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_cancel_check():
|
||||
results=cancel_check(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_check_id.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_checks():
|
||||
results=get_checks(
|
||||
ent_operational_account.get(),
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_balance():
|
||||
results=get_balance(
|
||||
ent_operational_seed.get(),
|
||||
ent_standby_seed.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
## Mod 8 Handlers
|
||||
|
||||
def operational_get_transaction():
|
||||
results=get_transaction(ent_operational_account.get(),
|
||||
ent_operational_look_up.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 1 Handlers
|
||||
|
||||
def get_standby_account():
|
||||
new_wallet=get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo=get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response=send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet=get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo=get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response=send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
# Create a new window with the title "Conditional Escrow Example"
|
||||
window=tk.Tk()
|
||||
window.title("Check Example")
|
||||
|
||||
# Form frame
|
||||
frm_form=tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed=tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account=tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance=tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount=tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination=tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_issuer=tk.Label(master=frm_form, text="Issuer")
|
||||
ent_standby_issuer=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_check_id=tk.Label(master=frm_form, text="Check ID")
|
||||
ent_standby_check_id=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_currency=tk.Label(master=frm_form, text="Currency")
|
||||
ent_standby_currency=tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results=tk.Label(master=frm_form, text="Results")
|
||||
text_standby_results=tk.Text(master=frm_form, height=20, width=65)
|
||||
|
||||
# Place fields in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="e")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standby_balance.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=3, column=1)
|
||||
lbl_standy_amount.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=4, column=1)
|
||||
lbl_standby_destination.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=5, column=1)
|
||||
lbl_standby_issuer.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_issuer.grid(row=6, column=1)
|
||||
lbl_standby_check_id.grid(row=7, column=0, sticky="e")
|
||||
ent_standby_check_id.grid(row=7, column=1)
|
||||
lbl_standby_currency.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_currency.grid(row=8, column=1)
|
||||
lbl_standby_results.grid(row=9, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=9, column=1, sticky="nw")
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
|
||||
lbl_operational_seed=tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account=tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance=tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount=tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination=tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_issuer=tk.Label(master=frm_form, text="Issuer")
|
||||
ent_operational_issuer=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_check_id=tk.Label(master=frm_form, text="Check ID")
|
||||
ent_operational_check_id=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_currency=tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results=tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results=tk.Text(master=frm_form, height=20, width=65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_issuer.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_issuer.grid(row=6, column=5, sticky="w")
|
||||
lbl_operational_check_id.grid(row=7, column=4, sticky="e")
|
||||
ent_operational_check_id.grid(row=7, column=5, sticky="w")
|
||||
lbl_operational_currency.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_currency.grid(row=8, column=5)
|
||||
lbl_operational_results.grid(row=9, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=9, column=5, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Get Standby Account Buttons
|
||||
btn_get_standby_account=tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command=get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky="nsew")
|
||||
btn_get_standby_account_info=tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command=get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky="nsew")
|
||||
btn_standby_send_xrp=tk.Button(master=frm_form, text="Send XRP >",
|
||||
command=standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column=2, sticky="nsew")
|
||||
btn_standby_send_check=tk.Button(master=frm_form, text="Send Check",
|
||||
command=standby_send_check)
|
||||
btn_standby_send_check.grid(row=4, column=2, sticky="nsew")
|
||||
btn_standby_get_checks=tk.Button(master=frm_form, text="Get Checks",
|
||||
command=standby_get_checks)
|
||||
btn_standby_get_checks.grid(row=5, column=2, sticky="nsew")
|
||||
btn_standby_cash_check=tk.Button(master=frm_form, text="Cash Check",
|
||||
command=standby_cash_check)
|
||||
btn_standby_cash_check.grid(row=6, column=2, sticky="nsew")
|
||||
btn_standby_cancel_check=tk.Button(master=frm_form, text="Cancel Check",
|
||||
command=standby_cancel_check)
|
||||
btn_standby_cancel_check.grid(row=7, column=2, sticky="nsew")
|
||||
btn_standby_get_balances=tk.Button(master=frm_form, text="Get Balances",
|
||||
command=standby_get_balance)
|
||||
btn_standby_get_balances.grid(row=8, column=2, sticky="nsew")
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account=tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command=get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky="nsew")
|
||||
btn_get_op_account_info=tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command=get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky="nsew")
|
||||
btn_op_send_xrp=tk.Button(master=frm_form, text="< Send XRP",
|
||||
command=operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column=3, sticky="nsew")
|
||||
btn_op_send_check=tk.Button(master=frm_form, text="Send Check",
|
||||
command=operational_send_check)
|
||||
btn_op_send_check.grid(row=4, column=3, sticky="nsew")
|
||||
btn_op_get_checks=tk.Button(master=frm_form, text="Get Checks",
|
||||
command=operational_get_checks)
|
||||
btn_op_get_checks.grid(row=5, column=3, sticky="nsew")
|
||||
btn_op_cash_check=tk.Button(master=frm_form, text="Cash Check",
|
||||
command=operational_cash_check)
|
||||
btn_op_cash_check.grid(row=6, column=3, sticky="nsew")
|
||||
btn_op_cancel_check=tk.Button(master=frm_form, text="Cancel Check",
|
||||
command=operational_cancel_check)
|
||||
btn_op_cancel_check.grid(row=7, column=3, sticky="nsew")
|
||||
btn_op_get_balances=tk.Button(master=frm_form, text="Get Balances",
|
||||
command=operational_get_balance)
|
||||
btn_op_get_balances.grid(row=8, column=3, sticky="nsew")
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
@@ -1,251 +0,0 @@
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod8 import create_time_escrow, finish_time_escrow, get_escrows, cancel_time_escrow, get_transaction
|
||||
|
||||
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
|
||||
## Mod 8 Handlers
|
||||
|
||||
def standby_create_time_escrow():
|
||||
results = create_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_escrow_finish.get(),
|
||||
ent_standby_escrow_cancel.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_finish_time_escrow():
|
||||
results = finish_time_escrow(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_escrow_owner.get(),
|
||||
ent_operational_sequence_number.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_escrows():
|
||||
results = get_escrows(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cancel_time_escrow():
|
||||
results = cancel_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_escrow_owner.get(),
|
||||
ent_standby_escrow_sequence_number.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_transaction():
|
||||
results = get_transaction(ent_operational_account.get(),
|
||||
ent_operational_look_up.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 1 Handlers
|
||||
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
# Create a new window with the title "Time-based Escrow Example"
|
||||
window = tk.Tk()
|
||||
window.title("Time-based Escrow Example")
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
|
||||
lbl_standby_escrow_finish = tk.Label(master=frm_form, text="Escrow Finish (seconds)")
|
||||
ent_standby_escrow_finish = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
|
||||
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results = tk.Label(master=frm_form, text="Results")
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place fields in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="e")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_escrow_finish.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_escrow_finish.grid(row=6, column=1)
|
||||
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
|
||||
ent_standby_escrow_cancel.grid(row=7, column=1)
|
||||
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_escrow_sequence_number.grid(row=8, column=1)
|
||||
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_escrow_owner.grid(row=9, column=1)
|
||||
lbl_standby_results.grid(row=10, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=10, column=1, sticky="nw")
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
|
||||
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_sequence_number.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_sequence_number.grid(row=6, column=5, sticky="w")
|
||||
lbl_operational_escrow_owner.grid(row=7, column=4, sticky="e")
|
||||
ent_operational_escrow_owner.grid(row=7, column=5, sticky="w")
|
||||
lbl_operational_look_up.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_look_up.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=10, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=10, column=5, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Get Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
|
||||
|
||||
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Time-based Escrow",
|
||||
command = standby_create_time_escrow)
|
||||
btn_standby_create_escrow.grid(row = 4, column = 2, sticky="nsew")
|
||||
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Time-based Escrow",
|
||||
command = standby_cancel_time_escrow)
|
||||
btn_standby_cancel_escrow.grid(row=5,column = 2, sticky="nsew")
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
|
||||
command = operational_finish_time_escrow)
|
||||
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Get Escrows",
|
||||
command = operational_get_escrows)
|
||||
btn_op_finish_escrow.grid(row = 5, column = 3, sticky="nsew")
|
||||
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
|
||||
command = operational_get_transaction)
|
||||
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
|
||||
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
@@ -1,269 +0,0 @@
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod8 import get_escrows, cancel_time_escrow, get_transaction
|
||||
from mod9 import create_conditional_escrow, finish_conditional_escrow, generate_condition
|
||||
|
||||
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
|
||||
## Mod 9 Handlers
|
||||
|
||||
def get_condition():
|
||||
results = generate_condition()
|
||||
ent_standby_escrow_condition.delete(0, tk.END)
|
||||
ent_standby_escrow_condition.insert(0, results[0])
|
||||
ent_operational_escrow_fulfillment.delete(0, tk.END)
|
||||
ent_operational_escrow_fulfillment.insert(0, results[1])
|
||||
|
||||
def standby_create_conditional_escrow():
|
||||
results = create_conditional_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_escrow_cancel.get(),
|
||||
ent_standby_escrow_condition.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_finish_conditional_escrow():
|
||||
results = finish_conditional_escrow(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_escrow_owner.get(),
|
||||
ent_operational_sequence_number.get(),
|
||||
ent_standby_escrow_condition.get(),
|
||||
ent_operational_escrow_fulfillment.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
## Mod 8 Handlers
|
||||
|
||||
def operational_get_escrows():
|
||||
results = get_escrows(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cancel_time_escrow():
|
||||
results = cancel_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_escrow_owner.get(),
|
||||
ent_standby_escrow_sequence_number.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_transaction():
|
||||
results = get_transaction(ent_operational_account.get(),
|
||||
ent_operational_look_up.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 1 Handlers
|
||||
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
# Create a new window with the title "Conditional Escrow Example"
|
||||
window = tk.Tk()
|
||||
window.title("Conditional Escrow Example")
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_condition = tk.Label(master=frm_form, text="Escrow Condition")
|
||||
ent_standby_escrow_condition = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
|
||||
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results = tk.Label(master=frm_form, text="Results")
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place fields in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="e")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_escrow_condition.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_escrow_condition.grid(row=6, column=1)
|
||||
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
|
||||
ent_standby_escrow_cancel.grid(row=7, column=1)
|
||||
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_escrow_sequence_number.grid(row=8, column=1)
|
||||
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_escrow_owner.grid(row=9, column=1)
|
||||
lbl_standby_results.grid(row=10, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=10, column=1, sticky="nw")
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_escrow_fulfillment = tk.Label(master=frm_form, text="Escrow Fulfillment")
|
||||
ent_operational_escrow_fulfillment = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
|
||||
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_escrow_fulfillment.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_escrow_fulfillment.grid(row=6, column=5, sticky="w")
|
||||
lbl_operational_sequence_number.grid(row=7, column=4, sticky="e")
|
||||
ent_operational_sequence_number.grid(row=7, column=5, sticky="w")
|
||||
lbl_operational_escrow_owner.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_escrow_owner.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_look_up.grid(row=9, column=4, sticky="e")
|
||||
ent_operational_look_up.grid(row=9, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=10, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=10, column=5, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Get Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
|
||||
btn_standby_get_condition = tk.Button(master=frm_form, text="Get Condition",
|
||||
command = get_condition)
|
||||
btn_standby_get_condition.grid(row=4, column=2, sticky="nsew")
|
||||
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Conditional Escrow",
|
||||
command = standby_create_conditional_escrow)
|
||||
btn_standby_create_escrow.grid(row=5, column = 2, sticky="nsew")
|
||||
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Escrow",
|
||||
command = standby_cancel_time_escrow)
|
||||
btn_standby_cancel_escrow.grid(row=6,column = 2, sticky="nsew")
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
|
||||
command = operational_finish_conditional_escrow)
|
||||
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
|
||||
btn_op_get_escrows = tk.Button(master=frm_form, text="Get Escrows",
|
||||
command = operational_get_escrows)
|
||||
btn_op_get_escrows.grid(row = 5, column = 3, sticky="nsew")
|
||||
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
|
||||
command = operational_get_transaction)
|
||||
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
|
||||
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
@@ -23,7 +23,7 @@ const softwallets = [
|
||||
{ href: "https://coin.space/", id: "wallet-coin", alt: "Coin Space" },
|
||||
{ href: "https://crossmark.io/", id: "wallet-crossmark", alt: "Crossmark Wallet" },
|
||||
{ href: "https://gatehub.net/", id: "wallet-gatehub", alt: "Gatehub", imgclasses: "invertible-img" },
|
||||
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
|
||||
{ href: "https://gemwallet.com/", id: "wallet-gem", alt: "Gem Wallet" },
|
||||
{ href: "https://joeywallet.xyz/", id: "wallet-joey", alt: "Joey Wallet" },
|
||||
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
|
||||
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" }
|
||||
|
||||
@@ -92,7 +92,7 @@ On supported platforms, see the [instructions on installing or updating `rippled
|
||||
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.1-1.el9.x86_64.rpm) | `c6d028db1e2a4da898df68e5a92a893bebf1d167a0539d15ae27435f2155ccb2` |
|
||||
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.1-1_amd64.deb) | `cc30c33012bd83ed793b38738870cf931a96ae106fde60b71685c766da1d22e3` |
|
||||
|
||||
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
|
||||
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/release-3.1/BUILD.md). The most recent commit in the git log should be the change setting the version:
|
||||
|
||||
```text
|
||||
commit c5988233d05bedddac28866ed37607f4869855f9
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user