Compare commits

...

27 Commits

Author SHA1 Message Date
Maria Shodunke
ef4343ab0e Update with remaining review comments 2026-02-16 18:57:41 +00:00
Maria Shodunke
fbbd88a069 Apply suggestions from code review
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-02-16 13:49:24 +00:00
Maria Shodunke
a57c4b7d76 Speed up vault_setup.py 2026-02-16 13:49:24 +00:00
Maria Shodunke
0c63250bdf Add Python SAV code examples 2026-02-16 13:49:24 +00:00
oeggert
60d7292eb6 Merge pull request #3495 from XRPLF/lending-python
Lending Protocol Tutorials and Python Code Samples
2026-02-13 17:33:37 -08:00
Oliver Eggert
10f57ddd43 add reviewer suggestions 2026-02-13 12:38:00 -08:00
Oliver Eggert
2cbd652135 update tutorials to remove asyncio reference 2026-02-13 11:48:14 -08:00
Oliver Eggert
636f175ada update code samples to use jsonrpcclient 2026-02-13 11:19:18 -08:00
Oliver Eggert
36d1448047 add python samples to create-a-loan, manage-a-loan, and pay-off-a-loan 2026-02-13 00:25:33 -08:00
Oliver Eggert
575615487a add python to claw-back-cover and deposit-and-withdraw-cover tutorials 2026-02-12 23:39:10 -08:00
Oliver Eggert
b12112187e add python to create loan broker tutorial 2026-02-12 23:08:16 -08:00
Oliver Eggert
cffcf05da6 update LoanSet py code samples to use local signing 2026-02-12 22:04:27 -08:00
Oliver Eggert
c2dd33cf37 update create loan tutorial for local signing 2026-02-12 20:58:36 -08:00
Oliver Eggert
9c729d9223 update LoanSet code samples to use local signing 2026-02-12 20:39:17 -08:00
Oliver Eggert
a7e2464d46 update library dependencies 2026-02-12 18:09:36 -08:00
Oliver Eggert
43c2bec558 add loan_pay script and update readme 2026-02-12 01:51:28 -08:00
Oliver Eggert
65f7ded0cf add loan_manage script and update readme 2026-02-12 01:07:42 -08:00
Oliver Eggert
a3ed1f2c34 add create_loan script and update readme 2026-02-11 23:55:04 -08:00
Oliver Eggert
d4a5f752d7 add cover_clawback script and update readme 2026-02-11 22:40:57 -08:00
Oliver Eggert
adbf7dbb2f add cover_deposit_and_withdraw script and update readme 2026-02-11 22:16:00 -08:00
Oliver Eggert
21bd1991e6 add readme, requirements.txt, and create_loan_broker script 2026-02-11 21:58:38 -08:00
Oliver Eggert
b2d86aef14 add setup script for python 2026-02-11 16:04:23 -08:00
Oliver Eggert
28e4927131 update number fields to strings 2026-02-11 16:02:41 -08:00
oeggert
5ad87dee4b Merge pull request #3487 from XRPLF/lending-use-case
Lending Protocol doc updates
2026-02-10 10:19:59 -08:00
oeggert
66caa79289 Update docs/use-cases/defi/institutional-credit-facilities.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2026-02-10 10:10:32 -08:00
Oliver Eggert
ff75fc0af9 update reference docs to latest spec 2026-02-09 22:09:26 -08:00
Oliver Eggert
1e2f9ea0b0 add user journeys section 2026-02-09 20:52:27 -08:00
43 changed files with 3093 additions and 436 deletions

2
.gitignore vendored
View File

