mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-09 13:22:47 +00:00
Compare commits
50 Commits
release-no
...
rr-fix-357
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f31b3e7ca4 | ||
|
|
7e6234d9cf | ||
|
|
b058513278 | ||
|
|
5f47643585 | ||
|
|
7036a75881 | ||
|
|
57fde744fd | ||
|
|
91b68bae6a | ||
|
|
201a250072 | ||
|
|
c04c9042b8 | ||
|
|
7f7903a336 | ||
|
|
78da4d6f3b | ||
|
|
17abd49d92 | ||
|
|
d0c622abf4 | ||
|
|
0ed490da0b | ||
|
|
5fe416654f | ||
|
|
e0aeab7157 | ||
|
|
84987aa2df | ||
|
|
175e0a96eb | ||
|
|
d4be722dbc | ||
|
|
839da237b6 | ||
|
|
00e636c562 | ||
|
|
6638ea8f6c | ||
|
|
dbabe8171d | ||
|
|
66522b160e | ||
|
|
16b63f9c88 | ||
|
|
32b14d2a14 | ||
|
|
72fb4710ec | ||
|
|
10431480f1 | ||
|
|
c5f81cea25 | ||
|
|
9393f40366 | ||
|
|
9f4fcc845f | ||
|
|
d54684f45a | ||
|
|
c505b992e0 | ||
|
|
26c966fa51 | ||
|
|
86618b900f | ||
|
|
b634b6902e | ||
|
|
1540e36b8b | ||
|
|
598a15eef5 | ||
|
|
7cd8e31a21 | ||
|
|
e56324c57c | ||
|
|
5ce6218fd5 | ||
|
|
9934414492 | ||
|
|
ba7694f472 | ||
|
|
102a7cc8b9 | ||
|
|
0ac42f6acf | ||
|
|
bc0bcfa89b | ||
|
|
f9b2bce755 | ||
|
|
91380d73e1 | ||
|
|
1ba708467b | ||
|
|
d1adbd575a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/go/go.sum
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInnerText } from '@redocly/realm/dist/server/plugins/markdown/markdoc/helpers/get-inner-text.js';
|
||||
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js';
|
||||
|
||||
import { dirname, relative, join as joinPath } from 'path';
|
||||
import markdoc from '@markdoc/markdoc';
|
||||
import moment from "moment";
|
||||
|
||||
export function blogPosts() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'blog-posts',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const posts = [];
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
// @ts-check
|
||||
|
||||
import { getInnerText } from '@redocly/realm/dist/server/plugins/markdown/markdoc/helpers/get-inner-text.js';
|
||||
import { getInnerText } from '@redocly/realm/dist/markdoc/helpers/get-inner-text.js';
|
||||
|
||||
import { dirname, relative, join as joinPath } from 'path';
|
||||
|
||||
export function codeSamples() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'code-samples',
|
||||
processContent: async (actions, { fs, cache }) => {
|
||||
try {
|
||||
const samples = [];
|
||||
|
||||
@@ -3,8 +3,9 @@ import { readSharedData } from '@redocly/realm/dist/server/utils/shared-data.js'
|
||||
const INDEX_PAGE_INFO_DATA_KEY = 'index-page-items';
|
||||
|
||||
export function indexPages() {
|
||||
/** @type {import("@redocly/realm/dist/server/plugins/types").PluginInstance } */
|
||||
/** @type {import("@redocly/realm/dist/server/types").ExternalPlugin } */
|
||||
const instance = {
|
||||
id: 'index-pages',
|
||||
// hook that gets executed after all routes were created
|
||||
async afterRoutesCreated(actions, { cache }) {
|
||||
// get all the routes that are ind pages
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
For translated verisons of this document, see the [@l10n folder](@l10n/).
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of, but not limited to characteristics like age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
3
_code-samples/delete-account/README.md
Normal file
3
_code-samples/delete-account/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Delete Account
|
||||
|
||||
Delete an account from the XRP Ledger, removing its data and sending its XRP to another account.
|
||||
4
_code-samples/delete-account/js/.env
Normal file
4
_code-samples/delete-account/js/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
45
_code-samples/delete-account/js/README.md
Normal file
45
_code-samples/delete-account/js/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Delete Account (JavaScript)
|
||||
|
||||
JavaScript sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
npm i
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ node delete-account.js
|
||||
Got new account from faucet:
|
||||
Address: rsuTU7xBF1u8jxKMw5UHvbKkLmvix7zQoe
|
||||
Seed: sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226794
|
||||
Validated ledger index: 15226538
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTpxrbDhe6M4YeHanSbCySFCZYrCk
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
node delete-account.js
|
||||
```
|
||||
205
_code-samples/delete-account/js/delete-account.js
Normal file
205
_code-samples/delete-account/js/delete-account.js
Normal file
@@ -0,0 +1,205 @@
|
||||
import { Client, Wallet, getBalanceChanges, validate } from 'xrpl'
|
||||
import 'dotenv/config'
|
||||
|
||||
const client = new Client('wss://s.altnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Where to send the deleted account's remaining XRP:
|
||||
const DESTINATION_ACCOUNT = 'rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo' // Testnet faucet
|
||||
|
||||
// Load the account to delete from .env file -----------------------------------
|
||||
// If the seed value is still the default, get a new account from the faucet.
|
||||
// It won't be deletable immediately.
|
||||
let wallet
|
||||
if (!process.env.ACCOUNT_SEED || process.env.ACCOUNT_SEED === 's████████████████████████████') {
|
||||
console.log("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = (await client.fundWallet()).wallet
|
||||
console.log(`Got new account from faucet:
|
||||
Address: ${wallet.address}
|
||||
Seed: ${wallet.seed}
|
||||
`)
|
||||
|
||||
console.log('Edit the .env file to add this seed, then wait until the account can be deleted.')
|
||||
} else {
|
||||
wallet = Wallet.fromSeed(process.env.ACCOUNT_SEED, { algorithm: process.env.ACCOUNT_ALGORITHM })
|
||||
console.log(`Loaded account: ${wallet.address}`)
|
||||
}
|
||||
|
||||
// Check account info to see if account can be deleted -------------------------
|
||||
let acctInfoResp
|
||||
try {
|
||||
acctInfoResp = await client.request({
|
||||
command: 'account_info',
|
||||
account: wallet.address,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_info failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let numProblems = 0
|
||||
|
||||
// Check if sequence number is too high
|
||||
const acctSeq = acctInfoResp.result.account_data.Sequence
|
||||
const lastValidatedLedgerIndex = acctInfoResp.result.ledger_index
|
||||
if (acctSeq + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account is too new to be deleted.
|
||||
Account sequence + 255: ${acctSeq + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(Sequence + 255 must be less than or equal to the ledger index)`)
|
||||
|
||||
// Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
const estWaitTimeS = (acctSeq + 255 - lastValidatedLedgerIndex) * 3.5
|
||||
if (estWaitTimeS < 120) {
|
||||
console.log(`Estimate: ${estWaitTimeS} seconds until account can be deleted`)
|
||||
} else {
|
||||
const estWaitTimeM = Math.round(estWaitTimeS / 60, 0)
|
||||
console.log(`Estimate: ${estWaitTimeM} minutes until account can be deleted`)
|
||||
}
|
||||
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account sequence number (${acctSeq}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if owner count is too high
|
||||
const ownerCount = acctInfoResp.result.account_data.OwnerCount
|
||||
if (ownerCount > 1000) {
|
||||
console.error(`Account owns too many objects in the ledger.
|
||||
Owner count: ${ownerCount}
|
||||
(Must be 1000 or less)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account owner count (${ownerCount}) is low enough.`)
|
||||
}
|
||||
|
||||
// Check if XRP balance is high enough
|
||||
// Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
// using server_state so that both are in drops
|
||||
let serverStateResp
|
||||
try {
|
||||
serverStateResp = await client.request({
|
||||
command: 'server_state'
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('server_state failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
const deletionCost = serverStateResp.result.state.validated_ledger?.reserve_inc
|
||||
if (!deletionCost) {
|
||||
console.error("Couldn't get reserve values from server. " +
|
||||
"Maybe it's not synced to the network?")
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const acctBalance = acctInfoResp.result.account_data.Balance
|
||||
if (acctBalance < deletionCost) {
|
||||
console.error(`Account does not have enough XRP to pay the cost of deletion.
|
||||
Balance: ${acctBalance}
|
||||
Cost of account deletion: ${deletionCost}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log(`OK: Account balance (${acctBalance} drops) is high enough.`)
|
||||
}
|
||||
|
||||
// Check if FirstNFTSequence is too high
|
||||
const firstNFTSeq = acctInfoResp.result.account_data.FirstNFTokenSequence || 0
|
||||
const mintedNFTs = acctInfoResp.result.account_data.MintedNFTokens || 0
|
||||
if (firstNFTSeq + mintedNFTs + 255 > lastValidatedLedgerIndex) {
|
||||
console.error(`Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: ${firstNFTSeq + mintedNFTs + 255}
|
||||
Validated ledger index: ${lastValidatedLedgerIndex}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the ledger index)`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: FirstNFTokenSequence + MintedNFTokens is low enough.')
|
||||
}
|
||||
|
||||
// Check that all issued NFTs have been burned
|
||||
const burnedNFTs = acctInfoResp.result.account_data.BurnedNFTokens || 0
|
||||
if (mintedNFTs > burnedNFTs) {
|
||||
console.error(`Account has issued NFTs outstanding.
|
||||
Number of NFTs minted: ${mintedNFTs}
|
||||
Number of NFTs burned: ${burnedNFTs}`)
|
||||
numProblems += 1
|
||||
} else {
|
||||
console.log('OK: No outstanding, un-burned NFTs')
|
||||
}
|
||||
|
||||
// Stop if any problems were found
|
||||
if (numProblems) {
|
||||
console.error(`A total of ${numProblems} problem(s) prevent the account from being deleted.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Check for deletion blockers -------------------------------------------------
|
||||
const blockers = []
|
||||
let marker
|
||||
const ledger_index = 'validated'
|
||||
while (true) {
|
||||
let accountObjResp
|
||||
try {
|
||||
accountObjResp = await client.request({
|
||||
command: 'account_objects',
|
||||
account: wallet.address,
|
||||
deletion_blockers_only: true,
|
||||
ledger_index,
|
||||
marker
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('account_objects failed with error:', err)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
for (const obj of accountObjResp.result.account_objects) {
|
||||
blockers.push(obj)
|
||||
}
|
||||
if (accountObjResp.result.marker) {
|
||||
marker = accountObjResp.result.marker
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!blockers.length) {
|
||||
console.log('OK: Account has no deletion blockers.')
|
||||
} else {
|
||||
console.log(`Account cannot be deleted until ${blockers.length} blocker(s) are removed:`)
|
||||
for (const blocker of blockers) {
|
||||
console.log(JSON.stringify(blocker, null, 2))
|
||||
}
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Delete the account ----------------------------------------------------------
|
||||
const accountDeleteTx = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: wallet.address,
|
||||
Destination: DESTINATION_ACCOUNT
|
||||
}
|
||||
validate(accountDeleteTx)
|
||||
|
||||
console.log('Signing and submitting the AccountDelete transaction:',
|
||||
JSON.stringify(accountDeleteTx, null, 2))
|
||||
const deleteTxResponse = await client.submitAndWait(accountDeleteTx, { wallet, autofill: true, failHard: true })
|
||||
|
||||
// Check result of the AccountDelete transaction -------------------------------
|
||||
console.log(JSON.stringify(deleteTxResponse.result, null, 2))
|
||||
const resultCode = deleteTxResponse.result.meta.TransactionResult
|
||||
if (resultCode !== 'tesSUCCESS') {
|
||||
console.error(`AccountDelete failed with code ${resultCode}.`)
|
||||
client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('Account deleted successfully.')
|
||||
const balanceChanges = getBalanceChanges(deleteTxResponse.result.meta)
|
||||
console.log('Balance changes:', JSON.stringify(balanceChanges, null, 2))
|
||||
|
||||
client.disconnect()
|
||||
10
_code-samples/delete-account/js/package.json
Normal file
10
_code-samples/delete-account/js/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "delete-account",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.3.1",
|
||||
"xrpl": "^4.6.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
4
_code-samples/delete-account/py/.env
Normal file
4
_code-samples/delete-account/py/.env
Normal file
@@ -0,0 +1,4 @@
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=s████████████████████████████
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
47
_code-samples/delete-account/py/README.md
Normal file
47
_code-samples/delete-account/py/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Delete Account (Python)
|
||||
|
||||
Python sample code showing how to delete an account from the XRP Ledger.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
If you run the script by default, it gets an account from the faucet and outputs the details to the console. Example:
|
||||
|
||||
```sh
|
||||
$ python delete-account.py
|
||||
Got new account from faucet:
|
||||
Address: rNqLzC9pVbphwwpTBNPjpx14QSauHH3kzv
|
||||
Seed: sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
|
||||
Edit the .env file to add this seed, then wait until the account can be deleted.
|
||||
Account is too new to be deleted.
|
||||
Account sequence + 255: 15226905
|
||||
Validated ledger index: 15226649
|
||||
(Sequence + 255 must be less than or equal to the ledger index)
|
||||
Estimate: 15 minutes until account can be deleted
|
||||
OK: Account owner count (0) is low enough.
|
||||
OK: Account balance (100000000 drops) is high enough.
|
||||
A total of 1 problem(s) prevent the account from being deleted.
|
||||
```
|
||||
|
||||
Edit the `.env` file to add the seed of the account to delete. For example:
|
||||
|
||||
```ini
|
||||
# Replace the seed with the seed of the account to delete.
|
||||
ACCOUNT_SEED=sEdTNEJgK3cVshBEakfVic4MMtWCETY
|
||||
# Change to secp256k1 if you generated the seed with that algorithm
|
||||
ACCOUNT_ALGORITHM=ed25519
|
||||
```
|
||||
|
||||
Then run the script again:
|
||||
|
||||
```sh
|
||||
python delete-account.py
|
||||
```
|
||||
199
_code-samples/delete-account/py/delete-account.py
Normal file
199
_code-samples/delete-account/py/delete-account.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import os
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet, generate_faucet_wallet
|
||||
from xrpl.models.requests import AccountInfo, ServerState, AccountObjects
|
||||
from xrpl.models.transactions import AccountDelete
|
||||
from xrpl.transaction import submit_and_wait
|
||||
from xrpl.utils import get_balance_changes
|
||||
|
||||
client = JsonRpcClient("https://s.altnet.rippletest.net:51234")
|
||||
|
||||
# Where to send the deleted account's remaining XRP:
|
||||
DESTINATION_ACCOUNT = "rJjHYTCPpNA3qAM8ZpCDtip3a8xg7B8PFo" # Testnet faucet
|
||||
|
||||
# Load the account to delete from .env file -----------------------------------
|
||||
# If the seed value is still the default, get a new account from the faucet.
|
||||
# It won't be deletable immediately.
|
||||
load_dotenv()
|
||||
account_seed = os.getenv("ACCOUNT_SEED")
|
||||
account_algorithm = os.getenv("ACCOUNT_ALGORITHM", "ed25519")
|
||||
|
||||
if account_seed == "s████████████████████████████" or not account_seed:
|
||||
print("Couldn't load seed from .env; getting account from the faucet.")
|
||||
wallet = generate_faucet_wallet(client)
|
||||
print(
|
||||
f"Got new account from faucet:\n"
|
||||
f" Address: {wallet.address}\n"
|
||||
f" Seed: {wallet.seed}\n"
|
||||
)
|
||||
|
||||
print(
|
||||
"Edit the .env file to add this seed, then wait until the account can be deleted."
|
||||
)
|
||||
else:
|
||||
wallet = Wallet.from_seed(account_seed, algorithm=account_algorithm)
|
||||
print(f"Loaded account: {wallet.address}")
|
||||
|
||||
# Check account info to see if account can be deleted -------------------------
|
||||
try:
|
||||
acct_info_resp = client.request(
|
||||
AccountInfo(account=wallet.address, ledger_index="validated")
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_info failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
acct_info_result = acct_info_resp.result
|
||||
num_problems = 0
|
||||
|
||||
# Check if sequence number is too high
|
||||
acct_seq = acct_info_result["account_data"]["Sequence"]
|
||||
last_validated_ledger_index = acct_info_result["ledger_index"]
|
||||
|
||||
if acct_seq + 255 > last_validated_ledger_index:
|
||||
print(
|
||||
f"Account is too new to be deleted.\n"
|
||||
f" Account sequence + 255: {acct_seq + 255}\n"
|
||||
f" Validated ledger index: {last_validated_ledger_index}\n"
|
||||
f" (Sequence + 255 must be less than or equal to the ledger index)"
|
||||
)
|
||||
|
||||
# Estimate time until deletability assuming ledgers close every ~3.5 seconds
|
||||
est_wait_time_s = (acct_seq + 255 - last_validated_ledger_index) * 3.5
|
||||
if est_wait_time_s < 120:
|
||||
print(f"Estimate: {est_wait_time_s} seconds until account can be deleted")
|
||||
else:
|
||||
est_wait_time_m = round(est_wait_time_s / 60)
|
||||
print(f"Estimate: {est_wait_time_m} minutes until account can be deleted")
|
||||
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account sequence number ({acct_seq}) is low enough.")
|
||||
|
||||
# Check if owner count is too high
|
||||
owner_count = acct_info_result["account_data"]["OwnerCount"]
|
||||
if owner_count > 1000:
|
||||
print(
|
||||
f"Account owns too many objects in the ledger.\n"
|
||||
f" Owner count: {owner_count}\n"
|
||||
f" (Must be 1000 or less)"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account owner count ({owner_count}) is low enough.")
|
||||
|
||||
# Check if XRP balance is high enough
|
||||
# Look up current incremental owner reserve to compare vs account's XRP balance
|
||||
# using server_state so that both are in drops
|
||||
try:
|
||||
server_state_resp = client.request(ServerState())
|
||||
except Exception as err:
|
||||
print("server_state failed with error:", err)
|
||||
exit(1)
|
||||
|
||||
validated_ledger = server_state_resp.result["state"].get("validated_ledger", {})
|
||||
deletion_cost = validated_ledger.get("reserve_inc")
|
||||
|
||||
if not deletion_cost:
|
||||
print(
|
||||
"Couldn't get reserve values from server. Maybe it's not synced to the network?"
|
||||
)
|
||||
print(json.dumps(server_state_resp.result, indent=2))
|
||||
exit(1)
|
||||
|
||||
acct_balance = int(acct_info_result["account_data"]["Balance"])
|
||||
if acct_balance < deletion_cost:
|
||||
print(
|
||||
f"Account does not have enough XRP to pay the cost of deletion.\n"
|
||||
f" Balance: {acct_balance}\n"
|
||||
f" Cost of account deletion: {deletion_cost}"
|
||||
)
|
||||
num_problems += 1
|
||||
else:
|
||||
print(f"OK: Account balance ({acct_balance} drops) is high enough.")
|
||||
|
||||
# Check if FirstNFTSequence is too high
|
||||
first_nfq_seq = acct_info_result["account_data"].get("FirstNFTokenSequence", 0)
|
||||
minted_nfts = acct_info_result["account_data"].get("MintedNFTokens", 0)
|
||||
if first_nfq_seq + minted_nfts + 255 > last_validated_ledger_index:
|
||||
print(f"""Account's FirstNFTokenSequence + MintedNFTokens + 255 is too high.
|
||||
Current total: {first_nfq_seq + minted_nfts + 255}
|
||||
Validated ledger index: {last_validated_ledger_index}
|
||||
(FirstNFTokenSequence + MintedNFTokens + 255 must be less than or equal to the the ledger index)""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: FirstNFTokenSequence + MintedNFTokens is low enough.")
|
||||
|
||||
# Check that all issued NFTs have been burned
|
||||
burned_nfts = acct_info_result["account_data"].get("BurnedNFTokens", 0)
|
||||
if minted_nfts > burned_nfts:
|
||||
print(f"""Account has NFTs outstanding.
|
||||
Number of NFTs minted: {minted_nfts}
|
||||
Number of NFTs burned: {burned_nfts}""")
|
||||
num_problems += 1
|
||||
else:
|
||||
print("OK: No outstanding, un-burned NFTs")
|
||||
|
||||
# Stop if any problems were found
|
||||
if num_problems:
|
||||
print(
|
||||
f"A total of {num_problems} problem(s) prevent the account from being deleted."
|
||||
)
|
||||
exit(1)
|
||||
|
||||
# Check for deletion blockers -------------------------------------------------
|
||||
blockers = []
|
||||
marker = None
|
||||
ledger_index = "validated"
|
||||
|
||||
while True:
|
||||
try:
|
||||
account_obj_resp = client.request(
|
||||
AccountObjects(
|
||||
account=wallet.address,
|
||||
deletion_blockers_only=True,
|
||||
ledger_index=ledger_index,
|
||||
marker=marker,
|
||||
)
|
||||
)
|
||||
except Exception as err:
|
||||
print(f"account_objects failed with error: {err}")
|
||||
exit(1)
|
||||
|
||||
blockers.extend(account_obj_resp.result["account_objects"])
|
||||
|
||||
marker = account_obj_resp.result.get("marker")
|
||||
if not marker:
|
||||
break
|
||||
|
||||
if not blockers:
|
||||
print("OK: Account has no deletion blockers.")
|
||||
else:
|
||||
print(f"Account cannot be deleted until {len(blockers)} blocker(s) are removed:")
|
||||
for blocker in blockers:
|
||||
print(json.dumps(blocker, indent=2))
|
||||
exit(1)
|
||||
|
||||
# Delete the account ----------------------------------------------------------
|
||||
account_delete_tx = AccountDelete(
|
||||
account=wallet.address, destination=DESTINATION_ACCOUNT
|
||||
)
|
||||
|
||||
print("Signing and submitting the AccountDelete transaction:")
|
||||
print(json.dumps(account_delete_tx.to_xrpl(), indent=2))
|
||||
delete_tx_response = submit_and_wait(account_delete_tx, client, wallet, fail_hard=True)
|
||||
|
||||
# Check result of the AccountDelete transaction -------------------------------
|
||||
print(json.dumps(delete_tx_response.result, indent=2))
|
||||
result_code = delete_tx_response.result["meta"]["TransactionResult"]
|
||||
|
||||
if result_code != "tesSUCCESS":
|
||||
print(f"AccountDelete failed with code {result_code}.")
|
||||
exit(1)
|
||||
|
||||
print("Account deleted successfully.")
|
||||
balance_changes = get_balance_changes(delete_tx_response.result["meta"])
|
||||
print("Balance changes:", json.dumps(balance_changes, indent=2))
|
||||
2
_code-samples/delete-account/py/requirements.txt
Normal file
2
_code-samples/delete-account/py/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
xrpl-py==4.5.0
|
||||
python-dotenv==1.2.1
|
||||
395
_code-samples/lending-protocol/go/README.md
Normal file
395
_code-samples/lending-protocol/go/README.md
Normal file
@@ -0,0 +1,395 @@
|
||||
# Lending Protocol Examples (Go)
|
||||
|
||||
This directory contains Go examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
|
||||
|
||||
## Setup
|
||||
|
||||
All commands should be run from this `go/` directory.
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan Broker
|
||||
|
||||
```sh
|
||||
go run ./create-loan-broker
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account.
|
||||
|
||||
```sh
|
||||
Loan broker/vault owner address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
Vault ID: A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9
|
||||
|
||||
=== Preparing LoanBrokerSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"ManagementFeeRate": 1000,
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"VaultID": "A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerSet transaction ===
|
||||
|
||||
Loan broker created successfully!
|
||||
|
||||
=== Loan Broker Information ===
|
||||
|
||||
LoanBroker ID: E4D9C485E101FAE449C8ACEC7FD039920CC02D2443687F2593DB397CC8EA670B
|
||||
LoanBroker Pseudo-Account Address: rMDsnf9CVRLRJzrL12Ex7nhstbni78y8af
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Claw Back First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-clawback
|
||||
```
|
||||
|
||||
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback.
|
||||
|
||||
```sh
|
||||
Loan broker address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
MPT issuer address: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW
|
||||
LoanBrokerID: 373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D
|
||||
MPT ID: 003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51
|
||||
|
||||
=== Cover Available ===
|
||||
|
||||
0 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Available After Deposit ===
|
||||
|
||||
1000 TSTUSD
|
||||
|
||||
=== Verifying Asset Issuer ===
|
||||
|
||||
MPT issuer account verified: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW. Proceeding to clawback.
|
||||
|
||||
=== Preparing LoanBrokerCoverClawback transaction ===
|
||||
|
||||
{
|
||||
"Account": "rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverClawback"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverClawback transaction ===
|
||||
|
||||
Successfully clawed back 1000 TSTUSD!
|
||||
|
||||
=== Final Cover Available After Clawback ===
|
||||
|
||||
0 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit and Withdraw First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-deposit-and-withdraw
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal.
|
||||
|
||||
```sh
|
||||
Loan broker address: rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV
|
||||
LoanBrokerID: 633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3
|
||||
MPT ID: 003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "2000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after deposit: 2000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverWithdraw"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
Cover withdraw successful!
|
||||
|
||||
=== Updated Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after withdraw: 1000 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan
|
||||
|
||||
```sh
|
||||
go run ./create-loan
|
||||
```
|
||||
|
||||
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information.
|
||||
|
||||
```sh
|
||||
Loan broker address: rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX
|
||||
Borrower address: rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp
|
||||
LoanBrokerID: 490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09
|
||||
|
||||
=== Preparing LoanSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"TransactionType": "LoanSet"
|
||||
}
|
||||
|
||||
=== Adding loan broker signature ===
|
||||
|
||||
TxnSignature: 9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F
|
||||
SigningPubKey: ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1
|
||||
|
||||
Signed loanSetTx for borrower to sign over:
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
|
||||
}
|
||||
|
||||
=== Adding borrower signature ===
|
||||
|
||||
Borrower TxnSignature: 5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005
|
||||
Borrower SigningPubKey: ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E
|
||||
|
||||
Fully signed LoanSet transaction:
|
||||
{
|
||||
"Account": "rKHVvo9vMQwm2xz44qfhHyDC2VwYKfzgrX",
|
||||
"Counterparty": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "ED2BF9FFE428F80E3E174476EA334E2109BAF6C7309BB08D56A6A97CE0432AD85E",
|
||||
"TxnSignature": "5578161BA5480216644D63428D4FAA6FC761BEA10D91FFB733636AB4EA7C6CC4E07E241BF5418D92FBE9F0133E97CC3E6A2CDC56C86C801438C1CBAC4497B005"
|
||||
},
|
||||
"Fee": "2",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LastLedgerSequence": 437349,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentTotal": 12,
|
||||
"PrincipalRequested": "1000",
|
||||
"Sequence": 6905,
|
||||
"SigningPubKey": "ED378AB6DCCD0401D6DB3358A9135CE43455A57DAF0CBC459E8D7B6611193690B1",
|
||||
"TransactionType": "LoanSet",
|
||||
"TxnSignature": "9984D6061F4B03734CDCC5A5367A928671FEE1486EFD335B6C875423FCB9FCEF2464F2A610B4DF31875567869696DC36D16F72AFB7D5F245B43C19415537F50F"
|
||||
}
|
||||
|
||||
=== Submitting signed LoanSet transaction ===
|
||||
|
||||
Loan created successfully!
|
||||
|
||||
=== Loan Information ===
|
||||
|
||||
{
|
||||
"Borrower": "rKU5hZEsT71BUgCnnPekEF3d2zn4AXsyqp",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "490DB29AD0CCFBDFE9A176F71AB7512497407CCA37E86F0C88CCDA1DF99A1F09",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanSequence": 4,
|
||||
"LoanServiceFee": "10",
|
||||
"NextPaymentDueDate": 829803181,
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentRemaining": 12,
|
||||
"PeriodicPayment": "83.55610375293148956",
|
||||
"PrincipalOutstanding": "1000",
|
||||
"StartDate": 827211181,
|
||||
"TotalValueOutstanding": "1003"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-manage
|
||||
```
|
||||
|
||||
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
|
||||
|
||||
```sh
|
||||
Loan broker address: rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX
|
||||
LoanID: 2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Total Amount Owed: 1001 TSTUSD.
|
||||
Payment Due Date: 2026-03-23 01:23:40
|
||||
|
||||
=== Preparing LoanManage transaction to impair loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 131072,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage impairment transaction ===
|
||||
|
||||
Loan impaired successfully!
|
||||
New Payment Due Date: 2026-02-21 00:24:10
|
||||
Grace Period: 60 seconds
|
||||
|
||||
=== Countdown until loan can be defaulted ===
|
||||
|
||||
Grace period expired. Loan can now be defaulted.
|
||||
|
||||
=== Preparing LoanManage transaction to default loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 65536,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage default transaction ===
|
||||
|
||||
Loan defaulted successfully!
|
||||
|
||||
=== Checking final loan status ===
|
||||
|
||||
Final loan flags: [tfLoanDefault tfLoanImpair]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pay a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-pay
|
||||
```
|
||||
|
||||
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry.
|
||||
|
||||
```sh
|
||||
Borrower address: rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2
|
||||
LoanID: D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A
|
||||
MPT ID: 003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Amount Owed: 1001 TSTUSD
|
||||
Loan Service Fee: 10 TSTUSD
|
||||
Total Payment Due (including fees): 1011 TSTUSD
|
||||
|
||||
=== Preparing LoanPay transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B",
|
||||
"value": "1011"
|
||||
},
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanPay"
|
||||
}
|
||||
|
||||
=== Submitting LoanPay transaction ===
|
||||
|
||||
Loan paid successfully!
|
||||
|
||||
=== Loan Status After Payment ===
|
||||
|
||||
Outstanding Loan Balance: Loan fully paid off!
|
||||
|
||||
=== Preparing LoanDelete transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanDelete"
|
||||
}
|
||||
|
||||
=== Submitting LoanDelete transaction ===
|
||||
|
||||
Loan deleted successfully!
|
||||
|
||||
=== Verifying Loan Deletion ===
|
||||
|
||||
Loan has been successfully removed from the XRP Ledger!
|
||||
```
|
||||
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// IMPORTANT: This example deposits and claws back first-loss capital from a
|
||||
// preconfigured LoanBroker entry. The first-loss capital is an MPT
|
||||
// with clawback enabled.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
// mptIssuanceEntryRequest looks up an MPTIssuance ledger entry by its MPT ID.
|
||||
// The library's GetLedgerEntry() method only supports lookup by ledger entry ID,
|
||||
// so this custom type is used with the generic Request() method.
|
||||
type mptIssuanceEntryRequest struct {
|
||||
common.BaseRequest
|
||||
MPTIssuance string `json:"mpt_issuance"`
|
||||
LedgerIndex common.LedgerSpecifier `json:"ledger_index,omitempty"`
|
||||
}
|
||||
|
||||
func (*mptIssuanceEntryRequest) Method() string { return "ledger_entry" }
|
||||
func (*mptIssuanceEntryRequest) Validate() error { return nil }
|
||||
func (*mptIssuanceEntryRequest) APIVersion() int { return 2 }
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mptIssuerWallet, err := wallet.FromSecret(setup["depositor"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("MPT issuer address: %s\n", mptIssuerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check cover available ----------------------
|
||||
fmt.Printf("\n=== Cover Available ===\n\n")
|
||||
coverInfo, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanBrokerID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentCoverAvailable := "0"
|
||||
if ca, ok := coverInfo.Node["CoverAvailable"].(string); ok {
|
||||
currentCoverAvailable = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract updated cover available after deposit ----------------------
|
||||
fmt.Printf("\n=== Cover Available After Deposit ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
currentCoverAvailable = node.ModifiedNode.FinalFields["CoverAvailable"].(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Verify issuer of cover asset matches ----------------------
|
||||
// Only the issuer of the asset can submit clawback transactions.
|
||||
// The asset must also have clawback enabled.
|
||||
fmt.Printf("\n=== Verifying Asset Issuer ===\n\n")
|
||||
assetIssuerInfo, err := client.Request(&mptIssuanceEntryRequest{
|
||||
MPTIssuance: mptID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
issuer := assetIssuerInfo.Result["node"].(map[string]any)["Issuer"].(string)
|
||||
if issuer != string(mptIssuerWallet.ClassicAddress) {
|
||||
fmt.Printf("Error: %s does not match account (%s) attempting clawback!\n", issuer, mptIssuerWallet.ClassicAddress)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("MPT issuer account verified: %s. Proceeding to clawback.\n", mptIssuerWallet.ClassicAddress)
|
||||
|
||||
// Prepare LoanBrokerCoverClawback transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverClawback transaction ===\n\n")
|
||||
lbID := types.LoanBrokerID(loanBrokerID)
|
||||
coverClawbackTx := transaction.LoanBrokerCoverClawback{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: mptIssuerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: &lbID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: currentCoverAvailable,
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverClawbackTx := coverClawbackTx.Flatten()
|
||||
coverClawbackTxJSON, _ := json.MarshalIndent(flatCoverClawbackTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverClawbackTxJSON))
|
||||
|
||||
// Sign, submit, and wait for clawback validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverClawback transaction ===\n\n")
|
||||
clawbackResponse, err := client.SubmitTxAndWait(flatCoverClawbackTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &mptIssuerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if clawbackResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to clawback cover: %s\n", clawbackResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully clawed back %s TSTUSD!\n", currentCoverAvailable)
|
||||
|
||||
// Extract final cover available ----------------------
|
||||
fmt.Printf("\n=== Final Cover Available After Clawback ===\n\n")
|
||||
for _, node := range clawbackResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
finalCover := "0"
|
||||
if ca, ok := node.ModifiedNode.FinalFields["CoverAvailable"].(string); ok {
|
||||
finalCover = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", finalCover)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// IMPORTANT: This example deposits and withdraws first-loss capital from a
|
||||
// preconfigured LoanBroker entry.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "2000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract cover balance from the transaction result
|
||||
fmt.Printf("\n=== Cover Balance ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
// First-loss capital is stored in the LoanBroker's pseudo-account.
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after deposit: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
coverWithdrawTx := transaction.LoanBrokerCoverWithdraw{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverWithdrawTx := coverWithdrawTx.Flatten()
|
||||
coverWithdrawTxJSON, _ := json.MarshalIndent(flatCoverWithdrawTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverWithdrawTxJSON))
|
||||
|
||||
// Sign, submit, and wait for withdraw validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
withdrawResponse, err := client.SubmitTxAndWait(flatCoverWithdrawTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if withdrawResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to withdraw cover: %s\n", withdrawResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover withdraw successful!\n")
|
||||
|
||||
// Extract updated cover balance from the transaction result
|
||||
fmt.Printf("\n=== Updated Cover Balance ===\n\n")
|
||||
for _, node := range withdrawResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after withdraw: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// IMPORTANT: This example creates a loan broker using an existing account
|
||||
// that has already created a PRIVATE vault.
|
||||
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
|
||||
// and loanBroker values with your own.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and VaultID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultID := setup["vaultID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker/vault owner address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("Vault ID: %s\n", vaultID)
|
||||
|
||||
// Prepare LoanBrokerSet transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerSet transaction ===\n\n")
|
||||
mgmtFeeRate := types.InterestRate(1000)
|
||||
loanBrokerSetTx := transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
VaultID: vaultID,
|
||||
ManagementFeeRate: &mgmtFeeRate,
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanBrokerSetTx := loanBrokerSetTx.Flatten()
|
||||
loanBrokerSetTxJSON, _ := json.MarshalIndent(flatLoanBrokerSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanBrokerSetTxJSON))
|
||||
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerSet transaction ===\n\n")
|
||||
loanBrokerSetResponse, err := client.SubmitTxAndWait(flatLoanBrokerSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanBrokerSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan broker: %s\n", loanBrokerSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan broker created successfully!\n")
|
||||
|
||||
// Extract loan broker information from the transaction result
|
||||
fmt.Printf("\n=== Loan Broker Information ===\n\n")
|
||||
for _, node := range loanBrokerSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker ID: %s\n", node.CreatedNode.LedgerIndex)
|
||||
fmt.Printf("LoanBroker Pseudo-Account Address: %s\n", node.CreatedNode.NewFields["Account"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
153
_code-samples/lending-protocol/go/create-loan/main.go
Normal file
153
_code-samples/lending-protocol/go/create-loan/main.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// IMPORTANT: This example creates a loan using a preconfigured
|
||||
// loan broker, borrower, and private vault.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
)
|
||||
|
||||
// ptr is a helper that returns a pointer to the given value,
|
||||
// used for setting optional transaction fields in Go.
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("Borrower address: %s\n", borrowerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
|
||||
// Prepare LoanSet transaction ----------------------
|
||||
// Account and Counterparty accounts can be swapped, but determines signing order.
|
||||
// Account signs first, Counterparty signs second.
|
||||
fmt.Printf("\n=== Preparing LoanSet transaction ===\n\n")
|
||||
|
||||
counterparty := borrowerWallet.ClassicAddress
|
||||
loanSetTx := transaction.LoanSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: "1000",
|
||||
Counterparty: &counterparty,
|
||||
InterestRate: ptr(types.InterestRate(500)),
|
||||
PaymentTotal: ptr(types.PaymentTotal(12)),
|
||||
PaymentInterval: ptr(types.PaymentInterval(2592000)),
|
||||
GracePeriod: ptr(types.GracePeriod(604800)),
|
||||
LoanOriginationFee: ptr(types.XRPLNumber("100")),
|
||||
LoanServiceFee: ptr(types.XRPLNumber("10")),
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field.
|
||||
// The result is cast to FlatTransaction, which is required by Autofill and signing methods.
|
||||
flatLoanSetTx := transaction.FlatTransaction(loanSetTx.Flatten())
|
||||
|
||||
// Autofill the transaction
|
||||
if err := client.Autofill(&flatLoanSetTx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
loanSetTxJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanSetTxJSON))
|
||||
|
||||
// Loan broker signs first
|
||||
fmt.Printf("\n=== Adding loan broker signature ===\n\n")
|
||||
_, _, err = loanBrokerWallet.Sign(flatLoanSetTx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("TxnSignature: %s\n", flatLoanSetTx["TxnSignature"])
|
||||
fmt.Printf("SigningPubKey: %s\n\n", flatLoanSetTx["SigningPubKey"])
|
||||
|
||||
loanBrokerSignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("Signed loanSetTx for borrower to sign over:\n%s\n", string(loanBrokerSignedJSON))
|
||||
|
||||
// Borrower signs second
|
||||
fmt.Printf("\n=== Adding borrower signature ===\n\n")
|
||||
fullySignedBlob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatLoanSetTx, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
borrowerSignatures := flatLoanSetTx["CounterpartySignature"].(map[string]any)
|
||||
fmt.Printf("Borrower TxnSignature: %s\n", borrowerSignatures["TxnSignature"])
|
||||
fmt.Printf("Borrower SigningPubKey: %s\n", borrowerSignatures["SigningPubKey"])
|
||||
|
||||
fullySignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", " ")
|
||||
fmt.Printf("\nFully signed LoanSet transaction:\n%s\n", string(fullySignedJSON))
|
||||
|
||||
// Submit and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting signed LoanSet transaction ===\n\n")
|
||||
|
||||
loanSetResponse, err := client.SubmitTxBlobAndWait(fullySignedBlob, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan: %s\n", loanSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan created successfully!\n")
|
||||
|
||||
// Extract loan information from the transaction result
|
||||
fmt.Printf("\n=== Loan Information ===\n\n")
|
||||
for _, node := range loanSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanJSON, _ := json.MarshalIndent(node.CreatedNode.NewFields, "", " ")
|
||||
fmt.Printf("%s\n", string(loanJSON))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
19
_code-samples/lending-protocol/go/go.mod
Normal file
19
_code-samples/lending-protocol/go/go.mod
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/Peersyst/xrpl-go v0.1.17
|
||||
|
||||
require (
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
)
|
||||
656
_code-samples/lending-protocol/go/lending-setup/main.go
Normal file
656
_code-samples/lending-protocol/go/lending-setup/main.go
Normal file
@@ -0,0 +1,656 @@
|
||||
// Setup script for lending protocol tutorials
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/pkg/crypto"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/faucet"
|
||||
ledger "github.com/Peersyst/xrpl-go/xrpl/ledger-entry-types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/account"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
requests "github.com/Peersyst/xrpl-go/xrpl/queries/transactions"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/rpc"
|
||||
rpctypes "github.com/Peersyst/xrpl-go/xrpl/rpc/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
)
|
||||
|
||||
// ptr is a helper that returns a pointer to the given value,
|
||||
// used for setting optional transaction fields in Go.
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func main() {
|
||||
fmt.Print("Setting up tutorial: 0/7\r")
|
||||
|
||||
// Connect to devnet
|
||||
cfg, err := rpc.NewClientConfig(
|
||||
"https://s.devnet.rippletest.net:51234",
|
||||
rpc.WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := rpc.NewClient(cfg)
|
||||
|
||||
submitOpts := func(w *wallet.Wallet) *rpctypes.SubmitOptions {
|
||||
return &rpctypes.SubmitOptions{Autofill: true, Wallet: w}
|
||||
}
|
||||
|
||||
// Create and fund wallets concurrently
|
||||
createAndFund := func(ch chan<- wallet.Wallet) {
|
||||
w, err := wallet.New(crypto.ED25519())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := client.FundWallet(&w); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Poll until account is validated on ledger
|
||||
funded := false
|
||||
for range 20 {
|
||||
_, err := client.Request(&account.InfoRequest{
|
||||
Account: w.GetAddress(),
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err == nil {
|
||||
funded = true
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !funded {
|
||||
panic("Issue funding account: " + w.GetAddress().String())
|
||||
}
|
||||
ch <- w
|
||||
}
|
||||
|
||||
lbWalletCh := make(chan wallet.Wallet, 1)
|
||||
brWalletCh := make(chan wallet.Wallet, 1)
|
||||
depWalletCh := make(chan wallet.Wallet, 1)
|
||||
credWalletCh := make(chan wallet.Wallet, 1)
|
||||
|
||||
go createAndFund(lbWalletCh)
|
||||
go createAndFund(brWalletCh)
|
||||
go createAndFund(depWalletCh)
|
||||
go createAndFund(credWalletCh)
|
||||
|
||||
loanBrokerWallet := <-lbWalletCh
|
||||
borrowerWallet := <-brWalletCh
|
||||
depositorWallet := <-depWalletCh
|
||||
credIssuerWallet := <-credWalletCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 1/7\r")
|
||||
|
||||
// Create tickets for parallel transactions
|
||||
extractTickets := func(resp *requests.TxResponse) []uint32 {
|
||||
var tickets []uint32
|
||||
for _, node := range resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Ticket" {
|
||||
ticketSeq, _ := node.CreatedNode.NewFields["TicketSequence"].(json.Number).Int64()
|
||||
tickets = append(tickets, uint32(ticketSeq))
|
||||
}
|
||||
}
|
||||
return tickets
|
||||
}
|
||||
|
||||
ciTicketCh := make(chan []uint32, 1)
|
||||
lbTicketCh := make(chan []uint32, 1)
|
||||
brTicketCh := make(chan []uint32, 1)
|
||||
dpTicketCh := make(chan []uint32, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 4,
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ciTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 4,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 2,
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.TicketCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
TicketCount: 2,
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dpTicketCh <- extractTickets(resp)
|
||||
}()
|
||||
|
||||
ciTickets := <-ciTicketCh
|
||||
lbTickets := <-lbTicketCh
|
||||
brTickets := <-brTicketCh
|
||||
dpTickets := <-dpTicketCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 2/7\r")
|
||||
|
||||
// Issue MPT with depositor
|
||||
// Set up credentials and domain with credentialIssuer
|
||||
credentialType := hex.EncodeToString([]byte("KYC-Verified"))
|
||||
|
||||
mptData := types.ParsedMPTokenMetadata{
|
||||
Ticker: "TSTUSD",
|
||||
Name: "Test USD MPT",
|
||||
Desc: ptr("A sample non-yield-bearing stablecoin backed by U.S. Treasuries."),
|
||||
Icon: "https://example.org/tstusd-icon.png",
|
||||
AssetClass: "rwa",
|
||||
AssetSubclass: ptr("stablecoin"),
|
||||
IssuerName: "Example Treasury Reserve Co.",
|
||||
URIs: []types.ParsedMPTokenMetadataURI{
|
||||
{
|
||||
URI: "https://exampletreasury.com/tstusd",
|
||||
Category: "website",
|
||||
Title: "Product Page",
|
||||
},
|
||||
{
|
||||
URI: "https://exampletreasury.com/tstusd/reserve",
|
||||
Category: "docs",
|
||||
Title: "Reserve Attestation",
|
||||
},
|
||||
},
|
||||
AdditionalInfo: map[string]any{
|
||||
"reserve_type": "U.S. Treasury Bills",
|
||||
"custody_provider": "Example Custodian Bank",
|
||||
"audit_frequency": "Monthly",
|
||||
"last_audit_date": "2026-01-15",
|
||||
"pegged_currency": "USD",
|
||||
},
|
||||
}
|
||||
mptMetadataHex, err := types.EncodeMPTokenMetadata(mptData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mptCh := make(chan *requests.TxResponse, 1)
|
||||
domainCh := make(chan *requests.TxResponse, 1)
|
||||
credLbCh := make(chan struct{}, 1)
|
||||
credBrCh := make(chan struct{}, 1)
|
||||
credDpCh := make(chan struct{}, 1)
|
||||
|
||||
// MPT issuance
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.MPTokenIssuanceCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Flags: transaction.TfMPTCanTransfer | transaction.TfMPTCanClawback | transaction.TfMPTCanTrade,
|
||||
},
|
||||
MaximumAmount: ptr(types.XRPCurrencyAmount(100000000)),
|
||||
TransferFee: ptr(uint16(0)),
|
||||
MPTokenMetadata: &mptMetadataHex,
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mptCh <- resp
|
||||
}()
|
||||
|
||||
// PermissionedDomainSet
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.PermissionedDomainSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[0],
|
||||
},
|
||||
AcceptedCredentials: types.AuthorizeCredentialList{
|
||||
{
|
||||
Credential: types.Credential{
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
},
|
||||
},
|
||||
},
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
domainCh <- resp
|
||||
}()
|
||||
|
||||
// CredentialCreate for loan broker
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[1],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: loanBrokerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credLbCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// CredentialCreate for borrower
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[2],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: borrowerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credBrCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// CredentialCreate for depositor
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: credIssuerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[3],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Subject: depositorWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&credIssuerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
credDpCh <- struct{}{}
|
||||
}()
|
||||
|
||||
mptResp := <-mptCh
|
||||
domainResp := <-domainCh
|
||||
<-credLbCh
|
||||
<-credBrCh
|
||||
<-credDpCh
|
||||
|
||||
// Extract MPT issuance ID
|
||||
mptID := string(*mptResp.Meta.MPTIssuanceID)
|
||||
|
||||
// Extract domain ID from transaction meta
|
||||
var domainID string
|
||||
for _, node := range domainResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "PermissionedDomain" {
|
||||
domainID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 3/7\r")
|
||||
|
||||
// Accept credentials and authorize MPT for each account
|
||||
lbCredCh := make(chan struct{}, 1)
|
||||
lbMptCh := make(chan struct{}, 1)
|
||||
brCredCh := make(chan struct{}, 1)
|
||||
brMptCh := make(chan struct{}, 1)
|
||||
depCredCh := make(chan struct{}, 1)
|
||||
|
||||
// Loan broker: accept credential
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[0],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Loan broker: authorize MPT
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[1],
|
||||
},
|
||||
MPTokenIssuanceID: mptID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbMptCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Borrower: accept credential
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[0],
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Borrower: authorize MPT
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.MPTokenAuthorize{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[1],
|
||||
},
|
||||
MPTokenIssuanceID: mptID,
|
||||
}).Flatten(), submitOpts(&borrowerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
brMptCh <- struct{}{}
|
||||
}()
|
||||
|
||||
// Depositor: accept credential only
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.CredentialAccept{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
CredentialType: types.CredentialType(credentialType),
|
||||
Issuer: credIssuerWallet.GetAddress(),
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
depCredCh <- struct{}{}
|
||||
}()
|
||||
|
||||
<-lbCredCh
|
||||
<-lbMptCh
|
||||
<-brCredCh
|
||||
<-brMptCh
|
||||
<-depCredCh
|
||||
|
||||
fmt.Print("Setting up tutorial: 4/7\r")
|
||||
|
||||
// Create private vault and distribute MPT to accounts concurrently
|
||||
vaultCh := make(chan *requests.TxResponse, 1)
|
||||
distLbCh := make(chan struct{}, 1)
|
||||
distBrCh := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.VaultCreate{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Flags: transaction.TfVaultPrivate,
|
||||
},
|
||||
Asset: ledger.Asset{MPTIssuanceID: mptID},
|
||||
DomainID: &domainID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultCh <- resp
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[0],
|
||||
},
|
||||
Destination: loanBrokerWallet.GetAddress(),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "5000",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
distLbCh <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.Payment{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[1],
|
||||
},
|
||||
Destination: borrowerWallet.GetAddress(),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "2500",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
distBrCh <- struct{}{}
|
||||
}()
|
||||
|
||||
vaultResp := <-vaultCh
|
||||
<-distLbCh
|
||||
<-distBrCh
|
||||
|
||||
var vaultID string
|
||||
for _, node := range vaultResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Vault" {
|
||||
vaultID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 5/7\r")
|
||||
|
||||
// Create LoanBroker and deposit MPT into vault
|
||||
lbSetCh := make(chan *requests.TxResponse, 1)
|
||||
vaultDepCh := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
resp, err := client.SubmitTxAndWait((&transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
},
|
||||
VaultID: vaultID,
|
||||
}).Flatten(), submitOpts(&loanBrokerWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lbSetCh <- resp
|
||||
}()
|
||||
|
||||
go func() {
|
||||
_, err := client.SubmitTxAndWait((&transaction.VaultDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: depositorWallet.GetAddress(),
|
||||
},
|
||||
VaultID: types.Hash256(vaultID),
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "50000000",
|
||||
},
|
||||
}).Flatten(), submitOpts(&depositorWallet))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultDepCh <- struct{}{}
|
||||
}()
|
||||
|
||||
lbSetResp := <-lbSetCh
|
||||
<-vaultDepCh
|
||||
|
||||
var loanBrokerID string
|
||||
for _, node := range lbSetResp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
loanBrokerID = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 6/7\r")
|
||||
|
||||
// Create 2 identical loans with complete repayment due in 30 days
|
||||
|
||||
// Helper function to create, sign, and submit a LoanSet transaction
|
||||
createLoan := func(ticketSequence uint32) *requests.TxResponse {
|
||||
counterparty := borrowerWallet.GetAddress()
|
||||
loanSetTx := &transaction.LoanSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.GetAddress(),
|
||||
Sequence: 0,
|
||||
TicketSequence: ticketSequence,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: "1000",
|
||||
Counterparty: &counterparty,
|
||||
InterestRate: ptr(types.InterestRate(500)),
|
||||
PaymentTotal: ptr(types.PaymentTotal(1)),
|
||||
PaymentInterval: ptr(types.PaymentInterval(2592000)),
|
||||
LoanOriginationFee: ptr(types.XRPLNumber("100")),
|
||||
LoanServiceFee: ptr(types.XRPLNumber("10")),
|
||||
}
|
||||
|
||||
flatTx := transaction.FlatTransaction(loanSetTx.Flatten())
|
||||
if err := client.Autofill(&flatTx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Loan broker signs first
|
||||
_, _, err := loanBrokerWallet.Sign(flatTx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Borrower signs second
|
||||
blob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatTx, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Submit and wait for validation
|
||||
resp, err := client.SubmitTxBlobAndWait(blob, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
loan1Ch := make(chan *requests.TxResponse, 1)
|
||||
loan2Ch := make(chan *requests.TxResponse, 1)
|
||||
|
||||
go func() { loan1Ch <- createLoan(lbTickets[2]) }()
|
||||
go func() { loan2Ch <- createLoan(lbTickets[3]) }()
|
||||
|
||||
loan1Resp := <-loan1Ch
|
||||
loan2Resp := <-loan2Ch
|
||||
|
||||
var loanID1, loanID2 string
|
||||
for _, node := range loan1Resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanID1 = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, node := range loan2Resp.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
|
||||
loanID2 = node.CreatedNode.LedgerIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Print("Setting up tutorial: 7/7\r")
|
||||
|
||||
// Write setup data to JSON file.
|
||||
// Using a struct to preserve field order.
|
||||
setupData := struct {
|
||||
Description string `json:"description"`
|
||||
LoanBroker any `json:"loanBroker"`
|
||||
Borrower any `json:"borrower"`
|
||||
Depositor any `json:"depositor"`
|
||||
CredentialIssuer any `json:"credentialIssuer"`
|
||||
DomainID string `json:"domainID"`
|
||||
MptID string `json:"mptID"`
|
||||
VaultID string `json:"vaultID"`
|
||||
LoanBrokerID string `json:"loanBrokerID"`
|
||||
LoanID1 string `json:"loanID1"`
|
||||
LoanID2 string `json:"loanID2"`
|
||||
}{
|
||||
Description: "This file is auto-generated by lending-setup. It stores XRPL account info for use in lending protocol tutorials.",
|
||||
LoanBroker: map[string]string{
|
||||
"address": string(loanBrokerWallet.ClassicAddress),
|
||||
"seed": loanBrokerWallet.Seed,
|
||||
},
|
||||
Borrower: map[string]string{
|
||||
"address": string(borrowerWallet.ClassicAddress),
|
||||
"seed": borrowerWallet.Seed,
|
||||
},
|
||||
Depositor: map[string]string{
|
||||
"address": string(depositorWallet.ClassicAddress),
|
||||
"seed": depositorWallet.Seed,
|
||||
},
|
||||
CredentialIssuer: map[string]string{
|
||||
"address": string(credIssuerWallet.ClassicAddress),
|
||||
"seed": credIssuerWallet.Seed,
|
||||
},
|
||||
DomainID: domainID,
|
||||
MptID: mptID,
|
||||
VaultID: vaultID,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
LoanID1: loanID1,
|
||||
LoanID2: loanID2,
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(setupData, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := os.WriteFile("lending-setup.json", jsonData, 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println("Setting up tutorial: Complete!")
|
||||
}
|
||||
195
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
195
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
@@ -0,0 +1,195 @@
|
||||
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
|
||||
// After the 60 seconds pass, this example defaults the loan.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/flag"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID1"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
|
||||
// Check loan status before impairment ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Total Amount Owed: %s TSTUSD.\n", loanStatus.Node["TotalValueOutstanding"])
|
||||
|
||||
// Convert Ripple Epoch timestamp to local date and time
|
||||
nextPaymentDueDate := int64(loanStatus.Node["NextPaymentDueDate"].(float64))
|
||||
paymentDue := time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
fmt.Printf("Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
|
||||
// Prepare LoanManage transaction to impair the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to impair loan ===\n\n")
|
||||
loanManageImpair := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageImpair.SetLoanImpairFlag()
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanManageImpair := loanManageImpair.Flatten()
|
||||
loanManageImpairJSON, _ := json.MarshalIndent(flatLoanManageImpair, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageImpairJSON))
|
||||
|
||||
// Sign, submit, and wait for impairment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage impairment transaction ===\n\n")
|
||||
impairResponse, err := client.SubmitTxAndWait(flatLoanManageImpair, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if impairResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to impair loan: %s\n", impairResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan impaired successfully!\n")
|
||||
|
||||
// Extract loan impairment info from transaction results ----------------------
|
||||
var loanNode map[string]any
|
||||
for _, node := range impairResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check grace period and next payment due date
|
||||
gracePeriod := int64(loanNode["GracePeriod"].(float64))
|
||||
nextPaymentDueDate = int64(loanNode["NextPaymentDueDate"].(float64))
|
||||
defaultTime := nextPaymentDueDate + gracePeriod
|
||||
paymentDue = time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
|
||||
fmt.Printf("New Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
fmt.Printf("Grace Period: %d seconds\n", gracePeriod)
|
||||
|
||||
// Convert current time to Ripple Epoch timestamp
|
||||
currentTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix())
|
||||
// Add a small buffer (5 seconds) to account for ledger close time
|
||||
secondsUntilDefault := defaultTime - currentTime + 5
|
||||
|
||||
// Countdown until loan can be defaulted ----------------------
|
||||
fmt.Printf("\n=== Countdown until loan can be defaulted ===\n\n")
|
||||
for secondsUntilDefault >= 0 {
|
||||
fmt.Printf("\r%d seconds...", secondsUntilDefault)
|
||||
time.Sleep(time.Second)
|
||||
secondsUntilDefault--
|
||||
}
|
||||
fmt.Print("\rGrace period expired. Loan can now be defaulted.\n")
|
||||
|
||||
// Prepare LoanManage transaction to default the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to default loan ===\n\n")
|
||||
loanManageDefault := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageDefault.SetLoanDefaultFlag()
|
||||
|
||||
flatLoanManageDefault := loanManageDefault.Flatten()
|
||||
loanManageDefaultJSON, _ := json.MarshalIndent(flatLoanManageDefault, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageDefaultJSON))
|
||||
|
||||
// Sign, submit, and wait for default validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage default transaction ===\n\n")
|
||||
defaultResponse, err := client.SubmitTxAndWait(flatLoanManageDefault, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if defaultResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to default loan: %s\n", defaultResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan defaulted successfully!\n")
|
||||
|
||||
// Verify loan default status from transaction results ----------------------
|
||||
fmt.Printf("\n=== Checking final loan status ===\n\n")
|
||||
for _, node := range defaultResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
loanFlags := uint32(loanNode["Flags"].(float64))
|
||||
|
||||
// Check which loan flags are set
|
||||
activeFlags := []string{}
|
||||
if flag.Contains(loanFlags, transaction.TfLoanDefault) {
|
||||
activeFlags = append(activeFlags, "tfLoanDefault")
|
||||
}
|
||||
if flag.Contains(loanFlags, transaction.TfLoanImpair) {
|
||||
activeFlags = append(activeFlags, "tfLoanImpair")
|
||||
}
|
||||
fmt.Printf("Final loan flags: %v\n", activeFlags)
|
||||
}
|
||||
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// IMPORTANT: This example pays off an existing loan and then deletes it.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID2"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nBorrower address: %s\n", borrowerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check initial loan status ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
totalValueOutstanding := loanStatus.Node["TotalValueOutstanding"].(string)
|
||||
loanServiceFee := loanStatus.Node["LoanServiceFee"].(string)
|
||||
|
||||
outstanding, _ := new(big.Int).SetString(totalValueOutstanding, 10)
|
||||
serviceFee, _ := new(big.Int).SetString(loanServiceFee, 10)
|
||||
totalPayment := new(big.Int).Add(outstanding, serviceFee).String()
|
||||
|
||||
fmt.Printf("Amount Owed: %s TSTUSD\n", totalValueOutstanding)
|
||||
fmt.Printf("Loan Service Fee: %s TSTUSD\n", loanServiceFee)
|
||||
fmt.Printf("Total Payment Due (including fees): %s TSTUSD\n", totalPayment)
|
||||
|
||||
// Prepare LoanPay transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanPay transaction ===\n\n")
|
||||
loanPayTx := transaction.LoanPay{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: totalPayment,
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanPayTx := loanPayTx.Flatten()
|
||||
loanPayTxJSON, _ := json.MarshalIndent(flatLoanPayTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanPayTxJSON))
|
||||
|
||||
// Sign, submit, and wait for payment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanPay transaction ===\n\n")
|
||||
payResponse, err := client.SubmitTxAndWait(flatLoanPayTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if payResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to pay loan: %s\n", payResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan paid successfully!\n")
|
||||
|
||||
// Extract updated loan info from transaction results ----------------------
|
||||
fmt.Printf("\n=== Loan Status After Payment ===\n\n")
|
||||
for _, node := range payResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
if balance, ok := node.ModifiedNode.FinalFields["TotalValueOutstanding"].(string); ok {
|
||||
fmt.Printf("Outstanding Loan Balance: %s TSTUSD\n", balance)
|
||||
} else {
|
||||
fmt.Printf("Outstanding Loan Balance: Loan fully paid off!\n")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanDelete transaction ----------------------
|
||||
// Either the loan broker or borrower can submit this transaction.
|
||||
fmt.Printf("\n=== Preparing LoanDelete transaction ===\n\n")
|
||||
loanDeleteTx := transaction.LoanDelete{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
|
||||
flatLoanDeleteTx := loanDeleteTx.Flatten()
|
||||
loanDeleteTxJSON, _ := json.MarshalIndent(flatLoanDeleteTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanDeleteTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deletion validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanDelete transaction ===\n\n")
|
||||
deleteResponse, err := client.SubmitTxAndWait(flatLoanDeleteTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if deleteResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to delete loan: %s\n", deleteResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan deleted successfully!\n")
|
||||
|
||||
// Verify loan deletion ----------------------
|
||||
fmt.Printf("\n=== Verifying Loan Deletion ===\n\n")
|
||||
_, err = client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
if err.Error() == "entryNotFound" {
|
||||
fmt.Printf("Loan has been successfully removed from the XRP Ledger!\n")
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Warning: Loan still exists in the ledger.\n")
|
||||
}
|
||||
}
|
||||
@@ -1372,6 +1372,61 @@ const events = [
|
||||
image: require("../static/img/events/meetup-london.png"),
|
||||
end_date: "February 18, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Ledger Meetup Poland: The Builder's Foundation",
|
||||
description:
|
||||
"XRPL Commons brings its ecosystem to Warsaw with a first pilot event alongside Neti. Join us at EXPO XXI during Polish Blockchain Week for a high-impact session with developers and founders shaping the next wave of the Agentic Web.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/boucntsh",
|
||||
location: "Warsaw, Poland",
|
||||
date: "March 24, 2026",
|
||||
image: require("../static/img/events/xrp-ledger-meetup-poland.jpg"),
|
||||
end_date: "March 24, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRPL Aquarium Demo Day #8 Social Impact",
|
||||
description:
|
||||
"The Aquarium Residency is a 12-week program for entrepreneurs & developers building on the XRP Ledger blockchain. Join us at our Paris HQ to connect with our residents, discover their projects focused on Social Impact, and engage with the XRPL community.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/2feub5uj",
|
||||
location: "Paris, France",
|
||||
date: "March 25, 2026",
|
||||
image: require("../static/img/events/aquarium-demo-day-8.jpg"),
|
||||
end_date: "March 25, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRPL & GDF Stablecoins Round table",
|
||||
description:
|
||||
"Invite-only Executive Roundtable on Stablecoins in Paris, hosted by XRPL Commons and Global Digital Finance. Senior leaders from finance, policy, and tech will explore institutional use cases, regulation, and real-world deployments shaping the future of digital money.",
|
||||
type: "conference",
|
||||
link: "https://luma.com/tgg0id1d",
|
||||
location: "Paris, France",
|
||||
date: "April 7, 2026",
|
||||
image: require("../static/img/events/gdf-stablecoins-roundtable.jpg"),
|
||||
end_date: "April 7, 2026",
|
||||
},
|
||||
{
|
||||
name: "HACK THE BLOCK 2026 Paris Blockchain Week XRPL Hackathon",
|
||||
description:
|
||||
"The flagship hackathon of Paris Blockchain Week, a 36-hour sprint where developers from around the world turn ideas into real on-chain solutions, backed by leading ecosystems and judged by Europe's top tech entrepreneurs and investors. Teams don't just compete, they get discovered.",
|
||||
type: "hackathon",
|
||||
link: "https://luma.com/Hacktheblock2026-PBW-XRPL",
|
||||
location: "Paris, France",
|
||||
date: "April 11, 2026",
|
||||
image: require("../static/img/events/hack-the-block-2026.jpg"),
|
||||
end_date: "April 12, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRPL Zone Paris",
|
||||
description:
|
||||
"Connecting XRPL Builders at Paris Blockchain Week 2026. XRPL Commons invites core ecosystem projects & builders to gather at our headquarters in the heart of Paris for a special edition of XRPL Zone Paris.",
|
||||
type: "zone",
|
||||
link: "https://luma.com/780xhfr7",
|
||||
location: "Paris, France",
|
||||
date: "April 14, 2026",
|
||||
image: require("../static/img/events/xrpl-zone-paris.jpg"),
|
||||
end_date: "April 14, 2026",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1453,33 +1508,33 @@ export default function Events() {
|
||||
<div className="pr-2 col">
|
||||
<img
|
||||
alt="xrp ledger events hero"
|
||||
src={require("../static/img/events/xrpl-hero.png")}
|
||||
src={require("../static/img/events/xrp-community-night-paris.png")}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5 pr-2 col">
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h2 className="mb-8 h4 h2-sm">
|
||||
{translate("XRP Community Night Denver")}
|
||||
{translate("XRP Community Night Paris")}
|
||||
</h2>
|
||||
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
|
||||
</div>
|
||||
<p className="mb-4">
|
||||
{translate(
|
||||
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
|
||||
"Attending Paris Blockchain Week? Join us for an evening with the XRP community in Paris. Connect with the users, builders and projects innovating with and utilizing XRP."
|
||||
)}
|
||||
</p>
|
||||
<div className=" my-3 event-small-gray">
|
||||
{translate("Location: Denver, CO")}
|
||||
{translate("Location: Paris, France")}
|
||||
</div>
|
||||
<div className="py-2 my-3 event-small-gray">
|
||||
{translate("February 18, 2026")}
|
||||
{translate("April 15, 2026")}
|
||||
</div>
|
||||
<div className="d-lg-block">
|
||||
<a
|
||||
className="btn btn-primary btn-arrow-out"
|
||||
target="_blank"
|
||||
href="https://luma.com/chz145tf?utm_source=xprlorg"
|
||||
href="https://luma.com/wnkqmmqy?utm_source=xprlorg"
|
||||
>
|
||||
{translate("Register Now")}
|
||||
</a>
|
||||
|
||||
@@ -730,12 +730,22 @@ const CommunityPage: React.FC = () => {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="td-img">
|
||||
<a
|
||||
href="https://discord.gg/xrpl"
|
||||
target="_blank"
|
||||
>
|
||||
<img className="discord-icon" alt="discord icon" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{translate(
|
||||
"Join the XRPL Discord to connect and network with builders, validators, and cryptocurrency enthusiasts."
|
||||
)}
|
||||
<a
|
||||
href="https://discord.gg/xrpl"
|
||||
target="_blank"
|
||||
>
|
||||
{translate(
|
||||
"Join the XRPL Discord to connect and network with builders, validators, and cryptocurrency enthusiasts."
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
@@ -749,12 +759,22 @@ const CommunityPage: React.FC = () => {
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="td-img">
|
||||
<img className="twitter-icon" alt="twitter icon" />
|
||||
<a
|
||||
href="https://x.com/RippleXDev"
|
||||
target="_blank"
|
||||
>
|
||||
<img className="twitter-icon" alt="twitter icon" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{translate(
|
||||
"Follow @RippleXDev on X for the latest updates on the XRP Ledger ecosystem."
|
||||
)}
|
||||
<a
|
||||
href="https://x.com/RippleXDev"
|
||||
target="_blank"
|
||||
>
|
||||
{translate(
|
||||
"Follow @RippleXDev on X for the latest updates on the XRP Ledger ecosystem."
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
@@ -768,12 +788,22 @@ const CommunityPage: React.FC = () => {
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="td-img">
|
||||
<img className="youtube-icon" alt="youtube icon" />
|
||||
<a
|
||||
href="https://youtube.com/playlist?list=PLl-QsmXvjodqxEjtUqEv3u2o2Zd6zqkNA&feature=shared"
|
||||
target="_blank"
|
||||
>
|
||||
<img className="youtube-icon" alt="youtube icon" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{translate(
|
||||
"APEX 2025: View keynote sessions from the Apex 2025 where developers, entrepreneurs, and industry leaders come together to learn, build, and celebrate all things XRP Ledger."
|
||||
)}
|
||||
<a
|
||||
href="https://youtube.com/playlist?list=PLl-QsmXvjodqxEjtUqEv3u2o2Zd6zqkNA&feature=shared"
|
||||
target="_blank"
|
||||
>
|
||||
{translate(
|
||||
"APEX 2025: View keynote sessions from the Apex 2025 where developers, entrepreneurs, and industry leaders come together to learn, build, and celebrate all things XRP Ledger."
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
@@ -787,12 +817,22 @@ const CommunityPage: React.FC = () => {
|
||||
</tr>
|
||||
<tr className="final-tr">
|
||||
<td className="td-img">
|
||||
<a
|
||||
href="https://learn.xrpl.org/react-3d-game/"
|
||||
target="_blank"
|
||||
>
|
||||
<img className="xrpl-icon" alt="xrpl icon" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
href="https://learn.xrpl.org/react-3d-game/"
|
||||
target="_blank"
|
||||
>
|
||||
{translate(
|
||||
"Explore DeFi-Island: A 3D open-source world on the XRPL testnet. Chat with residents, complete quests, and dive into this React.js-powered experience—all in your web browser."
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
|
||||
@@ -273,6 +273,8 @@
|
||||
[XChainAddClaimAttestation transaction]: /docs/references/protocol/transactions/types/xchainaddclaimattestation.md
|
||||
[XChainAddClaimAttestation transactions]: /docs/references/protocol/transactions/types/xchainaddclaimattestation.md
|
||||
[XChainBridge amendment]: /resources/known-amendments.md#xchainbridge
|
||||
[XChainClaim transaction]: /docs/references/protocol/transactions/types/xchainclaim.md
|
||||
[XChainClaim transactions]: /docs/references/protocol/transactions/types/xchainclaim.md
|
||||
[XChainCreateBridge transaction]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
[XChainCreateBridge transactions]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
[XChainCreateBridge]: /docs/references/protocol/transactions/types/xchaincreatebridge.md
|
||||
@@ -370,6 +372,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 Go]: /docs/tutorials/get-started/get-started-go.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
|
||||
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
|
||||
@@ -487,5 +490,6 @@
|
||||
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
|
||||
[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-go library]: https://github.com/XRPLF/xrpl-go
|
||||
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
|
||||
[xrpl-py library]: https://github.com/XRPLF/xrpl-py
|
||||
|
||||
@@ -6,7 +6,7 @@ labels:
|
||||
---
|
||||
# Deleting Accounts
|
||||
|
||||
The owner of an account can send an [AccountDelete transaction][] to deletes the account and related entries from the ledger, sending most of the account's remaining XRP balance to another account. To discourage wasteful creation and deletion of accounts, deleting an account requires burning a higher than usual amount of XRP as the [transaction cost](../transactions/transaction-cost.md).
|
||||
The owner of an account can send an [AccountDelete transaction][] to delete the account and related entries from the ledger, sending most of the account's remaining XRP balance to another account. To discourage wasteful creation and deletion of accounts, deleting an account requires burning a higher than usual amount of XRP as the [transaction cost](../transactions/transaction-cost.md).
|
||||
|
||||
Some types of associated ledger entries block an account from being deleted. For example, the issuer of a fungible token can't be deleted while anyone holds a nonzero balance of that token.
|
||||
|
||||
@@ -16,22 +16,32 @@ After an account has been deleted, it can be re-created in the ledger through th
|
||||
|
||||
To be deleted, an account must meet the following requirements:
|
||||
|
||||
- The account's `Sequence` number plus 256 must be less than the current [Ledger Index][].
|
||||
- The account must not have any "deletion blockers" in its owner directory. This includes cases where the account is a sender _or_ receiver of funds. See below for a full list of deletion blockers.
|
||||
- The account must own fewer than 1000 objects in the ledger.
|
||||
- The account's `Sequence` number plus 255 must be less than or equal to the current [Ledger Index][]. This is to protect against replaying old transactions.
|
||||
- The account must not have any "deletion blockers" in its owner directory. Deletion blockers are generally ledger entries that represent assets, obligations, or transfers of funds. See below for a full list of deletion blockers.
|
||||
- The account must own 1000 or fewer objects in the ledger.
|
||||
- The transaction must pay a special [transaction cost][] equal to at least the [owner reserve](reserves.md) for one item (currently {% $env.PUBLIC_OWNER_RESERVE %}).
|
||||
- If the account has issued any [NFTs](../tokens/nfts/index.md), they must all have been burned. Additionally, the account's `FirstNFTSequence` number plus `MintedNFTokens` number plus 255 must be less than or equal to the current ledger index. This is to protect against reusing `NFTokenID` values. {% amendment-disclaimer name="fixNFTokenRemint" /%}
|
||||
|
||||
### Deletion Blockers
|
||||
## Deletion Blockers
|
||||
|
||||
The following [ledger entry types](../../references/protocol/ledger-data/ledger-entry-types/index.md) are deletion blockers:
|
||||
The following table shows which [ledger entry types](../../references/protocol/ledger-data/ledger-entry-types/index.md) are deletion blockers. Any other types of ledger entries that an account owns are automatically deleted along with the account.
|
||||
|
||||
- `Escrow`
|
||||
- `PayChannel`
|
||||
- `RippleState` (trust line)
|
||||
- `Check`
|
||||
- `PermissionedDomain` {% amendment-disclaimer name="PermissionedDomains" /%}
|
||||
Some deletion blockers cannot be removed unilaterally. For example, if you have issued a token to someone else, you cannot delete your account as long as they hold your token. In other cases, you can remove the deletion blocker by sending a transaction that causes the entry to be removed from the ledger.
|
||||
|
||||
Any other types of ledger entries that an account owns are automatically deleted along with the account.
|
||||
| Entry Type | Related Amendment | How to remove |
|
||||
|---|---|---|
|
||||
| [Bridge][Bridge entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Cannot be removed. |
|
||||
| [Check][Check entry] | {% amendment-disclaimer name="Checks" compact=true /%} | Send a [CheckCancel transaction][] to cancel the check. |
|
||||
| [Escrow][Escrow entry] | (Core protocol) | Send an [EscrowCancel transaction][] to cancel the transaction if it has expired; send [EscrowFinish transaction][] to finish it if you can satisfy the timed and/or conditional requirements of the escrow. Otherwise, you can't remove the entry. |
|
||||
| [PayChannel][PayChannel entry] | (Core protocol) | Send a [PaymentChannelClaim transaction][] with the `tfClose` flag to request closing the channel; after the settle delay has passed, send another [PaymentChannelClaim transaction][] to fully close and remove the channel. |
|
||||
| [PermissionedDomain][PermissionedDomain entry] | {% amendment-disclaimer name="PermissionedDomains" compact=true /%} | Send a [PermissionedDomainDelete transaction][] to delete the domain. |
|
||||
| [RippleState][RippleState entry] | (Core protocol) | You can only remove the entry if the counterparty's settings are entirely default. If they are, you can remove it by getting the balance to 0 and setting your own settings to the default with a [TrustSet transaction][]. In the common case, the holder can remove the entry but the issuer cannot. |
|
||||
| [MPToken][MPToken entry] | {% amendment-disclaimer name="MPTokensV1" compact=true /%} | If you are the holder of the MPT, reduce your balance to 0 (for example, using a payment), then send an [MPTokenAuthorize transaction][] with the `tfMPTUnauthorize` flag. If you are the issuer of the MPT, you can't remove the entry. |
|
||||
| [MPTokenIssuance][MPTokenIssuance entry] | {% amendment-disclaimer name="MPTokensV1" compact=true /%} | Send an [MPTokenIssuanceDestroy transaction][]. You can only do this if there are no holders of the MPT. |
|
||||
| [NFTokenPage][NFTokenPage entry] | {% amendment-disclaimer name="NonFungibleTokensV1_1" compact=true /%} | Send [NFTokenBurn transactions][] to burn, or [NFTokenCreateOffer transactions][] to sell or transfer, each NFT you hold. |
|
||||
| [Vault][Vault entry] | {% amendment-disclaimer name="SingleAssetVault" compact=true /%} | Send a [VaultDelete transaction][] to delete the vault. You can only do this if the vault is empty. |
|
||||
| [XChainOwnedClaimID][XChainOwnedClaimID entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Send an [XChainClaim transaction][] to complete the cross-chain transfer. |
|
||||
| [XChainOwned<br>CreateAccountClaimID][XChainOwnedCreateAccountClaimID entry] | {% amendment-disclaimer name="XChainBridge" compact=true /%} | Send enough attestations ([XChainAddAccountCreateAttestation transactions][]) to create the new account. |
|
||||
|
||||
## Cost of Deleting
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ Clawback is disabled by default. To use clawback, you must send an [AccountSet t
|
||||
|
||||
| Field | JSON Type | [Internal Type][] | Required | Description |
|
||||
|:----------|:---------------------|:------------------|:---------|:------------------|
|
||||
| `Account` | String - [Address][] | AccountID | Yes | The issuer of the asset being clawed back. Only the issuer can submit this transaction. |
|
||||
| `Asset` | Object | Issue | Yes | Specifies the asset that the issuer wants to claw back from the AMM pool. The asset can be XRP, a token, or an MPT (see: [Specifying Without Amounts][]). The `issuer` field must match with `Account`. |
|
||||
| `Asset2` | Object | Issue | Yes | Specifies the other asset in the AMM's pool. The asset can be XRP, a token, or an MPT (see: [Specifying Without Amounts][]). |
|
||||
| `Asset` | Object | Issue | Yes | The asset to claw back, which can be a trust line token or MPT (see: [Specifying Without Amounts][]). The issuer must be the sender of this transaction. |
|
||||
| `Asset2` | Object | Issue | Yes | The other asset of the AMM pool to claw back from. The asset can be XRP, a trust line token, or an MPT (see: [Specifying Without Amounts][]). |
|
||||
| `Amount` | [Currency Amount][] | Amount | No | The maximum amount to claw back from the AMM account. The `currency` and `issuer` subfields should match the `Asset` subfields. If this field isn't specified, or the `value` subfield exceeds the holder's available tokens in the AMM, all of the holder's tokens are clawed back. |
|
||||
| `Holder` | String - [Address][] | AccountID | Yes | The account holding the asset to be clawed back. |
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ Provisionally issue a [credential](../../../../concepts/decentralized-storage/cr
|
||||
|
||||
{% raw-partial file="/docs/_snippets/tx-fields-intro.md" /%}
|
||||
|
||||
In addition to the [common fields][], CredentialCreate transactions use the following fields:
|
||||
|
||||
| Field | JSON Type | [Internal Type][] | Required? | Description |
|
||||
|:-----------------|:---------------------|:------------------|:----------|:------------|
|
||||
| `Subject` | String - [Address][] | AccountID | Yes | The subject of the credential. |
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
---
|
||||
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
|
||||
|
||||
Check that 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 255 must be lower than or equal to 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 1000 or fewer 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="// Check if FirstNFTSequence is too high" /%}
|
||||
{% /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="# Check if FirstNFTSequence is too high" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.5. Check NFT sequence number
|
||||
|
||||
Check the `FirstNFTokenSequence` and `MintedNFTokens` fields of the account. (If either field is absent, you can treat its value as `0` for this purpose.) For the account to be deletable, the sum of these two numbers plus 255 must be lower than or equal to the current ledger index.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check if FirstNFTSequence is too high" before="// Check that all issued NFTs have been burned" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check if FirstNFTSequence is too high" before="# Check that all issued NFTs have been burned" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.6. Check that all issued NFTs have been burned
|
||||
|
||||
If the account has issued any NFTs that are still present in the ledger, the account cannot be deleted. You can check to see if any exist by comparing the `MintedNFTokens` field and `BurnedNFTokens` fields of the account. (In both cases, if the field is omitted, treat its value as `0`.) If the `MintedNFTokens` value is larger, the account has issued at least one NFT that has not been burned yet.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Check that all issued NFTs have been burned" before="// Stop if any problems were found" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Check that all issued NFTs have been burned" before="# Stop if any problems were found" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
#### 3.7. Stop if the account can't be deleted
|
||||
|
||||
If any of the previous checks failed, you can't delete the account. Resolving the problems varies by type:
|
||||
|
||||
- If the account sequence or NFT sequence number is too low, wait for the ledger index to advance automatically and try again later. About 15 minutes should be enough.
|
||||
- If the account owns too many objects, or has issued NFTs outstanding, remove the offending objects.
|
||||
- If the account does not have enough XRP, you can send XRP to it so that it can pay the deletion cost, but of course in this case the account does not have enough XRP for you to recover any by deleting it. You can also wait and try again if the network [votes to lower the reserve requirements](../../../concepts/consensus-protocol/fee-voting.md), which would also lower the cost to delete an account.
|
||||
|
||||
The sample code does not try to handle these problems, and quits if any problems were found:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/js/delete-account.js" language="js" from="// Stop if any problems were found" before="// Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/delete-account/py/delete-account.py" language="py" from="# Stop if any problems were found" before="# Check for deletion blockers" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check for deletion blockers and remove them if possible
|
||||
|
||||
Some types of ledger entries 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" /%}
|
||||
@@ -31,6 +31,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -57,6 +58,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -77,6 +85,13 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID.
|
||||
@@ -92,6 +107,11 @@ This example uses preconfigured accounts, MPTs, and loan broker data from the `l
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" from="// Check for setup data" before="// Check cover available" /%}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lending-setup` script, but you can replace `loanBrokerWallet`, `mptIssuerWallet`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check initial cover available
|
||||
@@ -105,6 +125,9 @@ Check the initial cover (first-loss capital) available using the [ledger_entry m
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Check cover available" before="# Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" from="// Check cover available" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited.
|
||||
@@ -124,6 +147,11 @@ The `Amount` field specifies the MPT and amount to deposit as first-loss capital
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" 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.
|
||||
@@ -139,6 +167,9 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" 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.
|
||||
@@ -154,6 +185,9 @@ Retrieve the cover available from the transaction result by checking the `LoanBr
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" from="// Extract updated cover available" before="// Verify issuer of cover asset" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Verify the asset issuer
|
||||
@@ -167,6 +201,9 @@ Before executing a clawback, verify that the account submitting the transaction
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" 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.
|
||||
@@ -182,6 +219,9 @@ Create the [LoanBrokerCoverClawback transaction][] object.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" 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`.
|
||||
@@ -197,6 +237,9 @@ Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" 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.
|
||||
@@ -212,6 +255,9 @@ Retrieve the final cover available from the transaction result.
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-clawback/main.go" language="go" from="// Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -30,6 +30,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -56,6 +57,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -76,6 +84,13 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan-broker/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the vault owner account and vault ID. The vault owner will also be the loan broker.
|
||||
@@ -91,6 +106,11 @@ This example uses preconfigured accounts and vault data from the `lendingSetup.j
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan-broker/main.go" language="go" from="// Check for setup data" before="// Prepare LoanBrokerSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lending-setup` script, but you can replace `loanBrokerWallet` and `vaultID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanBrokerSet transaction
|
||||
@@ -104,6 +124,9 @@ Create the [LoanBrokerSet transaction][] object.
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Prepare LoanBrokerSet" before="# Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan-broker/main.go" language="go" 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).
|
||||
@@ -119,6 +142,9 @@ Sign and submit the `LoanBrokerSet` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan-broker/main.go" language="go" from="// Submit, sign" before="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -134,6 +160,9 @@ Retrieve the loan broker's information from the transaction result by checking f
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan-broker/main.go" language="go" from="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan broker pseudo-account is a special account that holds first-loss capital.
|
||||
|
||||
@@ -33,6 +33,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -59,6 +60,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -79,6 +87,13 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, borrower account, and loan broker ID.
|
||||
@@ -94,6 +109,11 @@ This example uses preconfigured accounts and loan broker data from the `lendingS
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" from="// Check for setup data" before="// Prepare LoanSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending-setup` script, but you can replace `loanBrokerWallet`, `borrowerWallet`, and `loanBrokerID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanSet transaction
|
||||
@@ -129,6 +149,20 @@ The loan terms include:
|
||||
- `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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" 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:
|
||||
- `PrincipalRequested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.
|
||||
- `InterestRate`: The annualized interest rate in 1/10th basis points (500 = 0.5%).
|
||||
- `PaymentTotal`: The number of payments to be made.
|
||||
- `PaymentInterval`: The number of seconds between payments (2592000 = 30 days).
|
||||
- `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 %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Add loan broker signature
|
||||
@@ -142,6 +176,9 @@ The loan broker (the `Account`) signs the transaction first, adding their `TxnSi
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" from="// Loan broker signs first" before="// Borrower signs second" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Add borrower signature
|
||||
@@ -155,6 +192,9 @@ The borrower (the `Counterparty`) signs the transaction second. Their `TxnSignat
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" from="// Borrower signs second" before="// Submit and wait" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 6. Submit LoanSet transaction
|
||||
@@ -168,6 +208,9 @@ Submit the fully signed `LoanSet` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" from="// Submit and wait" before="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
@@ -183,6 +226,9 @@ Retrieve the loan's information from the transaction result by checking for the
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/create-loan/main.go" language="go" from="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
@@ -32,6 +32,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -58,6 +59,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -78,6 +86,13 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, loan broker ID, and MPT issuance ID.
|
||||
@@ -93,6 +108,11 @@ This example uses preconfigured accounts and loan broker data from the `lendingS
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" from="// Check for setup data" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending-setup` script, but you can replace `loanBrokerWallet`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanBrokerCoverDeposit transaction
|
||||
@@ -110,6 +130,11 @@ The `Amount` field specifies the MPT and amount to deposit as first-loss capital
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" 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.
|
||||
@@ -125,6 +150,9 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" 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.
|
||||
@@ -140,6 +168,9 @@ Retrieve the cover balance from the transaction result by checking the `LoanBrok
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" from="// Extract cover balance" before="// Prepare LoanBrokerCoverWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `LoanBroker` pseudo-account address is the `Account` field, and `CoverAvailable` shows the cover balance.
|
||||
@@ -155,6 +186,9 @@ Create the [LoanBrokerCoverWithdraw transaction][] object.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" from="// Prepare LoanBrokerCoverWithdraw" before="// Sign, submit, and wait for withdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Submit LoanBrokerCoverWithdraw transaction
|
||||
@@ -168,6 +202,9 @@ Sign and submit the `LoanBrokerCoverWithdraw` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" 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.
|
||||
@@ -183,6 +220,9 @@ Retrieve the updated cover balance from the transaction result.
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/cover-deposit-and-withdraw/main.go" language="go" from="// Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `CoverAvailable` field now shows the reduced balance after the withdrawal.
|
||||
|
||||
@@ -33,6 +33,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -59,6 +60,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -80,6 +88,14 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
- `time`: Used for grace period countdown and date formatting.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account and loan ID.
|
||||
@@ -95,6 +111,11 @@ This example uses preconfigured accounts and loan data from the `lendingSetup.js
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" from="// Check for setup data" before="// Check loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending-setup` script, but you can replace `loanBrokerWallet` and `loanID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check loan status
|
||||
@@ -108,6 +129,9 @@ Check the current status of the loan using the [ledger_entry method][].
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" 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.
|
||||
@@ -123,6 +147,9 @@ Create the [LoanManage transaction][] with the `tfLoanImpair` flag.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" from="// Prepare LoanManage transaction to impair" before="// Sign, submit, and wait for impairment" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanManage impairment transaction
|
||||
@@ -136,6 +163,9 @@ Sign and submit the `LoanManage` transaction to impair the loan.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" 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.
|
||||
@@ -151,6 +181,9 @@ Retrieve the loan's grace period and updated payment due date from the transacti
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" 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.
|
||||
@@ -166,6 +199,9 @@ This countdown displays the remaining seconds in real-time. Once the grace perio
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" from="// Countdown until loan can be defaulted" before="// Prepare LoanManage transaction to default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 8. Prepare LoanManage transaction to default the loan
|
||||
@@ -179,6 +215,9 @@ After the grace period expires, create a `LoanManage` transaction with the `tfLo
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" from="// Prepare LoanManage transaction to default" before="// Sign, submit, and wait for default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 9. Submit LoanManage default transaction
|
||||
@@ -192,6 +231,9 @@ Sign and submit the `LoanManage` transaction to default the loan.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" 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.
|
||||
@@ -207,6 +249,9 @@ Confirm the loan has been defaulted by checking the loan flags.
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-manage/main.go" language="go" from="// Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan flags are parsed to confirm the `tfLoanDefault` flag is now set.
|
||||
|
||||
@@ -33,6 +33,7 @@ To complete this tutorial, you should:
|
||||
- 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.
|
||||
- **Go** with the [xrpl-go library][]. See [Get Started Using Go][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
@@ -59,6 +60,13 @@ source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
From the code sample folder, use `go` to install dependencies.
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
@@ -79,6 +87,14 @@ To get started, import the necessary libraries and instantiate a client to conne
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
|
||||
- `math/big`: Used for calculating the total payment amount.
|
||||
- `os` and `os/exec`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" before="// Check for setup data" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the borrower account, loan ID, and MPT issuance ID.
|
||||
@@ -94,6 +110,11 @@ This example uses preconfigured accounts and loan data from the `lendingSetup.js
|
||||
|
||||
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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" from="// Check for setup data" before="// Check initial loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending-setup` script, but you can replace `borrowerWallet`, `loanID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check loan status
|
||||
@@ -107,6 +128,9 @@ Check the current status of the loan using the [ledger_entry method][].
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" 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.
|
||||
@@ -126,6 +150,9 @@ Create the [LoanPay transaction][] with the total payment amount.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" from="// Prepare LoanPay transaction" before="// Sign, submit, and wait for payment validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanPay transaction
|
||||
@@ -139,6 +166,9 @@ Sign and submit the `LoanPay` transaction to the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" 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.
|
||||
@@ -154,6 +184,9 @@ Retrieve the loan balance from the transaction result by checking for the `Loan`
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" 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.
|
||||
@@ -169,6 +202,9 @@ Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" 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.
|
||||
@@ -184,6 +220,9 @@ Sign and submit the `LoanDelete` transaction.
|
||||
{% 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 %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" 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.
|
||||
@@ -199,6 +238,9 @@ Confirm that the loan has been removed from the XRP Ledger.
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/go/loan-pay/main.go" language="go" from="// Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted.
|
||||
|
||||
@@ -28,7 +28,7 @@ To learn about MPTs in general, go to the **Concept** page. For developer-focuse
|
||||
{% card-grid %}
|
||||
|
||||
{% xrpl-card title="Concept: Multi‑Purpose Tokens" body="Read the concept documentation to learn more about Multi-Purpose Tokens." href="docs/concepts/tokens/fungible-tokens/multi-purpose-tokens/" /%}
|
||||
{% xrpl-card title="Tutorial: Issue a Multi‑Purpose Token" body="Step‑by‑step, hands‑on tutorial to issue an MPT using the XRP Ledger SDKs." href="docs/tutorials/issue-a-multi-purpose-token/" /%}
|
||||
{% xrpl-card title="Tutorial: Issue a Multi‑Purpose Token" body="Step‑by‑step, hands‑on tutorial to issue an MPT using the XRP Ledger SDKs." href="docs/tutorials/tokens/mpts/issue-a-multi-purpose-token" /%}
|
||||
{% xrpl-card title="MPT Generator" body="Download the MPT Generator and learn how to create an asset-backed Treasury bill." href="#mpt-generator"/%}
|
||||
{% /card-grid %}
|
||||
|
||||
|
||||
1359
package-lock.json
generated
1359
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,10 +12,10 @@
|
||||
"keywords": [],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "6.5.2",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/view": "^6.22.2",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@redocly/realm": "0.130.4",
|
||||
"@redocly/realm": "0.131.2",
|
||||
"@uiw/codemirror-themes": "4.21.21",
|
||||
"@uiw/react-codemirror": "^4.21.21",
|
||||
"@xrplf/isomorphic": "^1.0.0-beta.1",
|
||||
|
||||
@@ -6,7 +6,6 @@ ignore:
|
||||
- _code-samples/create-amm/ts/tsconfig.json
|
||||
- resources/contribute-blog/_blog-template.md
|
||||
- resources/contribute-documentation/_tutorial-template.md
|
||||
- CODE-OF-CONDUCT.md
|
||||
l10n:
|
||||
defaultLocale: en-US
|
||||
locales:
|
||||
|
||||
@@ -326,7 +326,8 @@
|
||||
- page: docs/tutorials/best-practices/key-management/remove-a-regular-key-pair.md
|
||||
- page: docs/tutorials/best-practices/key-management/offline-account-setup.md
|
||||
- page: docs/tutorials/best-practices/key-management/set-up-multi-signing.md
|
||||
- page: docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction.md
|
||||
- page: docs/tutorials/best-practices/key-management/send-a-multi-signed-transaction.md
|
||||
- page: docs/tutorials/best-practices/key-management/delete-an-account.md
|
||||
- group: Advanced Developer Topics
|
||||
groupTranslationKey: sidebar.docs.tutorials.advancedDeveloperTopics
|
||||
expanded: false
|
||||
|
||||
BIN
static/img/events/aquarium-demo-day-8.jpg
Normal file
BIN
static/img/events/aquarium-demo-day-8.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
static/img/events/gdf-stablecoins-roundtable.jpg
Normal file
BIN
static/img/events/gdf-stablecoins-roundtable.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
static/img/events/hack-the-block-2026.jpg
Normal file
BIN
static/img/events/hack-the-block-2026.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
static/img/events/xrp-community-night-paris.png
Normal file
BIN
static/img/events/xrp-community-night-paris.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
static/img/events/xrp-ledger-meetup-poland.jpg
Normal file
BIN
static/img/events/xrp-ledger-meetup-poland.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
static/img/events/xrpl-zone-paris.jpg
Normal file
BIN
static/img/events/xrpl-zone-paris.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Reference in New Issue
Block a user