mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-02-27 01:02:25 +00:00
Compare commits
35 Commits
new-delete
...
tutorials-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5cd1b9f02 | ||
|
|
610dbb4eb7 | ||
|
|
80f0ef7e6e | ||
|
|
c181a6761b | ||
|
|
fcc3c08ac0 | ||
|
|
b37c9af494 | ||
|
|
cea7bd30c3 | ||
|
|
b437ab66f6 | ||
|
|
7dee115696 | ||
|
|
0233780fde | ||
|
|
46cf47742b | ||
|
|
dc23e5dd8c | ||
|
|
f58809a487 | ||
|
|
f051dc9486 | ||
|
|
e8bb125019 | ||
|
|
503c7813c0 | ||
|
|
fcc01a4903 | ||
|
|
65f9a8d53b | ||
|
|
f3e8db32dc | ||
|
|
77589c4793 | ||
|
|
208876246f | ||
|
|
5482b87704 | ||
|
|
5a763cb9c9 | ||
|
|
54637ac896 | ||
|
|
4568a71632 | ||
|
|
e8787b6ca5 | ||
|
|
7bcc77f315 | ||
|
|
21146f2a50 | ||
|
|
7ed537f90f | ||
|
|
bf9076021f | ||
|
|
7c839f5a03 | ||
|
|
0e5ac6d918 | ||
|
|
fca3a32783 | ||
|
|
d17dbd7db4 | ||
|
|
96abce379b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,7 +8,7 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
_code-samples/*/js/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
composer.lock
|
||||
|
||||
@@ -75,7 +75,7 @@ Si una dirección de reserva se ve comprometida, las consecuencias son similares
|
||||
- [Claves criptográficas](cryptographic-keys.md)
|
||||
- **Tutoriales:**
|
||||
- [Asignar par de claves regulares](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [Cambiar o eliminar par de claves regulares](/docs/tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Cambiar o eliminar par de claves regulares](/docs/tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **Referencias:**
|
||||
- [metodo account_info][]
|
||||
- [Transacción SetRegularKey][]
|
||||
|
||||
@@ -249,7 +249,7 @@ Los pasos para derivar par de claves de cuenta XRP Ledger secp256k1 desde un val
|
||||
- [Direcciones de emisión y operacionales](account-types.md)
|
||||
- **Tutoriales:**
|
||||
- [Asignación de par de claves normales](/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [Cambiar o eliminar par de claves normales](/docs/tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Cambiar o eliminar par de claves normales](/docs/tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **Referencias:**
|
||||
- [Transacción SetRegularKey][]
|
||||
- [Objeto de ledger AccountRoot](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md)
|
||||
|
||||
@@ -74,7 +74,7 @@ labels:
|
||||
- [暗号鍵](cryptographic-keys.md)
|
||||
- **チュートリアル:**
|
||||
- [レギュラーキーペアの割り当て](../../tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [レギュラーキーペアの変更または削除](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [レギュラーキーペアの変更または削除](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **リファレンス:**
|
||||
- [account_infoメソッド][]
|
||||
- [SetRegularKeyトランザクション][]
|
||||
|
||||
@@ -251,7 +251,7 @@ XRP Ledgerアカウントキーでのsecp256k1鍵導出に、Ed25519鍵導出よ
|
||||
- [発行アドレスと運用アドレス](account-types.md)
|
||||
- **チュートリアル:**
|
||||
- [レギュラーキーペアの割り当て](../../tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [レギュラーキーペアの変更または削除](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [レギュラーキーペアの変更または削除](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **リファレンス:**
|
||||
- [SetRegularKeyトランザクション][]
|
||||
- [AccountRootレジャーオブジェクト](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md)
|
||||
|
||||
@@ -158,7 +158,7 @@ For each persisted transaction without validated result:
|
||||
|
||||
{% admonition type="success" name="ヒント" %}[`AccountTxnID`フィールド](../../references/protocol/transactions/common-fields.md#accounttxnid)を使用すると、このような状況で冗長的なトランザクションが成功しないように防ぐことができます。{% /admonition %}
|
||||
|
||||
- 不正使用者に秘密鍵を使われてトランザクションを送信された場合。その場合は、可能であれば[キーペアをローテーション](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md)して、送信された他のトランザクションを確認します。また、ネットワークを監査して、その秘密鍵が大規模な侵入やセキュリティ侵害に関係していたかどうかを判断する必要があります。キーペアのローテーションに成功して、不正使用者がアカウントやシステムにアクセスできなくなったら、通常のアクティビティーを再開します。
|
||||
- 不正使用者に秘密鍵を使われてトランザクションを送信された場合。その場合は、可能であれば[キーペアをローテーション](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)して、送信された他のトランザクションを確認します。また、ネットワークを監査して、その秘密鍵が大規模な侵入やセキュリティ侵害に関係していたかどうかを判断する必要があります。キーペアのローテーションに成功して、不正使用者がアカウントやシステムにアクセスできなくなったら、通常のアクティビティーを再開します。
|
||||
|
||||
#### レジャーのギャップ
|
||||
|
||||
|
||||
@@ -75,10 +75,10 @@ Clioをインストールする前に、以下の条件を満たしている必
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ labels:
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
```
|
||||
|
||||
特に、フィンガープリントが一致することを確認してください。(上記の例では、フィンガープリントは三行目の`C001`で始まる部分です。)
|
||||
|
||||
@@ -577,16 +577,6 @@ Stay ahead of the curve with the latest developments in RWA tokenization on the
|
||||
Join the Developer Discord: 開発者ディスコードに参加
|
||||
Sign up for the Newsletter: ニュースレターに登録
|
||||
|
||||
# docs/tutorials/index.page.tsx
|
||||
Crypto Wallet and Blockchain Development Tutorials: 暗号資産ウォレットやブロックチェーン開発のチュートリアル
|
||||
These tutorials walk you through the basics of building a very simple XRP Ledger-connected application using your favorite programming language.: これらのチュートリアルでは、お好きなプログラミング言語を使って、XRP Ledgerに接続するアプリケーションを構築するための基礎について説明します。
|
||||
Get Started with SDKs: SDKを使って始める
|
||||
Using the xrpl.js client library.: クライアントライブラリxrpl.jsを使用。
|
||||
Using xrpl.py, a pure Python library.: Pythonライブラリxrpl.pyを使用。
|
||||
Using xrpl4j, a pure Java library.: Javaライブラリを使用。
|
||||
Using the XRPL_PHP client library.: PHPライブラリXRPL_PHPを使用。
|
||||
Access the XRP Ledger directly through the APIs of its core server.: コアサーバのAPIを通じてXRP Ledgerに直接アクセス。
|
||||
|
||||
# community/index.page.tsx
|
||||
XRPL Community: XRPLコミュニティ
|
||||
community.index.h1part1: 開発者とイノベーターによる
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// This component replaces the default Redocly Tabs functionality.
|
||||
// Original Tabs styling is preserved, but this adds full-page tab
|
||||
// switching and preserves tab preferences between pages.
|
||||
|
||||
import React, { useRef, useState, useEffect, useCallback, useMemo } from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import type { JSX } from 'react'
|
||||
|
||||
import { TabList } from '@redocly/theme/markdoc/components/Tabs/TabList'
|
||||
import { TabContent, TabsSize } from '@redocly/theme/markdoc/components/Tabs/Tabs'
|
||||
import type { TabItemProps } from '@redocly/theme/markdoc/components/Tabs/Tabs'
|
||||
import { getTabId } from '@redocly/theme/core/utils'
|
||||
|
||||
// Define tabs to sync by label and possible values. Must be lowercase.
|
||||
const syncGroups: Record<string, string[]> = {
|
||||
language: ['javascript', 'python', 'java', 'go', 'php'],
|
||||
protocol: ['websocket', 'json-rpc', 'commandline'],
|
||||
}
|
||||
|
||||
const syncableCategories: Record<string, string> = {}
|
||||
for (const [category, labels] of Object.entries(syncGroups)) {
|
||||
for (const label of labels) {
|
||||
syncableCategories[label] = category
|
||||
}
|
||||
}
|
||||
|
||||
function getCategory(label: string): string | undefined {
|
||||
return syncableCategories[label.toLowerCase()]
|
||||
}
|
||||
|
||||
// Local storage of tab preferences
|
||||
const storageKey = 'xrpl-preferred-tabs'
|
||||
|
||||
type Listener = (label: string) => void
|
||||
const listeners = new Set<Listener>()
|
||||
|
||||
function readPrefs(): Record<string, string> {
|
||||
try {
|
||||
const raw = localStorage.getItem(storageKey)
|
||||
return raw ? JSON.parse(raw) : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
function writePrefs(prefs: Record<string, string>): void {
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(prefs))
|
||||
} catch {
|
||||
// Local storage unavailable
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe(callback: Listener): () => void {
|
||||
listeners.add(callback)
|
||||
return () => {
|
||||
listeners.delete(callback)
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast(label: string): void {
|
||||
const category = getCategory(label)
|
||||
if (!category) return
|
||||
const prefs = readPrefs()
|
||||
prefs[category] = label.toLowerCase()
|
||||
writePrefs(prefs)
|
||||
listeners.forEach((cb) => cb(label))
|
||||
}
|
||||
|
||||
function getPreferredLabel(candidates: string[]): string | null {
|
||||
const prefs = readPrefs()
|
||||
for (const label of candidates) {
|
||||
const category = getCategory(label)
|
||||
if (category && prefs[category] === label.toLowerCase()) {
|
||||
return label
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Tabs rendering. Keep styling 1:1 with Redocly's Tabs.
|
||||
|
||||
type TabsProps = {
|
||||
children: React.ReactElement<TabItemProps>[]
|
||||
className?: string
|
||||
size: TabsSize
|
||||
forceReady?: boolean
|
||||
initialTab?: string
|
||||
}
|
||||
|
||||
export function Tabs({
|
||||
children,
|
||||
className,
|
||||
size,
|
||||
forceReady = false,
|
||||
initialTab: propInitialTab,
|
||||
}: TabsProps): JSX.Element {
|
||||
const [childrenArray, setChildrenArray] = useState<React.ReactElement<TabItemProps>[]>(
|
||||
React.Children.toArray(children) as React.ReactElement<TabItemProps>[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setChildrenArray(React.Children.toArray(children) as React.ReactElement<TabItemProps>[])
|
||||
}, [children])
|
||||
|
||||
const containerRef = useRef<HTMLUListElement | null>(null)
|
||||
const [isReady, setIsReady] = useState(false)
|
||||
|
||||
// Determine initial tab: stored preference > initial tab setting > first tab.
|
||||
const tabLabels = useMemo(() => childrenArray.map((child) => child.props.label), [childrenArray])
|
||||
const resolvedInitialTab = useMemo(() => {
|
||||
const defaultTab = childrenArray[0]?.props.label ?? ''
|
||||
const base = propInitialTab ?? defaultTab
|
||||
return getPreferredLabel(tabLabels) ?? base
|
||||
}, [childrenArray, propInitialTab, tabLabels])
|
||||
|
||||
const [activeTab, setActiveTab] = useState(resolvedInitialTab)
|
||||
|
||||
// Reset activeTab if tabs change and current selection no longer exists
|
||||
useEffect(() => {
|
||||
if (tabLabels.length > 0 && !tabLabels.includes(activeTab)) {
|
||||
setActiveTab(resolvedInitialTab)
|
||||
}
|
||||
}, [tabLabels, activeTab, resolvedInitialTab])
|
||||
|
||||
// Keep a ref to activeTab so the subscribe listener can read it without re-subscribing
|
||||
const activeTabRef = useRef(activeTab)
|
||||
activeTabRef.current = activeTab
|
||||
|
||||
// Subscribe to sync broadcasts (case-insensitive match)
|
||||
useEffect(() => {
|
||||
return subscribe((label: string) => {
|
||||
const match = tabLabels.find((l) => l.toLowerCase() === label.toLowerCase())
|
||||
if (match && match !== activeTabRef.current) {
|
||||
setActiveTab(match)
|
||||
}
|
||||
})
|
||||
}, [tabLabels])
|
||||
|
||||
// Wrap onTabChange to also broadcast the selected label
|
||||
const onTabChange = useCallback((label: string) => {
|
||||
setActiveTab(label)
|
||||
broadcast(label)
|
||||
}, [])
|
||||
|
||||
const handleReadyChange = useCallback((ready: boolean) => {
|
||||
setIsReady(ready)
|
||||
}, [])
|
||||
|
||||
// Tab rendering (from Redocly)
|
||||
return (
|
||||
<TabsContainer
|
||||
data-component-name="Markdoc/Tabs/Tabs"
|
||||
className={className}
|
||||
$isReady={isReady || forceReady}
|
||||
>
|
||||
<TabList
|
||||
size={size}
|
||||
childrenArray={childrenArray}
|
||||
activeTab={activeTab}
|
||||
onTabChange={onTabChange}
|
||||
containerRef={containerRef}
|
||||
onReadyChange={handleReadyChange}
|
||||
/>
|
||||
{childrenArray.map((child, index) => {
|
||||
const { label } = child.props
|
||||
const tabId = getTabId(label, index)
|
||||
return label === activeTab ? (
|
||||
<TabContent
|
||||
key={`content-${tabId}`}
|
||||
id={`panel-${tabId}`}
|
||||
aria-labelledby={`tab-${tabId}`}
|
||||
tabIndex={0}
|
||||
role="tabpanel"
|
||||
>
|
||||
{child.props.children}
|
||||
</TabContent>
|
||||
) : null
|
||||
})}
|
||||
</TabsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
// Styling (from Redocly)
|
||||
const TabsContainer = styled.div<{ $isReady: boolean }>`
|
||||
position: relative;
|
||||
color: var(--md-tabs-container-text-color);
|
||||
font-size: var(--md-tabs-container-font-size);
|
||||
font-family: var(--md-tabs-container-font-family);
|
||||
font-style: var(--md-tabs-container-font-style);
|
||||
font-weight: var(--md-tabs-container-font-weight);
|
||||
background-color: var(--md-tabs-container-bg-color);
|
||||
margin: var(--md-tabs-container-margin);
|
||||
padding: var(--md-tabs-container-padding);
|
||||
border: var(--md-tabs-container-border);
|
||||
|
||||
ol[class^='Tabs__TabList'] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
${({ $isReady }) =>
|
||||
!$isReady &&
|
||||
css`
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
`}
|
||||
`
|
||||
@@ -10,7 +10,6 @@ import { Button } from '@redocly/theme/components/Button/Button'
|
||||
export { default as XRPLoader } from '../components/XRPLoader'
|
||||
export { XRPLCard, CardGrid } from '../components/XRPLCard'
|
||||
export { AmendmentsTable, AmendmentDisclaimer, Badge } from '../components/Amendments'
|
||||
export { Tabs } from '../components/SyncedTabs'
|
||||
|
||||
export function IndexPageItems() {
|
||||
const { usePageSharedData } = useThemeHooks()
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
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'
|
||||
|
||||
export default function customPlugin() {
|
||||
const indexPagesInst = indexPages();
|
||||
const codeSamplesInst = codeSamples();
|
||||
const blogPostsInst = blogPosts();
|
||||
const tutorialLanguagesInst = tutorialLanguages();
|
||||
|
||||
|
||||
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
@@ -17,13 +16,11 @@ export default function customPlugin() {
|
||||
await indexPagesInst.processContent?.(content, actions);
|
||||
await codeSamplesInst.processContent?.(content, actions);
|
||||
await blogPostsInst.processContent?.(content, actions);
|
||||
await tutorialLanguagesInst.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);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* Plugin to detect languages supported in tutorial pages by scanning for tab labels.
|
||||
* This creates shared data that maps tutorial paths to their supported languages.
|
||||
*/
|
||||
export function tutorialLanguages() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
const instance = {
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
/** @type {Record<string, string[]>} */
|
||||
const tutorialLanguagesMap = {}
|
||||
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 {
|
||||
const { data } = await cache.load(relativePath, 'markdown-ast')
|
||||
const languages = extractLanguagesFromAst(data.ast)
|
||||
|
||||
if (languages.length > 0) {
|
||||
// Convert file path to URL path
|
||||
const urlPath = '/' + relativePath
|
||||
.replace(/\.md$/, '/')
|
||||
.replace(/\\/g, '/')
|
||||
tutorialLanguagesMap[urlPath] = languages
|
||||
}
|
||||
} catch (err) {
|
||||
continue // Skip files that can't be parsed.
|
||||
}
|
||||
}
|
||||
|
||||
actions.createSharedData('tutorial-languages', tutorialLanguagesMap)
|
||||
actions.addRouteSharedData('/docs/tutorials/', 'tutorial-languages', 'tutorial-languages')
|
||||
actions.addRouteSharedData('/ja/docs/tutorials/', 'tutorial-languages', 'tutorial-languages')
|
||||
actions.addRouteSharedData('/es-es/docs/tutorials/', 'tutorial-languages', 'tutorial-languages')
|
||||
} catch (e) {
|
||||
console.log('[tutorial-languages] Error:', e)
|
||||
}
|
||||
},
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract language names from tab labels in the markdown AST
|
||||
*/
|
||||
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') {
|
||||
const label = node.attributes?.label
|
||||
if (label) {
|
||||
const normalizedLang = normalizeLanguage(label)
|
||||
if (normalizedLang) {
|
||||
languages.add(normalizedLang)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(languages)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert tab labels like "JavaScript", "Python", etc. to lowercase keys
|
||||
* used for displaying the correct language icons on tutorial cards.
|
||||
*/
|
||||
function normalizeLanguage(label) {
|
||||
const labelLower = label.toLowerCase()
|
||||
|
||||
if (labelLower.includes('javascript') || labelLower === 'js') {
|
||||
return 'javascript'
|
||||
}
|
||||
if (labelLower.includes('python') || labelLower === 'py') {
|
||||
return 'python'
|
||||
}
|
||||
if (labelLower.includes('java') && !labelLower.includes('javascript')) {
|
||||
return 'java'
|
||||
}
|
||||
if (labelLower.includes('php')) {
|
||||
return 'php'
|
||||
}
|
||||
if (labelLower.includes('go') || labelLower === 'golang') {
|
||||
return 'go'
|
||||
}
|
||||
if (labelLower.includes('http') || labelLower.includes('websocket')) {
|
||||
return 'http'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function isNode(value) {
|
||||
return !!(value?.$$mdtype === 'Node')
|
||||
}
|
||||
|
||||
function visit(node, visitor) {
|
||||
if (!node) return
|
||||
|
||||
visitor(node)
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
if (!child || typeof child === 'string') {
|
||||
continue
|
||||
}
|
||||
visit(child, visitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Delete Account
|
||||
|
||||
Delete an account from the XRP Ledger, removing its data and sending its XRP to another account.
|
||||
@@ -1,4 +0,0 @@
|
||||
# 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
|
||||
@@ -1,45 +0,0 @@
|
||||
# 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 + 256: 15226794
|
||||
Validated ledger index: 15226538
|
||||
(Sequence + 256 must be less than 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
|
||||
```
|
||||
@@ -1,180 +0,0 @@
|
||||
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 + 256 >= lastValidatedLedgerIndex) {
|
||||
console.error(`Account is too new to be deleted.
|
||||
Account sequence + 256: ${acctSeq + 256}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(Sequence + 256 must be less than ledger index)`)
|
||||
|
||||
// Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
const estWaitTimeS = (acctSeq + 256 - 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 less than 1000)`)
|
||||
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.`)
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "delete-account",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1",
|
||||
"xrpl": "^4.6.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# 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
|
||||
@@ -1,46 +0,0 @@
|
||||
# Delete Account (Python)
|
||||
|
||||
Python sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
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 + 256: 15226905
|
||||
Validated ledger index: 15226649
|
||||
(Sequence + 256 must be less than 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
|
||||
```
|
||||
@@ -1,176 +0,0 @@
|
||||
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 + 256 >= last_validated_ledger_index:
|
||||
print(
|
||||
f"Account is too new to be deleted.\n"
|
||||
f" Account sequence + 256: {acct_seq + 256}\n"
|
||||
f" Validated ledger index: {last_validated_ledger_index}\n"
|
||||
f" (Sequence + 256 must be less than ledger index)"
|
||||
)
|
||||
|
||||
# Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
est_wait_time_s = (acct_seq + 256 - 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 less than 1000)"
|
||||
)
|
||||
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.")
|
||||
|
||||
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))
|
||||
@@ -1,2 +0,0 @@
|
||||
xrpl-py==4.5.0
|
||||
python-dotenv==1.2.1
|
||||
@@ -177,84 +177,84 @@ node createLoan.js
|
||||
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: rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz
|
||||
Borrower address: rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt
|
||||
LoanBrokerID: 3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5
|
||||
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
|
||||
Borrower address: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA
|
||||
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15
|
||||
|
||||
=== Preparing LoanSet transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"PrincipalRequested": "1000",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"PrincipalRequested": 1000,
|
||||
"InterestRate": 500,
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"LoanOriginationFee": 100,
|
||||
"LoanServiceFee": 10,
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"Sequence": 3212122,
|
||||
"LastLedgerSequence": 3212233,
|
||||
"Fee": "2"
|
||||
}
|
||||
|
||||
=== Adding loan broker signature ===
|
||||
|
||||
TxnSignature: F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A
|
||||
SigningPubKey: EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530
|
||||
TxnSignature: 44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400
|
||||
SigningPubKey: ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C
|
||||
|
||||
Signed loanSetTx for borrower to sign over:
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"PaymentTotal": 12,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"Fee": "2",
|
||||
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
|
||||
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"Flags": 0,
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 3212233,
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PrincipalRequested": "1000"
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 3212122,
|
||||
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400"
|
||||
}
|
||||
|
||||
=== Adding borrower signature ===
|
||||
|
||||
Borrower TxnSignature: 52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800
|
||||
Borrower SigningPubKey: EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62
|
||||
Borrower TxnSignature: 2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F
|
||||
Borrower SigningPubKey: ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA
|
||||
|
||||
Fully signed LoanSet transaction:
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"PaymentTotal": 12,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA",
|
||||
"TxnSignature": "2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F"
|
||||
},
|
||||
"Fee": "2",
|
||||
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
|
||||
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"Flags": 0,
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 3212233,
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62",
|
||||
"TxnSignature": "52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800"
|
||||
}
|
||||
"Sequence": 3212122,
|
||||
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400"
|
||||
}
|
||||
|
||||
=== Submitting signed LoanSet transaction ===
|
||||
@@ -264,19 +264,19 @@ Loan created successfully!
|
||||
=== Loan Information ===
|
||||
|
||||
{
|
||||
"Borrower": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"Borrower": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanSequence": 6,
|
||||
"LoanSequence": 3,
|
||||
"LoanServiceFee": "10",
|
||||
"NextPaymentDueDate": 826862960,
|
||||
"NextPaymentDueDate": 825408182,
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentRemaining": 12,
|
||||
"PeriodicPayment": "83.55610375293148956",
|
||||
"PrincipalOutstanding": "1000",
|
||||
"StartDate": 824270960,
|
||||
"StartDate": 822816182,
|
||||
"TotalValueOutstanding": "1003"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -41,42 +41,77 @@ const loanSetTx = await client.autofill({
|
||||
Account: loanBroker.address,
|
||||
Counterparty: borrower.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: '1000',
|
||||
PrincipalRequested: 1000,
|
||||
InterestRate: 500,
|
||||
PaymentTotal: 12,
|
||||
PaymentInterval: 2592000,
|
||||
GracePeriod: 604800,
|
||||
LoanOriginationFee: '100',
|
||||
LoanServiceFee: '10'
|
||||
LoanOriginationFee: 100,
|
||||
LoanServiceFee: 10
|
||||
})
|
||||
|
||||
console.log(JSON.stringify(loanSetTx, null, 2))
|
||||
|
||||
// Loan broker signs first
|
||||
console.log(`\n=== Adding loan broker signature ===\n`)
|
||||
const loanBrokerSigned = loanBroker.sign(loanSetTx)
|
||||
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
|
||||
const loanBrokerSignature = await client.request({
|
||||
command: 'sign',
|
||||
tx_json: loanSetTx,
|
||||
secret: loanBroker.seed
|
||||
})
|
||||
|
||||
console.log(`TxnSignature: ${loanBrokerSignedTx.TxnSignature}`)
|
||||
console.log(`SigningPubKey: ${loanBrokerSignedTx.SigningPubKey}\n`)
|
||||
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignedTx, null, 2)}`)
|
||||
const loanBrokerSignatureResult = loanBrokerSignature.result.tx_json
|
||||
|
||||
console.log(`TxnSignature: ${loanBrokerSignatureResult.TxnSignature}`)
|
||||
console.log(`SigningPubKey: ${loanBrokerSignatureResult.SigningPubKey}\n`)
|
||||
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignatureResult, null, 2)}`)
|
||||
|
||||
// Borrower signs second
|
||||
console.log(`\n=== Adding borrower signature ===\n`)
|
||||
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
|
||||
|
||||
console.log(`Borrower TxnSignature: ${fullySigned.tx.CounterpartySignature.TxnSignature}`)
|
||||
console.log(`Borrower SigningPubKey: ${fullySigned.tx.CounterpartySignature.SigningPubKey}`)
|
||||
const borrowerSignature = await client.request({
|
||||
command: 'sign',
|
||||
tx_json: loanBrokerSignatureResult,
|
||||
secret: borrower.seed,
|
||||
signature_target: 'CounterpartySignature'
|
||||
})
|
||||
|
||||
const borrowerSignatureResult = borrowerSignature.result.tx_json
|
||||
|
||||
console.log(`Borrower TxnSignature: ${borrowerSignatureResult.CounterpartySignature.TxnSignature}`)
|
||||
console.log(`Borrower SigningPubKey: ${borrowerSignatureResult.CounterpartySignature.SigningPubKey}`)
|
||||
|
||||
// Validate the transaction structure before submitting.
|
||||
xrpl.validate(fullySigned.tx)
|
||||
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(fullySigned.tx, null, 2)}`)
|
||||
xrpl.validate(borrowerSignatureResult)
|
||||
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(borrowerSignatureResult, null, 2)}`)
|
||||
|
||||
// Submit and wait for validation ----------------------
|
||||
console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
|
||||
|
||||
const submitResponse = await client.submitAndWait(fullySigned.tx)
|
||||
// Submit the transaction
|
||||
const submitResult = await client.submit(borrowerSignatureResult)
|
||||
const txHash = submitResult.result.tx_json.hash
|
||||
|
||||
// Helper function to check tx hash is validated
|
||||
async function validateTx (hash, maxRetries = 20) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
try {
|
||||
const tx = await client.request({ command: 'tx', transaction: hash })
|
||||
if (tx.result.validated) {
|
||||
return tx
|
||||
}
|
||||
} catch (error) {
|
||||
// Transaction not validated yet, check again
|
||||
}
|
||||
}
|
||||
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Validate the transaction
|
||||
const submitResponse = await validateTx(txHash)
|
||||
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = submitResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to create loan:', resultCode)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Setup script for lending protocol tutorials
|
||||
|
||||
import xrpl from 'xrpl'
|
||||
import fs from 'fs'
|
||||
|
||||
// Setup script for lending protocol tutorials
|
||||
|
||||
process.stdout.write('Setting up tutorial: 0/6\r')
|
||||
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
@@ -262,35 +262,73 @@ process.stdout.write('Setting up tutorial: 5/6\r')
|
||||
// Suppress unnecessary console warning from autofilling LoanSet.
|
||||
console.warn = () => {}
|
||||
|
||||
// Helper function to create, sign, and submit a LoanSet transaction
|
||||
async function createLoan (ticketSequence) {
|
||||
// Helper function to create and sign a LoanSet transaction
|
||||
async function createSignedLoanSetTx (ticketSequence) {
|
||||
const loanSetTx = await client.autofill({
|
||||
TransactionType: 'LoanSet',
|
||||
Account: loanBroker.address,
|
||||
Counterparty: borrower.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: '1000',
|
||||
PrincipalRequested: 1000,
|
||||
InterestRate: 500,
|
||||
PaymentTotal: 1,
|
||||
PaymentInterval: 2592000,
|
||||
LoanOriginationFee: '100',
|
||||
LoanServiceFee: '10',
|
||||
LoanOriginationFee: 100,
|
||||
LoanServiceFee: 10,
|
||||
Sequence: 0,
|
||||
TicketSequence: ticketSequence
|
||||
})
|
||||
|
||||
const loanBrokerSigned = loanBroker.sign(loanSetTx)
|
||||
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
|
||||
const loanBrokerSignature = await client.request({
|
||||
command: 'sign',
|
||||
tx_json: loanSetTx,
|
||||
secret: loanBroker.seed
|
||||
})
|
||||
|
||||
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
|
||||
const submitResponse = await client.submitAndWait(fullySigned.tx)
|
||||
const borrowerSignature = await client.request({
|
||||
command: 'sign',
|
||||
tx_json: loanBrokerSignature.result.tx_json,
|
||||
secret: borrower.seed,
|
||||
signature_target: 'CounterpartySignature'
|
||||
})
|
||||
|
||||
return submitResponse
|
||||
return borrowerSignature.result.tx_json
|
||||
}
|
||||
|
||||
// Create and submit both loans
|
||||
const [signedLoan1, signedLoan2] = await Promise.all([
|
||||
createSignedLoanSetTx(tickets[0]),
|
||||
createSignedLoanSetTx(tickets[1])
|
||||
])
|
||||
|
||||
const [submitLoan1, submitLoan2] = await Promise.all([
|
||||
client.submit(signedLoan1),
|
||||
client.submit(signedLoan2)
|
||||
])
|
||||
const hash1 = submitLoan1.result.tx_json.hash
|
||||
const hash2 = submitLoan2.result.tx_json.hash
|
||||
|
||||
// Helper function to check tx hash is validated
|
||||
async function validateTx (hash, maxRetries = 20) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
try {
|
||||
const tx = await client.request({ command: 'tx', transaction: hash })
|
||||
if (tx.result.validated) {
|
||||
return tx
|
||||
}
|
||||
} catch (error) {
|
||||
// Transaction not validated yet, check again
|
||||
}
|
||||
}
|
||||
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const [submitResponse1, submitResponse2] = await Promise.all([
|
||||
createLoan(tickets[0]),
|
||||
createLoan(tickets[1])
|
||||
validateTx(hash1),
|
||||
validateTx(hash2)
|
||||
])
|
||||
|
||||
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "lending-protocol-examples",
|
||||
"description": "Example code for creating and managing loans.",
|
||||
"dependencies": {
|
||||
"xrpl": "^4.6.0"
|
||||
"xrpl": "^4.5.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -1,403 +0,0 @@
|
||||
# Lending Protocol Examples (Python)
|
||||
|
||||
This directory contains Python 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
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan Broker
|
||||
|
||||
```sh
|
||||
python3 create_loan_broker.py
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
|
||||
|
||||
```sh
|
||||
Loan broker/vault owner address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
|
||||
Vault ID: 2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F
|
||||
|
||||
=== Preparing LoanBrokerSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"SigningPubKey": "",
|
||||
"VaultID": "2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F",
|
||||
"ManagementFeeRate": 1000
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerSet transaction ===
|
||||
|
||||
Loan broker created successfully!
|
||||
|
||||
=== Loan Broker Information ===
|
||||
|
||||
LoanBroker ID: 86911896026EA9DEAEFC1A7959BC05D8B1A1EC25B9960E8C54424B7DC41F8DA8
|
||||
LoanBroker Psuedo-Account Address: rPhpC2XGz7v5g2rPom7JSWJcic1cnkoBh9
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Claw Back First-loss Capital
|
||||
|
||||
```sh
|
||||
python3 cover_clawback.py
|
||||
```
|
||||
|
||||
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: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
|
||||
MPT issuer address: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d
|
||||
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
|
||||
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
|
||||
|
||||
=== Cover Available ===
|
||||
|
||||
1000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
|
||||
"TransactionType": "LoanBrokerCoverDeposit",
|
||||
"SigningPubKey": "",
|
||||
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Available After Deposit ===
|
||||
|
||||
2000 TSTUSD
|
||||
|
||||
=== Verifying Asset Issuer ===
|
||||
|
||||
MPT issuer account verified: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d. Proceeding to clawback.
|
||||
|
||||
=== Preparing LoanBrokerCoverClawback transaction ===
|
||||
|
||||
{
|
||||
"Account": "rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d",
|
||||
"TransactionType": "LoanBrokerCoverClawback",
|
||||
"SigningPubKey": "",
|
||||
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
|
||||
"value": "2000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverClawback transaction ===
|
||||
|
||||
Successfully clawed back 2000 TSTUSD!
|
||||
|
||||
=== Final Cover Available After Clawback ===
|
||||
|
||||
0 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit and Withdraw First-loss Capital
|
||||
|
||||
```sh
|
||||
python3 cover_deposit_and_withdraw.py
|
||||
```
|
||||
|
||||
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: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
|
||||
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
|
||||
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
|
||||
"TransactionType": "LoanBrokerCoverDeposit",
|
||||
"SigningPubKey": "",
|
||||
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
|
||||
"value": "2000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
|
||||
Cover balance after deposit: 2000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
{
|
||||
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
|
||||
"TransactionType": "LoanBrokerCoverWithdraw",
|
||||
"SigningPubKey": "",
|
||||
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
Cover withdraw successful!
|
||||
|
||||
=== Updated Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
|
||||
Cover balance after withdraw: 1000 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan
|
||||
|
||||
```sh
|
||||
python3 create_loan.py
|
||||
```
|
||||
|
||||
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: ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy
|
||||
Borrower address: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
|
||||
LoanBrokerID: 61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2
|
||||
|
||||
=== Preparing LoanSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
|
||||
"TransactionType": "LoanSet",
|
||||
"Fee": "2",
|
||||
"Sequence": 3652181,
|
||||
"LastLedgerSequence": 3674792,
|
||||
"SigningPubKey": "",
|
||||
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
|
||||
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"InterestRate": 500,
|
||||
"PrincipalRequested": "1000",
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800
|
||||
}
|
||||
|
||||
=== Adding loan broker signature ===
|
||||
|
||||
TxnSignature: 9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D
|
||||
SigningPubKey: ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47
|
||||
|
||||
Signed loan_set_tx for borrower to sign over:
|
||||
{
|
||||
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
|
||||
"TransactionType": "LoanSet",
|
||||
"Fee": "2",
|
||||
"Sequence": 3652181,
|
||||
"LastLedgerSequence": 3674792,
|
||||
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
|
||||
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
|
||||
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
|
||||
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"InterestRate": 500,
|
||||
"PrincipalRequested": "1000",
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800
|
||||
}
|
||||
|
||||
=== Adding borrower signature ===
|
||||
|
||||
Borrower TxnSignature: A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207
|
||||
Borrower SigningPubKey: ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC
|
||||
|
||||
Fully signed LoanSet transaction:
|
||||
{
|
||||
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
|
||||
"TransactionType": "LoanSet",
|
||||
"Fee": "2",
|
||||
"Sequence": 3652181,
|
||||
"LastLedgerSequence": 3674792,
|
||||
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
|
||||
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
|
||||
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
|
||||
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC",
|
||||
"TxnSignature": "A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207"
|
||||
},
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"InterestRate": 500,
|
||||
"PrincipalRequested": "1000",
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800
|
||||
}
|
||||
|
||||
=== Submitting signed LoanSet transaction ===
|
||||
|
||||
Loan created successfully!
|
||||
|
||||
=== Loan Information ===
|
||||
|
||||
{
|
||||
"Borrower": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanSequence": 4,
|
||||
"LoanServiceFee": "10",
|
||||
"NextPaymentDueDate": 826867870,
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentRemaining": 12,
|
||||
"PeriodicPayment": "83.55610375293148956",
|
||||
"PrincipalOutstanding": "1000",
|
||||
"StartDate": 824275870,
|
||||
"TotalValueOutstanding": "1003"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage a Loan
|
||||
|
||||
```sh
|
||||
python3 loan_manage.py
|
||||
```
|
||||
|
||||
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: r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn
|
||||
LoanID: E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Total Amount Owed: 1001 TSTUSD.
|
||||
Payment Due Date: 2026-03-14 02:01:51
|
||||
|
||||
=== Preparing LoanManage transaction to impair loan ===
|
||||
|
||||
{
|
||||
"Account": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
|
||||
"TransactionType": "LoanManage",
|
||||
"Flags": 131072,
|
||||
"SigningPubKey": "",
|
||||
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage impairment transaction ===
|
||||
|
||||
Loan impaired successfully!
|
||||
New Payment Due Date: 2026-02-12 01:01:50
|
||||
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": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
|
||||
"TransactionType": "LoanManage",
|
||||
"Flags": 65536,
|
||||
"SigningPubKey": "",
|
||||
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage default transaction ===
|
||||
|
||||
Loan defaulted successfully!
|
||||
|
||||
=== Checking final loan status ===
|
||||
|
||||
Final loan flags: ['TF_LOAN_DEFAULT', 'TF_LOAN_IMPAIR']
|
||||
```
|
||||
|
||||
## Pay a Loan
|
||||
|
||||
```sh
|
||||
python3 loan_pay.py
|
||||
```
|
||||
|
||||
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: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
|
||||
LoanID: A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD
|
||||
MPT ID: 0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Amount Owed: 1001 TSTUSD
|
||||
Loan Service Fee: 10 TSTUSD
|
||||
Total Payment Due (including fees): 1011 TSTUSD
|
||||
|
||||
=== Preparing LoanPay transaction ===
|
||||
|
||||
{
|
||||
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"TransactionType": "LoanPay",
|
||||
"SigningPubKey": "",
|
||||
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116",
|
||||
"value": "1011"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanPay transaction ===
|
||||
|
||||
Loan paid successfully!
|
||||
|
||||
=== Loan Status After Payment ===
|
||||
|
||||
Outstanding Loan Balance: Loan fully paid off!
|
||||
|
||||
=== Preparing LoanDelete transaction ===
|
||||
|
||||
{
|
||||
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
|
||||
"TransactionType": "LoanDelete",
|
||||
"SigningPubKey": "",
|
||||
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD"
|
||||
}
|
||||
|
||||
=== Submitting LoanDelete transaction ===
|
||||
|
||||
Loan deleted successfully!
|
||||
|
||||
=== Verifying Loan Deletion ===
|
||||
|
||||
Loan has been successfully removed from the XRP Ledger!
|
||||
```
|
||||
@@ -1,124 +0,0 @@
|
||||
# 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.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LedgerEntry, LoanBrokerCoverClawback, LoanBrokerCoverDeposit, MPTAmount
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts, loan_broker_id, and mpt_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
|
||||
mpt_issuer = Wallet.from_seed(setup_data["depositor"]["seed"])
|
||||
loan_broker_id = setup_data["loan_broker_id"]
|
||||
mpt_id = setup_data["mpt_id"]
|
||||
|
||||
print(f"\nLoan broker address: {loan_broker.address}")
|
||||
print(f"MPT issuer address: {mpt_issuer.address}")
|
||||
print(f"LoanBrokerID: {loan_broker_id}")
|
||||
print(f"MPT ID: {mpt_id}")
|
||||
|
||||
# Check cover available ----------------------
|
||||
print("\n=== Cover Available ===\n")
|
||||
cover_info = client.request(LedgerEntry(
|
||||
index=loan_broker_id,
|
||||
ledger_index="validated",
|
||||
))
|
||||
|
||||
current_cover_available = cover_info.result["node"].get("CoverAvailable", "0")
|
||||
print(f"{current_cover_available} TSTUSD")
|
||||
|
||||
# Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
|
||||
cover_deposit_tx = LoanBrokerCoverDeposit(
|
||||
account=loan_broker.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
|
||||
)
|
||||
|
||||
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for deposit validation ----------------------
|
||||
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
|
||||
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
|
||||
|
||||
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = deposit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to deposit cover: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Cover deposit successful!")
|
||||
|
||||
# Extract updated cover available after deposit ----------------------
|
||||
print("\n=== Cover Available After Deposit ===\n")
|
||||
loan_broker_node = next(
|
||||
node for node in deposit_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
|
||||
current_cover_available = loan_broker_node["ModifiedNode"]["FinalFields"]["CoverAvailable"]
|
||||
print(f"{current_cover_available} TSTUSD")
|
||||
|
||||
# Verify issuer of cover asset matches ----------------------
|
||||
# Only the issuer of the asset can submit clawback transactions.
|
||||
# The asset must also have clawback enabled.
|
||||
print("\n=== Verifying Asset Issuer ===\n")
|
||||
asset_issuer_info = client.request(LedgerEntry(
|
||||
mpt_issuance=mpt_id,
|
||||
ledger_index="validated",
|
||||
))
|
||||
|
||||
if asset_issuer_info.result["node"]["Issuer"] != mpt_issuer.address:
|
||||
issuer = asset_issuer_info.result["node"]["Issuer"]
|
||||
print(f"Error: {issuer} does not match account ({mpt_issuer.address}) attempting clawback!")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"MPT issuer account verified: {mpt_issuer.address}. Proceeding to clawback.")
|
||||
|
||||
# Prepare LoanBrokerCoverClawback transaction ----------------------
|
||||
print("\n=== Preparing LoanBrokerCoverClawback transaction ===\n")
|
||||
cover_clawback_tx = LoanBrokerCoverClawback(
|
||||
account=mpt_issuer.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value=current_cover_available),
|
||||
)
|
||||
|
||||
print(json.dumps(cover_clawback_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for clawback validation ----------------------
|
||||
print("\n=== Submitting LoanBrokerCoverClawback transaction ===\n")
|
||||
clawback_response = submit_and_wait(cover_clawback_tx, client, mpt_issuer)
|
||||
|
||||
if clawback_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = clawback_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to clawback cover: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Successfully clawed back {current_cover_available} TSTUSD!")
|
||||
|
||||
# Extract final cover available ----------------------
|
||||
print("\n=== Final Cover Available After Clawback ===\n")
|
||||
loan_broker_node = next(
|
||||
node for node in clawback_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
|
||||
print(f"{loan_broker_node['ModifiedNode']['FinalFields'].get('CoverAvailable', '0')} TSTUSD")
|
||||
@@ -1,95 +0,0 @@
|
||||
# IMPORTANT: This example deposits and withdraws first-loss capital from a
|
||||
# preconfigured LoanBroker entry.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LoanBrokerCoverDeposit, LoanBrokerCoverWithdraw, MPTAmount
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts and loan_broker_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
|
||||
loan_broker_id = setup_data["loan_broker_id"]
|
||||
mpt_id = setup_data["mpt_id"]
|
||||
|
||||
print(f"\nLoan broker address: {loan_broker.address}")
|
||||
print(f"LoanBrokerID: {loan_broker_id}")
|
||||
print(f"MPT ID: {mpt_id}")
|
||||
|
||||
# Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
|
||||
cover_deposit_tx = LoanBrokerCoverDeposit(
|
||||
account=loan_broker.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2000"),
|
||||
)
|
||||
|
||||
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for deposit validation ----------------------
|
||||
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
|
||||
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
|
||||
|
||||
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = deposit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to deposit cover: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Cover deposit successful!")
|
||||
|
||||
# Extract cover balance from the transaction result
|
||||
print("\n=== Cover Balance ===\n")
|
||||
loan_broker_node = next(
|
||||
node for node in deposit_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
# First-loss capital is stored in the LoanBroker's pseudo-account.
|
||||
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
|
||||
print(f"Cover balance after deposit: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")
|
||||
|
||||
# Prepare LoanBrokerCoverWithdraw transaction ----------------------
|
||||
print("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n")
|
||||
cover_withdraw_tx = LoanBrokerCoverWithdraw(
|
||||
account=loan_broker.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
|
||||
)
|
||||
|
||||
print(json.dumps(cover_withdraw_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for withdraw validation ----------------------
|
||||
print("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n")
|
||||
withdraw_response = submit_and_wait(cover_withdraw_tx, client, loan_broker)
|
||||
|
||||
if withdraw_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = withdraw_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to withdraw cover: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Cover withdraw successful!")
|
||||
|
||||
# Extract updated cover balance from the transaction result
|
||||
print("\n=== Updated Cover Balance ===\n")
|
||||
loan_broker_node = next(
|
||||
node for node in withdraw_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
|
||||
print(f"Cover balance after withdraw: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")
|
||||
@@ -1,89 +0,0 @@
|
||||
# IMPORTANT: This example creates a loan using a preconfigured
|
||||
# loan broker, borrower, and private vault.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LoanSet
|
||||
from xrpl.transaction import autofill, sign, sign_loan_set_by_counterparty, submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts and loan_broker_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
|
||||
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
|
||||
loan_broker_id = setup_data["loan_broker_id"]
|
||||
|
||||
print(f"\nLoan broker address: {loan_broker.address}")
|
||||
print(f"Borrower address: {borrower.address}")
|
||||
print(f"LoanBrokerID: {loan_broker_id}")
|
||||
|
||||
# Prepare LoanSet transaction ----------------------
|
||||
# Account and Counterparty accounts can be swapped, but determines signing order.
|
||||
# Account signs first, Counterparty signs second.
|
||||
print("\n=== Preparing LoanSet transaction ===\n")
|
||||
|
||||
loan_set_tx = autofill(LoanSet(
|
||||
account=loan_broker.address,
|
||||
counterparty=borrower.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
principal_requested="1000",
|
||||
interest_rate=500,
|
||||
payment_total=12,
|
||||
payment_interval=2592000,
|
||||
grace_period=604800,
|
||||
loan_origination_fee="100",
|
||||
loan_service_fee="10",
|
||||
), client)
|
||||
|
||||
print(json.dumps(loan_set_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Loan broker signs first.
|
||||
print("\n=== Adding loan broker signature ===\n")
|
||||
loan_broker_signed = sign(loan_set_tx, loan_broker)
|
||||
|
||||
print(f"TxnSignature: {loan_broker_signed.txn_signature}")
|
||||
print(f"SigningPubKey: {loan_broker_signed.signing_pub_key}\n")
|
||||
print(f"Signed loan_set_tx for borrower to sign over:\n{json.dumps(loan_broker_signed.to_xrpl(), indent=2)}")
|
||||
|
||||
# Borrower signs second.
|
||||
print("\n=== Adding borrower signature ===\n")
|
||||
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
|
||||
|
||||
print(f"Borrower TxnSignature: {fully_signed.tx.counterparty_signature.txn_signature}")
|
||||
print(f"Borrower SigningPubKey: {fully_signed.tx.counterparty_signature.signing_pub_key}")
|
||||
print(f"\nFully signed LoanSet transaction:\n{json.dumps(fully_signed.tx.to_xrpl(), indent=2)}")
|
||||
|
||||
# Submit and wait for validation ----------------------
|
||||
print("\n=== Submitting signed LoanSet transaction ===\n")
|
||||
submit_response = submit_and_wait(fully_signed.tx, client)
|
||||
|
||||
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = submit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to create loan: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan created successfully!")
|
||||
|
||||
# Extract loan information from the transaction result.
|
||||
print("\n=== Loan Information ===\n")
|
||||
loan_node = next(
|
||||
node for node in submit_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
print(json.dumps(loan_node["CreatedNode"]["NewFields"], indent=2))
|
||||
@@ -1,64 +0,0 @@
|
||||
# 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 vault_id
|
||||
# and loan_broker values with your own.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LoanBrokerSet
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts and vault_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
|
||||
vault_id = setup_data["vault_id"]
|
||||
|
||||
print(f"\nLoan broker/vault owner address: {loan_broker.address}")
|
||||
print(f"Vault ID: {vault_id}")
|
||||
|
||||
# Prepare LoanBrokerSet transaction ----------------------
|
||||
print("\n=== Preparing LoanBrokerSet transaction ===\n")
|
||||
loan_broker_set_tx = LoanBrokerSet(
|
||||
account=loan_broker.address,
|
||||
vault_id=vault_id,
|
||||
management_fee_rate=1000,
|
||||
)
|
||||
|
||||
print(json.dumps(loan_broker_set_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit, sign, and wait for validation ----------------------
|
||||
print("\n=== Submitting LoanBrokerSet transaction ===\n")
|
||||
submit_response = submit_and_wait(loan_broker_set_tx, client, loan_broker)
|
||||
|
||||
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = submit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to create loan broker: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan broker created successfully!")
|
||||
|
||||
# Extract loan broker information from the transaction result
|
||||
print("\n=== Loan Broker Information ===\n")
|
||||
loan_broker_node = next(
|
||||
node for node in submit_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
print(f"LoanBroker ID: {loan_broker_node['CreatedNode']['LedgerIndex']}")
|
||||
print(f"LoanBroker Psuedo-Account Address: {loan_broker_node['CreatedNode']['NewFields']['Account']}")
|
||||
@@ -1,366 +0,0 @@
|
||||
# Setup script for lending protocol tutorials
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
from xrpl.asyncio.clients import AsyncWebsocketClient
|
||||
from xrpl.asyncio.wallet import generate_faucet_wallet
|
||||
from xrpl.asyncio.transaction import submit_and_wait, autofill, sign
|
||||
from xrpl.transaction import sign_loan_set_by_counterparty
|
||||
from xrpl.models import (
|
||||
AccountObjects,
|
||||
Batch,
|
||||
BatchFlag,
|
||||
CredentialAccept,
|
||||
CredentialCreate,
|
||||
LoanBrokerSet,
|
||||
LoanSet,
|
||||
MPTAmount,
|
||||
MPTCurrency,
|
||||
MPTokenAuthorize,
|
||||
MPTokenIssuanceCreate,
|
||||
MPTokenIssuanceCreateFlag,
|
||||
Payment,
|
||||
PermissionedDomainSet,
|
||||
TicketCreate,
|
||||
VaultCreate,
|
||||
VaultDeposit,
|
||||
)
|
||||
from xrpl.models.transactions.vault_create import VaultCreateFlag
|
||||
from xrpl.models.transactions.deposit_preauth import Credential
|
||||
from xrpl.utils import encode_mptoken_metadata, str_to_hex
|
||||
|
||||
|
||||
async def main():
|
||||
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
|
||||
|
||||
print("Setting up tutorial: 0/6", end="\r")
|
||||
|
||||
# Create and fund wallets
|
||||
loan_broker, borrower, depositor, credential_issuer = await asyncio.gather(
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 1/6", end="\r")
|
||||
|
||||
# Issue MPT with depositor
|
||||
# Create tickets for later use with loan_broker
|
||||
# Set up credentials and domain with credential_issuer
|
||||
credential_type = str_to_hex("KYC-Verified")
|
||||
|
||||
mpt_data = {
|
||||
"ticker": "TSTUSD",
|
||||
"name": "Test USD MPT",
|
||||
"desc": "A sample non-yield-bearing stablecoin backed by U.S. Treasuries.",
|
||||
"icon": "https://example.org/tstusd-icon.png",
|
||||
"asset_class": "rwa",
|
||||
"asset_subclass": "stablecoin",
|
||||
"issuer_name": "Example Treasury Reserve Co.",
|
||||
"uris": [
|
||||
{
|
||||
"uri": "https://exampletreasury.com/tstusd",
|
||||
"category": "website",
|
||||
"title": "Product Page",
|
||||
},
|
||||
{
|
||||
"uri": "https://exampletreasury.com/tstusd/reserve",
|
||||
"category": "docs",
|
||||
"title": "Reserve Attestation",
|
||||
},
|
||||
],
|
||||
"additional_info": {
|
||||
"reserve_type": "U.S. Treasury Bills",
|
||||
"custody_provider": "Example Custodian Bank",
|
||||
"audit_frequency": "Monthly",
|
||||
"last_audit_date": "2026-01-15",
|
||||
"pegged_currency": "USD",
|
||||
},
|
||||
}
|
||||
|
||||
ticket_create_response, mpt_issuance_response, _ = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=loan_broker.address,
|
||||
ticket_count=2,
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
MPTokenIssuanceCreate(
|
||||
account=depositor.address,
|
||||
maximum_amount="100000000",
|
||||
transfer_fee=0,
|
||||
flags=(
|
||||
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER
|
||||
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_CLAWBACK
|
||||
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRADE
|
||||
),
|
||||
mptoken_metadata=encode_mptoken_metadata(mpt_data),
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=credential_issuer.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=loan_broker.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=borrower.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=depositor.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
PermissionedDomainSet(
|
||||
account=credential_issuer.address,
|
||||
accepted_credentials=[
|
||||
Credential(
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
)
|
||||
|
||||
# Extract ticket sequence numbers
|
||||
tickets = [
|
||||
node["CreatedNode"]["NewFields"]["TicketSequence"]
|
||||
for node in ticket_create_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
|
||||
]
|
||||
|
||||
# Extract MPT issuance ID
|
||||
mpt_id = mpt_issuance_response.result["meta"]["mpt_issuance_id"]
|
||||
|
||||
# Get domain ID
|
||||
credential_issuer_objects = await client.request(AccountObjects(
|
||||
account=credential_issuer.address,
|
||||
ledger_index="validated",
|
||||
))
|
||||
domain_id = next(
|
||||
node["index"]
|
||||
for node in credential_issuer_objects.result["account_objects"]
|
||||
if node["LedgerEntryType"] == "PermissionedDomain"
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 2/6", end="\r")
|
||||
|
||||
# Accept credentials and authorize MPT for each account
|
||||
await asyncio.gather(
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=loan_broker.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
CredentialAccept(
|
||||
account=loan_broker.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
MPTokenAuthorize(
|
||||
account=loan_broker.address,
|
||||
mptoken_issuance_id=mpt_id,
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=borrower.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
CredentialAccept(
|
||||
account=borrower.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
MPTokenAuthorize(
|
||||
account=borrower.address,
|
||||
mptoken_issuance_id=mpt_id,
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
borrower,
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialAccept(
|
||||
account=depositor.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 3/6", end="\r")
|
||||
|
||||
# Create private vault and distribute MPT to accounts
|
||||
vault_create_response, _ = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
VaultCreate(
|
||||
account=loan_broker.address,
|
||||
asset=MPTCurrency(mpt_issuance_id=mpt_id),
|
||||
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
|
||||
domain_id=domain_id,
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=depositor.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
Payment(
|
||||
account=depositor.address,
|
||||
destination=loan_broker.address,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
|
||||
),
|
||||
Payment(
|
||||
account=depositor.address,
|
||||
destination=borrower.address,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
)
|
||||
|
||||
vault_id = next(
|
||||
node["CreatedNode"]["LedgerIndex"]
|
||||
for node in vault_create_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Vault"
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 4/6", end="\r")
|
||||
|
||||
# Create LoanBroker and deposit MPT into vault
|
||||
loan_broker_set_response, _ = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
LoanBrokerSet(
|
||||
account=loan_broker.address,
|
||||
vault_id=vault_id,
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
VaultDeposit(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="50000000"),
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
)
|
||||
|
||||
loan_broker_id = next(
|
||||
node["CreatedNode"]["LedgerIndex"]
|
||||
for node in loan_broker_set_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 5/6", end="\r")
|
||||
|
||||
# Create 2 identical loans with complete repayment due in 30 days
|
||||
|
||||
# Helper function to create, sign, and submit a LoanSet transaction
|
||||
async def create_loan(ticket_sequence):
|
||||
loan_set_tx = await autofill(LoanSet(
|
||||
account=loan_broker.address,
|
||||
counterparty=borrower.address,
|
||||
loan_broker_id=loan_broker_id,
|
||||
principal_requested="1000",
|
||||
interest_rate=500,
|
||||
payment_total=1,
|
||||
payment_interval=2592000,
|
||||
loan_origination_fee="100",
|
||||
loan_service_fee="10",
|
||||
sequence=0,
|
||||
ticket_sequence=ticket_sequence,
|
||||
), client)
|
||||
|
||||
loan_broker_signed = sign(loan_set_tx, loan_broker)
|
||||
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
|
||||
submit_response = await submit_and_wait(fully_signed.tx, client)
|
||||
|
||||
return submit_response
|
||||
|
||||
submit_response_1, submit_response_2 = await asyncio.gather(
|
||||
create_loan(tickets[0]),
|
||||
create_loan(tickets[1]),
|
||||
)
|
||||
|
||||
loan_id_1 = next(
|
||||
node["CreatedNode"]["LedgerIndex"]
|
||||
for node in submit_response_1.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
|
||||
loan_id_2 = next(
|
||||
node["CreatedNode"]["LedgerIndex"]
|
||||
for node in submit_response_2.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 6/6", end="\r")
|
||||
|
||||
# Write setup data to JSON file
|
||||
setup_data = {
|
||||
"description": "This file is auto-generated by lending_setup.py. It stores XRPL account info for use in lending protocol tutorials.",
|
||||
"loan_broker": {
|
||||
"address": loan_broker.address,
|
||||
"seed": loan_broker.seed,
|
||||
},
|
||||
"borrower": {
|
||||
"address": borrower.address,
|
||||
"seed": borrower.seed,
|
||||
},
|
||||
"depositor": {
|
||||
"address": depositor.address,
|
||||
"seed": depositor.seed,
|
||||
},
|
||||
"credential_issuer": {
|
||||
"address": credential_issuer.address,
|
||||
"seed": credential_issuer.seed,
|
||||
},
|
||||
"domain_id": domain_id,
|
||||
"mpt_id": mpt_id,
|
||||
"vault_id": vault_id,
|
||||
"loan_broker_id": loan_broker_id,
|
||||
"loan_id_1": loan_id_1,
|
||||
"loan_id_2": loan_id_2,
|
||||
}
|
||||
|
||||
with open("lending_setup.json", "w") as f:
|
||||
json.dump(setup_data, f, indent=2)
|
||||
|
||||
print("Setting up tutorial: Complete!")
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
@@ -1,130 +0,0 @@
|
||||
# IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
|
||||
# After the 60 seconds pass, this example defaults the loan.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LedgerEntry, LoanManage
|
||||
from xrpl.models.transactions.loan_manage import LoanManageFlag
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import posix_to_ripple_time, ripple_time_to_posix
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts and loan_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
|
||||
loan_id = setup_data["loan_id_1"]
|
||||
|
||||
print(f"\nLoan broker address: {loan_broker.address}")
|
||||
print(f"LoanID: {loan_id}")
|
||||
|
||||
# Check loan status before impairment ----------------------
|
||||
print("\n=== Loan Status ===\n")
|
||||
loan_status = client.request(LedgerEntry(
|
||||
index=loan_id,
|
||||
ledger_index="validated",
|
||||
))
|
||||
|
||||
print(f"Total Amount Owed: {loan_status.result['node']['TotalValueOutstanding']} TSTUSD.")
|
||||
# Convert Ripple Epoch timestamp to local date and time
|
||||
next_payment_due_date = loan_status.result["node"]["NextPaymentDueDate"]
|
||||
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
|
||||
print(f"Payment Due Date: {payment_due}")
|
||||
|
||||
# Prepare LoanManage transaction to impair the loan ----------------------
|
||||
print("\n=== Preparing LoanManage transaction to impair loan ===\n")
|
||||
loan_manage_impair = LoanManage(
|
||||
account=loan_broker.address,
|
||||
loan_id=loan_id,
|
||||
flags=LoanManageFlag.TF_LOAN_IMPAIR,
|
||||
)
|
||||
|
||||
print(json.dumps(loan_manage_impair.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for impairment validation ----------------------
|
||||
print("\n=== Submitting LoanManage impairment transaction ===\n")
|
||||
impair_response = submit_and_wait(loan_manage_impair, client, loan_broker)
|
||||
|
||||
if impair_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = impair_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to impair loan: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan impaired successfully!")
|
||||
|
||||
# Extract loan impairment info from transaction results ----------------------
|
||||
loan_node = next(
|
||||
node for node in impair_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
|
||||
# Check grace period and next payment due date
|
||||
grace_period = loan_node["ModifiedNode"]["FinalFields"]["GracePeriod"]
|
||||
next_payment_due_date = loan_node["ModifiedNode"]["FinalFields"]["NextPaymentDueDate"]
|
||||
default_time = next_payment_due_date + grace_period
|
||||
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
|
||||
|
||||
print(f"New Payment Due Date: {payment_due}")
|
||||
print(f"Grace Period: {grace_period} seconds")
|
||||
|
||||
# Convert current time to Ripple Epoch timestamp
|
||||
current_time = posix_to_ripple_time(int(time.time()))
|
||||
seconds_until_default = default_time - current_time
|
||||
|
||||
# Countdown until loan can be defaulted ----------------------
|
||||
print("\n=== Countdown until loan can be defaulted ===\n")
|
||||
|
||||
while seconds_until_default >= 0:
|
||||
print(f"{seconds_until_default} seconds...", end="\r")
|
||||
time.sleep(1)
|
||||
seconds_until_default -= 1
|
||||
|
||||
print("\rGrace period expired. Loan can now be defaulted.")
|
||||
|
||||
# Prepare LoanManage transaction to default the loan ----------------------
|
||||
print("\n=== Preparing LoanManage transaction to default loan ===\n")
|
||||
loan_manage_default = LoanManage(
|
||||
account=loan_broker.address,
|
||||
loan_id=loan_id,
|
||||
flags=LoanManageFlag.TF_LOAN_DEFAULT,
|
||||
)
|
||||
|
||||
print(json.dumps(loan_manage_default.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for default validation ----------------------
|
||||
print("\n=== Submitting LoanManage default transaction ===\n")
|
||||
default_response = submit_and_wait(loan_manage_default, client, loan_broker)
|
||||
|
||||
if default_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = default_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to default loan: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan defaulted successfully!")
|
||||
|
||||
# Verify loan default status from transaction results ----------------------
|
||||
print("\n=== Checking final loan status ===\n")
|
||||
loan_node = next(
|
||||
node for node in default_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
loan_flags = loan_node["ModifiedNode"]["FinalFields"]["Flags"]
|
||||
active_flags = [f.name for f in LoanManageFlag if loan_flags & f.value]
|
||||
print(f"Final loan flags: {active_flags}")
|
||||
@@ -1,117 +0,0 @@
|
||||
# IMPORTANT: This example pays off an existing loan and then deletes it.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import LedgerEntry, LoanDelete, LoanPay, MPTAmount
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Set up client ----------------------
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
# If missing, lending_setup.py will generate the data.
|
||||
if not os.path.exists("lending_setup.json"):
|
||||
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "lending_setup.py"], check=True)
|
||||
|
||||
# Load preconfigured accounts, loan_id, and mpt_id.
|
||||
with open("lending_setup.json") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# You can replace these values with your own.
|
||||
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
|
||||
loan_id = setup_data["loan_id_2"]
|
||||
mpt_id = setup_data["mpt_id"]
|
||||
|
||||
print(f"\nBorrower address: {borrower.address}")
|
||||
print(f"LoanID: {loan_id}")
|
||||
print(f"MPT ID: {mpt_id}")
|
||||
|
||||
# Check initial loan status ----------------------
|
||||
print("\n=== Loan Status ===\n")
|
||||
loan_status = client.request(LedgerEntry(
|
||||
index=loan_id,
|
||||
ledger_index="validated",
|
||||
))
|
||||
|
||||
total_value_outstanding = loan_status.result["node"]["TotalValueOutstanding"]
|
||||
loan_service_fee = loan_status.result["node"]["LoanServiceFee"]
|
||||
total_payment = str(int(total_value_outstanding) + int(loan_service_fee))
|
||||
|
||||
print(f"Amount Owed: {total_value_outstanding} TSTUSD")
|
||||
print(f"Loan Service Fee: {loan_service_fee} TSTUSD")
|
||||
print(f"Total Payment Due (including fees): {total_payment} TSTUSD")
|
||||
|
||||
# Prepare LoanPay transaction ----------------------
|
||||
print("\n=== Preparing LoanPay transaction ===\n")
|
||||
loan_pay_tx = LoanPay(
|
||||
account=borrower.address,
|
||||
loan_id=loan_id,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value=total_payment),
|
||||
)
|
||||
|
||||
print(json.dumps(loan_pay_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for payment validation ----------------------
|
||||
print("\n=== Submitting LoanPay transaction ===\n")
|
||||
pay_response = submit_and_wait(loan_pay_tx, client, borrower)
|
||||
|
||||
if pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = pay_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to pay loan: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan paid successfully!")
|
||||
|
||||
# Extract updated loan info from transaction results ----------------------
|
||||
print("\n=== Loan Status After Payment ===\n")
|
||||
loan_node = next(
|
||||
node for node in pay_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
|
||||
)
|
||||
|
||||
final_balance = loan_node["ModifiedNode"]["FinalFields"].get("TotalValueOutstanding")
|
||||
if final_balance:
|
||||
print(f"Outstanding Loan Balance: {final_balance} TSTUSD")
|
||||
else:
|
||||
print("Outstanding Loan Balance: Loan fully paid off!")
|
||||
|
||||
# Prepare LoanDelete transaction ----------------------
|
||||
# Either the loan broker or borrower can submit this transaction.
|
||||
print("\n=== Preparing LoanDelete transaction ===\n")
|
||||
loan_delete_tx = LoanDelete(
|
||||
account=borrower.address,
|
||||
loan_id=loan_id,
|
||||
)
|
||||
|
||||
print(json.dumps(loan_delete_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Sign, submit, and wait for deletion validation ----------------------
|
||||
print("\n=== Submitting LoanDelete transaction ===\n")
|
||||
delete_response = submit_and_wait(loan_delete_tx, client, borrower)
|
||||
|
||||
if delete_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = delete_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to delete loan: {result_code}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loan deleted successfully!")
|
||||
|
||||
# Verify loan deletion ----------------------
|
||||
print("\n=== Verifying Loan Deletion ===\n")
|
||||
verify_response = client.request(LedgerEntry(
|
||||
index=loan_id,
|
||||
ledger_index="validated",
|
||||
))
|
||||
|
||||
if verify_response.is_successful():
|
||||
print("Warning: Loan still exists in the ledger.")
|
||||
elif verify_response.result.get("error") == "entryNotFound":
|
||||
print("Loan has been successfully removed from the XRP Ledger!")
|
||||
else:
|
||||
print(f"Error checking loan status: {verify_response.result.get('error')}")
|
||||
@@ -1 +0,0 @@
|
||||
xrpl-py>=4.5.0
|
||||
5
_code-samples/set-regular-key/README.md
Normal file
5
_code-samples/set-regular-key/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Use `SetRegularKey`
|
||||
|
||||
Use `SetRegularKey` to assign a key pair to a wallet and make a payment signed using the regular key wallet.
|
||||
|
||||
For more context, see [SetRegularKey](https://xrpl.org/setregularkey.html).
|
||||
10
_code-samples/set-regular-key/js/README.md
Normal file
10
_code-samples/set-regular-key/js/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Set Regular Key (JavaScript)
|
||||
|
||||
Demonstrates how to set a regular key for an XRP Ledger account.
|
||||
|
||||
Quick setup and usage:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npx ts-node setRegularKey.ts
|
||||
```
|
||||
13
_code-samples/set-regular-key/js/package.json
Normal file
13
_code-samples/set-regular-key/js/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "set-regular-key",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xrpl": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.0",
|
||||
"@types/node": "^20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
56
_code-samples/set-regular-key/js/setRegularKey.ts
Normal file
56
_code-samples/set-regular-key/js/setRegularKey.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Use `SetRegularKey` to assign a key pair to a wallet and make a payment signed using the regular key wallet.
|
||||
* Reference: https://xrpl.org/setregularkey.html
|
||||
*/
|
||||
import { Client, Payment, SetRegularKey } from 'xrpl'
|
||||
|
||||
const client = new Client('wss://s.altnet.rippletest.net:51233')
|
||||
|
||||
async function setRegularKey(): Promise<void> {
|
||||
await client.connect()
|
||||
const { wallet: wallet1 } = await client.fundWallet()
|
||||
const { wallet: wallet2 } = await client.fundWallet()
|
||||
const { wallet: regularKeyWallet } = await client.fundWallet()
|
||||
|
||||
console.log('Balances before payment')
|
||||
console.log(`Balance of ${wallet1.classicAddress} is ${await client.getXrpBalance(wallet1.classicAddress)}XRP`)
|
||||
console.log(`Balance of ${wallet2.classicAddress} is ${await client.getXrpBalance(wallet2.classicAddress)}XRP`)
|
||||
|
||||
// assigns key-pair(regularKeyWallet) to wallet1 using `SetRegularKey`.
|
||||
const tx: SetRegularKey = {
|
||||
TransactionType: 'SetRegularKey',
|
||||
Account: wallet1.classicAddress,
|
||||
RegularKey: regularKeyWallet.classicAddress,
|
||||
}
|
||||
|
||||
console.log('Submitting a SetRegularKey transaction...')
|
||||
const response = await client.submitAndWait(tx, {
|
||||
wallet: wallet1,
|
||||
})
|
||||
|
||||
console.log('Response for successful SetRegularKey tx')
|
||||
console.log(response)
|
||||
|
||||
/*
|
||||
* when wallet1 sends payment to wallet2 and
|
||||
* signs using the regular key wallet, the transaction goes through.
|
||||
*/
|
||||
const payment: Payment = {
|
||||
TransactionType: 'Payment',
|
||||
Account: wallet1.classicAddress,
|
||||
Destination: wallet2.classicAddress,
|
||||
Amount: '1000',
|
||||
}
|
||||
|
||||
const submitTx = await client.submit(payment, {
|
||||
wallet: regularKeyWallet,
|
||||
})
|
||||
console.log('Response for tx signed using Regular Key:')
|
||||
console.log(submitTx)
|
||||
console.log('Balances after payment:')
|
||||
console.log(`Balance of ${wallet1.classicAddress} is ${await client.getXrpBalance(wallet1.classicAddress)}XRP`)
|
||||
console.log(`Balance of ${wallet2.classicAddress} is ${await client.getXrpBalance(wallet2.classicAddress)}XRP`)
|
||||
|
||||
await client.disconnect()
|
||||
}
|
||||
void setRegularKey()
|
||||
10
_code-samples/set-regular-key/js/tsconfig.json
Normal file
10
_code-samples/set-regular-key/js/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
|
||||
12
_code-samples/set-regular-key/py/README.md
Normal file
12
_code-samples/set-regular-key/py/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Set Regular Key (Python)
|
||||
|
||||
Demonstrates how to set a regular key for an XRP Ledger account.
|
||||
|
||||
Quick setup and usage:
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
python setRegularKey.py
|
||||
```
|
||||
2
_code-samples/set-regular-key/py/requirements.txt
Normal file
2
_code-samples/set-regular-key/py/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
xrpl-py>=4.4.0
|
||||
|
||||
52
_code-samples/set-regular-key/py/setRegularKey.py
Normal file
52
_code-samples/set-regular-key/py/setRegularKey.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Example of how we can setting a regular key"""
|
||||
from xrpl.account import get_balance
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models.transactions import Payment, SetRegularKey
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import generate_faucet_wallet
|
||||
|
||||
# References
|
||||
# - https://xrpl.org/assign-a-regular-key-pair.html#assign-a-regular-key-pair
|
||||
# - https://xrpl.org/setregularkey.html#setregularkey
|
||||
# - https://xrpl.org/change-or-remove-a-regular-key-pair.html
|
||||
|
||||
# Create a client to connect to the test network
|
||||
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
|
||||
|
||||
# Creating two wallets to send money between
|
||||
wallet1 = generate_faucet_wallet(client, debug=True)
|
||||
wallet2 = generate_faucet_wallet(client, debug=True)
|
||||
regular_key_wallet = generate_faucet_wallet(client, debug=True)
|
||||
|
||||
# Both balances should be zero since nothing has been sent yet
|
||||
print("Balances before payment:")
|
||||
print(get_balance(wallet1.address, client))
|
||||
print(get_balance(wallet2.address, client))
|
||||
|
||||
# Assign key pair (regular_key_wallet) to wallet1 using SetRegularKey transaction
|
||||
tx = SetRegularKey(
|
||||
account=wallet1.address, regular_key=regular_key_wallet.address
|
||||
)
|
||||
|
||||
set_regular_key_response = submit_and_wait(tx, client, wallet1)
|
||||
|
||||
print("Response for successful SetRegularKey tx:")
|
||||
print(set_regular_key_response)
|
||||
|
||||
# Since regular_key_wallet is linked to wallet1,
|
||||
# walet1 can send payment to wallet2 and have regular_key_wallet sign it
|
||||
payment = Payment(
|
||||
account=wallet1.address,
|
||||
destination=wallet2.address,
|
||||
amount="1000",
|
||||
)
|
||||
|
||||
payment_response = submit_and_wait(payment, client, regular_key_wallet)
|
||||
|
||||
print("Response for tx signed using Regular Key:")
|
||||
print(payment_response)
|
||||
|
||||
# Balance after sending 1000 from wallet1 to wallet2
|
||||
print("Balances after payment:")
|
||||
print(get_balance(wallet1.address, client))
|
||||
print(get_balance(wallet2.address, client))
|
||||
@@ -69,9 +69,7 @@ try {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.data?.error === 'entryNotFound') {
|
||||
console.error(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
|
||||
} else {
|
||||
console.error(`Error checking MPT: ${error}`)
|
||||
console.log(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
|
||||
}
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs'
|
||||
|
||||
// Setup script for vault tutorials
|
||||
|
||||
process.stdout.write('Setting up tutorial: 0/5\r')
|
||||
process.stdout.write('Setting up tutorial: 0/7\r')
|
||||
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
@@ -21,106 +21,92 @@ const [
|
||||
client.fundWallet()
|
||||
])
|
||||
|
||||
// Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
|
||||
process.stdout.write('Setting up tutorial: 1/5\r')
|
||||
// Step 1: Create MPT issuance
|
||||
process.stdout.write('Setting up tutorial: 1/7\r')
|
||||
|
||||
const credType = 'VaultAccess'
|
||||
const [mptCreateResult] = await Promise.all([
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: "MPTokenIssuanceCreate",
|
||||
Account: mptIssuer.address,
|
||||
Flags:
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
|
||||
AssetScale: 2,
|
||||
TransferFee: 0,
|
||||
MaximumAmount: "1000000000000",
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: "USTST",
|
||||
name: "USTST Stablecoin",
|
||||
desc: "A test stablecoin token",
|
||||
icon: "example.org/ustst-icon.png",
|
||||
asset_class: "rwa",
|
||||
asset_subclass: "stablecoin",
|
||||
issuer_name: "Test Stablecoin Inc",
|
||||
uris: [
|
||||
{
|
||||
uri: "example.org/ustst",
|
||||
category: "website",
|
||||
title: "USTST Official Website",
|
||||
},
|
||||
{
|
||||
uri: "example.org/ustst/reserves",
|
||||
category: "attestation",
|
||||
title: "Reserve Attestation Reports",
|
||||
},
|
||||
{
|
||||
uri: "example.org/ustst/docs",
|
||||
category: "docs",
|
||||
title: "USTST Documentation",
|
||||
},
|
||||
],
|
||||
additional_info: {
|
||||
backing: "USD",
|
||||
reserve_ratio: "1:1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{ wallet: mptIssuer, autofill: true },
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: "Batch",
|
||||
Account: domainOwner.address,
|
||||
Flags: xrpl.BatchFlags.tfAllOrNothing,
|
||||
RawTransactions: [
|
||||
const mptCreateResult = await client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'MPTokenIssuanceCreate',
|
||||
Account: mptIssuer.address,
|
||||
Flags:
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
|
||||
AssetScale: 2,
|
||||
TransferFee: 0,
|
||||
MaximumAmount: '1000000000000',
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: 'USTST',
|
||||
name: 'USTST Stablecoin',
|
||||
desc: 'A test stablecoin token',
|
||||
icon: 'example.org/ustst-icon.png',
|
||||
asset_class: 'rwa',
|
||||
asset_subclass: 'stablecoin',
|
||||
issuer_name: 'Test Stablecoin Inc',
|
||||
uris: [
|
||||
{
|
||||
RawTransaction: {
|
||||
TransactionType: "PermissionedDomainSet",
|
||||
Account: domainOwner.address,
|
||||
AcceptedCredentials: [
|
||||
{
|
||||
Credential: {
|
||||
Issuer: domainOwner.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType),
|
||||
},
|
||||
},
|
||||
],
|
||||
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
|
||||
},
|
||||
uri: 'example.org/ustst',
|
||||
category: 'website',
|
||||
title: 'USTST Official Website'
|
||||
},
|
||||
{
|
||||
RawTransaction: {
|
||||
TransactionType: "CredentialCreate",
|
||||
Account: domainOwner.address,
|
||||
Subject: depositor.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType),
|
||||
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
|
||||
},
|
||||
uri: 'example.org/ustst/reserves',
|
||||
category: 'attestation',
|
||||
title: 'Reserve Attestation Reports'
|
||||
},
|
||||
{
|
||||
uri: 'example.org/ustst/docs',
|
||||
category: 'docs',
|
||||
title: 'USTST Documentation'
|
||||
}
|
||||
],
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true },
|
||||
),
|
||||
]);
|
||||
additional_info: {
|
||||
backing: 'USD',
|
||||
reserve_ratio: '1:1'
|
||||
}
|
||||
})
|
||||
},
|
||||
{ wallet: mptIssuer, autofill: true }
|
||||
)
|
||||
|
||||
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
|
||||
|
||||
// Get domain ID
|
||||
const domainOwnerObjects = await client.request({
|
||||
command: 'account_objects',
|
||||
account: domainOwner.address,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
const domainId = domainOwnerObjects.result.account_objects.find(
|
||||
(node) => node.LedgerEntryType === 'PermissionedDomain'
|
||||
).index
|
||||
// Step 2: Create Permissioned Domain
|
||||
process.stdout.write('Setting up tutorial: 2/7\r')
|
||||
|
||||
// Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
|
||||
process.stdout.write('Setting up tutorial: 2/5\r')
|
||||
const credType = 'VaultAccess'
|
||||
const domainResult = await client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'PermissionedDomainSet',
|
||||
Account: domainOwner.address,
|
||||
AcceptedCredentials: [
|
||||
{
|
||||
Credential: {
|
||||
Issuer: domainOwner.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType)
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true }
|
||||
)
|
||||
|
||||
const [, vaultCreateResult] = await Promise.all([
|
||||
const domainId = domainResult.result.meta.AffectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
// Step 3: Create depositor account with credentials and MPT balance
|
||||
process.stdout.write('Setting up tutorial: 3/7\r')
|
||||
|
||||
await Promise.all([
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: domainOwner.address,
|
||||
Subject: depositor.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType)
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true }
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'Batch',
|
||||
@@ -147,57 +133,10 @@ const [, vaultCreateResult] = await Promise.all([
|
||||
]
|
||||
},
|
||||
{ wallet: depositor, autofill: true }
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'VaultCreate',
|
||||
Account: vaultOwner.address,
|
||||
Asset: {
|
||||
mpt_issuance_id: mptIssuanceId
|
||||
},
|
||||
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
|
||||
DomainID: domainId,
|
||||
Data: xrpl.convertStringToHex(
|
||||
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
|
||||
),
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: 'SHARE1',
|
||||
name: 'Vault Shares',
|
||||
desc: 'Proportional ownership shares of the vault',
|
||||
icon: 'example.com/vault-shares-icon.png',
|
||||
asset_class: 'defi',
|
||||
issuer_name: 'Vault Owner',
|
||||
uris: [
|
||||
{
|
||||
uri: 'example.com/asset',
|
||||
category: 'website',
|
||||
title: 'Asset Website'
|
||||
},
|
||||
{
|
||||
uri: 'example.com/docs',
|
||||
category: 'docs',
|
||||
title: 'Docs'
|
||||
}
|
||||
],
|
||||
additional_info: {
|
||||
example_info: 'test'
|
||||
}
|
||||
}),
|
||||
AssetsMaximum: '0',
|
||||
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
|
||||
},
|
||||
{ wallet: vaultOwner, autofill: true }
|
||||
)
|
||||
])
|
||||
|
||||
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
|
||||
)
|
||||
const vaultID = vaultNode.CreatedNode.LedgerIndex
|
||||
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
|
||||
|
||||
// Step 3: Issuer sends payment to depositor
|
||||
process.stdout.write('Setting up tutorial: 3/5\r')
|
||||
process.stdout.write('Setting up tutorial: 4/7\r')
|
||||
|
||||
const paymentResult = await client.submitAndWait(
|
||||
{
|
||||
@@ -218,8 +157,59 @@ if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Step 4: Make an initial deposit so withdraw example has shares to work with
|
||||
process.stdout.write('Setting up tutorial: 4/5\r')
|
||||
// Step 5: Create a vault for deposit/withdraw examples
|
||||
process.stdout.write('Setting up tutorial: 5/7\r')
|
||||
|
||||
const vaultCreateResult = await client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'VaultCreate',
|
||||
Account: vaultOwner.address,
|
||||
Asset: {
|
||||
mpt_issuance_id: mptIssuanceId
|
||||
},
|
||||
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
|
||||
DomainID: domainId,
|
||||
Data: xrpl.convertStringToHex(
|
||||
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
|
||||
),
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: 'SHARE1',
|
||||
name: 'Vault Shares',
|
||||
desc: 'Proportional ownership shares of the vault',
|
||||
icon: 'example.com/vault-shares-icon.png',
|
||||
asset_class: 'defi',
|
||||
issuer_name: 'Vault Owner',
|
||||
uris: [
|
||||
{
|
||||
uri: 'example.com/asset',
|
||||
category: 'website',
|
||||
title: 'Asset Website'
|
||||
},
|
||||
{
|
||||
uri: 'example.com/docs',
|
||||
category: 'docs',
|
||||
title: 'Docs'
|
||||
}
|
||||
],
|
||||
additional_info: {
|
||||
example_info: 'test'
|
||||
}
|
||||
}),
|
||||
AssetsMaximum: '0',
|
||||
WithdrawalPolicy:
|
||||
xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
|
||||
},
|
||||
{ wallet: vaultOwner, autofill: true }
|
||||
)
|
||||
|
||||
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
|
||||
)
|
||||
const vaultID = vaultNode.CreatedNode.LedgerIndex
|
||||
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
|
||||
|
||||
// Step 6: Make an initial deposit so withdraw example has shares to work with
|
||||
process.stdout.write('Setting up tutorial: 6/7\r')
|
||||
|
||||
const initialDepositResult = await client.submitAndWait(
|
||||
{
|
||||
@@ -240,8 +230,8 @@ if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Step 5: Save setup data to file
|
||||
process.stdout.write('Setting up tutorial: 5/5\r')
|
||||
// Step 7: Save setup data to file
|
||||
process.stdout.write('Setting up tutorial: 7/7\r')
|
||||
|
||||
const setupData = {
|
||||
mptIssuer: {
|
||||
|
||||
@@ -52,13 +52,11 @@ try {
|
||||
ledger_index: "validated"
|
||||
})
|
||||
|
||||
const shareBalance = shareBalanceResult.result.node?.MPTAmount
|
||||
const shareBalance = shareBalanceResult.result.node.MPTAmount
|
||||
console.log(`Shares held: ${shareBalance}`)
|
||||
} catch (error) {
|
||||
if (error.data?.error === 'entryNotFound') {
|
||||
console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`)
|
||||
} else {
|
||||
console.error(`Error checking MPT: ${error}`)
|
||||
}
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
# Single Asset Vault Examples (Python)
|
||||
|
||||
This directory contains Python examples demonstrating how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Vault
|
||||
|
||||
```sh
|
||||
python create_vault.py
|
||||
```
|
||||
|
||||
The script should output the VaultCreate transaction, vault ID, and complete vault information:
|
||||
|
||||
```sh
|
||||
Vault owner address: rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu
|
||||
MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
|
||||
Permissioned domain ID: 76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E
|
||||
|
||||
|
||||
=== VaultCreate transaction ===
|
||||
{
|
||||
"Account": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
|
||||
"TransactionType": "VaultCreate",
|
||||
"Flags": 65536,
|
||||
"SigningPubKey": "",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
|
||||
},
|
||||
"Data": "7b226e223a20224c4154414d2046756e64204949222c202277223a20226578616d706c6566756e642e636f6d227d",
|
||||
"AssetsMaximum": "0",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
|
||||
"WithdrawalPolicy": 1
|
||||
}
|
||||
|
||||
=== Submitting VaultCreate transaction... ===
|
||||
Vault created successfully!
|
||||
|
||||
Vault ID: 3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D
|
||||
Vault pseudo-account address: rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm
|
||||
Share MPT issuance ID: 00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E
|
||||
|
||||
=== Getting vault_info... ===
|
||||
{
|
||||
"ledger_hash": "5851C21E353DEDEC5C6CC285E1E9835C378DCBBE5BA69CF33124DAC7EE5A08AD",
|
||||
"ledger_index": 3693379,
|
||||
"validated": true,
|
||||
"vault": {
|
||||
"Account": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
|
||||
},
|
||||
"Data": "7B226E223A20224C4154414D2046756E64204949222C202277223A20226578616D706C6566756E642E636F6D227D",
|
||||
"Flags": 65536,
|
||||
"LedgerEntryType": "Vault",
|
||||
"Owner": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
|
||||
"PreviousTxnLgrSeq": 3693379,
|
||||
"Sequence": 3693375,
|
||||
"ShareMPTID": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E",
|
||||
"WithdrawalPolicy": 1,
|
||||
"index": "3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D",
|
||||
"shares": {
|
||||
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
|
||||
"Flags": 60,
|
||||
"Issuer": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
|
||||
"PreviousTxnLgrSeq": 3693379,
|
||||
"Sequence": 1,
|
||||
"index": "EAD6924CB5DDA61CC5B85A6776A32E460FBFB0C34F5076A6A52005459B38043D",
|
||||
"mpt_issuance_id": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit into a Vault
|
||||
|
||||
```sh
|
||||
python deposit.py
|
||||
```
|
||||
|
||||
The script should output the vault state before and after the deposit, along with the depositor's share balance:
|
||||
|
||||
```sh
|
||||
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
|
||||
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
|
||||
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
|
||||
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
|
||||
|
||||
=== Getting initial vault state... ===
|
||||
- Total vault value: 1000
|
||||
- Available assets: 1000
|
||||
|
||||
=== Checking depositor's balance... ===
|
||||
Balance: 9000
|
||||
|
||||
=== VaultDeposit transaction ===
|
||||
{
|
||||
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
|
||||
"TransactionType": "VaultDeposit",
|
||||
"SigningPubKey": "",
|
||||
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
|
||||
"value": "1"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting VaultDeposit transaction... ===
|
||||
Deposit successful!
|
||||
|
||||
=== Vault state after deposit ===
|
||||
- Total vault value: 1001
|
||||
- Available assets: 1001
|
||||
|
||||
=== Depositor's share balance ===
|
||||
Shares held: 1001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Withdraw from a Vault
|
||||
|
||||
```sh
|
||||
python withdraw.py
|
||||
```
|
||||
|
||||
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
|
||||
|
||||
```sh
|
||||
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
|
||||
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
|
||||
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
|
||||
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
|
||||
|
||||
=== Getting initial vault state... ===
|
||||
Initial vault state:
|
||||
Assets Total: 1001
|
||||
Assets Available: 1001
|
||||
|
||||
=== Checking depositor's share balance... ===
|
||||
Shares held: 1001
|
||||
|
||||
=== Preparing VaultWithdraw transaction ===
|
||||
{
|
||||
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
|
||||
"TransactionType": "VaultWithdraw",
|
||||
"SigningPubKey": "",
|
||||
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
|
||||
"value": "1"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting VaultWithdraw transaction... ===
|
||||
Withdrawal successful!
|
||||
|
||||
=== Vault state after withdrawal ===
|
||||
Assets Total: 1000
|
||||
Assets Available: 1000
|
||||
|
||||
=== Depositor's share balance ==
|
||||
Shares held: 1000
|
||||
|
||||
=== Depositor's asset balance ==
|
||||
Balance: 9000
|
||||
```
|
||||
@@ -1,115 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import VaultCreate
|
||||
from xrpl.models.requests import VaultInfo
|
||||
from xrpl.models.transactions.vault_create import VaultCreateFlag, WithdrawalPolicy
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import str_to_hex, encode_mptoken_metadata
|
||||
from xrpl.wallet import generate_faucet_wallet
|
||||
|
||||
# Auto-run setup if needed
|
||||
if not os.path.exists("vault_setup.json"):
|
||||
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# Create and fund vault owner account
|
||||
vault_owner = generate_faucet_wallet(client)
|
||||
|
||||
# You can replace these values with your own
|
||||
mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
domain_id = setup_data["domain_id"]
|
||||
|
||||
print(f"Vault owner address: {vault_owner.address}")
|
||||
print(f"MPT issuance ID: {mpt_issuance_id}")
|
||||
print(f"Permissioned domain ID: {domain_id}\n")
|
||||
|
||||
# Prepare VaultCreate transaction ----------------------
|
||||
print("\n=== VaultCreate transaction ===")
|
||||
vault_create_tx = VaultCreate(
|
||||
account=vault_owner.address,
|
||||
asset={"mpt_issuance_id": mpt_issuance_id},
|
||||
flags=VaultCreateFlag.TF_VAULT_PRIVATE, # Omit TF_VAULT_PRIVATE flag for public vaults
|
||||
# To make vault shares non-transferable add the TF_VAULT_SHARE_NON_TRANSFERABLE flag:
|
||||
# flags=VaultCreateFlag.TF_VAULT_PRIVATE | VaultCreateFlag.TF_VAULT_SHARE_NON_TRANSFERABLE,
|
||||
domain_id=domain_id, # Omit for public vaults
|
||||
# Convert Vault data to a string (without excess whitespace), then string to hex.
|
||||
data=str_to_hex(json.dumps(
|
||||
{"n": "LATAM Fund II", "w": "examplefund.com"}
|
||||
)),
|
||||
# Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
|
||||
# See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
|
||||
mptoken_metadata=encode_mptoken_metadata({
|
||||
"ticker": "SHARE1",
|
||||
"name": "Vault shares",
|
||||
"desc": "Proportional ownership shares of the vault.",
|
||||
"icon": "example.com/asset-icon.png",
|
||||
"asset_class": "defi",
|
||||
"issuer_name": "Asset Issuer Name",
|
||||
"uris": [
|
||||
{
|
||||
"uri": "example.com/asset",
|
||||
"category": "website",
|
||||
"title": "Asset Website",
|
||||
},
|
||||
{
|
||||
"uri": "example.com/docs",
|
||||
"category": "docs",
|
||||
"title": "Docs",
|
||||
},
|
||||
],
|
||||
"additional_info": {
|
||||
"example_info": "test",
|
||||
},
|
||||
}),
|
||||
assets_maximum="0", # No cap
|
||||
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
|
||||
)
|
||||
|
||||
print(json.dumps(vault_create_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit, sign, and wait for validation ----------------------
|
||||
print("\n=== Submitting VaultCreate transaction... ===")
|
||||
submit_response = submit_and_wait(vault_create_tx, client, vault_owner, autofill=True)
|
||||
|
||||
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = submit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to create vault: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Vault created successfully!")
|
||||
|
||||
# Extract vault information from the transaction result
|
||||
affected_nodes = submit_response.result["meta"].get("AffectedNodes", [])
|
||||
vault_node = next(
|
||||
(node for node in affected_nodes
|
||||
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"),
|
||||
None
|
||||
)
|
||||
|
||||
if vault_node:
|
||||
print(f"\nVault ID: {vault_node['CreatedNode']['LedgerIndex']}")
|
||||
print(f"Vault pseudo-account address: {vault_node['CreatedNode']['NewFields']['Account']}")
|
||||
print(f"Share MPT issuance ID: {vault_node['CreatedNode']['NewFields']['ShareMPTID']}")
|
||||
|
||||
# Call vault_info method to retrieve the vault's information
|
||||
print("\n=== Getting vault_info... ===")
|
||||
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
|
||||
vault_info_response = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
print(json.dumps(vault_info_response.result, indent=2))
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
# IMPORTANT: This example deposits into an existing PRIVATE vault.
|
||||
# The depositor account used has valid credentials in the vault's Permissioned Domain.
|
||||
# Without valid credentials, the VaultDeposit transaction will fail.
|
||||
# If you want to deposit into a public vault, you can replace the vault_id and share_mpt_issuance_id
|
||||
# values with your own.
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import VaultDeposit
|
||||
from xrpl.models.requests import VaultInfo, LedgerEntry
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Auto-run setup if needed
|
||||
if not os.path.exists("vault_setup.json"):
|
||||
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# You can replace these values with your own
|
||||
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
|
||||
vault_id = setup_data["vault_id"]
|
||||
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
|
||||
|
||||
print(f"Depositor address: {depositor.address}")
|
||||
print(f"Vault ID: {vault_id}")
|
||||
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
|
||||
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
|
||||
|
||||
deposit_amount = 1
|
||||
|
||||
# Get initial vault state
|
||||
print("\n=== Getting initial vault state... ===")
|
||||
initial_vault_info = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
print(f" - Total vault value: {initial_vault_info.result['vault']['AssetsTotal']}")
|
||||
print(f" - Available assets: {initial_vault_info.result['vault']['AssetsAvailable']}")
|
||||
|
||||
# Check depositor's asset balance
|
||||
print("\n=== Checking depositor's balance... ===")
|
||||
try:
|
||||
# Use ledger_entry to get specific MPT issuance balance
|
||||
ledger_entry_result = client.request(
|
||||
LedgerEntry(
|
||||
mptoken={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"account": depositor.address
|
||||
},
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
balance = ledger_entry_result.result["node"]["MPTAmount"]
|
||||
print(f"Balance: {balance}")
|
||||
|
||||
# Check if balance is sufficient
|
||||
if int(balance) < deposit_amount:
|
||||
print(f"Error: Insufficient balance! Have {balance}, need {deposit_amount}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as error:
|
||||
error_data = getattr(error, 'data', {})
|
||||
if 'error' in error_data and error_data['error'] == 'entryNotFound':
|
||||
print(f"Error: The depositor doesn't hold any assets with ID: {asset_mpt_issuance_id}", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error checking MPT: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare VaultDeposit transaction
|
||||
print("\n=== VaultDeposit transaction ===")
|
||||
vault_deposit_tx = VaultDeposit(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"value": str(deposit_amount)
|
||||
}
|
||||
)
|
||||
|
||||
print(json.dumps(vault_deposit_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit VaultDeposit transaction
|
||||
print("\n=== Submitting VaultDeposit transaction... ===")
|
||||
deposit_result = submit_and_wait(vault_deposit_tx, client, depositor, autofill=True)
|
||||
|
||||
if deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = deposit_result.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to deposit: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Deposit successful!")
|
||||
|
||||
# Extract vault state from transaction metadata
|
||||
print("\n=== Vault state after deposit ===")
|
||||
affected_nodes = deposit_result.result["meta"]["AffectedNodes"]
|
||||
vault_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
modified = node["ModifiedNode"]
|
||||
if modified["LedgerEntryType"] == "Vault" and modified["LedgerIndex"] == vault_id:
|
||||
vault_node = node
|
||||
break
|
||||
|
||||
if vault_node:
|
||||
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
|
||||
print(f" - Total vault value: {vault_fields['AssetsTotal']}")
|
||||
print(f" - Available assets: {vault_fields['AssetsAvailable']}")
|
||||
|
||||
# Get the depositor's share balance
|
||||
print("\n=== Depositor's share balance ===")
|
||||
depositor_share_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
share_node = node["ModifiedNode"]
|
||||
fields = share_node["FinalFields"]
|
||||
elif "CreatedNode" in node:
|
||||
share_node = node["CreatedNode"]
|
||||
fields = share_node["NewFields"]
|
||||
else:
|
||||
continue
|
||||
|
||||
if (share_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
|
||||
depositor_share_node = node
|
||||
break
|
||||
|
||||
if depositor_share_node:
|
||||
if "ModifiedNode" in depositor_share_node:
|
||||
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
|
||||
else:
|
||||
share_fields = depositor_share_node["CreatedNode"]["NewFields"]
|
||||
print(f"Shares held: {share_fields['MPTAmount']}")
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
xrpl-py==4.5.0
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
|
||||
from xrpl.asyncio.clients import AsyncWebsocketClient
|
||||
from xrpl.asyncio.transaction import submit_and_wait
|
||||
from xrpl.asyncio.wallet import generate_faucet_wallet
|
||||
from xrpl.models import (
|
||||
Batch, BatchFlag, CredentialAccept, CredentialCreate, MPTokenAuthorize,
|
||||
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
|
||||
PermissionedDomainSet, VaultDeposit
|
||||
)
|
||||
from xrpl.models.requests import AccountObjects
|
||||
from xrpl.models.transactions.deposit_preauth import Credential
|
||||
from xrpl.models.transactions.vault_create import (
|
||||
VaultCreate, VaultCreateFlag, WithdrawalPolicy
|
||||
)
|
||||
from xrpl.utils import encode_mptoken_metadata, str_to_hex
|
||||
|
||||
|
||||
async def main():
|
||||
# Setup script for vault tutorials
|
||||
print("Setting up tutorial: 0/5", end="\r")
|
||||
|
||||
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
|
||||
# Create and fund all wallets concurrently
|
||||
mpt_issuer, domain_owner, depositor, vault_owner = await asyncio.gather(
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
generate_faucet_wallet(client),
|
||||
)
|
||||
|
||||
# Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
|
||||
print("Setting up tutorial: 1/5", end="\r")
|
||||
|
||||
cred_type = "VaultAccess"
|
||||
mpt_create_result, _ = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
MPTokenIssuanceCreate(
|
||||
account=mpt_issuer.address,
|
||||
flags=(
|
||||
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER |
|
||||
MPTokenIssuanceCreateFlag.TF_MPT_CAN_LOCK
|
||||
),
|
||||
asset_scale=2,
|
||||
transfer_fee=0,
|
||||
maximum_amount="1000000000000",
|
||||
mptoken_metadata=encode_mptoken_metadata({
|
||||
"ticker": "USTST",
|
||||
"name": "USTST Stablecoin",
|
||||
"desc": "A test stablecoin token",
|
||||
"icon": "example.org/ustst-icon.png",
|
||||
"asset_class": "rwa",
|
||||
"asset_subclass": "stablecoin",
|
||||
"issuer_name": "Test Stablecoin Inc",
|
||||
"uris": [
|
||||
{
|
||||
"uri": "example.org/ustst",
|
||||
"category": "website",
|
||||
"title": "USTST Official Website",
|
||||
},
|
||||
{
|
||||
"uri": "example.org/ustst/reserves",
|
||||
"category": "attestation",
|
||||
"title": "Reserve Attestation Reports",
|
||||
},
|
||||
{
|
||||
"uri": "example.org/ustst/docs",
|
||||
"category": "docs",
|
||||
"title": "USTST Documentation",
|
||||
},
|
||||
],
|
||||
"additional_info": {
|
||||
"backing": "USD",
|
||||
"reserve_ratio": "1:1",
|
||||
},
|
||||
}),
|
||||
),
|
||||
client,
|
||||
mpt_issuer,
|
||||
autofill=True
|
||||
),
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=domain_owner.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
PermissionedDomainSet(
|
||||
account=domain_owner.address,
|
||||
accepted_credentials=[
|
||||
Credential(
|
||||
issuer=domain_owner.address,
|
||||
credential_type=str_to_hex(cred_type)
|
||||
)
|
||||
],
|
||||
),
|
||||
CredentialCreate(
|
||||
account=domain_owner.address,
|
||||
subject=depositor.address,
|
||||
credential_type=str_to_hex(cred_type),
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
domain_owner,
|
||||
autofill=True
|
||||
)
|
||||
)
|
||||
|
||||
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
|
||||
|
||||
# Get domain ID
|
||||
domain_owner_objects = await client.request(AccountObjects(
|
||||
account=domain_owner.address,
|
||||
ledger_index="validated",
|
||||
))
|
||||
domain_id = next(
|
||||
node["index"]
|
||||
for node in domain_owner_objects.result["account_objects"]
|
||||
if node["LedgerEntryType"] == "PermissionedDomain"
|
||||
)
|
||||
|
||||
# Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
|
||||
print("Setting up tutorial: 2/5", end="\r")
|
||||
|
||||
_, vault_create_result = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
Batch(
|
||||
account=depositor.address,
|
||||
flags=BatchFlag.TF_ALL_OR_NOTHING,
|
||||
raw_transactions=[
|
||||
CredentialAccept(
|
||||
account=depositor.address,
|
||||
issuer=domain_owner.address,
|
||||
credential_type=str_to_hex(cred_type),
|
||||
),
|
||||
MPTokenAuthorize(
|
||||
account=depositor.address,
|
||||
mptoken_issuance_id=mpt_issuance_id,
|
||||
),
|
||||
],
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
autofill=True
|
||||
),
|
||||
submit_and_wait(
|
||||
VaultCreate(
|
||||
account=vault_owner.address,
|
||||
asset={"mpt_issuance_id": mpt_issuance_id},
|
||||
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
|
||||
domain_id=domain_id,
|
||||
data=str_to_hex(json.dumps(
|
||||
{"n": "LATAM Fund II", "w": "examplefund.com"}
|
||||
)),
|
||||
mptoken_metadata=encode_mptoken_metadata({
|
||||
"ticker": "SHARE1",
|
||||
"name": "Vault Shares",
|
||||
"desc": "Proportional ownership shares of the vault",
|
||||
"icon": "example.com/vault-shares-icon.png",
|
||||
"asset_class": "defi",
|
||||
"issuer_name": "Vault Owner",
|
||||
"uris": [
|
||||
{
|
||||
"uri": "example.com/asset",
|
||||
"category": "website",
|
||||
"title": "Asset Website",
|
||||
},
|
||||
{
|
||||
"uri": "example.com/docs",
|
||||
"category": "docs",
|
||||
"title": "Docs",
|
||||
},
|
||||
],
|
||||
"additional_info": {
|
||||
"example_info": "test",
|
||||
},
|
||||
}),
|
||||
assets_maximum="0",
|
||||
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
|
||||
),
|
||||
client,
|
||||
vault_owner,
|
||||
autofill=True
|
||||
),
|
||||
)
|
||||
|
||||
# Extract vault_id and vault_share_mpt_issuance_id
|
||||
vault_node = next(
|
||||
node for node in vault_create_result.result["meta"]["AffectedNodes"]
|
||||
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"
|
||||
)
|
||||
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
|
||||
vault_share_mpt_issuance_id = vault_node["CreatedNode"]["NewFields"]["ShareMPTID"]
|
||||
|
||||
# Step 3: Issuer sends payment to depositor
|
||||
print("Setting up tutorial: 3/5", end="\r")
|
||||
|
||||
payment_result = await submit_and_wait(
|
||||
Payment(
|
||||
account=mpt_issuer.address,
|
||||
destination=depositor.address,
|
||||
amount={
|
||||
"mpt_issuance_id": mpt_issuance_id,
|
||||
"value": "10000",
|
||||
},
|
||||
),
|
||||
client,
|
||||
mpt_issuer,
|
||||
autofill=True
|
||||
)
|
||||
|
||||
if payment_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"\nPayment failed: {payment_result.result['meta']['TransactionResult']}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 4: Make an initial deposit so withdraw example has shares to work with
|
||||
print("Setting up tutorial: 4/5", end="\r")
|
||||
|
||||
initial_deposit_result = await submit_and_wait(
|
||||
VaultDeposit(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount={
|
||||
"mpt_issuance_id": mpt_issuance_id,
|
||||
"value": "1000",
|
||||
},
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
autofill=True
|
||||
)
|
||||
|
||||
if initial_deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
print(f"\nInitial deposit failed: {initial_deposit_result.result['meta']['TransactionResult']}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 5: Save setup data to file
|
||||
print("Setting up tutorial: 5/5", end="\r")
|
||||
|
||||
setup_data = {
|
||||
"mpt_issuer": {
|
||||
"address": mpt_issuer.address,
|
||||
"seed": mpt_issuer.seed,
|
||||
},
|
||||
"mpt_issuance_id": mpt_issuance_id,
|
||||
"domain_owner": {
|
||||
"address": domain_owner.address,
|
||||
"seed": domain_owner.seed,
|
||||
},
|
||||
"domain_id": domain_id,
|
||||
"credential_type": cred_type,
|
||||
"depositor": {
|
||||
"address": depositor.address,
|
||||
"seed": depositor.seed,
|
||||
},
|
||||
"vault_owner": {
|
||||
"address": vault_owner.address,
|
||||
"seed": vault_owner.seed,
|
||||
},
|
||||
"vault_id": vault_id,
|
||||
"vault_share_mpt_issuance_id": vault_share_mpt_issuance_id,
|
||||
}
|
||||
|
||||
with open("vault_setup.json", "w") as f:
|
||||
json.dump(setup_data, f, indent=2)
|
||||
|
||||
print("Setting up tutorial: Complete!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,164 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.models import VaultWithdraw
|
||||
from xrpl.models.requests import VaultInfo, LedgerEntry
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
# Auto-run setup if needed
|
||||
if not os.path.exists("vault_setup.json"):
|
||||
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
|
||||
subprocess.run([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# You can replace these values with your own
|
||||
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
|
||||
vault_id = setup_data["vault_id"]
|
||||
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
|
||||
|
||||
print(f"Depositor address: {depositor.address}")
|
||||
print(f"Vault ID: {vault_id}")
|
||||
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
|
||||
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
|
||||
|
||||
withdraw_amount = 1
|
||||
|
||||
# Get initial vault state
|
||||
print("\n=== Getting initial vault state... ===")
|
||||
initial_vault_info = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
print("Initial vault state:")
|
||||
print(f" Assets Total: {initial_vault_info.result['vault']['AssetsTotal']}")
|
||||
print(f" Assets Available: {initial_vault_info.result['vault']['AssetsAvailable']}")
|
||||
|
||||
# Check depositor's share balance
|
||||
print("\n=== Checking depositor's share balance... ===")
|
||||
try:
|
||||
share_balance_result = client.request(
|
||||
LedgerEntry(
|
||||
mptoken={
|
||||
"mpt_issuance_id": share_mpt_issuance_id,
|
||||
"account": depositor.address
|
||||
},
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
share_balance = share_balance_result.result["node"]["MPTAmount"]
|
||||
print(f"Shares held: {share_balance}")
|
||||
except Exception as error:
|
||||
error_data = getattr(error, 'data', {})
|
||||
if 'error' in error_data and error_data['error'] == 'entryNotFound':
|
||||
print(f"Error: The depositor doesn't hold any vault shares with ID: {share_mpt_issuance_id}.", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error checking MPT: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare VaultWithdraw transaction
|
||||
print("\n=== Preparing VaultWithdraw transaction ===")
|
||||
vault_withdraw_tx = VaultWithdraw(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"value": str(withdraw_amount)
|
||||
}
|
||||
# Optional: Add destination field to send assets to a different account
|
||||
# destination="rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
|
||||
)
|
||||
|
||||
print(json.dumps(vault_withdraw_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit VaultWithdraw transaction
|
||||
print("\n=== Submitting VaultWithdraw transaction... ===")
|
||||
withdraw_result = submit_and_wait(vault_withdraw_tx, client, depositor, autofill=True)
|
||||
|
||||
if withdraw_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = withdraw_result.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to withdraw from vault: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Withdrawal successful!")
|
||||
|
||||
# Extract vault state from transaction metadata
|
||||
print("\n=== Vault state after withdrawal ===")
|
||||
affected_nodes = withdraw_result.result["meta"]["AffectedNodes"]
|
||||
vault_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node or "DeletedNode" in node:
|
||||
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
|
||||
if modified_node["LedgerEntryType"] == "Vault" and modified_node["LedgerIndex"] == vault_id:
|
||||
vault_node = node
|
||||
break
|
||||
|
||||
if vault_node:
|
||||
if "DeletedNode" in vault_node:
|
||||
print(" Vault empty (all assets withdrawn)")
|
||||
else:
|
||||
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
|
||||
print(f" Assets Total: {vault_fields['AssetsTotal']}")
|
||||
print(f" Assets Available: {vault_fields['AssetsAvailable']}")
|
||||
|
||||
# Get the depositor's share balance
|
||||
print("\n=== Depositor's share balance ===")
|
||||
depositor_share_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node or "DeletedNode" in node:
|
||||
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
|
||||
if "FinalFields" in modified_node:
|
||||
fields = modified_node["FinalFields"]
|
||||
if (modified_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
|
||||
depositor_share_node = node
|
||||
break
|
||||
|
||||
if depositor_share_node:
|
||||
if "DeletedNode" in depositor_share_node:
|
||||
print("No more shares held (redeemed all shares)")
|
||||
else:
|
||||
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
|
||||
print(f"Shares held: {share_fields['MPTAmount']}")
|
||||
|
||||
# Get the depositor's asset balance
|
||||
print("\n=== Depositor's asset balance ===")
|
||||
depositor_asset_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
asset_node = node["ModifiedNode"]
|
||||
fields = asset_node["FinalFields"]
|
||||
elif "CreatedNode" in node:
|
||||
asset_node = node["CreatedNode"]
|
||||
fields = asset_node["NewFields"]
|
||||
else:
|
||||
continue
|
||||
|
||||
if (asset_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == asset_mpt_issuance_id):
|
||||
depositor_asset_node = node
|
||||
break
|
||||
|
||||
if depositor_asset_node:
|
||||
if "ModifiedNode" in depositor_asset_node:
|
||||
asset_fields = depositor_asset_node["ModifiedNode"]["FinalFields"]
|
||||
else:
|
||||
asset_fields = depositor_asset_node["CreatedNode"]["NewFields"]
|
||||
print(f"Balance: {asset_fields['MPTAmount']}")
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-02-18"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
title: GPG Key Rotation
|
||||
description: Ripple has rotated the GPG key used to sign rippled packages.
|
||||
labels:
|
||||
- Advisories
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# GPG Key Rotation
|
||||
|
||||
Ripple has rotated the GPG key used to sign `rippled` packages. If you have an existing installation, you should download and trust the new key to prevent issues upgrading in the future. **Automatic upgrades will not work** until you have trusted the new key.
|
||||
|
||||
## Action Needed
|
||||
|
||||
Add Ripple's package-signing GPG key, then verify the fingerprint of the newly-added key.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Red Hat / CentOS" %}
|
||||
|
||||
```bash
|
||||
sudo rpm --import https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
|
||||
rpm -qi gpg-pubkey-ab06faa6 | gpg --show-keys --fingerprint
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Ubuntu / Debian" %}
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings && \
|
||||
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/ripple.gpg > /dev/null
|
||||
gpg --show-keys --fingerprint /etc/apt/keyrings/ripple.gpg
|
||||
```
|
||||
|
||||
Ensure the `signed-by` path in your Ripple source list refers to the location the key was downloaded. For example, on an Ubuntu 22.04 Jammy installation, `/etc/apt/sources.list.d/ripple.list` would contain:
|
||||
|
||||
```
|
||||
deb [signed-by=/etc/apt/keyrings/ripple.gpg] https://repos.ripple.com/repos/rippled-deb jammy stable
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The output should include an entry for Ripple such as the following:
|
||||
|
||||
```
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
{% admonition type="danger" name="Warning" %}
|
||||
Only trust this key if its fingerprint exactly matches: `E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6`.
|
||||
{% /admonition %}
|
||||
@@ -10,7 +10,6 @@
|
||||
- group: '2026'
|
||||
expanded: false
|
||||
items:
|
||||
- page: 2026/gpg-key-rotation.md
|
||||
- page: 2026/rippled-3.1.0.md
|
||||
- page: 2026/clio-2.7.0.md
|
||||
- group: '2025'
|
||||
|
||||
@@ -370,8 +370,7 @@
|
||||
[gateway_balances method]: /docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances.md
|
||||
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||
[Get Started Using JavaScript]: /docs/tutorials/get-started/get-started-javascript.md
|
||||
[Get Started Using Python]: /docs/tutorials/get-started/get-started-python.md
|
||||
[Get Started Using JavaScript]: /docs/tutorials/get-started/get-started-javascript
|
||||
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
|
||||
[identifying hash]: /docs/concepts/transactions/index.md#identifying-transactions
|
||||
[json command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
|
||||
@@ -488,4 +487,3 @@
|
||||
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
|
||||
[xrpl-py library]: https://github.com/XRPLF/xrpl-py
|
||||
|
||||
@@ -75,7 +75,7 @@ If a standby address is compromised, the consequences are like an operational ad
|
||||
- [Cryptographic Keys](cryptographic-keys.md)
|
||||
- **Tutorials:**
|
||||
- [Assign a Regular Key Pair](../../tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [Remove a Regular Key Pair](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [account_info method][]
|
||||
- [SetRegularKey transaction][]
|
||||
|
||||
@@ -251,7 +251,7 @@ The steps to derive the XRP Ledger's secp256k1 account key pair from a seed valu
|
||||
- [Issuing and Operational Addresses](account-types.md)
|
||||
- **Tutorials:**
|
||||
- [Assign a Regular Key Pair](../../tutorials/best-practices/key-management/assign-a-regular-key-pair.md)
|
||||
- [Remove a Regular Key Pair](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [SetRegularKey transaction][]
|
||||
- [AccountRoot ledger object](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md)
|
||||
|
||||
@@ -170,7 +170,7 @@ The difference between the two transaction failure cases (labeled (1) and (2) in
|
||||
|
||||
{% admonition type="success" name="Tip" %}The [`AccountTxnID` field](../../references/protocol/transactions/common-fields.md#accounttxnid) can help prevent redundant transactions from succeeding in this situation.{% /admonition %}
|
||||
|
||||
- A malicious actor may have used your secret key to send a transaction. If this is the case, [rotate your key pair](../../tutorials/best-practices/key-management/remove-a-regular-key-pair.md) if you can, and check for other transactions sent. You should also audit your network to determine if the secret key was part of a larger intrusion or security leak. When you successfully rotate your key pair and are certain that the malicious actor no longer has access to your accounts and systems, you can resume normal activities.
|
||||
- A malicious actor may have used your secret key to send a transaction. If this is the case, [rotate your key pair](../../tutorials/best-practices/key-management/change-or-remove-a-regular-key-pair.md) if you can, and check for other transactions sent. You should also audit your network to determine if the secret key was part of a larger intrusion or security leak. When you successfully rotate your key pair and are certain that the malicious actor no longer has access to your accounts and systems, you can resume normal activities.
|
||||
|
||||
|
||||
#### Ledger Gaps
|
||||
|
||||
@@ -81,10 +81,10 @@ Before you install Clio, you must meet the following requirements.
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@ Before you install `rippled`, you must meet the [System Requirements](system-req
|
||||
The output should include an entry for Ripple such as the following:
|
||||
|
||||
```
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
```
|
||||
|
||||
In particular, make sure that the fingerprint matches. (In the above example, the fingerprint is on the second line, starting with `C001`.)
|
||||
|
||||
@@ -68,10 +68,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
|
||||
| `LoanBrokerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `LoanBroker` owner directory. |
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the _Loan Broker_ associated with this loan. |
|
||||
| `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. |
|
||||
| `LoanOriginationFee` | String | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
|
||||
| `LoanServiceFee` | String | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
|
||||
| `LatePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
|
||||
| `ClosePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
|
||||
| `LoanOriginationFee` | Number | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
|
||||
| `LoanServiceFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
|
||||
| `LatePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
|
||||
| `ClosePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
|
||||
| `OverpaymentFee` | Number | UInt32 | Yes | The fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `InterestRate` | Number | UInt32 | Yes | The annualized interest rate of the loan, in 1/10th basis points. |
|
||||
| `LateInterestRate` | Number | UInt32 | Yes | The premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
@@ -83,10 +83,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
|
||||
| `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. |
|
||||
| `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. |
|
||||
| `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. |
|
||||
| `PrincipalOutstanding` | String | Number | Yes | The principal amount still owed on the loan. |
|
||||
| `TotalValueOutstanding` | String | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
|
||||
| `ManagementFeeOutstanding` | String | Number | Yes | The remaining management fee owed to the loan broker. |
|
||||
| `PeriodicPayment` | String | Number | Yes | The amount due for each payment interval. |
|
||||
| `PrincipalOutstanding` | Number | Number | Yes | The principal amount still owed on the loan. |
|
||||
| `TotalValueOutstanding` | Number | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
|
||||
| `ManagementFeeOutstanding` | Number | Number | Yes | The remaining management fee owed to the loan broker. |
|
||||
| `PeriodicPayment` | Number | Number | Yes | The amount due for each payment interval. |
|
||||
| `LoanScale` | Number | Int32 | No | The scale factor that ensures all computed amounts are rounded to the same number of decimal places. It is based on the total loan value at creation time. |
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
@@ -115,6 +115,7 @@ When the loan broker discovers that the borrower can't make an upcoming payment,
|
||||
The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order:
|
||||
|
||||
- The `Loan` space key `0x004C`.
|
||||
- The [AccountID][] of the Borrower account.
|
||||
- The `LoanBrokerID` of the associated `LoanBroker` ledger entry.
|
||||
- The `LoanSequence` number of the `LoanBroker` ledger entry.
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
|
||||
| `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. |
|
||||
| `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. |
|
||||
| `DebtTotal` | String | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
|
||||
| `DebtMaximum` | String | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
|
||||
| `CoverAvailable` | String | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
|
||||
| `DebtTotal` | Number | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
|
||||
| `DebtMaximum` | Number | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
|
||||
| `CoverAvailable` | Number | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
|
||||
| `CoverRateMinimum` | Number | UInt32 | Yes | The 1/10th basis point of the `DebtTotal` that the first-loss capital must cover. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `CoverRateLiquidation`| Number | UInt12 | Yes | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@ The `LoanBrokerCoverClawback` transaction claws back first-loss capital from a `
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. |
|
||||
| `Amount` | [Currency Amount][] | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). |
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. |
|
||||
| `Amount` | Object | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
@@ -40,10 +40,10 @@ Only the owner of the associated `LoanBroker` entry can initiate this transactio
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to deposit. |
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. |
|
||||
| `Amount` | Object | Amount | Yes | The amount of first-loss capital to deposit. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
@@ -40,11 +40,11 @@ Only the owner of the associated `LoanBroker` entry can initiate this transactio
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to withdraw. |
|
||||
| `Destination` | String | AccountID | No | An account to receive the assets. |
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:-------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
|
||||
| `Amount` | Object | Amount | Yes | The amount of first-loss capital to withdraw. |
|
||||
| `Destination` | String | AccountID | No | An account to receive the assets. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
@@ -45,7 +45,7 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
|
||||
| `LoanBrokerID` | String | Hash256 | No | The loan broker ID that the transaction is modifying. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata in hex format--limited to 256 bytes. |
|
||||
| `ManagementFeeRate` | Number | UInt16 | No | The 1/10th basis point fee charged by the lending protocol owner. Valid values range from `0` to `10000` (inclusive), representing 0% to 10%. |
|
||||
| `DebtMaximum` | String | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
|
||||
| `DebtMaximum` | Number | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
|
||||
| `CoverRateMinimum` | Number | UInt32 | No | The 1/10th basis point `DebtTotal` that the first-loss capital must cover. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |
|
||||
| `CoverRateLiquidation` | Number | UInt32 | No | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:-------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to manage. |
|
||||
| `Flags` | String | UInt32 | No | The flag to modify the loan. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [`flags` field], as follows:
|
||||
|
||||
| Field Name | Hex Value | Decimal Value | Description |
|
||||
|:----------------|:-------------|:--------------|:------------|
|
||||
|
||||
@@ -43,21 +43,20 @@ To see how loan payment transactions are calculated, see [transaction pseudo-cod
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:--------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount to pay toward the loan. |
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:--------------- |:----------|:-------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. |
|
||||
| `Amount` | Number | Amount | Yes | The amount to pay toward the loan. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
|
||||
|
||||
| Flag Name | Hex Value | Decimal Value | Description |
|
||||
|:--------------------|:-------------|:--------------|:------------|
|
||||
| `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the remaining payment amount should be treated as an overpayment. |
|
||||
| `tfLoanFullPayment` | `0x00020000` | 131072 | Indicates that the borrower is making a full early repayment. |
|
||||
| `tfLoanLatePayment` | `0x00040000` | 262144 | Indicates that the borrower is making a late loan payment. |
|
||||
| Flag Name | Hex Value | Decimal Value | Description |
|
||||
|:----------|:----------|:--------------|:------------|
|
||||
| `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the remaining payment amount should be treated as an overpayment. |
|
||||
| `tfLoanFullPayment` | `0x00020000` | 131072 | Indicates that the borrower is making a full early repayment. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
@@ -58,18 +58,19 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:--------------------------|:----------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry. |
|
||||
| `Flags` | String | UInt32 | No | Flags for the loan. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata in hex format (max 256 bytes). |
|
||||
| `Counterparty` | String | AccountID | No | The address of the counterparty of the loan. |
|
||||
| `LoanOriginationFee` | String | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. |
|
||||
| `LoanServiceFee` | String | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. |
|
||||
| `LatePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for late payments. |
|
||||
| `ClosePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. |
|
||||
| `LoanOriginationFee` | Number | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. |
|
||||
| `LoanServiceFee` | Number | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. |
|
||||
| `LatePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for late payments. |
|
||||
| `ClosePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. |
|
||||
| `OverpaymentFee` | Number | UInt32 | No | A fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `InterestRate` | Number | UInt32 | No | The annualized interest rate of the loan, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `LateInterestRate` | Number | UInt32 | No | A premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `CloseInterestRate` | Number | UInt32 | No | A fee charged for repaying the loan early, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `OverpaymentInterestRate` | Number | UInt32 | No | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `PrincipalRequested` | String | Number | Yes | The principal loan amount requested by the borrower. |
|
||||
| `PrincipalRequested` | Number | Number | Yes | The principal loan amount requested by the borrower. |
|
||||
| `PaymentTotal` | Number | UInt32 | No | The total number of payments to be made against the loan. |
|
||||
| `PaymentInterval` | Number | UInt32 | No | The number of seconds between loan payments. |
|
||||
| `GracePeriod` | Number | UInt32 | No | The number of seconds after the loan's payment due date when it can be defaulted. |
|
||||
@@ -112,7 +113,7 @@ Besides errors that can occur for all transactions, {% code-page-name /%} transa
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:-----------------------------------|
|
||||
| `temBAD_SIGNER` | - The transaction is missing a `CounterpartySignature` field.<br>- This transaction is part of a `Batch` transaction, but didn't specify a `Counterparty`. |
|
||||
| `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval` or less than `60` seconds. |
|
||||
| `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval`. |
|
||||
| `tecNO_ENTRY` | The `LoanBroker` doesn't exist. |
|
||||
| `tecNO_PERMISSION` | Neither the transaction sender's `Account` or the `Counterparty` field owns the associated `LoanBroker` ledger entry. |
|
||||
| `tecINSUFFICIENT_FUNDS` | - The `Vault` associated with the `LoanBroker` doesn't have enough assets to fund the loan.<br>- The `LoanBroker` ledger entry doesn't have enough first-loss capital to meet the minimum coverage requirement for the new total debt. |
|
||||
|
||||
@@ -9,7 +9,7 @@ labels:
|
||||
|
||||
This tutorial shows how to authorize a secondary key pair, called a _[regular key pair](../../../concepts/accounts/cryptographic-keys.md)_, to sign future transactions. Unlike the master key pair, which is mathematically linked to the account's address, you can remove or replace the regular key pair, which is better for security.
|
||||
|
||||
You can use these steps to assign a regular key pair for the first time or to replace an existing regular key pair with a new one. You can use this same process for key rotation as a proactive security measure.
|
||||
You can use these steps to assign a regular key pair for the first time or to replace an existing regular key pair with a new one.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -97,7 +97,7 @@ Use the [`Wallet.create()` class method](https://xrpl-py.readthedocs.io/en/stabl
|
||||
|
||||
Use a [SetRegularKey transaction][] to assign the new key pair to your account as a regular key pair.
|
||||
|
||||
{% admonition type="success" name="Tip: Rotating a Regular Key" %}This example signs the transaction using the master key pair, but you could also use an existing regular key pair, or a [multi-signing list](../../../concepts/accounts/multi-signing.md) if your account has multi-signing set up.{% /admonition %}
|
||||
{% admonition type="success" name="Tip" %}This example signs the transaction using the master key pair, but you could also use an existing regular key pair, or a [multi-signing list](../../../concepts/accounts/multi-signing.md) if your account has multi-signing set up.{% /admonition %}
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
@@ -156,7 +156,7 @@ Now that you're familiar with the benefits of assigning a regular key pair to an
|
||||
- [Multi-Signing](../../../concepts/accounts/multi-signing.md)
|
||||
- [Issuing and Operational Addresses](../../../concepts/accounts/account-types.md)
|
||||
- **Tutorials:**
|
||||
- [Remove a Regular Key Pair](remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](change-or-remove-a-regular-key-pair.md)
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- [List XRP as an Exchange](../../../use-cases/defi/list-xrp-as-an-exchange.md)
|
||||
- **References:**
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
---
|
||||
seo:
|
||||
description: Delete an account, sending its remaining XRP to another account.
|
||||
labels:
|
||||
- Accounts
|
||||
---
|
||||
# Delete an Account
|
||||
|
||||
This tutorial shows how to delete an [account](../../../concepts/accounts/index.md) from the XRP Ledger, including checking that it meets the [requirements for deletion](../../../concepts/accounts/deleting-accounts.md).
|
||||
|
||||
## Goals
|
||||
|
||||
By following this tutorial, you should learn how to:
|
||||
|
||||
- Check if an account can be deleted.
|
||||
- Delete an account.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger [client library](../../../references/client-libraries.md), such as **xrpl.js**, installed.
|
||||
- Have an XRP Ledger Testnet account to delete. If you create a new account as part of the tutorial, you must wait about 15 minutes for it to become eligible for deletion.
|
||||
- Know an address where you want to send the deleted account's remaining XRP. For this tutorial, you can use the address `rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo` to return funds to the Testnet faucet.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Connect and get accounts
|
||||
|
||||
To get started, import the client library and instantiate an API client. To delete an account, you need the address of an account to receive the deleted account's remaining XRP.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
The sample code also imports `dotenv` so that it can load environment variables from a `.env` file.
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" before="// Load the account to delete" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
The sample code also imports `python-dotenv` so that it can load environment variables from a `.env` file.
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" before="# Load the account to delete" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You need to instantiate a wallet instance for the account you want to delete. Since you can only delete an account that is at least ~15 minutes old, the sample code loads a seed value from a `.env` file. If you don't have an account seed defined in the `.env` file, you can get a new account from the faucet, but it won't be possible to delete it right away.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Load the account to delete" before="// Check account info" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Load the account to delete" before="# Check account info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check to see if the account can be deleted
|
||||
|
||||
Before deleting an account, you should check that it meets the requirements for deletion.
|
||||
|
||||
#### 3.1. Get account info
|
||||
|
||||
The first step to checking if an account can be deleted is to get its account info as of the latest validated ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check account info" before="// Check if sequence number is too high" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check account info" before="# Check if sequence number is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.2. Check sequence number
|
||||
|
||||
Compare the account's current sequence number, in the `Sequence` field of the account data, is low enough compared with the latest validated ledger index. For the account to be deletable, its sequence number plus 256 must be lower than the ledger index.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if sequence number is too high" before="// Check if owner count is too high" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if sequence number is too high" before="# Check if owner count is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.3. Check owner count
|
||||
|
||||
Check the `OwnerCount` field of the account data to see if the account owns too many other ledger entries. For an account to be deletable, it must own less than 1000 entries (of any type) in the ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if owner count is too high" before="// Check if XRP balance is high enough" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if owner count is too high" before="# Check if XRP balance is high enough" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.4. Check XRP balance
|
||||
|
||||
Deleting an account requires a special [transaction cost][] equal to the incremental owner reserve, so an account can't be deleted if its current XRP balance is less than that. To check if an account has enough XRP, use the [server_state method][] to look up the current incremental reserve and compare with the account's XRP balance in the `Balance` field of the account data.
|
||||
|
||||
{% admonition type="warning" name="Caution" %}The [server_info method][] returns reserve values as decimal XRP, whereas [server_state][server_state method] returns drops of XRP. An account's `Balance` field is in drops of XRP. Be sure to compare equivalent units! (1 XRP = 1 million drops){% /admonition %}
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if XRP balance is high enough" before="if (numProblems) {" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if XRP balance is high enough" before="if num_problems:" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If this or any of the previous checks failed, you can't delete the account.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="if (numProblems) {" before="// Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="if num_problems:" before="# Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check for deletion blockers and remove them if possible
|
||||
|
||||
Some types of ledger entry can block an account from being deleted. You can check for these types of entries using the [account_objects method][] with the `"deletion_blockers_only": true` parameter.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check for deletion blockers" before="// Delete the account" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check for deletion blockers" before="# Delete the account" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the account has deletion blockers, you may or may not be able to remove them by sending other transactions, depending on the ledger entry. For example, if one of the blockers is a `RippleState` entry, you may be able to remove it by reducing your balance to zero through payments or offers and using a [TrustSet transaction][] to return your settings to the default state. Since there are many possibilities, the sample code does not show how to remove deletion blockers.
|
||||
|
||||
### 5. Delete the account
|
||||
|
||||
If all the checks passed, send an [AccountDelete transaction][] to delete the account. Since this transaction type requires a much higher [transaction cost][] than normal, it's a good idea to submit the transaction with the "fail hard" setting enabled. This can save you from paying the transaction cost if the transaction was going to fail with a [`tec` result code](../../../references/protocol/transactions/transaction-results/tec-codes.md). (Fail hard stops the server from relaying the transaction to the network if the transaction provisionally fails, which catches common errors before the transaction can achieve a consensus.)
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Delete the account" before="// Check result of the AccountDelete transaction" /%}
|
||||
|
||||
If the transaction is successful, you can use [getBalanceChanges(...)](https://js.xrpl.org/functions/getBalanceChanges.html) to check the metadata and see how much XRP was delivered from the deleted account to the destination account.
|
||||
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check result of the AccountDelete transaction" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Delete the account" before="# Check result of the AccountDelete transaction" /%}
|
||||
|
||||
If the transaction is successful, you can use [get_balance_changes(...)](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.utils.html#xrpl.utils.get_balance_changes) to check the metadata and see how much XRP was delivered from the deleted account to the destination account.
|
||||
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check result of the AccountDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Deleting Accounts](../../../concepts/accounts/deleting-accounts.md)
|
||||
- [Transaction Cost](../../../concepts/transactions/transaction-cost.md)
|
||||
- **References:**
|
||||
- [AccountDelete transaction][]
|
||||
- [account_info method][]
|
||||
- [account_objects method][]
|
||||
- [server_state method][]
|
||||
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -137,7 +137,7 @@ For more information about this and related topics, see:
|
||||
- [Account Types](../../../concepts/accounts/account-types.md)
|
||||
- **Tutorials:**
|
||||
- [Assign a Regular Key Pair](assign-a-regular-key-pair.md)
|
||||
- [Remove a Regular Key Pair](remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](change-or-remove-a-regular-key-pair.md)
|
||||
- [Set Up Multi-Signing](set-up-multi-signing.md)
|
||||
- **References:**
|
||||
- [account_info method][]
|
||||
|
||||
@@ -127,7 +127,7 @@ There currently is no way to have more than one multi-signing list assigned to y
|
||||
At this point, you can [send a multi-signed transaction](send-a-multi-signed-transaction.md). You may also want to:
|
||||
|
||||
* [Disable the master key pair](disable-master-key-pair.md).
|
||||
* [Remove the regular key pair](remove-a-regular-key-pair.md) (if you previously set one)
|
||||
* [Remove the regular key pair](change-or-remove-a-regular-key-pair.md) (if you previously set one)
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ labels:
|
||||
- Decentralized Finance
|
||||
- Permissioned Domains
|
||||
---
|
||||
# Create Permissioned Domains in JavaScript
|
||||
# Create Permissioned Domains
|
||||
|
||||
Permissioned domains are controlled environments within the broader ecosystem of the XRP Ledger blockchain. Domains restrict access to other features such as Permissioned DEXes and Lending Protocols, only allowing access to them for accounts with specific credentials.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Add Assets to an AMM in JavaScript
|
||||
# Add Assets to an AMM
|
||||
|
||||
Follow the steps from the [Create an AMM](./create-an-automated-market-maker.md) tutorial before proceeding.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Trade with an AMM Auction Slot in JavaScript
|
||||
# Trade with an AMM Auction Slot
|
||||
|
||||
Follow the steps from the [Create an AMM](./create-an-automated-market-maker.md) tutorial before proceeding.
|
||||
|
||||
|
||||
14
docs/tutorials/defi/lending/index.md
Normal file
14
docs/tutorials/defi/lending/index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
seo:
|
||||
description: Pool funds in a single asset vault and enable on-chain, fixed-term, uncollateralized loans.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
- Single Asset Vault
|
||||
---
|
||||
# Set Up Lending
|
||||
|
||||
Pool funds in a single asset vault and enable on-chain, fixed-term, uncollateralized loans. The lending protocol is highly configurable, enabling loan brokers to easily tune risk appetite, depostitor protections, and economic incentives.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -35,8 +35,7 @@ To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../get-started/get-started-javascript.md) for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -48,22 +47,12 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
@@ -71,20 +60,13 @@ pip install -r requirements.txt
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" before="// Create and fund" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" before="# Create and fund" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, fund a vault owner account, define the MPT issuance ID for the vault's asset, and provide a permissioned domain ID to control who can deposit into the vault.
|
||||
@@ -92,17 +74,11 @@ Next, fund a vault owner account, define the MPT issuance ID for the vault's ass
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Create and fund" before="// Prepare VaultCreate" /%}
|
||||
|
||||
The example uses an existing MPT issuance and permissioned domain data from the `vaultSetup.js` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domainID`.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Create and fund" before="# Prepare VaultCreate" /%}
|
||||
|
||||
The example uses an existing MPT issuance and permissioned domain data from the `vault_setup.py` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domain_id`.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The example uses an existing MPT issuance and permissioned domain data from the `vaultSetup.js` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domainId`.
|
||||
|
||||
### 3. Prepare VaultCreate transaction
|
||||
|
||||
Create the [VaultCreate transaction][] object:
|
||||
@@ -110,24 +86,14 @@ Create the [VaultCreate transaction][] object:
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Prepare VaultCreate" before="// Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `tfVaultPrivate` flag and `DomainID` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
|
||||
|
||||
The `Data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
|
||||
|
||||
The `AssetsMaximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Prepare VaultCreate" before="# Submit, sign" /%}
|
||||
|
||||
The `tfVaultPrivate` flag and `domain_id` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
|
||||
|
||||
The `data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
|
||||
|
||||
The `assets_maximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Vault shares are **transferable** by default, meaning depositors can transfer their shares to other accounts. If you don't want the vault's shares to be transferable, enable the `tfVaultShareNonTransferable` flag.
|
||||
|
||||
@@ -139,9 +105,6 @@ Sign and submit the `VaultCreate` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Submit, sign" before="// Extract vault information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Submit, sign" before="# Extract vault information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -154,9 +117,6 @@ Retrieve the vault's information from the transaction result by checking for the
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Extract vault information" before="// Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Extract vault information" before="# Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You can also use the [vault_info method][] to retrieve the vault's details:
|
||||
@@ -165,9 +125,6 @@ You can also use the [vault_info method][] to retrieve the vault's details:
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This confirms that you have successfully created an empty single asset vault.
|
||||
|
||||
@@ -34,8 +34,7 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have access to an existing vault. This tutorial uses a preconfigured vault. To create your own vault, see [Create a Single Asset Vault](create-a-single-asset-vault.md).
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../get-started/get-started-javascript.md) for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -47,21 +46,12 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
@@ -69,20 +59,12 @@ pip install -r requirements.txt
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" before="// You can replace" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" before="# You can replace" /%}
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="import xrpl" before="// You can replace" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
@@ -91,18 +73,10 @@ Provide the depositing account and specify the vault details. The depositor must
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// You can replace" before="// Get initial vault" /%}
|
||||
|
||||
This example uses an existing vault, depositor account, and MPT from the `vaultSetup.js` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# You can replace" before="# Get initial vault" /%}
|
||||
|
||||
This example uses an existing vault, depositor account, and MPT from the `vault_setup.py` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The preconfigured depositor account has:
|
||||
This example uses an existing vault, depositor account, and MPT from the `vaultSetup.js` script, but you can replace these values with your own. The preconfigured depositor account has:
|
||||
|
||||
- Valid [Credentials](../../../../concepts/decentralized-storage/credentials.md) in the vault's [Permissioned Domain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md).
|
||||
- A positive balance of the MPT in the vault.
|
||||
@@ -115,10 +89,6 @@ Use the [vault_info method][] to retrieve the vault's current state, including i
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get initial vault" before="// Check depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get initial vault" before="# Check depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check depositor's asset balance
|
||||
@@ -129,10 +99,6 @@ Before depositing, verify that the depositor has sufficient balance of the vault
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Check depositor's asset balance" before="// Prepare VaultDeposit" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Check depositor's asset balance" before="# Prepare VaultDeposit" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Prepare VaultDeposit transaction
|
||||
@@ -142,17 +108,11 @@ Create a [VaultDeposit transaction][] object to deposit assets into the vault.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Prepare VaultDeposit" before="// Submit VaultDeposit" /%}
|
||||
|
||||
The transaction specifies the depositing account, the vault's unique identifier (`VaultID`), and the amount to deposit. The asset in the `Amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Prepare VaultDeposit" before="# Submit VaultDeposit" /%}
|
||||
|
||||
The transaction specifies the depositing account, the vault's unique identifier (`vault_id`), and the amount to deposit. The asset in the `amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The transaction specifies the depositing account, the vault's unique identifier (`VaultID`), and the amount to deposit. The asset in the `Amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
|
||||
|
||||
### 6. Submit VaultDeposit transaction
|
||||
|
||||
Submit the `VaultDeposit` transaction to the XRP Ledger.
|
||||
@@ -161,10 +121,6 @@ Submit the `VaultDeposit` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Submit VaultDeposit" before="// Extract vault state" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Submit VaultDeposit" before="# Extract vault state" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
When depositing into a private vault, the transaction verifies that the depositor has valid credentials in the vault's permissioned domain. Without valid credentials, the `VaultDeposit` transaction fails with a `tecNO_AUTH` error.
|
||||
@@ -186,10 +142,6 @@ After depositing, verify the vault's updated state. You can extract this informa
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Extract vault state" before="// Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Extract vault state" before="# Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, check that the depositing account has received the shares.
|
||||
@@ -198,10 +150,6 @@ Finally, check that the depositing account has received the shares.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The code checks for both `ModifiedNode` and `CreatedNode` because on the first deposit, a new MPToken entry is created for the depositor's shares (`CreatedNode`). On subsequent deposits, the depositor's existing share balance is updated (`ModifiedNode`).
|
||||
|
||||
13
docs/tutorials/defi/lending/use-single-asset-vaults/index.md
Normal file
13
docs/tutorials/defi/lending/use-single-asset-vaults/index.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
seo:
|
||||
description: Create, deposit into, and withdraw from single asset vaults.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
---
|
||||
# Use Single Asset Vaults
|
||||
|
||||
Single asset vaults aggregate assets from multiple depositors and make them available to other on-chain protocols.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -29,8 +29,7 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have previously deposited into a vault. This tutorial uses an account that has already deposited into a vault. To deposit your own asset, see [Deposit into a Vault](./deposit-into-a-vault.md).
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../../tutorials/get-started/get-started-javascript.md) for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -42,22 +41,12 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
@@ -65,39 +54,25 @@ pip install -r requirements.txt
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" before="// You can replace" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" before="# You can replace" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Provide the depositor account and specify the vault details.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// You can replace" before="console.log" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# You can replace" before="print" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `vault_setup.py` script, but you can replace these values with your own.
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="You can replace" before="// Get initial vault" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace `depositor`, `vaultID`, `assetMPTIssuanceId`, and `shareMPTIssuanceId` with your own values.
|
||||
|
||||
### 3. Check initial vault state
|
||||
|
||||
Before withdrawing, check the vault's current state to see its total assets and available liquidity.
|
||||
@@ -106,10 +81,6 @@ Before withdrawing, check the vault's current state to see its total assets and
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get initial vault" before="// Check depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get initial vault" before="# Check depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check share balance
|
||||
@@ -120,10 +91,6 @@ Verify that the depositor account has vault shares to redeem. If not, the transa
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Check depositor's share balance" before="// Prepare VaultWithdraw" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Check depositor's share balance" before="# Prepare VaultWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Prepare VaultWithdraw transaction
|
||||
@@ -133,21 +100,15 @@ Create a [VaultWithdraw transaction][] to withdraw assets from the vault.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Prepare VaultWithdraw" before="// Submit VaultWithdraw" /%}
|
||||
|
||||
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways:
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Prepare VaultWithdraw" before="# Submit VaultWithdraw" /%}
|
||||
|
||||
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`vault_id`), and the amount to withdraw or redeem. You can specify the `amount` field in two ways:
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways:
|
||||
|
||||
- **Asset amount**: When you specify an asset amount, the vault burns the necessary shares to provide that amount.
|
||||
- **Share amount**: When you specify a share amount, the vault converts those shares into the corresponding asset amount.
|
||||
|
||||
While not required, you can provide a destination account to receive the assets; if omitted, assets go to the account submitting the transaction.
|
||||
While not required, you can provide a `Destination` account to receive the assets; if omitted, assets go to the account specified in the `Account` field.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
You can withdraw from a vault regardless of whether it's private or public. If you hold vault shares, you can always redeem them, even if your credentials in a private vault's permissioned domain have expired or been revoked. This prevents you from being locked out of your funds.
|
||||
@@ -161,22 +122,18 @@ Submit the `VaultWithdraw` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Submit VaultWithdraw " before="// Extract vault state" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Submit VaultWithdraw" before="# Extract vault state" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
When the transaction succeeds:
|
||||
|
||||
- The vault calculates how many shares need to be burned to provide the requested asset amount.
|
||||
- The vault transfers the assets from its pseudo-account to the depositor account (or the destination account if specified).
|
||||
- The vault transfers the assets from its pseudo-account to the depositor account (or the `Destination` account if specified).
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
Transfer fees are not charged on `VaultWithdraw` transactions.
|
||||
{% /admonition %}
|
||||
|
||||
### 7. Verify withdrawal
|
||||
### 6. Verify withdrawal
|
||||
|
||||
After withdrawing, check the vault's state. You can extract this information directly from the transaction metadata.
|
||||
|
||||
@@ -184,10 +141,6 @@ After withdrawing, check the vault's state. You can extract this information dir
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Extract vault state" before="// Get the depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Extract vault state" before="# Get the depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Then, check the depositor's share balance:
|
||||
@@ -196,10 +149,6 @@ Then, check the depositor's share balance:
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's share balance" before="// Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's share balance" before="# Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, verify the correct asset amount has been received by the depositor account:
|
||||
@@ -208,10 +157,6 @@ Finally, verify the correct asset amount has been received by the depositor acco
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -30,7 +30,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -42,41 +41,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID.
|
||||
@@ -84,16 +67,11 @@ Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// This step checks" before="// Check cover available" /%}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# This step checks" before="# Check cover available" /%}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `mpt_issuer`, `loan_broker_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values.
|
||||
|
||||
### 3. Check initial cover available
|
||||
|
||||
Check the initial cover (first-loss capital) available using the [ledger_entry method][].
|
||||
@@ -102,9 +80,6 @@ Check the initial cover (first-loss capital) available using the [ledger_entry m
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Check cover available" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Check cover available" before="# Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited.
|
||||
@@ -116,17 +91,10 @@ Create the [LoanBrokerCoverDeposit transaction][] object.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit validation" /%}
|
||||
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit validation" /%}
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
|
||||
### 5. Submit LoanBrokerCoverDeposit transaction
|
||||
|
||||
@@ -136,9 +104,6 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for deposit validation" before="// Extract updated cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for deposit validation" before="# Extract updated cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -151,9 +116,6 @@ Retrieve the cover available from the transaction result by checking the `LoanBr
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract updated cover available" before="// Verify issuer of cover asset" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract updated cover available" before="# Verify issuer of cover asset" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Verify the asset issuer
|
||||
@@ -164,14 +126,11 @@ Before executing a clawback, verify that the account submitting the transaction
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Verify issuer of cover asset" before="// Prepare LoanBrokerCoverClawback" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Verify issuer of cover asset" before="# Prepare LoanBrokerCoverClawback" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Clawback functionality is disabled by default. In the case of MPTs, the `tfMPTCanClawback` flag must be enabled when the [MPTokenIssuanceCreate transaction][] is submitted. This tutorial uses an MPT that is already configured for clawback.
|
||||
|
||||
### 8. Prepare LoanBrokerCoverClawback transaction
|
||||
### 7. Prepare LoanBrokerCoverClawback transaction
|
||||
|
||||
Create the [LoanBrokerCoverClawback transaction][] object.
|
||||
|
||||
@@ -179,14 +138,11 @@ Create the [LoanBrokerCoverClawback transaction][] object.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverClawback" before="// Sign, submit, and wait for clawback validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverClawback" before="# Sign, submit, and wait for clawback validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
In this example we claw back the entire amount, but you can specify any amount so long as it doesn't exceed the available cover or reduce the cover below the minimum required by the `LoanBroker`.
|
||||
|
||||
### 9. Submit LoanBrokerCoverClawback transaction
|
||||
### 8. Submit LoanBrokerCoverClawback transaction
|
||||
|
||||
Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
|
||||
|
||||
@@ -194,14 +150,11 @@ Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for clawback validation" before="// Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for clawback validation" before="# Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 10. Check final cover available
|
||||
### 9. Check final cover available
|
||||
|
||||
Retrieve the final cover available from the transaction result.
|
||||
|
||||
@@ -209,9 +162,6 @@ Retrieve the final cover available from the transaction result.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -29,7 +29,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -41,41 +40,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the vault owner account and vault ID. The vault owner will also be the loan broker.
|
||||
@@ -83,30 +66,22 @@ Next, load the vault owner account and vault ID. The vault owner will also be th
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// This step checks" before="// Prepare LoanBrokerSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lendingSetup.js` script, but you can replace `loanBroker` and `vaultID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# This step checks" before="# Prepare LoanBrokerSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lending_setup.py` script, but you can replace `loan_broker` and `vault_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lendingSetup.js` script, but you can replace `loanBroker` and `vaultID` with your own values.
|
||||
|
||||
### 3. Prepare LoanBrokerSet transaction
|
||||
|
||||
Create the [LoanBrokerSet transaction][] object.
|
||||
Create the [LoanBrokerSet transaction][] object:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Prepare LoanBrokerSet" before="// Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Prepare LoanBrokerSet" before="# Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The management fee rate is set in 1/10th basis points. A value of `1000` equals 1% (100 basis points).
|
||||
The `ManagementFeeRate` is set in 1/10th basis points. A value of `1000` equals 1% (100 basis points).
|
||||
|
||||
### 4. Submit LoanBrokerSet transaction
|
||||
|
||||
@@ -116,9 +91,6 @@ Sign and submit the `LoanBrokerSet` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Submit, sign" before="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Submit, sign" before="# Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -131,9 +103,6 @@ Retrieve the loan broker's information from the transaction result by checking f
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan broker pseudo-account is a special account that holds first-loss capital.
|
||||
|
||||
@@ -32,7 +32,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -44,41 +43,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, borrower account, and loan broker ID.
|
||||
@@ -86,23 +69,20 @@ Next, load the loan broker account, borrower account, and loan broker ID.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// This step checks" before="// Prepare LoanSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `borrower`, and `loanBrokerID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# This step checks" before="# Prepare LoanSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `borrower`, and `loan_broker_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `borrower`, and `loanBrokerID` with your own values.
|
||||
|
||||
### 3. Prepare LoanSet transaction
|
||||
|
||||
Create the [LoanSet transaction][] object with the loan terms.
|
||||
Create the [LoanSet transaction][] object with the loan terms:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Prepare LoanSet" before="// Loan broker signs first" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `Account` field is the loan broker, and the `Counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `Account` signs first, and the `Counterparty` signs second.
|
||||
|
||||
@@ -114,60 +94,39 @@ The loan terms include:
|
||||
- `GracePeriod`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).
|
||||
- `LoanOriginationFee`: A one-time fee charged when the loan is created, paid in the borrowed asset.
|
||||
- `LoanServiceFee`: A fee charged with every loan payment, paid in the borrowed asset.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Prepare LoanSet" before="# Loan broker signs first" /%}
|
||||
|
||||
The `account` field is the loan broker, and the `counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `account` signs first, and the `counterparty` signs second.
|
||||
|
||||
The loan terms include:
|
||||
- `principal_requested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.
|
||||
- `interest_rate`: The annualized interest rate in 1/10th basis points (500 = 0.5%).
|
||||
- `payment_total`: The number of payments to be made.
|
||||
- `payment_interval`: The number of seconds between payments (2592000 = 30 days).
|
||||
- `grace_period`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).
|
||||
- `loan_origination_fee`: A one-time fee charged when the loan is created, paid in the borrowed asset.
|
||||
- `loan_service_fee`: A fee charged with every loan payment, paid in the borrowed asset.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Add loan broker signature
|
||||
|
||||
The loan broker (the `Account`) signs the transaction first, adding their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object.
|
||||
The loan broker (the `Account`) signs the transaction first, using the [sign method][]:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Loan broker signs first" before="// Borrower signs second" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Loan broker signs first" before="# Borrower signs second" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan broker adds their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object.
|
||||
|
||||
### 5. Add borrower signature
|
||||
|
||||
The borrower (the `Counterparty`) signs the transaction second. Their `TxnSignature` and `SigningPubKey` are stored in a `CounterpartySignature` field, which is added to the `LoanSet` transaction object.
|
||||
The borrower (the `Counterparty`) signs the transaction second, using the [sign method][]:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Borrower signs second" before="// Submit and wait" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Borrower signs second" before="# Submit and wait" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The borrower must specify `signature_target: 'CounterpartySignature'`. This adds the borrower's signatures to a `CounterpartySignature` object, which includes the borrower's `TxnSignature` and `SigningPubKey`.
|
||||
|
||||
### 6. Submit LoanSet transaction
|
||||
|
||||
Submit the fully signed `LoanSet` transaction to the XRP Ledger.
|
||||
Sign and submit the fully signed `LoanSet` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Submit and wait" before="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Submit and wait" before="# Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -180,9 +139,6 @@ Retrieve the loan's information from the transaction result by checking for the
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -31,7 +31,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -43,41 +42,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, loan broker ID, and MPT issuance ID.
|
||||
@@ -85,34 +68,22 @@ Next, load the loan broker account, loan broker ID, and MPT issuance ID.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// This step checks" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# This step checks" before="# Prepare LoanBrokerCoverDeposit" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `loan_broker_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `loanBrokerID`, and `mptID` with your own values.
|
||||
|
||||
### 3. Prepare LoanBrokerCoverDeposit transaction
|
||||
|
||||
Create the [LoanBrokerCoverDeposit transaction][] object.
|
||||
Create the [LoanBrokerCoverDeposit transaction][] object:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit" /%}
|
||||
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit" /%}
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
|
||||
### 4. Submit LoanBrokerCoverDeposit transaction
|
||||
|
||||
@@ -122,9 +93,6 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for deposit" before="// Extract cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for deposit" before="# Extract cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -137,24 +105,18 @@ Retrieve the cover balance from the transaction result by checking the `LoanBrok
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract cover balance" before="// Prepare LoanBrokerCoverWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract cover balance" before="# Prepare LoanBrokerCoverWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `LoanBroker` pseudo-account address is the `Account` field, and `CoverAvailable` shows the cover balance.
|
||||
|
||||
### 6. Prepare LoanBrokerCoverWithdraw transaction
|
||||
|
||||
Create the [LoanBrokerCoverWithdraw transaction][] object.
|
||||
Create the [LoanBrokerCoverWithdraw transaction][] object:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverWithdraw" before="// Sign, submit, and wait for withdraw" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverWithdraw" before="# Sign, submit, and wait for withdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Submit LoanBrokerCoverWithdraw transaction
|
||||
@@ -165,9 +127,6 @@ Sign and submit the `LoanBrokerCoverWithdraw` transaction to the XRP Ledger.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for withdraw" before="// Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for withdraw" before="# Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -180,9 +139,6 @@ Retrieve the updated cover balance from the transaction result.
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `CoverAvailable` field now shows the reduced balance after the withdrawal.
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
seo:
|
||||
description: Create and manage loans on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
---
|
||||
# Use the Lending Protocol
|
||||
|
||||
The Lending Protocol enables you to create highly configurable loans on the XRP Ledger.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -32,7 +32,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -44,42 +43,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
- `time` and `datetime`: Used for grace period countdown and date formatting.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account and loan ID.
|
||||
@@ -87,70 +69,53 @@ Next, load the loan broker account and loan ID.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// This step checks" before="// Check loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# This step checks" before="# Check loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `loan_broker` and `loan_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values.
|
||||
|
||||
### 3. Check loan status
|
||||
|
||||
Check the current status of the loan using the [ledger_entry method][].
|
||||
Check the current status of the loan using the [ledger_entry method][]:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Check loan status" before="// Prepare LoanManage transaction to impair" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Check loan status" before="# Prepare LoanManage transaction to impair" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This shows the total amount owed and the next payment due date. The [Ripple Epoch][] timestamp is converted to a readable date format.
|
||||
This shows the total amount owed and the next payment due date. The [Ripple Epoch][] timestamp is converted to a JavaScript Date object for readability.
|
||||
|
||||
### 4. Prepare LoanManage transaction to impair the loan
|
||||
|
||||
Create the [LoanManage transaction][] with the `tfLoanImpair` flag.
|
||||
Create the [LoanManage transaction][] with the `tfLoanImpair` flag:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to impair" before="// Sign, submit, and wait for impairment" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to impair" before="# Sign, submit, and wait for impairment" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanManage impairment transaction
|
||||
|
||||
Sign and submit the `LoanManage` transaction to impair the loan.
|
||||
Sign and submit the `LoanManage` transaction to impair the loan:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for impairment" before="// Extract loan impairment info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for impairment" before="# Extract loan impairment info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 6. Get loan impairment information
|
||||
|
||||
Retrieve the loan's grace period and updated payment due date from the transaction result by checking for the `Loan` entry in the transaction metadata.
|
||||
Retrieve the loan's grace period and updated payment due date from the transaction result by checking for the `Loan` entry in the transaction metadata:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Extract loan impairment info" before="// Countdown until loan can be defaulted" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Extract loan impairment info" before="# Countdown until loan can be defaulted" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan can only be defaulted after the grace period expires. The example calculates when the grace period ends and displays a countdown.
|
||||
@@ -163,53 +128,41 @@ This countdown displays the remaining seconds in real-time. Once the grace perio
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Countdown until loan can be defaulted" before="// Prepare LoanManage transaction to default" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Countdown until loan can be defaulted" before="# Prepare LoanManage transaction to default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 8. Prepare LoanManage transaction to default the loan
|
||||
|
||||
After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag.
|
||||
After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to default" before="// Sign, submit, and wait for default" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to default" before="# Sign, submit, and wait for default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 9. Submit LoanManage default transaction
|
||||
|
||||
Sign and submit the `LoanManage` transaction to default the loan.
|
||||
Sign and submit the `LoanManage` transaction to default the loan:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for default" before="// Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for default" before="# Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 10. Verify loan default status
|
||||
|
||||
Confirm the loan has been defaulted by checking the loan flags.
|
||||
Confirm the loan has been defaulted by checking the loan flags:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan flags are parsed to confirm the `tfLoanDefault` flag is now set.
|
||||
The `parseTransactionFlags` function converts the numeric flags to a readable format, showing that the `tfLoanDefault` flag is now set.
|
||||
|
||||
## See Also
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ To complete this tutorial, you should:
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -44,41 +43,25 @@ You can find the complete source code for this tutorial's examples in the {% rep
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
From the code sample folder, use npm to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the borrower account, loan ID, and MPT issuance ID.
|
||||
@@ -86,27 +69,19 @@ Next, load the borrower account, loan ID, and MPT issuance ID.
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// This step checks" before="// Check initial loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# This step checks" before="# Check initial loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `borrower`, `loan_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values.
|
||||
|
||||
### 3. Check loan status
|
||||
|
||||
Check the current status of the loan using the [ledger_entry method][].
|
||||
Check the current status of the loan using the [ledger_entry method][]:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Check initial loan status" before="// Prepare LoanPay transaction" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Check initial loan status" before="# Prepare LoanPay transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `TotalValueOutstanding` field contains the remaining principal plus accrued interest; the `LoanServiceFee` is an additional fee charged per payment. Add these together to calculate the total payment.
|
||||
@@ -117,88 +92,70 @@ Other fees can be charged on a loan, such as late or early payment fees. These a
|
||||
|
||||
### 4. Prepare LoanPay transaction
|
||||
|
||||
Create the [LoanPay transaction][] with the total payment amount.
|
||||
Create the [LoanPay transaction][] with the total payment amount:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanPay transaction" before="// Sign, submit, and wait for payment validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanPay transaction" before="# Sign, submit, and wait for payment validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanPay transaction
|
||||
|
||||
Sign and submit the `LoanPay` transaction to the XRP Ledger.
|
||||
Sign and submit the `LoanPay` transaction to the XRP Ledger:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for payment validation" before="// Extract updated loan info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for payment validation" before="# Extract updated loan info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 6. Check loan balance
|
||||
|
||||
Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata.
|
||||
Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Extract updated loan info" before="// Prepare LoanDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Extract updated loan info" before="# Prepare LoanDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If `TotalValueOutstanding` is absent from the loan metadata, the loan has been fully paid off and is ready for deletion.
|
||||
|
||||
### 7. Prepare LoanDelete transaction
|
||||
|
||||
Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger.
|
||||
Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanDelete transaction" before="// Sign, submit, and wait for deletion validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanDelete transaction" before="# Sign, submit, and wait for deletion validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Either the loan broker or the borrower can submit a `LoanDelete` transaction. In this example, the borrower deletes their own paid off loan.
|
||||
|
||||
### 8. Submit LoanDelete transaction
|
||||
|
||||
Sign and submit the `LoanDelete` transaction.
|
||||
Sign and submit the `LoanDelete` transaction:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for deletion validation" before="// Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for deletion validation" before="# Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 9. Verify loan deletion
|
||||
|
||||
Confirm that the loan has been removed from the XRP Ledger.
|
||||
Confirm that the loan has been removed from the XRP Ledger:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted.
|
||||
|
||||
29
docs/tutorials/index.md
Normal file
29
docs/tutorials/index.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
seo:
|
||||
description: Learn how to get started building on the XRP Ledger with these helpful crypto wallet and blockchain tutorials for developers.
|
||||
---
|
||||
# Crypto Wallet and Blockchain Development Tutorials
|
||||
|
||||
The XRP Ledger tutorials walk you through the steps to learn and get started with the XRP Ledger and to use the ledger for advanced use cases.
|
||||
|
||||
## Get Started with SDKs
|
||||
|
||||
These tutorials walk you through the basics of building a very simple XRP Ledger-connected application using your favorite programming language.
|
||||
|
||||
{% card-grid %}
|
||||
|
||||
{% xrpl-card title="Javascript" body="Using the xrpl.js client library." href="/docs/tutorials/get-started/get-started-javascript/" image="/img/logos/javascript.svg" imageAlt="Javascript logo" /%}
|
||||
|
||||
{% xrpl-card title="Python" body="Using xrpl.py, a pure Python library." href="/docs/tutorials/get-started/get-started-python/" image="/img/logos/python.svg" imageAlt="Python logo" /%}
|
||||
|
||||
{% xrpl-card title="Go" body="Using xrpl-go, a pure Go library." href="/docs/tutorials/get-started/get-started-go/" image="/img/logos/golang.svg" imageAlt="Go logo" /%}
|
||||
|
||||
{% xrpl-card title="Java" body="Using xrpl4j, a pure Java library." href="/docs/tutorials/get-started/get-started-java/" image="/img/logos/java.svg" imageAlt="Java logo" /%}
|
||||
|
||||
{% xrpl-card title="PHP" body="Using the XRPL_PHP client library." href="/docs/tutorials/get-started/get-started-php/" image="/img/logos/php.svg" imageAlt="PHP logo" /%}
|
||||
|
||||
{% xrpl-card title="HTTP & WebSocket APIs" body="Access the XRP Ledger directly through the APIs of its core server." href="/docs/tutorials/get-started/get-started-http-websocket-apis/" image="/img/logos/globe.svg" imageAlt="globe icon" /%}
|
||||
|
||||
{% /card-grid %}
|
||||
|
||||
<!-- TODO: Add new sections for the new tutorial categories -->
|
||||
@@ -1,363 +0,0 @@
|
||||
import { useThemeHooks } from "@redocly/theme/core/hooks"
|
||||
import { Link } from "@redocly/theme/components/Link/Link"
|
||||
|
||||
export const frontmatter = {
|
||||
seo: {
|
||||
title: "Tutorials",
|
||||
description:
|
||||
"Learn how to get started building on the XRP Ledger with these helpful crypto wallet and blockchain tutorials for developers.",
|
||||
},
|
||||
}
|
||||
|
||||
const langIcons: Record<string, { src: string; alt: string }> = {
|
||||
javascript: { src: "/img/logos/javascript.svg", alt: "JavaScript" },
|
||||
python: { src: "/img/logos/python.svg", alt: "Python" },
|
||||
java: { src: "/img/logos/java.svg", alt: "Java" },
|
||||
go: { src: "/img/logos/golang.svg", alt: "Go" },
|
||||
php: { src: "/img/logos/php.svg", alt: "PHP" },
|
||||
http: { src: "/img/logos/globe.svg", alt: "HTTP / WebSocket" },
|
||||
xrpl: { src: "/img/logos/xrp-mark.svg", alt: "XRP Ledger" },
|
||||
}
|
||||
|
||||
// Type for the tutorial languages map from the plugin
|
||||
type TutorialLanguagesMap = Record<string, string[]>
|
||||
|
||||
interface Tutorial {
|
||||
title: string
|
||||
body?: string
|
||||
path: string
|
||||
icon?: string // Single language icon (for single-language tutorials)
|
||||
}
|
||||
|
||||
interface TutorialSection {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
tutorials: Tutorial[]
|
||||
}
|
||||
|
||||
// Get Started tutorials -----------------
|
||||
const getStartedTutorials: Tutorial[] = [
|
||||
{
|
||||
title: "JavaScript",
|
||||
body: "Using the xrpl.js client library.",
|
||||
path: "/docs/tutorials/get-started/get-started-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Python",
|
||||
body: "Using xrpl.py, a pure Python library.",
|
||||
path: "/docs/tutorials/get-started/get-started-python/",
|
||||
icon: "python",
|
||||
},
|
||||
{
|
||||
title: "Go",
|
||||
body: "Using xrpl-go, a pure Go library.",
|
||||
path: "/docs/tutorials/get-started/get-started-go/",
|
||||
icon: "go",
|
||||
},
|
||||
{
|
||||
title: "Java",
|
||||
body: "Using xrpl4j, a pure Java library.",
|
||||
path: "/docs/tutorials/get-started/get-started-java/",
|
||||
icon: "java",
|
||||
},
|
||||
{
|
||||
title: "PHP",
|
||||
body: "Using the XRPL_PHP client library.",
|
||||
path: "/docs/tutorials/get-started/get-started-php/",
|
||||
icon: "php",
|
||||
},
|
||||
{
|
||||
title: "HTTP & WebSocket APIs",
|
||||
body: "Access the XRP Ledger directly through the APIs of its core server.",
|
||||
path: "/docs/tutorials/get-started/get-started-http-websocket-apis/",
|
||||
icon: "http",
|
||||
},
|
||||
]
|
||||
|
||||
// Other tutorial sections -----------------
|
||||
// Languages are auto-detected from the markdown files by the tutorial-languages plugin.
|
||||
// Only specify `icon` for single-language tutorials without tabs.
|
||||
const sections: TutorialSection[] = [
|
||||
{
|
||||
id: "tokens",
|
||||
title: "Tokens",
|
||||
description: "Create and manage tokens on the XRP Ledger.",
|
||||
tutorials: [
|
||||
{
|
||||
title: "Issue a Multi-Purpose Token",
|
||||
body: "Issue new tokens using the v2 fungible token standard.",
|
||||
path: "/docs/tutorials/tokens/mpts/issue-a-multi-purpose-token/",
|
||||
},
|
||||
{
|
||||
title: "Issue a Fungible Token",
|
||||
body: "Issue new tokens using the v1 fungible token standard.",
|
||||
path: "/docs/tutorials/tokens/fungible-tokens/issue-a-fungible-token/",
|
||||
},
|
||||
{
|
||||
title: "Mint and Burn NFTs Using JavaScript",
|
||||
body: "Create new NFTs, retrieve existing tokens, and burn the ones you no longer need.",
|
||||
path: "/docs/tutorials/tokens/nfts/mint-and-burn-nfts-js/",
|
||||
icon: "javascript",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "payments",
|
||||
title: "Payments",
|
||||
description: "Transfer XRP and issued currencies using various payment types.",
|
||||
tutorials: [
|
||||
{
|
||||
title: "Send XRP",
|
||||
body: "Send a direct XRP payment to another account.",
|
||||
path: "/docs/tutorials/payments/send-xrp/",
|
||||
},
|
||||
{
|
||||
title: "Sending MPTs in JavaScript",
|
||||
body: "Send a Multi-Purpose Token (MPT) to another account with the JavaScript SDK.",
|
||||
path: "/docs/tutorials/tokens/mpts/sending-mpts-in-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Create Trust Line and Send Currency in JavaScript",
|
||||
body: "Set up trust lines and send issued currencies with the JavaScript SDK.",
|
||||
path: "/docs/tutorials/payments/create-trust-line-send-currency-in-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Create Trust Line and Send Currency in Python",
|
||||
body: "Set up trust lines and send issued currencies with the Python SDK.",
|
||||
path: "/docs/tutorials/payments/create-trust-line-send-currency-in-python/",
|
||||
icon: "python",
|
||||
},
|
||||
{
|
||||
title: "Send a Conditional Escrow",
|
||||
body: "Send an escrow that can be released when a specific crypto-condition is fulfilled.",
|
||||
path: "/docs/tutorials/payments/send-a-conditional-escrow/",
|
||||
},
|
||||
{
|
||||
title: "Send a Timed Escrow",
|
||||
body: "Send an escrow whose only condition for release is that a specific time has passed.",
|
||||
path: "/docs/tutorials/payments/send-a-timed-escrow/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "defi",
|
||||
title: "DeFi",
|
||||
description: "Trade, provide liquidity, and lend using native XRP Ledger DeFi features.",
|
||||
tutorials: [
|
||||
{
|
||||
title: "Create an Automated Market Maker",
|
||||
body: "Set up an AMM for a token pair and provide liquidity.",
|
||||
path: "/docs/tutorials/defi/dex/create-an-automated-market-maker/",
|
||||
},
|
||||
{
|
||||
title: "Trade in the Decentralized Exchange",
|
||||
body: "Buy and sell tokens in the Decentralized Exchange (DEX).",
|
||||
path: "/docs/tutorials/defi/dex/trade-in-the-decentralized-exchange/",
|
||||
},
|
||||
{
|
||||
title: "Create a Loan Broker",
|
||||
body: "Set up a loan broker to create and manage loans.",
|
||||
path: "/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan-broker/",
|
||||
},
|
||||
{
|
||||
title: "Create a Loan",
|
||||
body: "Create a loan on the XRP Ledger.",
|
||||
path: "/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan/",
|
||||
},
|
||||
{
|
||||
title: "Create a Single Asset Vault",
|
||||
body: "Create a single asset vault on the XRP Ledger.",
|
||||
path: "/docs/tutorials/defi/lending/use-single-asset-vaults/create-a-single-asset-vault/",
|
||||
},
|
||||
{
|
||||
title: "Deposit into a Vault",
|
||||
body: "Deposit assets into a vault and receive shares.",
|
||||
path: "/docs/tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "best-practices",
|
||||
title: "Best Practices",
|
||||
description: "Learn recommended patterns for building reliable, secure applications on the XRP Ledger.",
|
||||
tutorials: [
|
||||
{
|
||||
title: "API Usage",
|
||||
body: "Best practices for using XRP Ledger APIs.",
|
||||
path: "/docs/tutorials/best-practices/api-usage/",
|
||||
},
|
||||
{
|
||||
title: "Use Tickets",
|
||||
body: "Use tickets to send transactions out of the normal order.",
|
||||
path: "/docs/tutorials/best-practices/transaction-sending/use-tickets/",
|
||||
},
|
||||
{
|
||||
title: "Send a Single Account Batch Transaction",
|
||||
body: "Group multiple transactions together and execute them as a single atomic operation.",
|
||||
path: "/docs/tutorials/best-practices/transaction-sending/send-a-single-account-batch-transaction/",
|
||||
},
|
||||
{
|
||||
title: "Assign a Regular Key Pair",
|
||||
body: "Assign a regular key pair for signing transactions.",
|
||||
path: "/docs/tutorials/best-practices/key-management/assign-a-regular-key-pair/",
|
||||
},
|
||||
{
|
||||
title: "Set Up Multi-Signing",
|
||||
body: "Configure multi-signing for enhanced security.",
|
||||
path: "/docs/tutorials/best-practices/key-management/set-up-multi-signing/",
|
||||
},
|
||||
{
|
||||
title: "Send a Multi-Signed Transaction",
|
||||
body: "Send a transaction with multiple signatures.",
|
||||
path: "/docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "sample-apps",
|
||||
title: "Sample Apps",
|
||||
description: "Build complete, end-to-end applications like wallets and credential services.",
|
||||
tutorials: [
|
||||
{
|
||||
title: "Build a Browser Wallet in JavaScript",
|
||||
body: "Build a browser wallet for the XRP Ledger using JavaScript and various libraries.",
|
||||
path: "/docs/tutorials/sample-apps/build-a-browser-wallet-in-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Build a Desktop Wallet in JavaScript",
|
||||
body: "Build a desktop wallet for the XRP Ledger using JavaScript, the Electron Framework, and various libraries.",
|
||||
path: "/docs/tutorials/sample-apps/build-a-desktop-wallet-in-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Build a Desktop Wallet in Python",
|
||||
body: "Build a desktop wallet for the XRP Ledger using Python and various libraries.",
|
||||
path: "/docs/tutorials/sample-apps/build-a-desktop-wallet-in-python/",
|
||||
icon: "python",
|
||||
},
|
||||
{
|
||||
title: "Credential Issuing Service in JavaScript",
|
||||
body: "Build a credential issuing service using the JavaScript SDK.",
|
||||
path: "/docs/tutorials/sample-apps/credential-issuing-service-in-javascript/",
|
||||
icon: "javascript",
|
||||
},
|
||||
{
|
||||
title: "Credential Issuing Service in Python",
|
||||
body: "Build a credential issuing service using the Python SDK.",
|
||||
path: "/docs/tutorials/sample-apps/credential-issuing-service-in-python/",
|
||||
icon: "python",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function TutorialCard({
|
||||
tutorial,
|
||||
detectedLanguages,
|
||||
showFooter = false,
|
||||
translate,
|
||||
}: {
|
||||
tutorial: Tutorial
|
||||
detectedLanguages?: string[]
|
||||
showFooter?: boolean
|
||||
translate: (text: string) => string
|
||||
}) {
|
||||
// Get icons: manual icon takes priority, then auto-detected languages, then XRPL fallback
|
||||
const icons = tutorial.icon && langIcons[tutorial.icon]
|
||||
? [langIcons[tutorial.icon]]
|
||||
: detectedLanguages && detectedLanguages.length > 0
|
||||
? detectedLanguages.map((lang) => langIcons[lang]).filter(Boolean)
|
||||
: [langIcons.xrpl]
|
||||
|
||||
return (
|
||||
<Link to={tutorial.path} className="card">
|
||||
<div className="card-header d-flex align-items-center flex-wrap">
|
||||
{icons.map((icon, idx) => (
|
||||
<span className="circled-logo" key={idx}>
|
||||
<img src={icon.src} alt={icon.alt} />
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<h4 className="card-title h5">{translate(tutorial.title)}</h4>
|
||||
{tutorial.body && <p className="card-text">{translate(tutorial.body)}</p>}
|
||||
</div>
|
||||
{showFooter && <div className="card-footer"></div>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TutorialsIndex() {
|
||||
const { useTranslate, usePageSharedData } = useThemeHooks()
|
||||
const { translate } = useTranslate()
|
||||
|
||||
// Get auto-detected languages from the plugin (maps tutorial paths to language arrays).
|
||||
const tutorialLanguages = usePageSharedData<TutorialLanguagesMap>("tutorial-languages") || {}
|
||||
|
||||
return (
|
||||
<main className="landing page-tutorials landing-builtin-bg">
|
||||
<section className="container-new py-26">
|
||||
<div className="col-lg-8 mx-auto text-lg-center">
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h1 className="mb-0">
|
||||
{translate("Crypto Wallet and Blockchain Development Tutorials")}
|
||||
</h1>
|
||||
<h6 className="eyebrow mb-3">{translate("Tutorials")}</h6>
|
||||
</div>
|
||||
{/* Table of Contents */}
|
||||
<nav className="mt-4">
|
||||
<ul className="page-toc no-sideline d-flex flex-wrap justify-content-center gap-2 mb-0">
|
||||
<li><a href="#get-started">{translate("Get Started with SDKs")}</a></li>
|
||||
{sections.map((section) => (
|
||||
<li key={section.id}><a href={`#${section.id}`}>{translate(section.title)}</a></li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Get Started */}
|
||||
<section className="container-new pt-10 pb-20" id="get-started">
|
||||
<div className="col-12 col-xl-8 p-0">
|
||||
<h3 className="h4 mb-3">{translate("Get Started with SDKs")}</h3>
|
||||
<p className="mb-4">
|
||||
{translate("These tutorials walk you through the basics of building a very simple XRP Ledger-connected application using your favorite programming language.")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="row tutorial-cards">
|
||||
{getStartedTutorials.map((tutorial, idx) => (
|
||||
<div key={idx} className="col-lg-4 col-md-6 mb-5">
|
||||
<TutorialCard tutorial={tutorial} showFooter translate={translate} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Other Tutorials */}
|
||||
{sections.map((section) => (
|
||||
<section className="container-new pt-10 pb-10" key={section.id} id={section.id}>
|
||||
<div className="col-12 col-xl-8 p-0">
|
||||
<h3 className="h4 mb-3">{translate(section.title)}</h3>
|
||||
<p className="mb-4">{translate(section.description)}</p>
|
||||
</div>
|
||||
<div className="row tutorial-cards">
|
||||
{section.tutorials.slice(0, 6).map((tutorial, idx) => (
|
||||
<div key={idx} className="col-lg-4 col-md-6 mb-5">
|
||||
<TutorialCard
|
||||
tutorial={tutorial}
|
||||
detectedLanguages={tutorialLanguages[tutorial.path]}
|
||||
translate={translate}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -276,7 +276,7 @@ After the transaction is validated, you can confirm the status of the Global Fre
|
||||
- **Tutorials:**
|
||||
- [Enable No Freeze](enable-no-freeze.md)
|
||||
- [Freeze a Trust Line](freeze-a-trust-line.md)
|
||||
- [Remove a Regular Key Pair](../../best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](../../best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [account_lines method][]
|
||||
- [account_info method][]
|
||||
|
||||
@@ -347,7 +347,7 @@ As before, wait for the transaction to be validated by consensus.
|
||||
- **Tutorials:**
|
||||
- [Enable No Freeze](enable-no-freeze.md)
|
||||
- [Enact Global Freeze](enact-global-freeze.md)
|
||||
- [Remove a Regular Key Pair](../../best-practices/key-management/remove-a-regular-key-pair.md)
|
||||
- [Change or Remove a Regular Key Pair](../../best-practices/key-management/change-or-remove-a-regular-key-pair.md)
|
||||
- **References:**
|
||||
- [account_lines method][]
|
||||
- [account_info method][]
|
||||
|
||||
@@ -5,7 +5,7 @@ labels:
|
||||
- Tokens
|
||||
- MPT
|
||||
---
|
||||
# Sending MPTs in JavaScript
|
||||
# Sending MPTs
|
||||
|
||||
To send an MPT to another account, the receiving account must first authorize the receipt of the MPT, based on its MPToken Issuance ID. This is to prevent malicious users from spamming accounts with unwanted tokens that could negatively impact storage and XRP reserves.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ labels:
|
||||
- XRP
|
||||
---
|
||||
|
||||
# Batch Mint NFTs Using Python
|
||||
# Batch Mint NFTs
|
||||
|
||||
You can create an application that mints multiple NFTs at one time, using a `for` loop to send one transaction after another.
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ The XRPL lending protocol addresses these challenges through:
|
||||
|
||||
### Regulatory Compliance
|
||||
|
||||
- Accounts on the XRPL can be vetted by a trusted credential issuer. Credentials can be issued and revoked based on relevant criteria, such as credit score.
|
||||
- Permissioned Domains act as a gateway, limiting who can access the credit facilities based on accepted credentials you define.
|
||||
- Accounts on the XRPL can be vetted by a trusted credential issuer. Credentials can be issued and revoked, based around relevant criteria, such as credit score.
|
||||
- Permissioned Domains act as a gateway, limiting who can access the credit facilities, based on accepted credentials you define.
|
||||
- All credential and loan info is transparent on the XRPL, which makes compliance reporting and monitoring simpler and tamper-proof.
|
||||
|
||||
|
||||
@@ -56,46 +56,18 @@ The XRPL lending protocol addresses these challenges through:
|
||||
- Built-in first-loss capital features automatically protect against asset losses from defaults.
|
||||
|
||||
|
||||
## User Journeys
|
||||
## Implementation Steps
|
||||
|
||||
There are three users that enable institutional credit facilities on the XRP Ledger: Loan Brokers, Lenders, and Borrowers. The tabs below outline which features and transactions each user typically uses in the lending process.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Loan Broker" %}
|
||||
As a **Loan Broker**, I need to:
|
||||
- Create a [LoanBroker entry][] to define the configuration of a Lending Protocol.
|
||||
- Maintain the required [first-loss capital](../../concepts/tokens/lending-protocol.md#first-loss-capital) to protect deposits in my Single Asset Vault.
|
||||
|
||||
| Step | Description | Technical Implementation |
|
||||
|:-------------------------------|:------------|:-------------------------|
|
||||
| Vault Setup | The Loan Broker creates a Single Asset Vault to aggregate one type of asset to lend out. They define a [permissioned domain][] to ensure only accounts that meet KYB (Know Your Business) compliance requirements can deposit into the vault. | - [Create Permissioned Domains](../../tutorials/compliance-features/create-permissioned-domains-in-javascript.md)<br>- [Create a Single Asset Vault](../../tutorials/defi/lending/use-single-asset-vaults/create-a-single-asset-vault.md) |
|
||||
| Lending Protocol Setup | The Loan Broker sets up the Lending Protocol instance, linking it to the Single Asset Vault they created, and defining parameters such as payment fees. | [Create a Loan Broker](../../tutorials/defi/lending/use-the-lending-protocol/create-a-loan-broker.md) |
|
||||
| First-loss Capital Maintenance | The Loan Broker deposits first-loss capital into the Lending Protocol to meet the minimum cover required. When there is excess cover, they withdraw first-loss capital. | [Deposit and Withdraw First-Loss Capital](../../tutorials/defi/lending/use-the-lending-protocol/deposit-and-withdraw-cover.md) |
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Lender" %}
|
||||
As a **Lender**, I need to:
|
||||
- Authorize my account to deposit assets into a Single Asset Vault, so that I can deploy idle liquidity to generate yield.
|
||||
- Redeem vault shares to realize my earnings and return assets to my account.
|
||||
|
||||
| Step | Description | Technical Implementation |
|
||||
|:----------------|:------------|:-------------------------|
|
||||
| Onboarding | The Lender triggers a verification workflow with the Loan Broker managing the Lending Protocol. The Loan Broker can issue their own credentials or utilize a credential issuer. Upon successful KYB, a credential is issued and the Lender accepts the credential. | [Build a Credential Issuing Service](../../tutorials/sample-apps/credential-issuing-service-in-javascript.md) |
|
||||
| Deposit Asset | The Lender deposits assets into a Single Asset Vault to lend out. Vault shares are minted and sent back to the Lender, representing their stake in the vault. | [Deposit into a Vault](../../tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault.md) |
|
||||
| Withdraw Asset | Vault shares are yield-bearing assets; the vault collects interest and fees on loans, which increases the underlying value of each vault share. The Lender collects their deposit (plus yield) by redeeming vault shares. | [Withdraw from a Vault](../../tutorials/defi/lending/use-single-asset-vaults/withdraw-from-a-vault.md) |
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Borrower" %}
|
||||
As a **Borrower**, I need to:
|
||||
- Authorize my account to request loans from a Loan Broker.
|
||||
- Repay my loan.
|
||||
|
||||
| Step | Description | Technical Implementation |
|
||||
|:-----------------|:------------|:-------------------------|
|
||||
| Onboarding | The Borrower triggers a verification workflow with the Loan Broker managing the Lending Protocol. The Loan Broker can issue their own credentials or utilize a credential issuer. Upon successful KYB, a credential is issued and the Borrower accepts the credential. | [Build a Credential Issuing Service](../../tutorials/sample-apps/credential-issuing-service-in-javascript.md) |
|
||||
| Loan Application | The Borrower applies for a loan with the Loan Broker. Both parties agree on the terms and co-sign the loan. | [Create a Loan](../../tutorials/defi/lending/use-the-lending-protocol/create-a-loan.md) |
|
||||
| Repayment | The Borrower makes payments on principal, interest, and fees according to the loan agreement. | [Pay Off a Loan](../../tutorials/defi/lending/use-the-lending-protocol/pay-off-a-loan.md) |
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
1. Set Up Credential System
|
||||
- Select or become a credential issuer.
|
||||
- Define required credentials for borrowers.
|
||||
- Set up Permissioned Domains to protect your lending protocol and stay compliant with regulations.
|
||||
2. Set Up Asset Vaults
|
||||
- Set up vaults for different lending assets.
|
||||
- Define public/private access parameters.
|
||||
- Establish vault management policies.
|
||||
3. Deploy Lending Protocol
|
||||
- Create a LoanBroker and configure lending parameters.
|
||||
- Create and manage loans, including fees, impairment and default settings.
|
||||
- Set up monitoring and reporting systems.
|
||||
- Withdraw and repay loans.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user