@@ -8,7 +8,7 @@ yarn-error.log
*.iml *.iml
.venv/ .venv/
_code-samples/*/js/package-lock.json _code-samples/*/js/package-lock.json
_code-samples/*/js/*[Ss]etup.json _code-samples/*/*/*[Ss]etup.json
# PHP # PHP
composer.lock composer.lock

View File

@@ -177,84 +177,84 @@ node createLoan.js
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information: 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 ```sh
Loan broker address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY Loan broker address: rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz
Borrower address: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA Borrower address: rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15 LoanBrokerID: 3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5
=== Preparing LoanSet transaction === === Preparing LoanSet transaction ===
{ {
"TransactionType": "LoanSet", "TransactionType": "LoanSet",
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY", "Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA", "Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15", "LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"PrincipalRequested": 1000, "PrincipalRequested": "1000",
"InterestRate": 500, "InterestRate": 500,
"PaymentTotal": 12, "PaymentTotal": 12,
"PaymentInterval": 2592000, "PaymentInterval": 2592000,
"GracePeriod": 604800, "GracePeriod": 604800,
"LoanOriginationFee": 100, "LoanOriginationFee": "100",
"LoanServiceFee": 10, "LoanServiceFee": "10",
"Flags": 0, "Flags": 0,
"Sequence": 3212122, "Sequence": 3670743,
"LastLedgerSequence": 3212233, "LastLedgerSequence": 3673248,
"Fee": "2" "Fee": "2"
} }
=== Adding loan broker signature === === Adding loan broker signature ===
TxnSignature: 44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400 TxnSignature: F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A
SigningPubKey: ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C SigningPubKey: EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530
Signed loanSetTx for borrower to sign over: Signed loanSetTx for borrower to sign over:
{ {
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY", "TransactionType": "LoanSet",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"Fee": "2",
"Flags": 0, "Flags": 0,
"Sequence": 3670743,
"LastLedgerSequence": 3673248,
"PaymentInterval": 2592000,
"GracePeriod": 604800, "GracePeriod": 604800,
"PaymentTotal": 12,
"InterestRate": 500, "InterestRate": 500,
"LastLedgerSequence": 3212233, "LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15", "Fee": "2",
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanOriginationFee": "100", "LoanOriginationFee": "100",
"LoanServiceFee": "10", "LoanServiceFee": "10",
"PaymentInterval": 2592000, "PrincipalRequested": "1000"
"PaymentTotal": 12,
"PrincipalRequested": "1000",
"Sequence": 3212122,
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C",
"TransactionType": "LoanSet",
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400"
} }
=== Adding borrower signature === === Adding borrower signature ===
Borrower TxnSignature: 2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F Borrower TxnSignature: 52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800
Borrower SigningPubKey: ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA Borrower SigningPubKey: EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62
Fully signed LoanSet transaction: Fully signed LoanSet transaction:
{ {
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY", "TransactionType": "LoanSet",
"Counterparty": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
"CounterpartySignature": {
"SigningPubKey": "ED4C7C0127EFEAFD04B2CDFA1CA3A8EF5933227C610031DF2130010B73CBBBDCDA",
"TxnSignature": "2D17F5BAED2540CD875B009A99B02649E24A5DCDFDC5BAFCB2DC41F998FE4AFBDD6BDF8BDF1C3C857ED8DD638F10BEA10295812155D9759E3ADED9D6208F150F"
},
"Fee": "2",
"Flags": 0, "Flags": 0,
"Sequence": 3670743,
"LastLedgerSequence": 3673248,
"PaymentInterval": 2592000,
"GracePeriod": 604800, "GracePeriod": 604800,
"PaymentTotal": 12,
"InterestRate": 500, "InterestRate": 500,
"LastLedgerSequence": 3212233, "LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15", "Fee": "2",
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"LoanOriginationFee": "100", "LoanOriginationFee": "100",
"LoanServiceFee": "10", "LoanServiceFee": "10",
"PaymentInterval": 2592000,
"PaymentTotal": 12,
"PrincipalRequested": "1000", "PrincipalRequested": "1000",
"Sequence": 3212122, "CounterpartySignature": {
"SigningPubKey": "ED37EF81218C3C97389A11F07C8339C2880CEAF1A8C6EB539C616D69EF5EBC688C", "SigningPubKey": "EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62",
"TransactionType": "LoanSet", "TxnSignature": "52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800"
"TxnSignature": "44348B918E780608534A9499B9990470E6A3C8E5C7DAC33BF2A5EFA0C292D17B3267D3A177A363CC832D6C6DA36E41CB64909C39CA5D55CF36D232DA49022400" }
} }
=== Submitting signed LoanSet transaction === === Submitting signed LoanSet transaction ===
@@ -264,19 +264,19 @@ Loan created successfully!
=== Loan Information === === Loan Information ===
{ {
"Borrower": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA", "Borrower": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
"GracePeriod": 604800, "GracePeriod": 604800,
"InterestRate": 500, "InterestRate": 500,
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15", "LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
"LoanOriginationFee": "100", "LoanOriginationFee": "100",
"LoanSequence": 3, "LoanSequence": 6,
"LoanServiceFee": "10", "LoanServiceFee": "10",
"NextPaymentDueDate": 825408182, "NextPaymentDueDate": 826862960,
"PaymentInterval": 2592000, "PaymentInterval": 2592000,
"PaymentRemaining": 12, "PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956", "PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000", "PrincipalOutstanding": "1000",
"StartDate": 822816182, "StartDate": 824270960,
"TotalValueOutstanding": "1003" "TotalValueOutstanding": "1003"
} }
``` ```

View File

@@ -41,77 +41,42 @@ const loanSetTx = await client.autofill({
Account: loanBroker.address, Account: loanBroker.address,
Counterparty: borrower.address, Counterparty: borrower.address,
LoanBrokerID: loanBrokerID, LoanBrokerID: loanBrokerID,
PrincipalRequested: 1000, PrincipalRequested: '1000',
InterestRate: 500, InterestRate: 500,
PaymentTotal: 12, PaymentTotal: 12,
PaymentInterval: 2592000, PaymentInterval: 2592000,
GracePeriod: 604800, GracePeriod: 604800,
LoanOriginationFee: 100, LoanOriginationFee: '100',
LoanServiceFee: 10 LoanServiceFee: '10'
}) })
console.log(JSON.stringify(loanSetTx, null, 2)) console.log(JSON.stringify(loanSetTx, null, 2))
// Loan broker signs first // Loan broker signs first
console.log(`\n=== Adding loan broker signature ===\n`) console.log(`\n=== Adding loan broker signature ===\n`)
const loanBrokerSignature = await client.request({ const loanBrokerSigned = loanBroker.sign(loanSetTx)
command: 'sign', const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
tx_json: loanSetTx,
secret: loanBroker.seed
})
const loanBrokerSignatureResult = loanBrokerSignature.result.tx_json console.log(`TxnSignature: ${loanBrokerSignedTx.TxnSignature}`)
console.log(`SigningPubKey: ${loanBrokerSignedTx.SigningPubKey}\n`)
console.log(`TxnSignature: ${loanBrokerSignatureResult.TxnSignature}`) console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignedTx, null, 2)}`)
console.log(`SigningPubKey: ${loanBrokerSignatureResult.SigningPubKey}\n`)
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignatureResult, null, 2)}`)
// Borrower signs second // Borrower signs second
console.log(`\n=== Adding borrower signature ===\n`) console.log(`\n=== Adding borrower signature ===\n`)
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
const borrowerSignature = await client.request({ console.log(`Borrower TxnSignature: ${fullySigned.tx.CounterpartySignature.TxnSignature}`)
command: 'sign', console.log(`Borrower SigningPubKey: ${fullySigned.tx.CounterpartySignature.SigningPubKey}`)
tx_json: loanBrokerSignatureResult,
secret: borrower.seed,
signature_target: 'CounterpartySignature'
})
const borrowerSignatureResult = borrowerSignature.result.tx_json
console.log(`Borrower TxnSignature: ${borrowerSignatureResult.CounterpartySignature.TxnSignature}`)
console.log(`Borrower SigningPubKey: ${borrowerSignatureResult.CounterpartySignature.SigningPubKey}`)
// Validate the transaction structure before submitting. // Validate the transaction structure before submitting.
xrpl.validate(borrowerSignatureResult) xrpl.validate(fullySigned.tx)
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(borrowerSignatureResult, null, 2)}`) console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(fullySigned.tx, null, 2)}`)
// Submit and wait for validation ---------------------- // Submit and wait for validation ----------------------
console.log(`\n=== Submitting signed LoanSet transaction ===\n`) console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
// Submit the transaction const submitResponse = await client.submitAndWait(fullySigned.tx)
const submitResult = await client.submit(borrowerSignatureResult)
const txHash = submitResult.result.tx_json.hash
// Helper function to check tx hash is validated
async function validateTx (hash, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const tx = await client.request({ command: 'tx', transaction: hash })
if (tx.result.validated) {
return tx
}
} catch (error) {
// Transaction not validated yet, check again
}
}
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
await client.disconnect()
process.exit(1)
}
// Validate the transaction
const submitResponse = await validateTx(txHash)
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') { if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan:', resultCode) console.error('Error: Unable to create loan:', resultCode)

View File

@@ -1,8 +1,8 @@
// Setup script for lending protocol tutorials
import xrpl from 'xrpl' import xrpl from 'xrpl'
import fs from 'fs' import fs from 'fs'
// Setup script for lending protocol tutorials
process.stdout.write('Setting up tutorial: 0/6\r') process.stdout.write('Setting up tutorial: 0/6\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233') const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
@@ -262,73 +262,35 @@ process.stdout.write('Setting up tutorial: 5/6\r')
// Suppress unnecessary console warning from autofilling LoanSet. // Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {} console.warn = () => {}
// Helper function to create and sign a LoanSet transaction // Helper function to create, sign, and submit a LoanSet transaction
async function createSignedLoanSetTx (ticketSequence) { async function createLoan (ticketSequence) {
const loanSetTx = await client.autofill({ const loanSetTx = await client.autofill({
TransactionType: 'LoanSet', TransactionType: 'LoanSet',
Account: loanBroker.address, Account: loanBroker.address,
Counterparty: borrower.address, Counterparty: borrower.address,
LoanBrokerID: loanBrokerID, LoanBrokerID: loanBrokerID,
PrincipalRequested: 1000, PrincipalRequested: '1000',
InterestRate: 500, InterestRate: 500,
PaymentTotal: 1, PaymentTotal: 1,
PaymentInterval: 2592000, PaymentInterval: 2592000,
LoanOriginationFee: 100, LoanOriginationFee: '100',
LoanServiceFee: 10, LoanServiceFee: '10',
Sequence: 0, Sequence: 0,
TicketSequence: ticketSequence TicketSequence: ticketSequence
}) })
const loanBrokerSignature = await client.request({ const loanBrokerSigned = loanBroker.sign(loanSetTx)
command: 'sign', const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
tx_json: loanSetTx,
secret: loanBroker.seed
})
const borrowerSignature = await client.request({ const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
command: 'sign', const submitResponse = await client.submitAndWait(fullySigned.tx)
tx_json: loanBrokerSignature.result.tx_json,
secret: borrower.seed,
signature_target: 'CounterpartySignature'
})
return borrowerSignature.result.tx_json return submitResponse
}
// Create and submit both loans
const [signedLoan1, signedLoan2] = await Promise.all([
createSignedLoanSetTx(tickets[0]),
createSignedLoanSetTx(tickets[1])
])
const [submitLoan1, submitLoan2] = await Promise.all([
client.submit(signedLoan1),
client.submit(signedLoan2)
])
const hash1 = submitLoan1.result.tx_json.hash
const hash2 = submitLoan2.result.tx_json.hash
// Helper function to check tx hash is validated
async function validateTx (hash, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const tx = await client.request({ command: 'tx', transaction: hash })
if (tx.result.validated) {
return tx
}
} catch (error) {
// Transaction not validated yet, check again
}
}
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
await client.disconnect()
process.exit(1)
} }
const [submitResponse1, submitResponse2] = await Promise.all([ const [submitResponse1, submitResponse2] = await Promise.all([
validateTx(hash1), createLoan(tickets[0]),
validateTx(hash2) createLoan(tickets[1])
]) ])
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node => const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>

View File

@@ -2,7 +2,7 @@
"name": "lending-protocol-examples", "name": "lending-protocol-examples",
"description": "Example code for creating and managing loans.", "description": "Example code for creating and managing loans.",
"dependencies": { "dependencies": {
"xrpl": "^4.5.0" "xrpl": "^4.6.0"
}, },
"type": "module" "type": "module"
} }

View File

@@ -0,0 +1,403 @@
# Lending Protocol Examples (Python)
This directory contains Python examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
## Setup
Install dependencies before running any examples:
```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
---
## Create a Loan Broker
```sh
python3 create_loan_broker.py
```
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
```sh
Loan broker/vault owner address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
Vault ID: 2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F
=== Preparing LoanBrokerSet transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerSet",
"SigningPubKey": "",
"VaultID": "2B71E8E1323BFC8F2AC27F8C217870B63921EFA0C02DF7BA8B099C7DC6A1D00F",
"ManagementFeeRate": 1000
}
=== Submitting LoanBrokerSet transaction ===
Loan broker created successfully!
=== Loan Broker Information ===
LoanBroker ID: 86911896026EA9DEAEFC1A7959BC05D8B1A1EC25B9960E8C54424B7DC41F8DA8
LoanBroker Psuedo-Account Address: rPhpC2XGz7v5g2rPom7JSWJcic1cnkoBh9
```
---
## Claw Back First-loss Capital
```sh
python3 cover_clawback.py
```
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback:
```sh
Loan broker address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
MPT issuer address: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
=== Cover Available ===
1000 TSTUSD
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverDeposit",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Available After Deposit ===
2000 TSTUSD
=== Verifying Asset Issuer ===
MPT issuer account verified: rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d. Proceeding to clawback.
=== Preparing LoanBrokerCoverClawback transaction ===
{
"Account": "rNzJg2EVwo56eAoBxz5WnTfmgoLbfaAT8d",
"TransactionType": "LoanBrokerCoverClawback",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverClawback transaction ===
Successfully clawed back 2000 TSTUSD!
=== Final Cover Available After Clawback ===
0 TSTUSD
```
---
## Deposit and Withdraw First-loss Capital
```sh
python3 cover_deposit_and_withdraw.py
```
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal:
```sh
Loan broker address: rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn
LoanBrokerID: 041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0
MPT ID: 0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2
=== Preparing LoanBrokerCoverDeposit transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverDeposit",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "2000"
}
}
=== Submitting LoanBrokerCoverDeposit transaction ===
Cover deposit successful!
=== Cover Balance ===
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
Cover balance after deposit: 2000 TSTUSD
=== Preparing LoanBrokerCoverWithdraw transaction ===
{
"Account": "rBeEX3qQzP3UL5WMwZAzdPPpzckH73YvBn",
"TransactionType": "LoanBrokerCoverWithdraw",
"SigningPubKey": "",
"LoanBrokerID": "041E256F124841FF81DF105C62A72676BFD746975F86786166B689F304BE96E0",
"Amount": {
"mpt_issuance_id": "0037A8ED99701AFEC4BCC3A39299252CA41838059572E7F2",
"value": "1000"
}
}
=== Submitting LoanBrokerCoverWithdraw transaction ===
Cover withdraw successful!
=== Updated Cover Balance ===
LoanBroker Pseudo-Account: rUrs1bkhQyh1nxE7u99H92U2Tg8Pogw1bZ
Cover balance after withdraw: 1000 TSTUSD
```
---
## Create a Loan
```sh
python3 create_loan.py
```
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information:
```sh
Loan broker address: ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy
Borrower address: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
LoanBrokerID: 61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2
=== Preparing LoanSet transaction ===
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Adding loan broker signature ===
TxnSignature: 9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D
SigningPubKey: ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47
Signed loan_set_tx for borrower to sign over:
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Adding borrower signature ===
Borrower TxnSignature: A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207
Borrower SigningPubKey: ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC
Fully signed LoanSet transaction:
{
"Account": "ra3aoaincCNBQ7uxvHDgFbtCbVw1VNQkZy",
"TransactionType": "LoanSet",
"Fee": "2",
"Sequence": 3652181,
"LastLedgerSequence": 3674792,
"SigningPubKey": "ED0DC8C222C4BB86CE07165CD0486C598B8146C3150EE40AF48921983DED98FA47",
"TxnSignature": "9756E70F33B359FAEA789D732E752401DE41CAB1A3711517B576DBFF4D89B6A01C234A379391C48B3D88CB031BD679A7EDE4F4BB67AA7297EEE25EA29FF6BD0D",
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"Counterparty": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"CounterpartySignature": {
"SigningPubKey": "ED36B94636EC0F98BB5F6EC58039E23A8C8F1521D2EC1B32C0422A86718C9B95DC",
"TxnSignature": "A0A515BFB131EDC7A8B74F7A66F9DA1DEE25B099373F581BDA340C95F918CEA91E3F4D2019A8DBAFEC53012038839FEA48436D61970B0834F6DDEA64B1776207"
},
"LoanOriginationFee": "100",
"LoanServiceFee": "10",
"InterestRate": 500,
"PrincipalRequested": "1000",
"PaymentTotal": 12,
"PaymentInterval": 2592000,
"GracePeriod": 604800
}
=== Submitting signed LoanSet transaction ===
Loan created successfully!
=== Loan Information ===
{
"Borrower": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"GracePeriod": 604800,
"InterestRate": 500,
"LoanBrokerID": "61A1D6B0F019C5D5BD039AC3DBE2E31813471567854D07D278564E4E2463ABD2",
"LoanOriginationFee": "100",
"LoanSequence": 4,
"LoanServiceFee": "10",
"NextPaymentDueDate": 826867870,
"PaymentInterval": 2592000,
"PaymentRemaining": 12,
"PeriodicPayment": "83.55610375293148956",
"PrincipalOutstanding": "1000",
"StartDate": 824275870,
"TotalValueOutstanding": "1003"
}
```
---
## Manage a Loan
```sh
python3 loan_manage.py
```
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
```sh
Loan broker address: r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn
LoanID: E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322
=== Loan Status ===
Total Amount Owed: 1001 TSTUSD.
Payment Due Date: 2026-03-14 02:01:51
=== Preparing LoanManage transaction to impair loan ===
{
"Account": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
"TransactionType": "LoanManage",
"Flags": 131072,
"SigningPubKey": "",
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
}
=== Submitting LoanManage impairment transaction ===
Loan impaired successfully!
New Payment Due Date: 2026-02-12 01:01:50
Grace Period: 60 seconds
=== Countdown until loan can be defaulted ===
Grace period expired. Loan can now be defaulted.
=== Preparing LoanManage transaction to default loan ===
{
"Account": "r9x3etrs2GZSF73vQ8endi9CWpKr5N2Rjn",
"TransactionType": "LoanManage",
"Flags": 65536,
"SigningPubKey": "",
"LoanID": "E86DB385401D361A33DD74C8E1B44D7F996E9BA02724BCD44127F60BE057A322"
}
=== Submitting LoanManage default transaction ===
Loan defaulted successfully!
=== Checking final loan status ===
Final loan flags: ['TF_LOAN_DEFAULT', 'TF_LOAN_IMPAIR']
```
## Pay a Loan
```sh
python3 loan_pay.py
```
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry:
```sh
Borrower address: raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL
LoanID: A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD
MPT ID: 0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116
=== Loan Status ===
Amount Owed: 1001 TSTUSD
Loan Service Fee: 10 TSTUSD
Total Payment Due (including fees): 1011 TSTUSD
=== Preparing LoanPay transaction ===
{
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"TransactionType": "LoanPay",
"SigningPubKey": "",
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD",
"Amount": {
"mpt_issuance_id": "0037BA4C909352D28BF9580F1D536AF4F7E07649B5B6E116",
"value": "1011"
}
}
=== Submitting LoanPay transaction ===
Loan paid successfully!
=== Loan Status After Payment ===
Outstanding Loan Balance: Loan fully paid off!
=== Preparing LoanDelete transaction ===
{
"Account": "raXnMyDFQWVhvVuyb2oK3oCLGZhemkLqKL",
"TransactionType": "LoanDelete",
"SigningPubKey": "",
"LoanID": "A9CC92540995E49B39E79883A22FF10A374BF2CB32763E89AA986B613E16D5FD"
}
=== Submitting LoanDelete transaction ===
Loan deleted successfully!
=== Verifying Loan Deletion ===
Loan has been successfully removed from the XRP Ledger!
```

View File

@@ -0,0 +1,124 @@
# IMPORTANT: This example deposits and claws back first-loss capital from a
# preconfigured LoanBroker entry. The first-loss capital is an MPT
# with clawback enabled.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanBrokerCoverClawback, LoanBrokerCoverDeposit, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts, loan_broker_id, and mpt_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
mpt_issuer = Wallet.from_seed(setup_data["depositor"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
mpt_id = setup_data["mpt_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"MPT issuer address: {mpt_issuer.address}")
print(f"LoanBrokerID: {loan_broker_id}")
print(f"MPT ID: {mpt_id}")
# Check cover available ----------------------
print("\n=== Cover Available ===\n")
cover_info = client.request(LedgerEntry(
index=loan_broker_id,
ledger_index="validated",
))
current_cover_available = cover_info.result["node"].get("CoverAvailable", "0")
print(f"{current_cover_available} TSTUSD")
# Prepare LoanBrokerCoverDeposit transaction ----------------------
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
cover_deposit_tx = LoanBrokerCoverDeposit(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
)
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deposit validation ----------------------
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit cover: {result_code}")
sys.exit(1)
print("Cover deposit successful!")
# Extract updated cover available after deposit ----------------------
print("\n=== Cover Available After Deposit ===\n")
loan_broker_node = next(
node for node in deposit_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
current_cover_available = loan_broker_node["ModifiedNode"]["FinalFields"]["CoverAvailable"]
print(f"{current_cover_available} TSTUSD")
# Verify issuer of cover asset matches ----------------------
# Only the issuer of the asset can submit clawback transactions.
# The asset must also have clawback enabled.
print("\n=== Verifying Asset Issuer ===\n")
asset_issuer_info = client.request(LedgerEntry(
mpt_issuance=mpt_id,
ledger_index="validated",
))
if asset_issuer_info.result["node"]["Issuer"] != mpt_issuer.address:
issuer = asset_issuer_info.result["node"]["Issuer"]
print(f"Error: {issuer} does not match account ({mpt_issuer.address}) attempting clawback!")
sys.exit(1)
print(f"MPT issuer account verified: {mpt_issuer.address}. Proceeding to clawback.")
# Prepare LoanBrokerCoverClawback transaction ----------------------
print("\n=== Preparing LoanBrokerCoverClawback transaction ===\n")
cover_clawback_tx = LoanBrokerCoverClawback(
account=mpt_issuer.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value=current_cover_available),
)
print(json.dumps(cover_clawback_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for clawback validation ----------------------
print("\n=== Submitting LoanBrokerCoverClawback transaction ===\n")
clawback_response = submit_and_wait(cover_clawback_tx, client, mpt_issuer)
if clawback_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = clawback_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to clawback cover: {result_code}")
sys.exit(1)
print(f"Successfully clawed back {current_cover_available} TSTUSD!")
# Extract final cover available ----------------------
print("\n=== Final Cover Available After Clawback ===\n")
loan_broker_node = next(
node for node in clawback_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"{loan_broker_node['ModifiedNode']['FinalFields'].get('CoverAvailable', '0')} TSTUSD")

View File

@@ -0,0 +1,95 @@
# IMPORTANT: This example deposits and withdraws first-loss capital from a
# preconfigured LoanBroker entry.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanBrokerCoverDeposit, LoanBrokerCoverWithdraw, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_broker_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
mpt_id = setup_data["mpt_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"LoanBrokerID: {loan_broker_id}")
print(f"MPT ID: {mpt_id}")
# Prepare LoanBrokerCoverDeposit transaction ----------------------
print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n")
cover_deposit_tx = LoanBrokerCoverDeposit(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2000"),
)
print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deposit validation ----------------------
print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n")
deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker)
if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit cover: {result_code}")
sys.exit(1)
print("Cover deposit successful!")
# Extract cover balance from the transaction result
print("\n=== Cover Balance ===\n")
loan_broker_node = next(
node for node in deposit_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
# First-loss capital is stored in the LoanBroker's pseudo-account.
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
print(f"Cover balance after deposit: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")
# Prepare LoanBrokerCoverWithdraw transaction ----------------------
print("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n")
cover_withdraw_tx = LoanBrokerCoverWithdraw(
account=loan_broker.address,
loan_broker_id=loan_broker_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"),
)
print(json.dumps(cover_withdraw_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for withdraw validation ----------------------
print("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n")
withdraw_response = submit_and_wait(cover_withdraw_tx, client, loan_broker)
if withdraw_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = withdraw_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to withdraw cover: {result_code}")
sys.exit(1)
print("Cover withdraw successful!")
# Extract updated cover balance from the transaction result
print("\n=== Updated Cover Balance ===\n")
loan_broker_node = next(
node for node in withdraw_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"LoanBroker Pseudo-Account: {loan_broker_node['ModifiedNode']['FinalFields']['Account']}")
print(f"Cover balance after withdraw: {loan_broker_node['ModifiedNode']['FinalFields']['CoverAvailable']} TSTUSD")

View File

@@ -0,0 +1,89 @@
# IMPORTANT: This example creates a loan using a preconfigured
# loan broker, borrower, and private vault.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanSet
from xrpl.transaction import autofill, sign, sign_loan_set_by_counterparty, submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_broker_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
loan_broker_id = setup_data["loan_broker_id"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"Borrower address: {borrower.address}")
print(f"LoanBrokerID: {loan_broker_id}")
# Prepare LoanSet transaction ----------------------
# Account and Counterparty accounts can be swapped, but determines signing order.
# Account signs first, Counterparty signs second.
print("\n=== Preparing LoanSet transaction ===\n")
loan_set_tx = autofill(LoanSet(
account=loan_broker.address,
counterparty=borrower.address,
loan_broker_id=loan_broker_id,
principal_requested="1000",
interest_rate=500,
payment_total=12,
payment_interval=2592000,
grace_period=604800,
loan_origination_fee="100",
loan_service_fee="10",
), client)
print(json.dumps(loan_set_tx.to_xrpl(), indent=2))
# Loan broker signs first.
print("\n=== Adding loan broker signature ===\n")
loan_broker_signed = sign(loan_set_tx, loan_broker)
print(f"TxnSignature: {loan_broker_signed.txn_signature}")
print(f"SigningPubKey: {loan_broker_signed.signing_pub_key}\n")
print(f"Signed loan_set_tx for borrower to sign over:\n{json.dumps(loan_broker_signed.to_xrpl(), indent=2)}")
# Borrower signs second.
print("\n=== Adding borrower signature ===\n")
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
print(f"Borrower TxnSignature: {fully_signed.tx.counterparty_signature.txn_signature}")
print(f"Borrower SigningPubKey: {fully_signed.tx.counterparty_signature.signing_pub_key}")
print(f"\nFully signed LoanSet transaction:\n{json.dumps(fully_signed.tx.to_xrpl(), indent=2)}")
# Submit and wait for validation ----------------------
print("\n=== Submitting signed LoanSet transaction ===\n")
submit_response = submit_and_wait(fully_signed.tx, client)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create loan: {result_code}")
sys.exit(1)
print("Loan created successfully!")
# Extract loan information from the transaction result.
print("\n=== Loan Information ===\n")
loan_node = next(
node for node in submit_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
print(json.dumps(loan_node["CreatedNode"]["NewFields"], indent=2))

View File

@@ -0,0 +1,64 @@
# IMPORTANT: This example creates a loan broker using an existing account
# that has already created a PRIVATE vault.
# If you want to create a loan broker for a PUBLIC vault, you can replace the vault_id
# and loan_broker values with your own.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LoanBrokerSet
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and vault_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
vault_id = setup_data["vault_id"]
print(f"\nLoan broker/vault owner address: {loan_broker.address}")
print(f"Vault ID: {vault_id}")
# Prepare LoanBrokerSet transaction ----------------------
print("\n=== Preparing LoanBrokerSet transaction ===\n")
loan_broker_set_tx = LoanBrokerSet(
account=loan_broker.address,
vault_id=vault_id,
management_fee_rate=1000,
)
print(json.dumps(loan_broker_set_tx.to_xrpl(), indent=2))
# Submit, sign, and wait for validation ----------------------
print("\n=== Submitting LoanBrokerSet transaction ===\n")
submit_response = submit_and_wait(loan_broker_set_tx, client, loan_broker)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create loan broker: {result_code}")
sys.exit(1)
print("Loan broker created successfully!")
# Extract loan broker information from the transaction result
print("\n=== Loan Broker Information ===\n")
loan_broker_node = next(
node for node in submit_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print(f"LoanBroker ID: {loan_broker_node['CreatedNode']['LedgerIndex']}")
print(f"LoanBroker Psuedo-Account Address: {loan_broker_node['CreatedNode']['NewFields']['Account']}")

View File

@@ -0,0 +1,366 @@
# Setup script for lending protocol tutorials
import asyncio
import json
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.asyncio.transaction import submit_and_wait, autofill, sign
from xrpl.transaction import sign_loan_set_by_counterparty
from xrpl.models import (
AccountObjects,
Batch,
BatchFlag,
CredentialAccept,
CredentialCreate,
LoanBrokerSet,
LoanSet,
MPTAmount,
MPTCurrency,
MPTokenAuthorize,
MPTokenIssuanceCreate,
MPTokenIssuanceCreateFlag,
Payment,
PermissionedDomainSet,
TicketCreate,
VaultCreate,
VaultDeposit,
)
from xrpl.models.transactions.vault_create import VaultCreateFlag
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
print("Setting up tutorial: 0/6", end="\r")
# Create and fund wallets
loan_broker, borrower, depositor, credential_issuer = await asyncio.gather(
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
)
print("Setting up tutorial: 1/6", end="\r")
# Issue MPT with depositor
# Create tickets for later use with loan_broker
# Set up credentials and domain with credential_issuer
credential_type = str_to_hex("KYC-Verified")
mpt_data = {
"ticker": "TSTUSD",
"name": "Test USD MPT",
"desc": "A sample non-yield-bearing stablecoin backed by U.S. Treasuries.",
"icon": "https://example.org/tstusd-icon.png",
"asset_class": "rwa",
"asset_subclass": "stablecoin",
"issuer_name": "Example Treasury Reserve Co.",
"uris": [
{
"uri": "https://exampletreasury.com/tstusd",
"category": "website",
"title": "Product Page",
},
{
"uri": "https://exampletreasury.com/tstusd/reserve",
"category": "docs",
"title": "Reserve Attestation",
},
],
"additional_info": {
"reserve_type": "U.S. Treasury Bills",
"custody_provider": "Example Custodian Bank",
"audit_frequency": "Monthly",
"last_audit_date": "2026-01-15",
"pegged_currency": "USD",
},
}
ticket_create_response, mpt_issuance_response, _ = await asyncio.gather(
submit_and_wait(
TicketCreate(
account=loan_broker.address,
ticket_count=2,
),
client,
loan_broker,
),
submit_and_wait(
MPTokenIssuanceCreate(
account=depositor.address,
maximum_amount="100000000",
transfer_fee=0,
flags=(
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_CLAWBACK
| MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRADE
),
mptoken_metadata=encode_mptoken_metadata(mpt_data),
),
client,
depositor,
),
submit_and_wait(
Batch(
account=credential_issuer.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialCreate(
account=credential_issuer.address,
subject=loan_broker.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=borrower.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=depositor.address,
credential_type=credential_type,
),
PermissionedDomainSet(
account=credential_issuer.address,
accepted_credentials=[
Credential(
issuer=credential_issuer.address,
credential_type=credential_type,
),
],
),
],
),
client,
credential_issuer,
),
)
# Extract ticket sequence numbers
tickets = [
node["CreatedNode"]["NewFields"]["TicketSequence"]
for node in ticket_create_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
]
# Extract MPT issuance ID
mpt_id = mpt_issuance_response.result["meta"]["mpt_issuance_id"]
# Get domain ID
credential_issuer_objects = await client.request(AccountObjects(
account=credential_issuer.address,
ledger_index="validated",
))
domain_id = next(
node["index"]
for node in credential_issuer_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
)
print("Setting up tutorial: 2/6", end="\r")
# Accept credentials and authorize MPT for each account
await asyncio.gather(
submit_and_wait(
Batch(
account=loan_broker.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=loan_broker.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=loan_broker.address,
mptoken_issuance_id=mpt_id,
),
],
),
client,
loan_broker,
),
submit_and_wait(
Batch(
account=borrower.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=borrower.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=borrower.address,
mptoken_issuance_id=mpt_id,
),
],
),
client,
borrower,
),
submit_and_wait(
CredentialAccept(
account=depositor.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
client,
depositor,
),
)
print("Setting up tutorial: 3/6", end="\r")
# Create private vault and distribute MPT to accounts
vault_create_response, _ = await asyncio.gather(
submit_and_wait(
VaultCreate(
account=loan_broker.address,
asset=MPTCurrency(mpt_issuance_id=mpt_id),
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
domain_id=domain_id,
),
client,
loan_broker,
),
submit_and_wait(
Batch(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
Payment(
account=depositor.address,
destination=loan_broker.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
),
Payment(
account=depositor.address,
destination=borrower.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
),
],
),
client,
depositor,
),
)
vault_id = next(
node["CreatedNode"]["LedgerIndex"]
for node in vault_create_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Vault"
)
print("Setting up tutorial: 4/6", end="\r")
# Create LoanBroker and deposit MPT into vault
loan_broker_set_response, _ = await asyncio.gather(
submit_and_wait(
LoanBrokerSet(
account=loan_broker.address,
vault_id=vault_id,
),
client,
loan_broker,
),
submit_and_wait(
VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="50000000"),
),
client,
depositor,
),
)
loan_broker_id = next(
node["CreatedNode"]["LedgerIndex"]
for node in loan_broker_set_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print("Setting up tutorial: 5/6", end="\r")
# Create 2 identical loans with complete repayment due in 30 days
# Helper function to create, sign, and submit a LoanSet transaction
async def create_loan(ticket_sequence):
loan_set_tx = await autofill(LoanSet(
account=loan_broker.address,
counterparty=borrower.address,
loan_broker_id=loan_broker_id,
principal_requested="1000",
interest_rate=500,
payment_total=1,
payment_interval=2592000,
loan_origination_fee="100",
loan_service_fee="10",
sequence=0,
ticket_sequence=ticket_sequence,
), client)
loan_broker_signed = sign(loan_set_tx, loan_broker)
fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed)
submit_response = await submit_and_wait(fully_signed.tx, client)
return submit_response
submit_response_1, submit_response_2 = await asyncio.gather(
create_loan(tickets[0]),
create_loan(tickets[1]),
)
loan_id_1 = next(
node["CreatedNode"]["LedgerIndex"]
for node in submit_response_1.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
loan_id_2 = next(
node["CreatedNode"]["LedgerIndex"]
for node in submit_response_2.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
print("Setting up tutorial: 6/6", end="\r")
# Write setup data to JSON file
setup_data = {
"description": "This file is auto-generated by lending_setup.py. It stores XRPL account info for use in lending protocol tutorials.",
"loan_broker": {
"address": loan_broker.address,
"seed": loan_broker.seed,
},
"borrower": {
"address": borrower.address,
"seed": borrower.seed,
},
"depositor": {
"address": depositor.address,
"seed": depositor.seed,
},
"credential_issuer": {
"address": credential_issuer.address,
"seed": credential_issuer.seed,
},
"domain_id": domain_id,
"mpt_id": mpt_id,
"vault_id": vault_id,
"loan_broker_id": loan_broker_id,
"loan_id_1": loan_id_1,
"loan_id_2": loan_id_2,
}
with open("lending_setup.json", "w") as f:
json.dump(setup_data, f, indent=2)
print("Setting up tutorial: Complete!")
asyncio.run(main())

View File

@@ -0,0 +1,130 @@
# IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
# After the 60 seconds pass, this example defaults the loan.
import json
import os
import subprocess
import sys
import time
from datetime import datetime
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanManage
from xrpl.models.transactions.loan_manage import LoanManageFlag
from xrpl.transaction import submit_and_wait
from xrpl.utils import posix_to_ripple_time, ripple_time_to_posix
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts and loan_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"])
loan_id = setup_data["loan_id_1"]
print(f"\nLoan broker address: {loan_broker.address}")
print(f"LoanID: {loan_id}")
# Check loan status before impairment ----------------------
print("\n=== Loan Status ===\n")
loan_status = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
print(f"Total Amount Owed: {loan_status.result['node']['TotalValueOutstanding']} TSTUSD.")
# Convert Ripple Epoch timestamp to local date and time
next_payment_due_date = loan_status.result["node"]["NextPaymentDueDate"]
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
print(f"Payment Due Date: {payment_due}")
# Prepare LoanManage transaction to impair the loan ----------------------
print("\n=== Preparing LoanManage transaction to impair loan ===\n")
loan_manage_impair = LoanManage(
account=loan_broker.address,
loan_id=loan_id,
flags=LoanManageFlag.TF_LOAN_IMPAIR,
)
print(json.dumps(loan_manage_impair.to_xrpl(), indent=2))
# Sign, submit, and wait for impairment validation ----------------------
print("\n=== Submitting LoanManage impairment transaction ===\n")
impair_response = submit_and_wait(loan_manage_impair, client, loan_broker)
if impair_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = impair_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to impair loan: {result_code}")
sys.exit(1)
print("Loan impaired successfully!")
# Extract loan impairment info from transaction results ----------------------
loan_node = next(
node for node in impair_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
# Check grace period and next payment due date
grace_period = loan_node["ModifiedNode"]["FinalFields"]["GracePeriod"]
next_payment_due_date = loan_node["ModifiedNode"]["FinalFields"]["NextPaymentDueDate"]
default_time = next_payment_due_date + grace_period
payment_due = datetime.fromtimestamp(ripple_time_to_posix(next_payment_due_date))
print(f"New Payment Due Date: {payment_due}")
print(f"Grace Period: {grace_period} seconds")
# Convert current time to Ripple Epoch timestamp
current_time = posix_to_ripple_time(int(time.time()))
seconds_until_default = default_time - current_time
# Countdown until loan can be defaulted ----------------------
print("\n=== Countdown until loan can be defaulted ===\n")
while seconds_until_default >= 0:
print(f"{seconds_until_default} seconds...", end="\r")
time.sleep(1)
seconds_until_default -= 1
print("\rGrace period expired. Loan can now be defaulted.")
# Prepare LoanManage transaction to default the loan ----------------------
print("\n=== Preparing LoanManage transaction to default loan ===\n")
loan_manage_default = LoanManage(
account=loan_broker.address,
loan_id=loan_id,
flags=LoanManageFlag.TF_LOAN_DEFAULT,
)
print(json.dumps(loan_manage_default.to_xrpl(), indent=2))
# Sign, submit, and wait for default validation ----------------------
print("\n=== Submitting LoanManage default transaction ===\n")
default_response = submit_and_wait(loan_manage_default, client, loan_broker)
if default_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = default_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to default loan: {result_code}")
sys.exit(1)
print("Loan defaulted successfully!")
# Verify loan default status from transaction results ----------------------
print("\n=== Checking final loan status ===\n")
loan_node = next(
node for node in default_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
loan_flags = loan_node["ModifiedNode"]["FinalFields"]["Flags"]
active_flags = [f.name for f in LoanManageFlag if loan_flags & f.value]
print(f"Final loan flags: {active_flags}")

View File

@@ -0,0 +1,117 @@
# IMPORTANT: This example pays off an existing loan and then deletes it.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import LedgerEntry, LoanDelete, LoanPay, MPTAmount
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Set up client ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# This step checks for the necessary setup data to run the lending protocol tutorials.
# If missing, lending_setup.py will generate the data.
if not os.path.exists("lending_setup.json"):
print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n")
subprocess.run([sys.executable, "lending_setup.py"], check=True)
# Load preconfigured accounts, loan_id, and mpt_id.
with open("lending_setup.json") as f:
setup_data = json.load(f)
# You can replace these values with your own.
borrower = Wallet.from_seed(setup_data["borrower"]["seed"])
loan_id = setup_data["loan_id_2"]
mpt_id = setup_data["mpt_id"]
print(f"\nBorrower address: {borrower.address}")
print(f"LoanID: {loan_id}")
print(f"MPT ID: {mpt_id}")
# Check initial loan status ----------------------
print("\n=== Loan Status ===\n")
loan_status = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
total_value_outstanding = loan_status.result["node"]["TotalValueOutstanding"]
loan_service_fee = loan_status.result["node"]["LoanServiceFee"]
total_payment = str(int(total_value_outstanding) + int(loan_service_fee))
print(f"Amount Owed: {total_value_outstanding} TSTUSD")
print(f"Loan Service Fee: {loan_service_fee} TSTUSD")
print(f"Total Payment Due (including fees): {total_payment} TSTUSD")
# Prepare LoanPay transaction ----------------------
print("\n=== Preparing LoanPay transaction ===\n")
loan_pay_tx = LoanPay(
account=borrower.address,
loan_id=loan_id,
amount=MPTAmount(mpt_issuance_id=mpt_id, value=total_payment),
)
print(json.dumps(loan_pay_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for payment validation ----------------------
print("\n=== Submitting LoanPay transaction ===\n")
pay_response = submit_and_wait(loan_pay_tx, client, borrower)
if pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = pay_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to pay loan: {result_code}")
sys.exit(1)
print("Loan paid successfully!")
# Extract updated loan info from transaction results ----------------------
print("\n=== Loan Status After Payment ===\n")
loan_node = next(
node for node in pay_response.result["meta"]["AffectedNodes"]
if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan"
)
final_balance = loan_node["ModifiedNode"]["FinalFields"].get("TotalValueOutstanding")
if final_balance:
print(f"Outstanding Loan Balance: {final_balance} TSTUSD")
else:
print("Outstanding Loan Balance: Loan fully paid off!")
# Prepare LoanDelete transaction ----------------------
# Either the loan broker or borrower can submit this transaction.
print("\n=== Preparing LoanDelete transaction ===\n")
loan_delete_tx = LoanDelete(
account=borrower.address,
loan_id=loan_id,
)
print(json.dumps(loan_delete_tx.to_xrpl(), indent=2))
# Sign, submit, and wait for deletion validation ----------------------
print("\n=== Submitting LoanDelete transaction ===\n")
delete_response = submit_and_wait(loan_delete_tx, client, borrower)
if delete_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = delete_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to delete loan: {result_code}")
sys.exit(1)
print("Loan deleted successfully!")
# Verify loan deletion ----------------------
print("\n=== Verifying Loan Deletion ===\n")
verify_response = client.request(LedgerEntry(
index=loan_id,
ledger_index="validated",
))
if verify_response.is_successful():
print("Warning: Loan still exists in the ledger.")
elif verify_response.result.get("error") == "entryNotFound":
print("Loan has been successfully removed from the XRP Ledger!")
else:
print(f"Error checking loan status: {verify_response.result.get('error')}")

View File

@@ -0,0 +1 @@
xrpl-py>=4.5.0

View File

@@ -69,7 +69,9 @@ try {
} }
} catch (error) { } catch (error) {
if (error.data?.error === 'entryNotFound') { if (error.data?.error === 'entryNotFound') {
console.log(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`) console.error(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
} else {
console.error(`Error checking MPT: ${error}`)
} }
await client.disconnect() await client.disconnect()
process.exit(1) process.exit(1)

View File

@@ -3,7 +3,7 @@ import fs from 'fs'
// Setup script for vault tutorials // Setup script for vault tutorials
process.stdout.write('Setting up tutorial: 0/7\r') process.stdout.write('Setting up tutorial: 0/5\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233') const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect() await client.connect()
@@ -21,92 +21,106 @@ const [
client.fundWallet() client.fundWallet()
]) ])
// Step 1: Create MPT issuance // Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
process.stdout.write('Setting up tutorial: 1/7\r') process.stdout.write('Setting up tutorial: 1/5\r')
const mptCreateResult = await client.submitAndWait( const credType = 'VaultAccess'
{ const [mptCreateResult] = await Promise.all([
TransactionType: 'MPTokenIssuanceCreate', client.submitAndWait(
Account: mptIssuer.address, {
Flags: TransactionType: "MPTokenIssuanceCreate",
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer | Account: mptIssuer.address,
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock, Flags:
AssetScale: 2, xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
TransferFee: 0, xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
MaximumAmount: '1000000000000', AssetScale: 2,
MPTokenMetadata: xrpl.encodeMPTokenMetadata({ TransferFee: 0,
ticker: 'USTST', MaximumAmount: "1000000000000",
name: 'USTST Stablecoin', MPTokenMetadata: xrpl.encodeMPTokenMetadata({
desc: 'A test stablecoin token', ticker: "USTST",
icon: 'example.org/ustst-icon.png', name: "USTST Stablecoin",
asset_class: 'rwa', desc: "A test stablecoin token",
asset_subclass: 'stablecoin', icon: "example.org/ustst-icon.png",
issuer_name: 'Test Stablecoin Inc', asset_class: "rwa",
uris: [ asset_subclass: "stablecoin",
issuer_name: "Test Stablecoin Inc",
uris: [
{
uri: "example.org/ustst",
category: "website",
title: "USTST Official Website",
},
{
uri: "example.org/ustst/reserves",
category: "attestation",
title: "Reserve Attestation Reports",
},
{
uri: "example.org/ustst/docs",
category: "docs",
title: "USTST Documentation",
},
],
additional_info: {
backing: "USD",
reserve_ratio: "1:1",
},
}),
},
{ wallet: mptIssuer, autofill: true },
),
client.submitAndWait(
{
TransactionType: "Batch",
Account: domainOwner.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{ {
uri: 'example.org/ustst', RawTransaction: {
category: 'website', TransactionType: "PermissionedDomainSet",
title: 'USTST Official Website' Account: domainOwner.address,
AcceptedCredentials: [
{
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
},
},
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
}, },
{ {
uri: 'example.org/ustst/reserves', RawTransaction: {
category: 'attestation', TransactionType: "CredentialCreate",
title: 'Reserve Attestation Reports' Account: domainOwner.address,
Subject: depositor.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
}, },
{
uri: 'example.org/ustst/docs',
category: 'docs',
title: 'USTST Documentation'
}
], ],
additional_info: { },
backing: 'USD', { wallet: domainOwner, autofill: true },
reserve_ratio: '1:1' ),
} ]);
})
},
{ wallet: mptIssuer, autofill: true }
)
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
// Step 2: Create Permissioned Domain // Get domain ID
process.stdout.write('Setting up tutorial: 2/7\r') const domainOwnerObjects = await client.request({
command: 'account_objects',
account: domainOwner.address,
ledger_index: 'validated'
})
const domainId = domainOwnerObjects.result.account_objects.find(
(node) => node.LedgerEntryType === 'PermissionedDomain'
).index
const credType = 'VaultAccess' // Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
const domainResult = await client.submitAndWait( process.stdout.write('Setting up tutorial: 2/5\r')
{
TransactionType: 'PermissionedDomainSet',
Account: domainOwner.address,
AcceptedCredentials: [
{
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType)
}
}
]
},
{ wallet: domainOwner, autofill: true }
)
const domainId = domainResult.result.meta.AffectedNodes.find( const [, vaultCreateResult] = await Promise.all([
(node) => node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
).CreatedNode.LedgerIndex
// Step 3: Create depositor account with credentials and MPT balance
process.stdout.write('Setting up tutorial: 3/7\r')
await Promise.all([
client.submitAndWait(
{
TransactionType: 'CredentialCreate',
Account: domainOwner.address,
Subject: depositor.address,
CredentialType: xrpl.convertStringToHex(credType)
},
{ wallet: domainOwner, autofill: true }
),
client.submitAndWait( client.submitAndWait(
{ {
TransactionType: 'Batch', TransactionType: 'Batch',
@@ -133,10 +147,57 @@ await Promise.all([
] ]
}, },
{ wallet: depositor, autofill: true } { wallet: depositor, autofill: true }
),
client.submitAndWait(
{
TransactionType: 'VaultCreate',
Account: vaultOwner.address,
Asset: {
mpt_issuance_id: mptIssuanceId
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainId,
Data: xrpl.convertStringToHex(
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
),
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: 'SHARE1',
name: 'Vault Shares',
desc: 'Proportional ownership shares of the vault',
icon: 'example.com/vault-shares-icon.png',
asset_class: 'defi',
issuer_name: 'Vault Owner',
uris: [
{
uri: 'example.com/asset',
category: 'website',
title: 'Asset Website'
},
{
uri: 'example.com/docs',
category: 'docs',
title: 'Docs'
}
],
additional_info: {
example_info: 'test'
}
}),
AssetsMaximum: '0',
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
},
{ wallet: vaultOwner, autofill: true }
) )
]) ])
process.stdout.write('Setting up tutorial: 4/7\r') const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
)
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
// Step 3: Issuer sends payment to depositor
process.stdout.write('Setting up tutorial: 3/5\r')
const paymentResult = await client.submitAndWait( const paymentResult = await client.submitAndWait(
{ {
@@ -157,59 +218,8 @@ if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
process.exit(1) process.exit(1)
} }
// Step 5: Create a vault for deposit/withdraw examples // Step 4: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 5/7\r') process.stdout.write('Setting up tutorial: 4/5\r')
const vaultCreateResult = await client.submitAndWait(
{
TransactionType: 'VaultCreate',
Account: vaultOwner.address,
Asset: {
mpt_issuance_id: mptIssuanceId
},
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
DomainID: domainId,
Data: xrpl.convertStringToHex(
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
),
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: 'SHARE1',
name: 'Vault Shares',
desc: 'Proportional ownership shares of the vault',
icon: 'example.com/vault-shares-icon.png',
asset_class: 'defi',
issuer_name: 'Vault Owner',
uris: [
{
uri: 'example.com/asset',
category: 'website',
title: 'Asset Website'
},
{
uri: 'example.com/docs',
category: 'docs',
title: 'Docs'
}
],
additional_info: {
example_info: 'test'
}
}),
AssetsMaximum: '0',
WithdrawalPolicy:
xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
},
{ wallet: vaultOwner, autofill: true }
)
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
)
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
// Step 6: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 6/7\r')
const initialDepositResult = await client.submitAndWait( const initialDepositResult = await client.submitAndWait(
{ {
@@ -230,8 +240,8 @@ if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
process.exit(1) process.exit(1)
} }
// Step 7: Save setup data to file // Step 5: Save setup data to file
process.stdout.write('Setting up tutorial: 7/7\r') process.stdout.write('Setting up tutorial: 5/5\r')
const setupData = { const setupData = {
mptIssuer: { mptIssuer: {

View File

@@ -52,11 +52,13 @@ try {
ledger_index: "validated" ledger_index: "validated"
}) })
const shareBalance = shareBalanceResult.result.node.MPTAmount const shareBalance = shareBalanceResult.result.node?.MPTAmount
console.log(`Shares held: ${shareBalance}`) console.log(`Shares held: ${shareBalance}`)
} catch (error) { } catch (error) {
if (error.data?.error === 'entryNotFound') { if (error.data?.error === 'entryNotFound') {
console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`) console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`)
} else {
console.error(`Error checking MPT: ${error}`)
} }
await client.disconnect() await client.disconnect()
process.exit(1) process.exit(1)

View File

@@ -0,0 +1,187 @@
# Single Asset Vault Examples (Python)
This directory contains Python examples demonstrating how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
## Setup
Install dependencies before running any examples:
```sh
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
---
## Create a Vault
```sh
python create_vault.py
```
The script should output the VaultCreate transaction, vault ID, and complete vault information:
```sh
Vault owner address: rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu
MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Permissioned domain ID: 76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E
=== VaultCreate transaction ===
{
"Account": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
"TransactionType": "VaultCreate",
"Flags": 65536,
"SigningPubKey": "",
"Asset": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
},
"Data": "7b226e223a20224c4154414d2046756e64204949222c202277223a20226578616d706c6566756e642e636f6d227d",
"AssetsMaximum": "0",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
"WithdrawalPolicy": 1
}
=== Submitting VaultCreate transaction... ===
Vault created successfully!
Vault ID: 3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D
Vault pseudo-account address: rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm
Share MPT issuance ID: 00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E
=== Getting vault_info... ===
{
"ledger_hash": "5851C21E353DEDEC5C6CC285E1E9835C378DCBBE5BA69CF33124DAC7EE5A08AD",
"ledger_index": 3693379,
"validated": true,
"vault": {
"Account": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
"Asset": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03"
},
"Data": "7B226E223A20224C4154414D2046756E64204949222C202277223A20226578616D706C6566756E642E636F6D227D",
"Flags": 65536,
"LedgerEntryType": "Vault",
"Owner": "rfsTcqjyg7j2xfJFNbd9u8mt65yrGZvLnu",
"OwnerNode": "0",
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
"PreviousTxnLgrSeq": 3693379,
"Sequence": 3693375,
"ShareMPTID": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E",
"WithdrawalPolicy": 1,
"index": "3E5BB3E4789603CC20D7A874ECBA36B74188F1B991EC9199DFA129FDB44D846D",
"shares": {
"DomainID": "76397457A19E093654F74848E5255E6111FDC0A2BF9FB2143F7C2C33424E1B3E",
"Flags": 60,
"Issuer": "rPgYFS3qFrUYQ3qWpF9RLKc9ECkGhgADtm",
"LedgerEntryType": "MPTokenIssuance",
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
"OutstandingAmount": "0",
"OwnerNode": "0",
"PreviousTxnID": "4B29E4DBA09CBDCAF591792ACFFB5F8717AD230185207C10F10B2A405FB2D576",
"PreviousTxnLgrSeq": 3693379,
"Sequence": 1,
"index": "EAD6924CB5DDA61CC5B85A6776A32E460FBFB0C34F5076A6A52005459B38043D",
"mpt_issuance_id": "00000001F8CD8CC81FFDDC9887627F42390E85DB32D44D0E"
}
}
}
```
---
## Deposit into a Vault
```sh
python deposit.py
```
The script should output the vault state before and after the deposit, along with the depositor's share balance:
```sh
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
=== Getting initial vault state... ===
- Total vault value: 1000
- Available assets: 1000
=== Checking depositor's balance... ===
Balance: 9000
=== VaultDeposit transaction ===
{
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
"TransactionType": "VaultDeposit",
"SigningPubKey": "",
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
"Amount": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
"value": "1"
}
}
=== Submitting VaultDeposit transaction... ===
Deposit successful!
=== Vault state after deposit ===
- Total vault value: 1001
- Available assets: 1001
=== Depositor's share balance ===
Shares held: 1001
```
---
## Withdraw from a Vault
```sh
python withdraw.py
```
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
```sh
Depositor address: r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK
Vault ID: 9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925
Asset MPT issuance ID: 00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03
Vault share MPT issuance ID: 00000001890BF384C217368D89BBB82B814B94B2597702B1
=== Getting initial vault state... ===
Initial vault state:
Assets Total: 1001
Assets Available: 1001
=== Checking depositor's share balance... ===
Shares held: 1001
=== Preparing VaultWithdraw transaction ===
{
"Account": "r4pfiPR5y4GTbajHXzUS29KBDHUdxR8kCK",
"TransactionType": "VaultWithdraw",
"SigningPubKey": "",
"VaultID": "9966AF609568AFFCB3AEDEAC340B6AABB23C0483F013E186E83AF27EDA82C925",
"Amount": {
"mpt_issuance_id": "00385B21AF216177F319AC73F25F0FCBCDA09330D1D50D03",
"value": "1"
}
}
=== Submitting VaultWithdraw transaction... ===
Withdrawal successful!
=== Vault state after withdrawal ===
Assets Total: 1000
Assets Available: 1000
=== Depositor's share balance ==
Shares held: 1000
=== Depositor's asset balance ==
Balance: 9000
```

View File

@@ -0,0 +1,115 @@
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultCreate
from xrpl.models.requests import VaultInfo
from xrpl.models.transactions.vault_create import VaultCreateFlag, WithdrawalPolicy
from xrpl.transaction import submit_and_wait
from xrpl.utils import str_to_hex, encode_mptoken_metadata
from xrpl.wallet import generate_faucet_wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run(["python", "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# Create and fund vault owner account
vault_owner = generate_faucet_wallet(client)
# You can replace these values with your own
mpt_issuance_id = setup_data["mpt_issuance_id"]
domain_id = setup_data["domain_id"]
print(f"Vault owner address: {vault_owner.address}")
print(f"MPT issuance ID: {mpt_issuance_id}")
print(f"Permissioned domain ID: {domain_id}\n")
# Prepare VaultCreate transaction ----------------------
print("\n=== VaultCreate transaction ===")
vault_create_tx = VaultCreate(
account=vault_owner.address,
asset={"mpt_issuance_id": mpt_issuance_id},
flags=VaultCreateFlag.TF_VAULT_PRIVATE, # Omit TF_VAULT_PRIVATE flag for public vaults
# To make vault shares non-transferable add the TF_VAULT_SHARE_NON_TRANSFERABLE flag:
# flags=VaultCreateFlag.TF_VAULT_PRIVATE | VaultCreateFlag.TF_VAULT_SHARE_NON_TRANSFERABLE,
domain_id=domain_id, # Omit for public vaults
# Convert Vault data to a string (without excess whitespace), then string to hex.
data=str_to_hex(json.dumps(
{"n": "LATAM Fund II", "w": "examplefund.com"}
)),
# Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
# See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
mptoken_metadata=encode_mptoken_metadata({
"ticker": "SHARE1",
"name": "Vault shares",
"desc": "Proportional ownership shares of the vault.",
"icon": "example.com/asset-icon.png",
"asset_class": "defi",
"issuer_name": "Asset Issuer Name",
"uris": [
{
"uri": "example.com/asset",
"category": "website",
"title": "Asset Website",
},
{
"uri": "example.com/docs",
"category": "docs",
"title": "Docs",
},
],
"additional_info": {
"example_info": "test",
},
}),
assets_maximum="0", # No cap
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
)
print(json.dumps(vault_create_tx.to_xrpl(), indent=2))
# Submit, sign, and wait for validation ----------------------
print("\n=== Submitting VaultCreate transaction... ===")
submit_response = submit_and_wait(vault_create_tx, client, vault_owner, autofill=True)
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = submit_response.result["meta"]["TransactionResult"]
print(f"Error: Unable to create vault: {result_code}", file=sys.stderr)
sys.exit(1)
print("Vault created successfully!")
# Extract vault information from the transaction result
affected_nodes = submit_response.result["meta"].get("AffectedNodes", [])
vault_node = next(
(node for node in affected_nodes
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"),
None
)
if vault_node:
print(f"\nVault ID: {vault_node['CreatedNode']['LedgerIndex']}")
print(f"Vault pseudo-account address: {vault_node['CreatedNode']['NewFields']['Account']}")
print(f"Share MPT issuance ID: {vault_node['CreatedNode']['NewFields']['ShareMPTID']}")
# Call vault_info method to retrieve the vault's information
print("\n=== Getting vault_info... ===")
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
vault_info_response = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print(json.dumps(vault_info_response.result, indent=2))

View File

@@ -0,0 +1,149 @@
# IMPORTANT: This example deposits into an existing PRIVATE vault.
# The depositor account used has valid credentials in the vault's Permissioned Domain.
# Without valid credentials, the VaultDeposit transaction will fail.
# If you want to deposit into a public vault, you can replace the vault_id and share_mpt_issuance_id
# values with your own.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultDeposit
from xrpl.models.requests import VaultInfo, LedgerEntry
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run(["python", "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# You can replace these values with your own
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
vault_id = setup_data["vault_id"]
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
print(f"Depositor address: {depositor.address}")
print(f"Vault ID: {vault_id}")
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
deposit_amount = 1
# Get initial vault state
print("\n=== Getting initial vault state... ===")
initial_vault_info = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print(f" - Total vault value: {initial_vault_info.result['vault']['AssetsTotal']}")
print(f" - Available assets: {initial_vault_info.result['vault']['AssetsAvailable']}")
# Check depositor's asset balance
print("\n=== Checking depositor's balance... ===")
try:
# Use ledger_entry to get specific MPT issuance balance
ledger_entry_result = client.request(
LedgerEntry(
mptoken={
"mpt_issuance_id": asset_mpt_issuance_id,
"account": depositor.address
},
ledger_index="validated"
)
)
balance = ledger_entry_result.result["node"]["MPTAmount"]
print(f"Balance: {balance}")
# Check if balance is sufficient
if int(balance) < deposit_amount:
print(f"Error: Insufficient balance! Have {balance}, need {deposit_amount}", file=sys.stderr)
sys.exit(1)
except Exception as error:
error_data = getattr(error, 'data', {})
if 'error' in error_data and error_data['error'] == 'entryNotFound':
print(f"Error: The depositor doesn't hold any assets with ID: {asset_mpt_issuance_id}", file=sys.stderr)
else:
print(f"Error checking MPT: {error}", file=sys.stderr)
sys.exit(1)
# Prepare VaultDeposit transaction
print("\n=== VaultDeposit transaction ===")
vault_deposit_tx = VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": asset_mpt_issuance_id,
"value": str(deposit_amount)
}
)
print(json.dumps(vault_deposit_tx.to_xrpl(), indent=2))
# Submit VaultDeposit transaction
print("\n=== Submitting VaultDeposit transaction... ===")
deposit_result = submit_and_wait(vault_deposit_tx, client, depositor, autofill=True)
if deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_result.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit: {result_code}", file=sys.stderr)
sys.exit(1)
print("Deposit successful!")
# Extract vault state from transaction metadata
print("\n=== Vault state after deposit ===")
affected_nodes = deposit_result.result["meta"]["AffectedNodes"]
vault_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
modified = node["ModifiedNode"]
if modified["LedgerEntryType"] == "Vault" and modified["LedgerIndex"] == vault_id:
vault_node = node
break
if vault_node:
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
print(f" - Total vault value: {vault_fields['AssetsTotal']}")
print(f" - Available assets: {vault_fields['AssetsAvailable']}")
# Get the depositor's share balance
print("\n=== Depositor's share balance ===")
depositor_share_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
share_node = node["ModifiedNode"]
fields = share_node["FinalFields"]
elif "CreatedNode" in node:
share_node = node["CreatedNode"]
fields = share_node["NewFields"]
else:
continue
if (share_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
depositor_share_node = node
break
if depositor_share_node:
if "ModifiedNode" in depositor_share_node:
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
else:
share_fields = depositor_share_node["CreatedNode"]["NewFields"]
print(f"Shares held: {share_fields['MPTAmount']}")

View File

@@ -0,0 +1,2 @@
xrpl-py==4.5.0

View File

@@ -0,0 +1,273 @@
import asyncio
import json
import sys
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.transaction import submit_and_wait
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.models import (
Batch, BatchFlag, CredentialAccept, CredentialCreate, MPTokenAuthorize,
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
PermissionedDomainSet, VaultDeposit
)
from xrpl.models.requests import AccountObjects
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.models.transactions.vault_create import (
VaultCreate, VaultCreateFlag, WithdrawalPolicy
)
from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
# Setup script for vault tutorials
print("Setting up tutorial: 0/5", end="\r")
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
# Create and fund all wallets concurrently
mpt_issuer, domain_owner, depositor, vault_owner = await asyncio.gather(
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
)
# Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
print("Setting up tutorial: 1/5", end="\r")
cred_type = "VaultAccess"
mpt_create_result, _ = await asyncio.gather(
submit_and_wait(
MPTokenIssuanceCreate(
account=mpt_issuer.address,
flags=(
MPTokenIssuanceCreateFlag.TF_MPT_CAN_TRANSFER |
MPTokenIssuanceCreateFlag.TF_MPT_CAN_LOCK
),
asset_scale=2,
transfer_fee=0,
maximum_amount="1000000000000",
mptoken_metadata=encode_mptoken_metadata({
"ticker": "USTST",
"name": "USTST Stablecoin",
"desc": "A test stablecoin token",
"icon": "example.org/ustst-icon.png",
"asset_class": "rwa",
"asset_subclass": "stablecoin",
"issuer_name": "Test Stablecoin Inc",
"uris": [
{
"uri": "example.org/ustst",
"category": "website",
"title": "USTST Official Website",
},
{
"uri": "example.org/ustst/reserves",
"category": "attestation",
"title": "Reserve Attestation Reports",
},
{
"uri": "example.org/ustst/docs",
"category": "docs",
"title": "USTST Documentation",
},
],
"additional_info": {
"backing": "USD",
"reserve_ratio": "1:1",
},
}),
),
client,
mpt_issuer,
autofill=True
),
submit_and_wait(
Batch(
account=domain_owner.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
PermissionedDomainSet(
account=domain_owner.address,
accepted_credentials=[
Credential(
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type)
)
],
),
CredentialCreate(
account=domain_owner.address,
subject=depositor.address,
credential_type=str_to_hex(cred_type),
),
],
),
client,
domain_owner,
autofill=True
)
)
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
# Get domain ID
domain_owner_objects = await client.request(AccountObjects(
account=domain_owner.address,
ledger_index="validated",
))
domain_id = next(
node["index"]
for node in domain_owner_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
)
# Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
print("Setting up tutorial: 2/5", end="\r")
_, vault_create_result = await asyncio.gather(
submit_and_wait(
Batch(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=depositor.address,
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type),
),
MPTokenAuthorize(
account=depositor.address,
mptoken_issuance_id=mpt_issuance_id,
),
],
),
client,
depositor,
autofill=True
),
submit_and_wait(
VaultCreate(
account=vault_owner.address,
asset={"mpt_issuance_id": mpt_issuance_id},
flags=VaultCreateFlag.TF_VAULT_PRIVATE,
domain_id=domain_id,
data=str_to_hex(json.dumps(
{"n": "LATAM Fund II", "w": "examplefund.com"}
)),
mptoken_metadata=encode_mptoken_metadata({
"ticker": "SHARE1",
"name": "Vault Shares",
"desc": "Proportional ownership shares of the vault",
"icon": "example.com/vault-shares-icon.png",
"asset_class": "defi",
"issuer_name": "Vault Owner",
"uris": [
{
"uri": "example.com/asset",
"category": "website",
"title": "Asset Website",
},
{
"uri": "example.com/docs",
"category": "docs",
"title": "Docs",
},
],
"additional_info": {
"example_info": "test",
},
}),
assets_maximum="0",
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
),
client,
vault_owner,
autofill=True
),
)
# Extract vault_id and vault_share_mpt_issuance_id
vault_node = next(
node for node in vault_create_result.result["meta"]["AffectedNodes"]
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"
)
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
vault_share_mpt_issuance_id = vault_node["CreatedNode"]["NewFields"]["ShareMPTID"]
# Step 3: Issuer sends payment to depositor
print("Setting up tutorial: 3/5", end="\r")
payment_result = await submit_and_wait(
Payment(
account=mpt_issuer.address,
destination=depositor.address,
amount={
"mpt_issuance_id": mpt_issuance_id,
"value": "10000",
},
),
client,
mpt_issuer,
autofill=True
)
if payment_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"\nPayment failed: {payment_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 4: Make an initial deposit so withdraw example has shares to work with
print("Setting up tutorial: 4/5", end="\r")
initial_deposit_result = await submit_and_wait(
VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": mpt_issuance_id,
"value": "1000",
},
),
client,
depositor,
autofill=True
)
if initial_deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
print(f"\nInitial deposit failed: {initial_deposit_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 5: Save setup data to file
print("Setting up tutorial: 5/5", end="\r")
setup_data = {
"mpt_issuer": {
"address": mpt_issuer.address,
"seed": mpt_issuer.seed,
},
"mpt_issuance_id": mpt_issuance_id,
"domain_owner": {
"address": domain_owner.address,
"seed": domain_owner.seed,
},
"domain_id": domain_id,
"credential_type": cred_type,
"depositor": {
"address": depositor.address,
"seed": depositor.seed,
},
"vault_owner": {
"address": vault_owner.address,
"seed": vault_owner.seed,
},
"vault_id": vault_id,
"vault_share_mpt_issuance_id": vault_share_mpt_issuance_id,
}
with open("vault_setup.json", "w") as f:
json.dump(setup_data, f, indent=2)
print("Setting up tutorial: Complete!")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -0,0 +1,164 @@
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultWithdraw
from xrpl.models.requests import VaultInfo, LedgerEntry
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run(["python", "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# You can replace these values with your own
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
vault_id = setup_data["vault_id"]
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
print(f"Depositor address: {depositor.address}")
print(f"Vault ID: {vault_id}")
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
withdraw_amount = 1
# Get initial vault state
print("\n=== Getting initial vault state... ===")
initial_vault_info = client.request(
VaultInfo(
vault_id=vault_id,
ledger_index="validated"
)
)
print("Initial vault state:")
print(f" Assets Total: {initial_vault_info.result['vault']['AssetsTotal']}")
print(f" Assets Available: {initial_vault_info.result['vault']['AssetsAvailable']}")
# Check depositor's share balance
print("\n=== Checking depositor's share balance... ===")
try:
share_balance_result = client.request(
LedgerEntry(
mptoken={
"mpt_issuance_id": share_mpt_issuance_id,
"account": depositor.address
},
ledger_index="validated"
)
)
share_balance = share_balance_result.result["node"]["MPTAmount"]
print(f"Shares held: {share_balance}")
except Exception as error:
error_data = getattr(error, 'data', {})
if 'error' in error_data and error_data['error'] == 'entryNotFound':
print(f"Error: The depositor doesn't hold any vault shares with ID: {share_mpt_issuance_id}.", file=sys.stderr)
else:
print(f"Error checking MPT: {error}", file=sys.stderr)
sys.exit(1)
# Prepare VaultWithdraw transaction
print("\n=== Preparing VaultWithdraw transaction ===")
vault_withdraw_tx = VaultWithdraw(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": asset_mpt_issuance_id,
"value": str(withdraw_amount)
}
# Optional: Add destination field to send assets to a different account
# destination="rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
)
print(json.dumps(vault_withdraw_tx.to_xrpl(), indent=2))
# Submit VaultWithdraw transaction
print("\n=== Submitting VaultWithdraw transaction... ===")
withdraw_result = submit_and_wait(vault_withdraw_tx, client, depositor, autofill=True)
if withdraw_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = withdraw_result.result["meta"]["TransactionResult"]
print(f"Error: Unable to withdraw from vault: {result_code}", file=sys.stderr)
sys.exit(1)
print("Withdrawal successful!")
# Extract vault state from transaction metadata
print("\n=== Vault state after withdrawal ===")
affected_nodes = withdraw_result.result["meta"]["AffectedNodes"]
vault_node = None
for node in affected_nodes:
if "ModifiedNode" in node or "DeletedNode" in node:
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
if modified_node["LedgerEntryType"] == "Vault" and modified_node["LedgerIndex"] == vault_id:
vault_node = node
break
if vault_node:
if "DeletedNode" in vault_node:
print(" Vault empty (all assets withdrawn)")
else:
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
print(f" Assets Total: {vault_fields['AssetsTotal']}")
print(f" Assets Available: {vault_fields['AssetsAvailable']}")
# Get the depositor's share balance
print("\n=== Depositor's share balance ===")
depositor_share_node = None
for node in affected_nodes:
if "ModifiedNode" in node or "DeletedNode" in node:
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
if "FinalFields" in modified_node:
fields = modified_node["FinalFields"]
if (modified_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
depositor_share_node = node
break
if depositor_share_node:
if "DeletedNode" in depositor_share_node:
print("No more shares held (redeemed all shares)")
else:
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
print(f"Shares held: {share_fields['MPTAmount']}")
# Get the depositor's asset balance
print("\n=== Depositor's asset balance ===")
depositor_asset_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
asset_node = node["ModifiedNode"]
fields = asset_node["FinalFields"]
elif "CreatedNode" in node:
asset_node = node["CreatedNode"]
fields = asset_node["NewFields"]
else:
continue
if (asset_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == asset_mpt_issuance_id):
depositor_asset_node = node
break
if depositor_asset_node:
if "ModifiedNode" in depositor_asset_node:
asset_fields = depositor_asset_node["ModifiedNode"]["FinalFields"]
else:
asset_fields = depositor_asset_node["CreatedNode"]["NewFields"]
print(f"Balance: {asset_fields['MPTAmount']}")

View File

@@ -370,6 +370,7 @@
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.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_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
[Get Started Using JavaScript]: /docs/tutorials/javascript/build-apps/get-started.md [Get Started Using JavaScript]: /docs/tutorials/javascript/build-apps/get-started.md
[Get Started Using Python]: /docs/tutorials/python/build-apps/get-started.md
[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal [hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
[identifying hash]: /docs/concepts/transactions/index.md#identifying-transactions [identifying hash]: /docs/concepts/transactions/index.md#identifying-transactions
[json command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md [json command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
@@ -486,3 +487,4 @@
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.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 [wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
[xrpl.js library]: https://github.com/XRPLF/xrpl.js [xrpl.js library]: https://github.com/XRPLF/xrpl.js
[xrpl-py library]: https://github.com/XRPLF/xrpl-py

View File

@@ -68,10 +68,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `LoanBrokerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `LoanBroker` owner directory. | | `LoanBrokerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `LoanBroker` owner directory. |
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the _Loan Broker_ associated with this loan. | | `LoanBrokerID` | String | Hash256 | Yes | The ID of the _Loan Broker_ associated with this loan. |
| `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. | | `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. |
| `LoanOriginationFee` | Number | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. | | `LoanOriginationFee` | String | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
| `LoanServiceFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. | | `LoanServiceFee` | String | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
| `LatePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. | | `LatePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
| `ClosePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. | | `ClosePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
| `OverpaymentFee` | Number | UInt32 | Yes | The fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `OverpaymentFee` | Number | UInt32 | Yes | The fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `InterestRate` | Number | UInt32 | Yes | The annualized interest rate of the loan, in 1/10th basis points. | | `InterestRate` | Number | UInt32 | Yes | The annualized interest rate of the loan, in 1/10th basis points. |
| `LateInterestRate` | Number | UInt32 | Yes | The premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `LateInterestRate` | Number | UInt32 | Yes | The premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
@@ -83,10 +83,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. | | `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. |
| `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. | | `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. |
| `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. | | `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. |
| `PrincipalOutstanding` | Number | Number | Yes | The principal amount still owed on the loan. | | `PrincipalOutstanding` | String | Number | Yes | The principal amount still owed on the loan. |
| `TotalValueOutstanding` | Number | Number | Yes | The total amount owed on the loan, including remaining principal and fees. | | `TotalValueOutstanding` | String | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
| `ManagementFeeOutstanding` | Number | Number | Yes | The remaining management fee owed to the loan broker. | | `ManagementFeeOutstanding` | String | Number | Yes | The remaining management fee owed to the loan broker. |
| `PeriodicPayment` | Number | Number | Yes | The amount due for each payment interval. | | `PeriodicPayment` | String | Number | Yes | The amount due for each payment interval. |
| `LoanScale` | Number | Int32 | No | The scale factor that ensures all computed amounts are rounded to the same number of decimal places. It is based on the total loan value at creation time. | | `LoanScale` | Number | Int32 | No | The scale factor that ensures all computed amounts are rounded to the same number of decimal places. It is based on the total loan value at creation time. |
{% admonition type="info" name="Note" %} {% admonition type="info" name="Note" %}
@@ -115,7 +115,6 @@ When the loan broker discovers that the borrower can't make an upcoming payment,
The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order: The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order:
- The `Loan` space key `0x004C`. - The `Loan` space key `0x004C`.
- The [AccountID][] of the Borrower account.
- The `LoanBrokerID` of the associated `LoanBroker` ledger entry. - The `LoanBrokerID` of the associated `LoanBroker` ledger entry.
- The `LoanSequence` number of the `LoanBroker` ledger entry. - The `LoanSequence` number of the `LoanBroker` ledger entry.

View File

@@ -65,9 +65,9 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. | | `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. |
| `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. | | `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. |
| `DebtTotal` | Number | Number | Yes | The total asset amount the protocol owes the vault, including interest. | | `DebtTotal` | String | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
| `DebtMaximum` | Number | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. | | `DebtMaximum` | String | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
| `CoverAvailable` | Number | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. | | `CoverAvailable` | String | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
| `CoverRateMinimum` | Number | UInt32 | Yes | The 1/10th basis point of the `DebtTotal` that the first-loss capital must cover. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `CoverRateMinimum` | Number | UInt32 | Yes | The 1/10th basis point of the `DebtTotal` that the first-loss capital must cover. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `CoverRateLiquidation`| Number | UInt12 | Yes | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `CoverRateLiquidation`| Number | UInt12 | Yes | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |

View File

@@ -38,10 +38,10 @@ The `LoanBrokerCoverClawback` transaction claws back first-loss capital from a `
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields: In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:----------|:--------------|:----------|:------------| |:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. | | `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. |
| `Amount` | Object | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). | | `Amount` | [Currency Amount][] | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). |
## Error Cases ## Error Cases

View File

@@ -40,10 +40,10 @@ Only the owner of the associated `LoanBroker` entry can initiate this transactio
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields: In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:----------|:--------------|:----------|:------------| |:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. | | `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. |
| `Amount` | Object | Amount | Yes | The amount of first-loss capital to deposit. | | `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to deposit. |
## Error Cases ## Error Cases

View File

@@ -40,11 +40,11 @@ Only the owner of the associated `LoanBroker` entry can initiate this transactio
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields: In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:----------|:-------------|:----------|:------------| |:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. | | `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
| `Amount` | Object | Amount | Yes | The amount of first-loss capital to withdraw. | | `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to withdraw. |
| `Destination` | String | AccountID | No | An account to receive the assets. | | `Destination` | String | AccountID | No | An account to receive the assets. |
## Error Cases ## Error Cases

View File

@@ -45,7 +45,7 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| `LoanBrokerID` | String | Hash256 | No | The loan broker ID that the transaction is modifying. | | `LoanBrokerID` | String | Hash256 | No | The loan broker ID that the transaction is modifying. |
| `Data` | String | Blob | No | Arbitrary metadata in hex format--limited to 256 bytes. | | `Data` | String | Blob | No | Arbitrary metadata in hex format--limited to 256 bytes. |
| `ManagementFeeRate` | Number | UInt16 | No | The 1/10th basis point fee charged by the lending protocol owner. Valid values range from `0` to `10000` (inclusive), representing 0% to 10%. | | `ManagementFeeRate` | Number | UInt16 | No | The 1/10th basis point fee charged by the lending protocol owner. Valid values range from `0` to `10000` (inclusive), representing 0% to 10%. |
| `DebtMaximum` | Number | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. | | `DebtMaximum` | String | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
| `CoverRateMinimum` | Number | UInt32 | No | The 1/10th basis point `DebtTotal` that the first-loss capital must cover. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. | | `CoverRateMinimum` | Number | UInt32 | No | The 1/10th basis point `DebtTotal` that the first-loss capital must cover. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |
| `CoverRateLiquidation` | Number | UInt32 | No | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. | | `CoverRateLiquidation` | Number | UInt32 | No | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |

View File

@@ -38,12 +38,11 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:----------|:-------------|:----------|:------------| |:-------------- |:----------|:-------------|:----------|:------------|
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to manage. | | `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to manage. |
| `Flags` | String | UInt32 | No | The flag to modify the loan. |
## {% $frontmatter.seo.title %} Flags ## {% $frontmatter.seo.title %} Flags
Transactions of the {% code-page-name /%} type support additional values in the [`flags` field], as follows: Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
| Field Name | Hex Value | Decimal Value | Description | | Field Name | Hex Value | Decimal Value | Description |
|:----------------|:-------------|:--------------|:------------| |:----------------|:-------------|:--------------|:------------|

View File

@@ -43,20 +43,21 @@ To see how loan payment transactions are calculated, see [transaction pseudo-cod
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields: In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:--------------- |:----------|:-------------|:----------|:------------| |:--------------- |:--------------------|:--------------|:----------|:------------|
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. | | `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. |
| `Amount` | Number | Amount | Yes | The amount to pay toward the loan. | | `Amount` | [Currency Amount][] | Amount | Yes | The amount to pay toward the loan. |
## {% $frontmatter.seo.title %} Flags ## {% $frontmatter.seo.title %} Flags
Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows: Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
| Flag Name | Hex Value | Decimal Value | Description | | Flag Name | Hex Value | Decimal Value | Description |
|:----------|:----------|:--------------|:------------| |:--------------------|:-------------|:--------------|:------------|
| `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the remaining payment amount should be treated as an overpayment. | | `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the remaining payment amount should be treated as an overpayment. |
| `tfLoanFullPayment` | `0x00020000` | 131072 | Indicates that the borrower is making a full early repayment. | | `tfLoanFullPayment` | `0x00020000` | 131072 | Indicates that the borrower is making a full early repayment. |
| `tfLoanLatePayment` | `0x00040000` | 262144 | Indicates that the borrower is making a late loan payment. |
## Error Cases ## Error Cases

View File

@@ -58,19 +58,18 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| Field Name | JSON Type | Internal Type | Required? | Description | | Field Name | JSON Type | Internal Type | Required? | Description |
|:--------------------------|:----------|:--------------|:----------|:------------| |:--------------------------|:----------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry. | | `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry. |
| `Flags` | String | UInt32 | No | Flags for the loan. |
| `Data` | String | Blob | No | Arbitrary metadata in hex format (max 256 bytes). | | `Data` | String | Blob | No | Arbitrary metadata in hex format (max 256 bytes). |
| `Counterparty` | String | AccountID | No | The address of the counterparty of the loan. | | `Counterparty` | String | AccountID | No | The address of the counterparty of the loan. |
| `LoanOriginationFee` | Number | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. | | `LoanOriginationFee` | String | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. |
| `LoanServiceFee` | Number | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. | | `LoanServiceFee` | String | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. |
| `LatePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for late payments. | | `LatePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for late payments. |
| `ClosePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. | | `ClosePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. |
| `OverpaymentFee` | Number | UInt32 | No | A fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `OverpaymentFee` | Number | UInt32 | No | A fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `InterestRate` | Number | UInt32 | No | The annualized interest rate of the loan, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `InterestRate` | Number | UInt32 | No | The annualized interest rate of the loan, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `LateInterestRate` | Number | UInt32 | No | A premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `LateInterestRate` | Number | UInt32 | No | A premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `CloseInterestRate` | Number | UInt32 | No | A fee charged for repaying the loan early, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `CloseInterestRate` | Number | UInt32 | No | A fee charged for repaying the loan early, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `OverpaymentInterestRate` | Number | UInt32 | No | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. | | `OverpaymentInterestRate` | Number | UInt32 | No | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
| `PrincipalRequested` | Number | Number | Yes | The principal loan amount requested by the borrower. | | `PrincipalRequested` | String | Number | Yes | The principal loan amount requested by the borrower. |
| `PaymentTotal` | Number | UInt32 | No | The total number of payments to be made against the loan. | | `PaymentTotal` | Number | UInt32 | No | The total number of payments to be made against the loan. |
| `PaymentInterval` | Number | UInt32 | No | The number of seconds between loan payments. | | `PaymentInterval` | Number | UInt32 | No | The number of seconds between loan payments. |
| `GracePeriod` | Number | UInt32 | No | The number of seconds after the loan's payment due date when it can be defaulted. | | `GracePeriod` | Number | UInt32 | No | The number of seconds after the loan's payment due date when it can be defaulted. |
@@ -113,7 +112,7 @@ Besides errors that can occur for all transactions, {% code-page-name /%} transa
| Error Code | Description | | Error Code | Description |
|:--------------------------|:-----------------------------------| |:--------------------------|:-----------------------------------|
| `temBAD_SIGNER` | - The transaction is missing a `CounterpartySignature` field.<br>- This transaction is part of a `Batch` transaction, but didn't specify a `Counterparty`. | | `temBAD_SIGNER` | - The transaction is missing a `CounterpartySignature` field.<br>- This transaction is part of a `Batch` transaction, but didn't specify a `Counterparty`. |
| `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval`. | | `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval` or less than `60` seconds. |
| `tecNO_ENTRY` | The `LoanBroker` doesn't exist. | | `tecNO_ENTRY` | The `LoanBroker` doesn't exist. |
| `tecNO_PERMISSION` | Neither the transaction sender's `Account` or the `Counterparty` field owns the associated `LoanBroker` ledger entry. | | `tecNO_PERMISSION` | Neither the transaction sender's `Account` or the `Counterparty` field owns the associated `LoanBroker` ledger entry. |
| `tecINSUFFICIENT_FUNDS` | - The `Vault` associated with the `LoanBroker` doesn't have enough assets to fund the loan.<br>- The `LoanBroker` ledger entry doesn't have enough first-loss capital to meet the minimum coverage requirement for the new total debt. | | `tecINSUFFICIENT_FUNDS` | - The `Vault` associated with the `LoanBroker` doesn't have enough assets to fund the loan.<br>- The `LoanBroker` ledger entry doesn't have enough first-loss capital to meet the minimum coverage requirement for the new total debt. |

View File

@@ -35,7 +35,8 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../javascript/build-apps/get-started.md) for setup steps. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -47,12 +48,22 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies:
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, use `pip` to install dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@@ -60,13 +71,20 @@ npm install xrpl
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
{% tabs %}
{% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection and transaction handling. - `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial setup scripts. - `fs` and `child_process`: Used to run tutorial setup scripts.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" before="// Create and fund" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" before="// Create and fund" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
- `xrpl`: Used for XRPL client connection and transaction handling.
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" before="# Create and fund" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, fund a vault owner account, define the MPT issuance ID for the vault's asset, and provide a permissioned domain ID to control who can deposit into the vault. Next, fund a vault owner account, define the MPT issuance ID for the vault's asset, and provide a permissioned domain ID to control who can deposit into the vault.
@@ -74,11 +92,17 @@ Next, fund a vault owner account, define the MPT issuance ID for the vault's ass
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Create and fund" before="// Prepare VaultCreate" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Create and fund" before="// Prepare VaultCreate" /%}
The example uses an existing MPT issuance and permissioned domain data from the `vaultSetup.js` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domainID`.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Create and fund" before="# Prepare VaultCreate" /%}
The example uses an existing MPT issuance and permissioned domain data from the `vault_setup.py` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domain_id`.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
The example uses an existing MPT issuance and permissioned domain data from the `vaultSetup.js` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domainId`.
### 3. Prepare VaultCreate transaction ### 3. Prepare VaultCreate transaction
Create the [VaultCreate transaction][] object: Create the [VaultCreate transaction][] object:
@@ -86,14 +110,24 @@ Create the [VaultCreate transaction][] object:
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Prepare VaultCreate" before="// Submit, sign" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Prepare VaultCreate" before="// Submit, sign" /%}
{% /tab %}
{% /tabs %}
The `tfVaultPrivate` flag and `DomainID` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead. The `tfVaultPrivate` flag and `DomainID` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
The `Data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem. The `Data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
The `AssetsMaximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed. The `AssetsMaximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Prepare VaultCreate" before="# Submit, sign" /%}
The `tfVaultPrivate` flag and `domain_id` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
The `data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
The `assets_maximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
{% /tab %}
{% /tabs %}
Vault shares are **transferable** by default, meaning depositors can transfer their shares to other accounts. If you don't want the vault's shares to be transferable, enable the `tfVaultShareNonTransferable` flag. Vault shares are **transferable** by default, meaning depositors can transfer their shares to other accounts. If you don't want the vault's shares to be transferable, enable the `tfVaultShareNonTransferable` flag.
@@ -105,6 +139,9 @@ Sign and submit the `VaultCreate` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Submit, sign" before="// Extract vault information" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Submit, sign" before="// Extract vault information" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Submit, sign" before="# Extract vault information" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -117,6 +154,9 @@ Retrieve the vault's information from the transaction result by checking for the
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Extract vault information" before="// Call vault_info" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Extract vault information" before="// Call vault_info" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Extract vault information" before="# Call vault_info" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
You can also use the [vault_info method][] to retrieve the vault's details: You can also use the [vault_info method][] to retrieve the vault's details:
@@ -125,6 +165,9 @@ You can also use the [vault_info method][] to retrieve the vault's details:
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Call vault_info" /%} {% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Call vault_info" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Call vault_info" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
This confirms that you have successfully created an empty single asset vault. This confirms that you have successfully created an empty single asset vault.

View File

@@ -34,7 +34,8 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have access to an existing vault. This tutorial uses a preconfigured vault. To create your own vault, see [Create a Single Asset Vault](./create-a-single-asset-vault.md). - Have access to an existing vault. This tutorial uses a preconfigured vault. To create your own vault, see [Create a Single Asset Vault](./create-a-single-asset-vault.md).
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../javascript/build-apps/get-started.md) for setup steps. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -46,12 +47,21 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies:
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, use `pip` to install dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@@ -59,12 +69,20 @@ npm install xrpl
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
{% tabs %}
{% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection and transaction handling. - `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial setup scripts. - `fs` and `child_process`: Used to run tutorial setup scripts.
{% tabs %} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" before="// You can replace" /%}
{% tab label="JavaScript" %} {% /tab %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="import xrpl" before="// You can replace" /%}
{% tab label="Python" %}
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
- `xrpl`: Used for XRPL client connection and transaction handling.
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" before="# You can replace" /%}
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@@ -73,10 +91,18 @@ Provide the depositing account and specify the vault details. The depositor must
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// You can replace" before="// Get initial vault" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// You can replace" before="// Get initial vault" /%}
This example uses an existing vault, depositor account, and MPT from the `vaultSetup.js` script, but you can replace these values with your own.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# You can replace" before="# Get initial vault" /%}
This example uses an existing vault, depositor account, and MPT from the `vault_setup.py` script, but you can replace these values with your own.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses an existing vault, depositor account, and MPT from the `vaultSetup.js` script, but you can replace these values with your own. The preconfigured depositor account has: The preconfigured depositor account has:
- Valid [Credentials](../../../../concepts/decentralized-storage/credentials.md) in the vault's [Permissioned Domain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md). - Valid [Credentials](../../../../concepts/decentralized-storage/credentials.md) in the vault's [Permissioned Domain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md).
- A positive balance of the MPT in the vault. - A positive balance of the MPT in the vault.
@@ -89,6 +115,10 @@ Use the [vault_info method][] to retrieve the vault's current state, including i
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get initial vault" before="// Check depositor's asset balance" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get initial vault" before="// Check depositor's asset balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get initial vault" before="# Check depositor's asset balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 4. Check depositor's asset balance ### 4. Check depositor's asset balance
@@ -99,6 +129,10 @@ Before depositing, verify that the depositor has sufficient balance of the vault
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Check depositor's asset balance" before="// Prepare VaultDeposit" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Check depositor's asset balance" before="// Prepare VaultDeposit" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Check depositor's asset balance" before="# Prepare VaultDeposit" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 5. Prepare VaultDeposit transaction ### 5. Prepare VaultDeposit transaction
@@ -108,10 +142,16 @@ Create a [VaultDeposit transaction][] object to deposit assets into the vault.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Prepare VaultDeposit" before="// Submit VaultDeposit" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Prepare VaultDeposit" before="// Submit VaultDeposit" /%}
{% /tab %}
{% /tabs %}
The transaction specifies the depositing account, the vault's unique identifier (`VaultID`), and the amount to deposit. The asset in the `Amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error. The transaction specifies the depositing account, the vault's unique identifier (`VaultID`), and the amount to deposit. The asset in the `Amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Prepare VaultDeposit" before="# Submit VaultDeposit" /%}
The transaction specifies the depositing account, the vault's unique identifier (`vault_id`), and the amount to deposit. The asset in the `amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
{% /tab %}
{% /tabs %}
### 6. Submit VaultDeposit transaction ### 6. Submit VaultDeposit transaction
@@ -121,6 +161,10 @@ Submit the `VaultDeposit` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Submit VaultDeposit" before="// Extract vault state" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Submit VaultDeposit" before="// Extract vault state" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Submit VaultDeposit" before="# Extract vault state" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
When depositing into a private vault, the transaction verifies that the depositor has valid credentials in the vault's permissioned domain. Without valid credentials, the `VaultDeposit` transaction fails with a `tecNO_AUTH` error. When depositing into a private vault, the transaction verifies that the depositor has valid credentials in the vault's permissioned domain. Without valid credentials, the `VaultDeposit` transaction fails with a `tecNO_AUTH` error.
@@ -142,6 +186,10 @@ After depositing, verify the vault's updated state. You can extract this informa
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Extract vault state" before="// Get the depositor's" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Extract vault state" before="// Get the depositor's" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Extract vault state" before="# Get the depositor's" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Finally, check that the depositing account has received the shares. Finally, check that the depositing account has received the shares.
@@ -150,6 +198,10 @@ Finally, check that the depositing account has received the shares.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get the depositor's" /%} {% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get the depositor's" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get the depositor's" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The code checks for both `ModifiedNode` and `CreatedNode` because on the first deposit, a new MPToken entry is created for the depositor's shares (`CreatedNode`). On subsequent deposits, the depositor's existing share balance is updated (`ModifiedNode`). The code checks for both `ModifiedNode` and `CreatedNode` because on the first deposit, a new MPToken entry is created for the depositor's shares (`CreatedNode`). On subsequent deposits, the depositor's existing share balance is updated (`ModifiedNode`).

View File

@@ -29,7 +29,8 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have previously deposited into a vault. This tutorial uses an account that has already deposited into a vault. To deposit your own asset, see [Deposit into a Vault](./deposit-into-a-vault.md). - Have previously deposited into a vault. This tutorial uses an account that has already deposited into a vault. To deposit your own asset, see [Deposit into a Vault](./deposit-into-a-vault.md).
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../../tutorials/javascript/build-apps/get-started.md) for setup steps. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -41,12 +42,22 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies:
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, use `pip` to install dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
@@ -54,25 +65,39 @@ npm install xrpl
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
{% tabs %}
{% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection and transaction handling. - `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial setup scripts. - `fs` and `child_process`: Used to run tutorial setup scripts.
{% tabs %}
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" before="// You can replace" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" before="// You can replace" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
- `xrpl`: Used for XRPL client connection and transaction handling.
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" before="# You can replace" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Provide the depositor account and specify the vault details. Provide the depositor account and specify the vault details.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="You can replace" before="// Get initial vault" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// You can replace" before="console.log" /%}
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace these values with your own.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# You can replace" before="print" /%}
This example uses preconfigured accounts and vault data from the `vault_setup.py` script, but you can replace these values with your own.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace `depositor`, `vaultID`, `assetMPTIssuanceId`, and `shareMPTIssuanceId` with your own values.
### 3. Check initial vault state ### 3. Check initial vault state
Before withdrawing, check the vault's current state to see its total assets and available liquidity. Before withdrawing, check the vault's current state to see its total assets and available liquidity.
@@ -81,6 +106,10 @@ Before withdrawing, check the vault's current state to see its total assets and
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get initial vault" before="// Check depositor's share balance" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get initial vault" before="// Check depositor's share balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get initial vault" before="# Check depositor's share balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 4. Check share balance ### 4. Check share balance
@@ -91,6 +120,10 @@ Verify that the depositor account has vault shares to redeem. If not, the transa
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Check depositor's share balance" before="// Prepare VaultWithdraw" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Check depositor's share balance" before="// Prepare VaultWithdraw" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Check depositor's share balance" before="# Prepare VaultWithdraw" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 5. Prepare VaultWithdraw transaction ### 5. Prepare VaultWithdraw transaction
@@ -100,15 +133,21 @@ Create a [VaultWithdraw transaction][] to withdraw assets from the vault.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Prepare VaultWithdraw" before="// Submit VaultWithdraw" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Prepare VaultWithdraw" before="// Submit VaultWithdraw" /%}
{% /tab %}
{% /tabs %}
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways: The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways:
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Prepare VaultWithdraw" before="# Submit VaultWithdraw" /%}
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`vault_id`), and the amount to withdraw or redeem. You can specify the `amount` field in two ways:
{% /tab %}
{% /tabs %}
- **Asset amount**: When you specify an asset amount, the vault burns the necessary shares to provide that amount. - **Asset amount**: When you specify an asset amount, the vault burns the necessary shares to provide that amount.
- **Share amount**: When you specify a share amount, the vault converts those shares into the corresponding asset amount. - **Share amount**: When you specify a share amount, the vault converts those shares into the corresponding asset amount.
While not required, you can provide a `Destination` account to receive the assets; if omitted, assets go to the account specified in the `Account` field. While not required, you can provide a destination account to receive the assets; if omitted, assets go to the account submitting the transaction.
{% admonition type="info" name="Note" %} {% admonition type="info" name="Note" %}
You can withdraw from a vault regardless of whether it's private or public. If you hold vault shares, you can always redeem them, even if your credentials in a private vault's permissioned domain have expired or been revoked. This prevents you from being locked out of your funds. You can withdraw from a vault regardless of whether it's private or public. If you hold vault shares, you can always redeem them, even if your credentials in a private vault's permissioned domain have expired or been revoked. This prevents you from being locked out of your funds.
@@ -122,18 +161,22 @@ Submit the `VaultWithdraw` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Submit VaultWithdraw " before="// Extract vault state" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Submit VaultWithdraw " before="// Extract vault state" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Submit VaultWithdraw" before="# Extract vault state" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
When the transaction succeeds: When the transaction succeeds:
- The vault calculates how many shares need to be burned to provide the requested asset amount. - The vault calculates how many shares need to be burned to provide the requested asset amount.
- The vault transfers the assets from its pseudo-account to the depositor account (or the `Destination` account if specified). - The vault transfers the assets from its pseudo-account to the depositor account (or the destination account if specified).
{% admonition type="info" name="Note" %} {% admonition type="info" name="Note" %}
Transfer fees are not charged on `VaultWithdraw` transactions. Transfer fees are not charged on `VaultWithdraw` transactions.
{% /admonition %} {% /admonition %}
### 6. Verify withdrawal ### 7. Verify withdrawal
After withdrawing, check the vault's state. You can extract this information directly from the transaction metadata. After withdrawing, check the vault's state. You can extract this information directly from the transaction metadata.
@@ -141,6 +184,10 @@ After withdrawing, check the vault's state. You can extract this information dir
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Extract vault state" before="// Get the depositor's share balance" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Extract vault state" before="// Get the depositor's share balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Extract vault state" before="# Get the depositor's share balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Then, check the depositor's share balance: Then, check the depositor's share balance:
@@ -149,6 +196,10 @@ Then, check the depositor's share balance:
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's share balance" before="// Get the depositor's asset balance" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's share balance" before="// Get the depositor's asset balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's share balance" before="# Get the depositor's asset balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Finally, verify the correct asset amount has been received by the depositor account: Finally, verify the correct asset amount has been received by the depositor account:
@@ -157,6 +208,10 @@ Finally, verify the correct asset amount has been received by the depositor acco
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's asset balance" /%} {% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's asset balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's asset balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
## See Also ## See Also

View File

@@ -30,6 +30,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -41,25 +42,41 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies. From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID. Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID.
@@ -67,10 +84,15 @@ Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// This step checks" before="// Check cover available" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// This step checks" before="// Check cover available" /%}
{% /tab %}
{% /tabs %}
This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values. This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# This step checks" before="# Check cover available" /%}
This example uses preconfigured accounts, MPTs, and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `mpt_issuer`, `loan_broker_id`, and `mpt_id` with your own values.
{% /tab %}
{% /tabs %}
### 3. Check initial cover available ### 3. Check initial cover available
@@ -80,6 +102,9 @@ Check the initial cover (first-loss capital) available using the [ledger_entry m
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Check cover available" before="// Prepare LoanBrokerCoverDeposit" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Check cover available" before="// Prepare LoanBrokerCoverDeposit" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Check cover available" before="# Prepare LoanBrokerCoverDeposit" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited. If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited.
@@ -91,10 +116,17 @@ Create the [LoanBrokerCoverDeposit transaction][] object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit validation" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit validation" /%}
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit validation" /%}
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
The `Amount` field specifies the MPT and amount to deposit as first-loss capital. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
### 5. Submit LoanBrokerCoverDeposit transaction ### 5. Submit LoanBrokerCoverDeposit transaction
@@ -104,6 +136,9 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for deposit validation" before="// Extract updated cover available" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for deposit validation" before="// Extract updated cover available" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for deposit validation" before="# Extract updated cover available" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -116,6 +151,9 @@ Retrieve the cover available from the transaction result by checking the `LoanBr
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract updated cover available" before="// Verify issuer of cover asset" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract updated cover available" before="// Verify issuer of cover asset" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract updated cover available" before="# Verify issuer of cover asset" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 7. Verify the asset issuer ### 7. Verify the asset issuer
@@ -126,11 +164,14 @@ Before executing a clawback, verify that the account submitting the transaction
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Verify issuer of cover asset" before="// Prepare LoanBrokerCoverClawback" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Verify issuer of cover asset" before="// Prepare LoanBrokerCoverClawback" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Verify issuer of cover asset" before="# Prepare LoanBrokerCoverClawback" /%}
{% /tab %}
{% /tabs %} {% /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. 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.
### 7. Prepare LoanBrokerCoverClawback transaction ### 8. Prepare LoanBrokerCoverClawback transaction
Create the [LoanBrokerCoverClawback transaction][] object. Create the [LoanBrokerCoverClawback transaction][] object.
@@ -138,11 +179,14 @@ Create the [LoanBrokerCoverClawback transaction][] object.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverClawback" before="// Sign, submit, and wait for clawback validation" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverClawback" before="// Sign, submit, and wait for clawback validation" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverClawback" before="# Sign, submit, and wait for clawback validation" /%}
{% /tab %}
{% /tabs %} {% /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`. 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`.
### 8. Submit LoanBrokerCoverClawback transaction ### 9. Submit LoanBrokerCoverClawback transaction
Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger. Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
@@ -150,11 +194,14 @@ Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for clawback validation" before="// Extract final cover available" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for clawback validation" before="// Extract final cover available" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for clawback validation" before="# Extract final cover available" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
### 9. Check final cover available ### 10. Check final cover available
Retrieve the final cover available from the transaction result. Retrieve the final cover available from the transaction result.
@@ -162,6 +209,9 @@ Retrieve the final cover available from the transaction result.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract final cover available" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract final cover available" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract final cover available" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
## See Also ## See Also

View File

@@ -29,6 +29,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -40,25 +41,41 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the vault owner account and vault ID. The vault owner will also be the loan broker. Next, load the vault owner account and vault ID. The vault owner will also be the loan broker.
@@ -66,22 +83,30 @@ Next, load the vault owner account and vault ID. The vault owner will also be th
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// This step checks" before="// Prepare LoanBrokerSet" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// This step checks" before="// Prepare LoanBrokerSet" /%}
This example uses preconfigured accounts and vault data from the `lendingSetup.js` script, but you can replace `loanBroker` and `vaultID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# This step checks" before="# Prepare LoanBrokerSet" /%}
This example uses preconfigured accounts and vault data from the `lending_setup.py` script, but you can replace `loan_broker` and `vault_id` with your own values.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and vault data from the `lendingSetup.js` script, but you can replace `loanBroker` and `vaultID` with your own values.
### 3. Prepare LoanBrokerSet transaction ### 3. Prepare LoanBrokerSet transaction
Create the [LoanBrokerSet transaction][] object: Create the [LoanBrokerSet transaction][] object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Prepare LoanBrokerSet" before="// Submit, sign" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Prepare LoanBrokerSet" before="// Submit, sign" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Prepare LoanBrokerSet" before="# Submit, sign" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The `ManagementFeeRate` is set in 1/10th basis points. A value of `1000` equals 1% (100 basis points). The management fee rate is set in 1/10th basis points. A value of `1000` equals 1% (100 basis points).
### 4. Submit LoanBrokerSet transaction ### 4. Submit LoanBrokerSet transaction
@@ -91,6 +116,9 @@ Sign and submit the `LoanBrokerSet` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Submit, sign" before="// Extract loan broker" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Submit, sign" before="// Extract loan broker" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Submit, sign" before="# Extract loan broker" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -103,6 +131,9 @@ Retrieve the loan broker's information from the transaction result by checking f
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Extract loan broker" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Extract loan broker" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Extract loan broker" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The loan broker pseudo-account is a special account that holds first-loss capital. The loan broker pseudo-account is a special account that holds first-loss capital.

View File

@@ -32,6 +32,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -43,25 +44,41 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the loan broker account, borrower account, and loan broker ID. Next, load the loan broker account, borrower account, and loan broker ID.
@@ -69,20 +86,23 @@ Next, load the loan broker account, borrower account, and loan broker ID.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// This step checks" before="// Prepare LoanSet" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// This step checks" before="// Prepare LoanSet" /%}
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `borrower`, and `loanBrokerID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# This step checks" before="# Prepare LoanSet" /%}
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `borrower`, and `loan_broker_id` with your own values.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `borrower`, and `loanBrokerID` with your own values.
### 3. Prepare LoanSet transaction ### 3. Prepare LoanSet transaction
Create the [LoanSet transaction][] object with the loan terms: Create the [LoanSet transaction][] object with the loan terms.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Prepare LoanSet" before="// Loan broker signs first" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Prepare LoanSet" before="// Loan broker signs first" /%}
{% /tab %}
{% /tabs %}
The `Account` field is the loan broker, and the `Counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `Account` signs first, and the `Counterparty` signs second. 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.
@@ -94,39 +114,60 @@ The loan terms include:
- `GracePeriod`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 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. - `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. - `LoanServiceFee`: A fee charged with every loan payment, paid in the borrowed asset.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Prepare LoanSet" before="# Loan broker signs first" /%}
The `account` field is the loan broker, and the `counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `account` signs first, and the `counterparty` signs second.
The loan terms include:
- `principal_requested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.
- `interest_rate`: The annualized interest rate in 1/10th basis points (500 = 0.5%).
- `payment_total`: The number of payments to be made.
- `payment_interval`: The number of seconds between payments (2592000 = 30 days).
- `grace_period`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).
- `loan_origination_fee`: A one-time fee charged when the loan is created, paid in the borrowed asset.
- `loan_service_fee`: A fee charged with every loan payment, paid in the borrowed asset.
{% /tab %}
{% /tabs %}
### 4. Add loan broker signature ### 4. Add loan broker signature
The loan broker (the `Account`) signs the transaction first, using the [sign method][]: The loan broker (the `Account`) signs the transaction first, adding their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Loan broker signs first" before="// Borrower signs second" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Loan broker signs first" before="// Borrower signs second" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Loan broker signs first" before="# Borrower signs second" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The loan broker adds their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object.
### 5. Add borrower signature ### 5. Add borrower signature
The borrower (the `Counterparty`) signs the transaction second, using the [sign method][]: The borrower (the `Counterparty`) signs the transaction second. Their `TxnSignature` and `SigningPubKey` are stored in a `CounterpartySignature` field, which is added to the `LoanSet` transaction object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Borrower signs second" before="// Submit and wait" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Borrower signs second" before="// Submit and wait" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Borrower signs second" before="# Submit and wait" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The borrower must specify `signature_target: 'CounterpartySignature'`. This adds the borrower's signatures to a `CounterpartySignature` object, which includes the borrower's `TxnSignature` and `SigningPubKey`.
### 6. Submit LoanSet transaction ### 6. Submit LoanSet transaction
Sign and submit the fully signed `LoanSet` transaction to the XRP Ledger. Submit the fully signed `LoanSet` transaction to the XRP Ledger.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Submit and wait" before="// Extract loan information" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Submit and wait" before="// Extract loan information" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Submit and wait" before="# Extract loan information" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -139,6 +180,9 @@ Retrieve the loan's information from the transaction result by checking for the
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Extract loan information" /%} {% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Extract loan information" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Extract loan information" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
## See Also ## See Also

View File

@@ -31,6 +31,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -42,25 +43,41 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the loan broker account, loan broker ID, and MPT issuance ID. Next, load the loan broker account, loan broker ID, and MPT issuance ID.
@@ -68,22 +85,34 @@ Next, load the loan broker account, loan broker ID, and MPT issuance ID.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// This step checks" before="// Prepare LoanBrokerCoverDeposit" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// This step checks" before="// Prepare LoanBrokerCoverDeposit" /%}
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `loanBrokerID`, and `mptID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# This step checks" before="# Prepare LoanBrokerCoverDeposit" /%}
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `loan_broker_id`, and `mpt_id` with your own values.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `loanBrokerID`, and `mptID` with your own values.
### 3. Prepare LoanBrokerCoverDeposit transaction ### 3. Prepare LoanBrokerCoverDeposit transaction
Create the [LoanBrokerCoverDeposit transaction][] object: Create the [LoanBrokerCoverDeposit transaction][] object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit" /%}
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit" /%}
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
The `Amount` field specifies the MPT and amount to deposit as first-loss capital. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
### 4. Submit LoanBrokerCoverDeposit transaction ### 4. Submit LoanBrokerCoverDeposit transaction
@@ -93,6 +122,9 @@ Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for deposit" before="// Extract cover balance" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for deposit" before="// Extract cover balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for deposit" before="# Extract cover balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -105,18 +137,24 @@ Retrieve the cover balance from the transaction result by checking the `LoanBrok
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract cover balance" before="// Prepare LoanBrokerCoverWithdraw" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract cover balance" before="// Prepare LoanBrokerCoverWithdraw" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract cover balance" before="# Prepare LoanBrokerCoverWithdraw" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The `LoanBroker` pseudo-account address is the `Account` field, and `CoverAvailable` shows the cover balance. The `LoanBroker` pseudo-account address is the `Account` field, and `CoverAvailable` shows the cover balance.
### 6. Prepare LoanBrokerCoverWithdraw transaction ### 6. Prepare LoanBrokerCoverWithdraw transaction
Create the [LoanBrokerCoverWithdraw transaction][] object: Create the [LoanBrokerCoverWithdraw transaction][] object.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverWithdraw" before="// Sign, submit, and wait for withdraw" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverWithdraw" before="// Sign, submit, and wait for withdraw" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverWithdraw" before="# Sign, submit, and wait for withdraw" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 7. Submit LoanBrokerCoverWithdraw transaction ### 7. Submit LoanBrokerCoverWithdraw transaction
@@ -127,6 +165,9 @@ Sign and submit the `LoanBrokerCoverWithdraw` transaction to the XRP Ledger.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for withdraw" before="// Extract updated cover balance" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for withdraw" before="// Extract updated cover balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for withdraw" before="# Extract updated cover balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
@@ -139,6 +180,9 @@ Retrieve the updated cover balance from the transaction result.
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract updated cover balance" /%} {% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract updated cover balance" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract updated cover balance" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The `CoverAvailable` field now shows the reduced balance after the withdrawal. The `CoverAvailable` field now shows the reduced balance after the withdrawal.

View File

@@ -32,6 +32,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -43,25 +44,42 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
- `time` and `datetime`: Used for grace period countdown and date formatting.
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the loan broker account and loan ID. Next, load the loan broker account and loan ID.
@@ -69,53 +87,70 @@ Next, load the loan broker account and loan ID.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// This step checks" before="// Check loan status" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// This step checks" before="// Check loan status" /%}
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# This step checks" before="# Check loan status" /%}
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `loan_broker` and `loan_id` with your own values.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values.
### 3. Check loan status ### 3. Check loan status
Check the current status of the loan using the [ledger_entry method][]: Check the current status of the loan using the [ledger_entry method][].
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Check loan status" before="// Prepare LoanManage transaction to impair" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Check loan status" before="// Prepare LoanManage transaction to impair" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Check loan status" before="# Prepare LoanManage transaction to impair" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
This shows the total amount owed and the next payment due date. The [Ripple Epoch][] timestamp is converted to a JavaScript Date object for readability. This shows the total amount owed and the next payment due date. The [Ripple Epoch][] timestamp is converted to a readable date format.
### 4. Prepare LoanManage transaction to impair the loan ### 4. Prepare LoanManage transaction to impair the loan
Create the [LoanManage transaction][] with the `tfLoanImpair` flag: Create the [LoanManage transaction][] with the `tfLoanImpair` flag.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to impair" before="// Sign, submit, and wait for impairment" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to impair" before="// Sign, submit, and wait for impairment" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to impair" before="# Sign, submit, and wait for impairment" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 5. Submit LoanManage impairment transaction ### 5. Submit LoanManage impairment transaction
Sign and submit the `LoanManage` transaction to impair the loan: Sign and submit the `LoanManage` transaction to impair the loan.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for impairment" before="// Extract loan impairment info" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for impairment" before="// Extract loan impairment info" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for impairment" before="# Extract loan impairment info" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
### 6. Get loan impairment information ### 6. Get loan impairment information
Retrieve the loan's grace period and updated payment due date from the transaction result by checking for the `Loan` entry in the transaction metadata: Retrieve the loan's grace period and updated payment due date from the transaction result by checking for the `Loan` entry in the transaction metadata.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Extract loan impairment info" before="// Countdown until loan can be defaulted" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Extract loan impairment info" before="// Countdown until loan can be defaulted" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Extract loan impairment info" before="# Countdown until loan can be defaulted" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The loan can only be defaulted after the grace period expires. The example calculates when the grace period ends and displays a countdown. The loan can only be defaulted after the grace period expires. The example calculates when the grace period ends and displays a countdown.
@@ -128,41 +163,53 @@ This countdown displays the remaining seconds in real-time. Once the grace perio
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Countdown until loan can be defaulted" before="// Prepare LoanManage transaction to default" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Countdown until loan can be defaulted" before="// Prepare LoanManage transaction to default" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Countdown until loan can be defaulted" before="# Prepare LoanManage transaction to default" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 8. Prepare LoanManage transaction to default the loan ### 8. Prepare LoanManage transaction to default the loan
After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag: After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to default" before="// Sign, submit, and wait for default" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to default" before="// Sign, submit, and wait for default" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to default" before="# Sign, submit, and wait for default" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 9. Submit LoanManage default transaction ### 9. Submit LoanManage default transaction
Sign and submit the `LoanManage` transaction to default the loan: Sign and submit the `LoanManage` transaction to default the loan.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for default" before="// Verify loan default status" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for default" before="// Verify loan default status" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for default" before="# Verify loan default status" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
### 10. Verify loan default status ### 10. Verify loan default status
Confirm the loan has been defaulted by checking the loan flags: Confirm the loan has been defaulted by checking the loan flags.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Verify loan default status" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Verify loan default status" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Verify loan default status" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
The `parseTransactionFlags` function converts the numeric flags to a readable format, showing that the `tfLoanDefault` flag is now set. The loan flags are parsed to confirm the `tfLoanDefault` flag is now set.
## See Also ## See Also

View File

@@ -32,6 +32,7 @@ To complete this tutorial, you should:
- Have a basic understanding of the XRP Ledger. - Have a basic understanding of the XRP Ledger.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - 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. - **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code ## Source Code
@@ -43,25 +44,41 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies: From the code sample folder, use `npm` to install dependencies.
```bash ```bash
npm install xrpl npm install xrpl
``` ```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
### 2. Set up client and accounts ### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial setup scripts.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `fs` and `child_process`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" before="// This step checks" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" before="// This step checks" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `json`: Used for loading and formatting JSON data.
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" before="# This step checks" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Next, load the borrower account, loan ID, and MPT issuance ID. Next, load the borrower account, loan ID, and MPT issuance ID.
@@ -69,19 +86,27 @@ Next, load the borrower account, loan ID, and MPT issuance ID.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// This step checks" before="// Check initial loan status" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// This step checks" before="// Check initial loan status" /%}
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# This step checks" before="# Check initial loan status" /%}
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `borrower`, `loan_id`, and `mpt_id` with your own values.
{% /tab %} {% /tab %}
{% /tabs %} {% /tabs %}
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values.
### 3. Check loan status ### 3. Check loan status
Check the current status of the loan using the [ledger_entry method][]: Check the current status of the loan using the [ledger_entry method][].
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Check initial loan status" before="// Prepare LoanPay transaction" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Check initial loan status" before="// Prepare LoanPay transaction" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Check initial loan status" before="# Prepare LoanPay transaction" /%}
{% /tab %}
{% /tabs %} {% /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. 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.
@@ -92,70 +117,88 @@ Other fees can be charged on a loan, such as late or early payment fees. These a
### 4. Prepare LoanPay transaction ### 4. Prepare LoanPay transaction
Create the [LoanPay transaction][] with the total payment amount: Create the [LoanPay transaction][] with the total payment amount.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanPay transaction" before="// Sign, submit, and wait for payment validation" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanPay transaction" before="// Sign, submit, and wait for payment validation" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanPay transaction" before="# Sign, submit, and wait for payment validation" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
### 5. Submit LoanPay transaction ### 5. Submit LoanPay transaction
Sign and submit the `LoanPay` transaction to the XRP Ledger: Sign and submit the `LoanPay` transaction to the XRP Ledger.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for payment validation" before="// Extract updated loan info" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for payment validation" before="// Extract updated loan info" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for payment validation" before="# Extract updated loan info" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
### 6. Check loan balance ### 6. Check loan balance
Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata: Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Extract updated loan info" before="// Prepare LoanDelete transaction" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Extract updated loan info" before="// Prepare LoanDelete transaction" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Extract updated loan info" before="# Prepare LoanDelete transaction" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
If `TotalValueOutstanding` is absent from the loan metadata, the loan has been fully paid off and is ready for deletion. If `TotalValueOutstanding` is absent from the loan metadata, the loan has been fully paid off and is ready for deletion.
### 7. Prepare LoanDelete transaction ### 7. Prepare LoanDelete transaction
Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger: Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanDelete transaction" before="// Sign, submit, and wait for deletion validation" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanDelete transaction" before="// Sign, submit, and wait for deletion validation" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanDelete transaction" before="# Sign, submit, and wait for deletion validation" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Either the loan broker or the borrower can submit a `LoanDelete` transaction. In this example, the borrower deletes their own paid off loan. Either the loan broker or the borrower can submit a `LoanDelete` transaction. In this example, the borrower deletes their own paid off loan.
### 8. Submit LoanDelete transaction ### 8. Submit LoanDelete transaction
Sign and submit the `LoanDelete` transaction: Sign and submit the `LoanDelete` transaction.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for deletion validation" before="// Verify loan deletion" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for deletion validation" before="// Verify loan deletion" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for deletion validation" before="# Verify loan deletion" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
### 9. Verify loan deletion ### 9. Verify loan deletion
Confirm that the loan has been removed from the XRP Ledger: Confirm that the loan has been removed from the XRP Ledger.
{% tabs %} {% tabs %}
{% tab label="JavaScript" %} {% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Verify loan deletion" /%} {% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Verify loan deletion" /%}
{% /tab %} {% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Verify loan deletion" /%}
{% /tab %}
{% /tabs %} {% /tabs %}
If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted. If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted.

View File

@@ -44,8 +44,8 @@ The XRPL lending protocol addresses these challenges through:
### Regulatory Compliance ### Regulatory Compliance
- Accounts on the XRPL can be vetted by a trusted credential issuer. Credentials can be issued and revoked, based around relevant criteria, such as credit score. - Accounts on the XRPL can be vetted by a trusted credential issuer. Credentials can be issued and revoked based on relevant criteria, such as credit score.
- Permissioned Domains act as a gateway, limiting who can access the credit facilities, based on accepted credentials you define. - Permissioned Domains act as a gateway, limiting who can access the credit facilities based on accepted credentials you define.
- All credential and loan info is transparent on the XRPL, which makes compliance reporting and monitoring simpler and tamper-proof. - All credential and loan info is transparent on the XRPL, which makes compliance reporting and monitoring simpler and tamper-proof.
@@ -56,18 +56,46 @@ The XRPL lending protocol addresses these challenges through:
- Built-in first-loss capital features automatically protect against asset losses from defaults. - Built-in first-loss capital features automatically protect against asset losses from defaults.
## Implementation Steps ## User Journeys
1. Set Up Credential System There are three users that enable institutional credit facilities on the XRP Ledger: Loan Brokers, Lenders, and Borrowers. The tabs below outline which features and transactions each user typically uses in the lending process.
- Select or become a credential issuer.
- Define required credentials for borrowers. {% tabs %}
- Set up Permissioned Domains to protect your lending protocol and stay compliant with regulations. {% tab label="Loan Broker" %}
2. Set Up Asset Vaults As a **Loan Broker**, I need to:
- Set up vaults for different lending assets. - Create a [LoanBroker entry][] to define the configuration of a Lending Protocol.
- Define public/private access parameters. - Maintain the required [first-loss capital](../../concepts/tokens/lending-protocol.md#first-loss-capital) to protect deposits in my Single Asset Vault.
- Establish vault management policies.
3. Deploy Lending Protocol | Step | Description | Technical Implementation |
- Create a LoanBroker and configure lending parameters. |:-------------------------------|:------------|:-------------------------|
- Create and manage loans, including fees, impairment and default settings. | Vault Setup | The Loan Broker creates a Single Asset Vault to aggregate one type of asset to lend out. They define a [permissioned domain][] to ensure only accounts that meet KYB (Know Your Business) compliance requirements can deposit into the vault. | - [Create Permissioned Domains](../../tutorials/javascript/compliance/create-permissioned-domains.md)<br>- [Create a Single Asset Vault](../../tutorials/how-tos/set-up-lending/use-single-asset-vaults/create-a-single-asset-vault.md) |
- Set up monitoring and reporting systems. | Lending Protocol Setup | The Loan Broker sets up the Lending Protocol instance, linking it to the Single Asset Vault they created, and defining parameters such as payment fees. | [Create a Loan Broker](../../tutorials/how-tos/set-up-lending/use-the-lending-protocol/create-a-loan-broker.md) |
- Withdraw and repay loans. | First-loss Capital Maintenance | The Loan Broker deposits first-loss capital into the Lending Protocol to meet the minimum cover required. When there is excess cover, they withdraw first-loss capital. | [Deposit and Withdraw First-Loss Capital](../../tutorials/how-tos/set-up-lending/use-the-lending-protocol/deposit-and-withdraw-cover.md) |
{% /tab %}
{% tab label="Lender" %}
As a **Lender**, I need to:
- Authorize my account to deposit assets into a Single Asset Vault, so that I can deploy idle liquidity to generate yield.
- Redeem vault shares to realize my earnings and return assets to my account.
| Step | Description | Technical Implementation |
|:----------------|:------------|:-------------------------|
| Onboarding | The Lender triggers a verification workflow with the Loan Broker managing the Lending Protocol. The Loan Broker can issue their own credentials or utilize a credential issuer. Upon successful KYB, a credential is issued and the Lender accepts the credential. | [Build a Credential Issuing Service](../../tutorials/javascript/build-apps/credential-issuing-service.md) |
| Deposit Asset | The Lender deposits assets into a Single Asset Vault to lend out. Vault shares are minted and sent back to the Lender, representing their stake in the vault. | [Deposit into a Vault](../../tutorials/how-tos/set-up-lending/use-single-asset-vaults/deposit-into-a-vault.md) |
| Withdraw Asset | Vault shares are yield-bearing assets; the vault collects interest and fees on loans, which increases the underlying value of each vault share. The Lender collects their deposit (plus yield) by redeeming vault shares. | [Withdraw from a Vault](../../tutorials/how-tos/set-up-lending/use-single-asset-vaults/withdraw-from-a-vault.md) |
{% /tab %}
{% tab label="Borrower" %}
As a **Borrower**, I need to:
- Authorize my account to request loans from a Loan Broker.
- Repay my loan.
| Step | Description | Technical Implementation |
|:-----------------|:------------|:-------------------------|
| Onboarding | The Borrower triggers a verification workflow with the Loan Broker managing the Lending Protocol. The Loan Broker can issue their own credentials or utilize a credential issuer. Upon successful KYB, a credential is issued and the Borrower accepts the credential. | [Build a Credential Issuing Service](../../tutorials/javascript/build-apps/credential-issuing-service.md) |
| Loan Application | The Borrower applies for a loan with the Loan Broker. Both parties agree on the terms and co-sign the loan. | [Create a Loan](../../tutorials/how-tos/set-up-lending/use-the-lending-protocol/create-a-loan.md) |
| Repayment | The Borrower makes payments on principal, interest, and fees according to the loan agreement. | [Pay Off a Loan](../../tutorials/how-tos/set-up-lending/use-the-lending-protocol/pay-off-a-loan.md) |
{% /tab %}
{% /tabs %}
{% raw-partial file="/docs/_snippets/common-links.md" /%}