mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-03-04 11:42:28 +00:00
Compare commits
150 Commits
qa-carouse
...
add-feedba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
803eab8048 | ||
|
|
9a026823e0 | ||
|
|
368214678e | ||
|
|
61fc160523 | ||
|
|
dc3db2c4f2 | ||
|
|
3ca8de9646 | ||
|
|
2ca8706e41 | ||
|
|
dc0778b666 | ||
|
|
7268a0e52e | ||
|
|
025c262f3e | ||
|
|
f26c5921b8 | ||
|
|
4094d1f76a | ||
|
|
62288d6c3b | ||
|
|
f5f08d8690 | ||
|
|
88c28dfd05 | ||
|
|
c297fc5960 | ||
|
|
fa9ddb8d67 | ||
|
|
3c14520ad0 | ||
|
|
a83e6e3a1a | ||
|
|
79e800baae | ||
|
|
646d4a7381 | ||
|
|
62238e2ba8 | ||
|
|
f211351f4c | ||
|
|
7c41d016db | ||
|
|
0c6283624b | ||
|
|
7a4041865a | ||
|
|
a2c7afb400 | ||
|
|
8750c196b9 | ||
|
|
91c7014676 | ||
|
|
37ca34a5a1 | ||
|
|
4d4ce6b9e1 | ||
|
|
c8f5f8456c | ||
|
|
43e61b4844 | ||
|
|
fbbd88a069 | ||
|
|
a57c4b7d76 | ||
|
|
0c63250bdf | ||
|
|
60d7292eb6 | ||
|
|
10f57ddd43 | ||
|
|
2cbd652135 | ||
|
|
636f175ada | ||
|
|
36d1448047 | ||
|
|
575615487a | ||
|
|
b12112187e | ||
|
|
cffcf05da6 | ||
|
|
c2dd33cf37 | ||
|
|
9c729d9223 | ||
|
|
a7e2464d46 | ||
|
|
9af8e7849e | ||
|
|
43c2bec558 | ||
|
|
65f7ded0cf | ||
|
|
a3ed1f2c34 | ||
|
|
d4a5f752d7 | ||
|
|
adbf7dbb2f | ||
|
|
21bd1991e6 | ||
|
|
b2d86aef14 | ||
|
|
28e4927131 | ||
|
|
c62d5090d0 | ||
|
|
19ec38e7d8 | ||
|
|
f119214cea | ||
|
|
69d4113536 | ||
|
|
f5988554db | ||
|
|
5ad87dee4b | ||
|
|
66caa79289 | ||
|
|
ff75fc0af9 | ||
|
|
1e2f9ea0b0 | ||
|
|
eecdaccd55 | ||
|
|
260f8afd8b | ||
|
|
f1eab6f8ae | ||
|
|
934d7c3ff8 | ||
|
|
883a6a1d29 | ||
|
|
c5f38c1a07 | ||
|
|
6a11249b3d | ||
|
|
f2200e2a51 | ||
|
|
ff6e5932f6 | ||
|
|
776a4f290e | ||
|
|
821ada37ba | ||
|
|
8d83ddcf63 | ||
|
|
61e700ed2b | ||
|
|
1c35d320d9 | ||
|
|
63bf0b61ec | ||
|
|
0324ff52b6 | ||
|
|
fdb839295d | ||
|
|
22eaf502a5 | ||
|
|
104977125b | ||
|
|
8a1fb62712 | ||
|
|
c59e930061 | ||
|
|
2a088dfcba | ||
|
|
3d877de05a | ||
|
|
8e8e2fc676 | ||
|
|
ade482a349 | ||
|
|
d6c68d6a2d | ||
|
|
5d18b40746 | ||
|
|
a5f8580e0a | ||
|
|
794f588008 | ||
|
|
799a51f528 | ||
|
|
fef973d443 | ||
|
|
04c33adeb8 | ||
|
|
6611b82f5b | ||
|
|
1a5762b36f | ||
|
|
92b0d8b9d3 | ||
|
|
b3ff5bf1a4 | ||
|
|
695007d3db | ||
|
|
f9aebc83b9 | ||
|
|
fb94fed151 | ||
|
|
778c676664 | ||
|
|
2b6971a89d | ||
|
|
47fb4632bd | ||
|
|
e32c12a359 | ||
|
|
ab14511bb4 | ||
|
|
75f861cfed | ||
|
|
b60c72cdf3 | ||
|
|
f73ebc41bd | ||
|
|
ab7d6a09e9 | ||
|
|
1c6ade3aba | ||
|
|
d9d884543b | ||
|
|
6d2259e30a | ||
|
|
900a4f01ba | ||
|
|
94e4173441 | ||
|
|
9dffd66faf | ||
|
|
ec6bbff42f | ||
|
|
1a0310bf90 | ||
|
|
d2c2b91b0a | ||
|
|
baf0f4e819 | ||
|
|
db9dd303ae | ||
|
|
e181ee6e0f | ||
|
|
af79cb6cf2 | ||
|
|
98bea864bc | ||
|
|
804e51b6b1 | ||
|
|
ca245d72ee | ||
|
|
b0f04a34ed | ||
|
|
ba4ac4c923 | ||
|
|
a5475869c5 | ||
|
|
053c4bb5a2 | ||
|
|
9ceb186fb4 | ||
|
|
18542eb915 | ||
|
|
d8655b4a0c | ||
|
|
3b276c6f19 | ||
|
|
898e698bec | ||
|
|
e5049e53f9 | ||
|
|
afd636e69d | ||
|
|
a5b914caee | ||
|
|
df7cd95784 | ||
|
|
5a9357553c | ||
|
|
21d27c36bb | ||
|
|
d153a017b2 | ||
|
|
20a873ae55 | ||
|
|
e54a5a4ea6 | ||
|
|
8f05a58f12 | ||
|
|
d8849f03f9 | ||
|
|
4b8b714e5b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
composer.lock
|
||||
|
||||
@@ -75,10 +75,10 @@ Clioをインストールする前に、以下の条件を満たしている必
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ labels:
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
特に、フィンガープリントが一致することを確認してください。(上記の例では、フィンガープリントは三行目の`C001`で始まる部分です。)
|
||||
|
||||
@@ -155,6 +155,8 @@ amendment.table.status: ステータス
|
||||
amendment.status.enabled: 有効
|
||||
amendment.status.eta: 予定
|
||||
amendment.status.openForVoting: 投票中
|
||||
amendment.status.inactive: 無効
|
||||
amendment.status.inactiveButton: 詳細を取得する
|
||||
|
||||
# index.page.tsx
|
||||
home.hero.h1part1: ビジネスのための
|
||||
|
||||
@@ -23,6 +23,7 @@ type AmendmentsCachePayload = {
|
||||
|
||||
// API data caching
|
||||
const amendmentsEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/vote/main/'
|
||||
const amendmentsInfoEndpoint = 'https://vhs.prod.ripplex.io/v1/network/amendments/info/main/'
|
||||
const amendmentsCacheKey = 'xrpl.amendments.mainnet.cache'
|
||||
const amendmentsTTL = 15 * 60 * 1000 // 15 minutes in milliseconds
|
||||
|
||||
@@ -180,6 +181,8 @@ function AmendmentBadge(props: { amendment: Amendment }) {
|
||||
const enabledLabel = translate("amendment.status.enabled", "Enabled")
|
||||
const votingLabel = translate("amendment.status.openForVoting", "Open for Voting")
|
||||
const etaLabel = translate("amendment.status.eta", "Expected")
|
||||
const inactiveLabel = translate("amendment.status.inactive", "Inactive")
|
||||
const inactiveButton = translate("amendment.status.inactiveButton", "Get details")
|
||||
|
||||
React.useEffect(() => {
|
||||
const amendment = props.amendment
|
||||
@@ -202,10 +205,16 @@ function AmendmentBadge(props: { amendment: Amendment }) {
|
||||
else if (amendment.consensus) {
|
||||
setStatus(`${votingLabel}: ${amendment.consensus}`)
|
||||
setColor('80d0e0')
|
||||
setHref(undefined) // No link for voting amendments
|
||||
setHref(undefined)
|
||||
}
|
||||
}, [props.amendment, enabledLabel, etaLabel, votingLabel])
|
||||
|
||||
// Fallback: amendment is inactive
|
||||
else {
|
||||
setStatus(`${inactiveLabel}: ${inactiveButton}`)
|
||||
setColor('lightgrey')
|
||||
setHref(`/resources/known-amendments#${amendment.name.toLowerCase()}`)
|
||||
}
|
||||
}, [props.amendment, enabledLabel, etaLabel, votingLabel, inactiveLabel])
|
||||
|
||||
// Split the status at the colon to create two-color badge
|
||||
const parts = status.split(':')
|
||||
const label = shieldsIoEscape(parts[0])
|
||||
@@ -257,15 +266,32 @@ export function AmendmentDisclaimer(props: {
|
||||
const response = await fetch(amendmentsEndpoint)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
throw new Error(`HTTP error! Status: ${response.status}`)
|
||||
}
|
||||
|
||||
const data: AmendmentsResponse = await response.json()
|
||||
writeAmendmentsCache(data.amendments)
|
||||
|
||||
const found = data.amendments.find(a => a.name === props.name)
|
||||
|
||||
// 3. If not found in live data, try the info endpoint.
|
||||
if (!found) {
|
||||
throw new Error(`Couldn't find ${props.name} amendment in status table.`)
|
||||
|
||||
const infoResponse = await fetch(amendmentsInfoEndpoint)
|
||||
|
||||
if (!infoResponse.ok) {
|
||||
throw new Error(`HTTP error from info endpoint! Status: ${infoResponse.status}`)
|
||||
}
|
||||
|
||||
const infoData: AmendmentsResponse = await infoResponse.json()
|
||||
const foundInInfo = infoData.amendments.find(a => a.name === props.name)
|
||||
|
||||
if (!foundInInfo) {
|
||||
throw new Error(`Couldn't find ${props.name} amendment in status tables.`)
|
||||
}
|
||||
|
||||
setStatus(foundInInfo)
|
||||
return
|
||||
}
|
||||
|
||||
setStatus(found)
|
||||
@@ -389,6 +415,8 @@ export function Badge(props: {
|
||||
"更新": "blue", // ja: updated in
|
||||
"in development": "lightgrey",
|
||||
"開発中": "lightgrey", // ja: in development
|
||||
"inactive": "lightgrey",
|
||||
"無効": "lightgrey" // ja: inactive
|
||||
}
|
||||
|
||||
let childstrings = ""
|
||||
|
||||
@@ -273,6 +273,8 @@ ul.nav.navbar-nav {
|
||||
--language-picker-background-color: var(--color-gray-8);
|
||||
--select-list-bg-color: var(--color-gray-8);
|
||||
|
||||
--button-bg-color-secondary: var(--color-gray-8);
|
||||
|
||||
--footer-title-text-color: black;
|
||||
--bg-color: var(--color-gray-9);
|
||||
--bg-color-raised: var(--color-gray-8);
|
||||
|
||||
3
_code-samples/lending-protocol/README.md
Normal file
3
_code-samples/lending-protocol/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Lending Protocol Examples
|
||||
|
||||
Code samples showing 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.
|
||||
394
_code-samples/lending-protocol/js/README.md
Normal file
394
_code-samples/lending-protocol/js/README.md
Normal file
@@ -0,0 +1,394 @@
|
||||
# Lending Protocol Examples (JavaScript)
|
||||
|
||||
This directory contains JavaScript 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
|
||||
npm i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan Broker
|
||||
|
||||
```sh
|
||||
node createLoanBroker.js
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account:
|
||||
|
||||
```sh
|
||||
Loan broker/vault owner address: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
|
||||
Vault ID: 33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68
|
||||
|
||||
=== Preparing LoanBrokerSet transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"VaultID": "33E51DD0333775E37F2CC1EB0DA788F9C663AF919DC23ED595A8D69330E5CD68",
|
||||
"ManagementFeeRate": 1000
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerSet transaction ===
|
||||
|
||||
Loan broker created successfully!
|
||||
|
||||
=== Loan Broker Information ===
|
||||
|
||||
LoanBroker ID: 0AA13C8A8E95D8F2D9EF1FA1B15EF4668EF779A678D1D24D099C532E126E8BBF
|
||||
LoanBroker Psuedo-Account Address: rfhftuQGpqUVRcERZbY9htJshijKur7dS4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Claw Back First-loss Capital
|
||||
|
||||
```sh
|
||||
node coverClawback.js
|
||||
```
|
||||
|
||||
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: r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU
|
||||
MPT issuer address: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3
|
||||
LoanBrokerID: 655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442
|
||||
MPT ID: 00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E
|
||||
|
||||
=== Cover Available ===
|
||||
|
||||
0 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverDeposit",
|
||||
"Account": "r9tQSk5rQdjjVGn1brt8K5XNYFvNSLv3xU",
|
||||
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Available After Deposit ===
|
||||
|
||||
1000 TSTUSD
|
||||
|
||||
=== Verifying Asset Issuer ===
|
||||
|
||||
MPT issuer account verified: rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3. Proceeding to clawback.
|
||||
|
||||
=== Preparing LoanBrokerCoverClawback transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverClawback",
|
||||
"Account": "rJ7DiJdcThwLD5rZjC7D1neXmvLFAGk9t3",
|
||||
"LoanBrokerID": "655C32ADFCA0712F3CB32CA034C29FE3DE9DE876A86141F0902FB1E05DA0E442",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "00349F41BFA01892C83AC779E4BBB80C8CE3B92D401E4B6E",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverClawback transaction ===
|
||||
|
||||
Successfully clawed back 1000 TSTUSD!
|
||||
|
||||
=== Final Cover Available After Clawback ===
|
||||
|
||||
0 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit and Withdraw First-loss Capital
|
||||
|
||||
```sh
|
||||
node coverDepositAndWithdraw.js
|
||||
```
|
||||
|
||||
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: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
|
||||
LoanBrokerID: F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15
|
||||
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverDeposit",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
|
||||
"value": "2000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
|
||||
Cover balance after deposit: 2000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverWithdraw",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"LoanBrokerID": "F133118D55342F7F78188BDC9259E8593853010878C9F6CEA0E2F56D829C6B15",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
Cover withdraw successful!
|
||||
|
||||
=== Updated Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rf5FREUsutDyDAaVPPvZnNmoEETr21sPDd
|
||||
Cover balance after withdraw: 1000 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan
|
||||
|
||||
```sh
|
||||
node createLoan.js
|
||||
```
|
||||
|
||||
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information:
|
||||
|
||||
```sh
|
||||
Loan broker address: rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz
|
||||
Borrower address: rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt
|
||||
LoanBrokerID: 3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5
|
||||
|
||||
=== Preparing LoanSet transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"PrincipalRequested": "1000",
|
||||
"InterestRate": 500,
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"Fee": "2"
|
||||
}
|
||||
|
||||
=== Adding loan broker signature ===
|
||||
|
||||
TxnSignature: F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A
|
||||
SigningPubKey: EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530
|
||||
|
||||
Signed loanSetTx for borrower to sign over:
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"PaymentTotal": 12,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"Fee": "2",
|
||||
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
|
||||
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PrincipalRequested": "1000"
|
||||
}
|
||||
|
||||
=== Adding borrower signature ===
|
||||
|
||||
Borrower TxnSignature: 52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800
|
||||
Borrower SigningPubKey: EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62
|
||||
|
||||
Fully signed LoanSet transaction:
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Flags": 0,
|
||||
"Sequence": 3670743,
|
||||
"LastLedgerSequence": 3673248,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"PaymentTotal": 12,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"Fee": "2",
|
||||
"SigningPubKey": "EDDABC72936FF734FA56D6C60C064D48C5DA9911C8B7C26C4AEAC06534B5D7C530",
|
||||
"TxnSignature": "F8B2F2AB960191991FC48120A48A089B479018A6469466E43E6F974E1345B32688D59D381E6BC18B6CA383235B708FE4FB44527C51E5B29BCDCC4A08C340A00A",
|
||||
"Account": "rn6CD8i3Yc3UGagSosZfegG7hpXxwgVAgz",
|
||||
"Counterparty": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanServiceFee": "10",
|
||||
"PrincipalRequested": "1000",
|
||||
"CounterpartySignature": {
|
||||
"SigningPubKey": "EDE624A07899AEF826DF2A3E2A325F69BC1F169D23F08091E9042644D6B06D3D62",
|
||||
"TxnSignature": "52E16B88F5640F637A05E59AB2BE0DBFE4FBE7F1D7580C2A39D4981F6066A7C42047A401B953CDAB4993954A85D73DE35F69317EE8279D23ECB4958AA10C0800"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting signed LoanSet transaction ===
|
||||
|
||||
Loan created successfully!
|
||||
|
||||
=== Loan Information ===
|
||||
|
||||
{
|
||||
"Borrower": "rN2PMxegkEMZHin78o7wSs1JeYjxAvAfvt",
|
||||
"GracePeriod": 604800,
|
||||
"InterestRate": 500,
|
||||
"LoanBrokerID": "3CDEA7CEB9F2ECDD76CD41A864F4E3B5DB9C91AEDBD0906EE466FDD21CCF49B5",
|
||||
"LoanOriginationFee": "100",
|
||||
"LoanSequence": 6,
|
||||
"LoanServiceFee": "10",
|
||||
"NextPaymentDueDate": 826862960,
|
||||
"PaymentInterval": 2592000,
|
||||
"PaymentRemaining": 12,
|
||||
"PeriodicPayment": "83.55610375293148956",
|
||||
"PrincipalOutstanding": "1000",
|
||||
"StartDate": 824270960,
|
||||
"TotalValueOutstanding": "1003"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Manage a Loan
|
||||
|
||||
```sh
|
||||
node loanManage.js
|
||||
```
|
||||
|
||||
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: rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY
|
||||
LoanID: D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Total Amount Owed: 1001 TSTUSD.
|
||||
Payment Due Date: 2/25/2026, 11:58:20 PM
|
||||
|
||||
=== Preparing LoanManage transaction to impair loan ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanManage",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
|
||||
"Flags": 131072
|
||||
}
|
||||
|
||||
=== Submitting LoanManage impairment transaction ===
|
||||
|
||||
Loan impaired successfully!
|
||||
New Payment Due Date: 1/27/2026, 12:05:02 AM
|
||||
Grace Period: 60 seconds
|
||||
|
||||
=== Countdown until loan can be defaulted ===
|
||||
|
||||
Grace period expired. Loan can now be defaulted.
|
||||
|
||||
=== Preparing LoanManage transaction to default loan ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanManage",
|
||||
"Account": "rKL3u76wNGdF2Th4EvCuHV5885T6h2iFTY",
|
||||
"LoanID": "D28764B238CF3F7D7BF4AFD07394838EDD5F278B838F97A55BEAEC1E5152719C",
|
||||
"Flags": 65536
|
||||
}
|
||||
|
||||
=== Submitting LoanManage default transaction ===
|
||||
|
||||
Loan defaulted successfully!
|
||||
|
||||
=== Checking final loan status ===
|
||||
|
||||
Final loan flags (parsed): {"tfLoanDefault":true,"tfLoanImpair":true}
|
||||
```
|
||||
|
||||
## Pay a Loan
|
||||
|
||||
```sh
|
||||
node loanPay.js
|
||||
```
|
||||
|
||||
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: r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA
|
||||
LoanID: 8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E
|
||||
MPT ID: 0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Amount Owed: 1001 TSTUSD
|
||||
Loan Service Fee: 10 TSTUSD
|
||||
Total Payment Due (including fees): 1011 TSTUSD
|
||||
|
||||
=== Preparing LoanPay transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanPay",
|
||||
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0031034FF84EB2E8348A34F0A8889A54F45F180E80F12341",
|
||||
"value": "1011"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting LoanPay transaction ===
|
||||
|
||||
Loan paid successfully!
|
||||
|
||||
=== Loan Status After Payment ===
|
||||
|
||||
Outstanding Loan Balance: Loan fully paid off!
|
||||
|
||||
=== Preparing LoanDelete transaction ===
|
||||
|
||||
{
|
||||
"TransactionType": "LoanDelete",
|
||||
"Account": "r46Ef5jjnaY7CDP7g22sQgSJJPQEBSmbWA",
|
||||
"LoanID": "8AC2B4425E604E7BB1082DD2BF2CA902B5087143B7775BE0A4DA954D3F52D06E"
|
||||
}
|
||||
|
||||
=== Submitting LoanDelete transaction ===
|
||||
|
||||
Loan deleted successfully!
|
||||
|
||||
=== Verifying Loan Deletion ===
|
||||
|
||||
Loan has been successfully removed from the XRP Ledger!
|
||||
```
|
||||
143
_code-samples/lending-protocol/js/coverClawback.js
Normal file
143
_code-samples/lending-protocol/js/coverClawback.js
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
|
||||
const mptIssuer = xrpl.Wallet.fromSeed(setupData.depositor.seed)
|
||||
const loanBrokerID = setupData.loanBrokerID
|
||||
const mptID = setupData.mptID
|
||||
|
||||
console.log(`\nLoan broker address: ${loanBroker.address}`)
|
||||
console.log(`MPT issuer address: ${mptIssuer.address}`)
|
||||
console.log(`LoanBrokerID: ${loanBrokerID}`)
|
||||
console.log(`MPT ID: ${mptID}`)
|
||||
|
||||
// Check cover available ----------------------
|
||||
console.log(`\n=== Cover Available ===\n`)
|
||||
const coverInfo = await client.request({
|
||||
command: 'ledger_entry',
|
||||
index: loanBrokerID,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
|
||||
let currentCoverAvailable = coverInfo.result.node.CoverAvailable || '0'
|
||||
console.log(`${currentCoverAvailable} TSTUSD`)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
|
||||
const coverDepositTx = {
|
||||
TransactionType: 'LoanBrokerCoverDeposit',
|
||||
Account: loanBroker.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '1000'
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(coverDepositTx)
|
||||
console.log(JSON.stringify(coverDepositTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
|
||||
const depositResponse = await client.submitAndWait(coverDepositTx, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = depositResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to deposit cover:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Cover deposit successful!')
|
||||
|
||||
// Extract updated cover available after deposit ----------------------
|
||||
console.log(`\n=== Cover Available After Deposit ===\n`)
|
||||
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
|
||||
)
|
||||
|
||||
currentCoverAvailable = loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable
|
||||
console.log(`${currentCoverAvailable} TSTUSD`)
|
||||
|
||||
// Verify issuer of cover asset matches ----------------------
|
||||
// Only the issuer of the asset can submit clawback transactions.
|
||||
// The asset must also have clawback enabled.
|
||||
console.log(`\n=== Verifying Asset Issuer ===\n`)
|
||||
const assetIssuerInfo = await client.request({
|
||||
command: 'ledger_entry',
|
||||
mpt_issuance: mptID,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
|
||||
if (assetIssuerInfo.result.node.Issuer !== mptIssuer.address) {
|
||||
console.error(`Error: ${assetIssuerInfo.result.node.Issuer} does not match account (${mptIssuer.address}) attempting clawback!`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`MPT issuer account verified: ${mptIssuer.address}. Proceeding to clawback.`)
|
||||
|
||||
// Prepare LoanBrokerCoverClawback transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanBrokerCoverClawback transaction ===\n`)
|
||||
const coverClawbackTx = {
|
||||
TransactionType: 'LoanBrokerCoverClawback',
|
||||
Account: mptIssuer.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: currentCoverAvailable
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(coverClawbackTx)
|
||||
console.log(JSON.stringify(coverClawbackTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for clawback validation ----------------------
|
||||
console.log(`\n=== Submitting LoanBrokerCoverClawback transaction ===\n`)
|
||||
const clawbackResponse = await client.submitAndWait(coverClawbackTx, {
|
||||
wallet: mptIssuer,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (clawbackResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = clawbackResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to clawback cover:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`Successfully clawed back ${currentCoverAvailable} TSTUSD!`)
|
||||
|
||||
// Extract final cover available ----------------------
|
||||
console.log(`\n=== Final Cover Available After Clawback ===\n`)
|
||||
loanBrokerNode = clawbackResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
|
||||
)
|
||||
|
||||
console.log(`${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable || '0'} TSTUSD`)
|
||||
|
||||
await client.disconnect()
|
||||
108
_code-samples/lending-protocol/js/coverDepositAndWithdraw.js
Normal file
108
_code-samples/lending-protocol/js/coverDepositAndWithdraw.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// IMPORTANT: This example deposits and withdraws first-loss capital from a
|
||||
// preconfigured LoanBroker entry.
|
||||
|
||||
import fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
|
||||
const loanBrokerID = setupData.loanBrokerID
|
||||
const mptID = setupData.mptID
|
||||
|
||||
console.log(`\nLoan broker address: ${loanBroker.address}`)
|
||||
console.log(`LoanBrokerID: ${loanBrokerID}`)
|
||||
console.log(`MPT ID: ${mptID}`)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`)
|
||||
const coverDepositTx = {
|
||||
TransactionType: 'LoanBrokerCoverDeposit',
|
||||
Account: loanBroker.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '2000'
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(coverDepositTx)
|
||||
console.log(JSON.stringify(coverDepositTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`)
|
||||
const depositResponse = await client.submitAndWait(coverDepositTx, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = depositResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to deposit cover:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Cover deposit successful!')
|
||||
|
||||
// Extract cover balance from the transaction result
|
||||
console.log(`\n=== Cover Balance ===\n`)
|
||||
let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
|
||||
)
|
||||
// First-loss capital is stored in the LoanBroker's pseudo-account.
|
||||
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
|
||||
console.log(`Cover balance after deposit: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
|
||||
|
||||
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n`)
|
||||
const coverWithdrawTx = {
|
||||
TransactionType: 'LoanBrokerCoverWithdraw',
|
||||
Account: loanBroker.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '1000'
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(coverWithdrawTx)
|
||||
console.log(JSON.stringify(coverWithdrawTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for withdraw validation ----------------------
|
||||
console.log(`\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n`)
|
||||
const withdrawResponse = await client.submitAndWait(coverWithdrawTx, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
if (withdrawResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = withdrawResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to withdraw cover:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Cover withdraw successful!')
|
||||
|
||||
// Extract updated cover balance from the transaction result
|
||||
console.log(`\n=== Updated Cover Balance ===\n`)
|
||||
loanBrokerNode = withdrawResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'LoanBroker'
|
||||
)
|
||||
console.log(`LoanBroker Pseudo-Account: ${loanBrokerNode.ModifiedNode.FinalFields.Account}`)
|
||||
console.log(`Cover balance after withdraw: ${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable} TSTUSD`)
|
||||
|
||||
await client.disconnect()
|
||||
95
_code-samples/lending-protocol/js/createLoan.js
Normal file
95
_code-samples/lending-protocol/js/createLoan.js
Normal file
@@ -0,0 +1,95 @@
|
||||
// IMPORTANT: This example creates a loan using a preconfigured
|
||||
// loan broker, borrower, and private vault.
|
||||
|
||||
import fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
|
||||
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
|
||||
const loanBrokerID = setupData.loanBrokerID
|
||||
|
||||
console.log(`\nLoan broker address: ${loanBroker.address}`)
|
||||
console.log(`Borrower address: ${borrower.address}`)
|
||||
console.log(`LoanBrokerID: ${loanBrokerID}`)
|
||||
|
||||
// Prepare LoanSet transaction ----------------------
|
||||
// Account and Counterparty accounts can be swapped, but determines signing order.
|
||||
// Account signs first, Counterparty signs second.
|
||||
console.log(`\n=== Preparing LoanSet transaction ===\n`)
|
||||
|
||||
// Suppress unnecessary console warning from autofilling LoanSet.
|
||||
console.warn = () => {}
|
||||
|
||||
const loanSetTx = await client.autofill({
|
||||
TransactionType: 'LoanSet',
|
||||
Account: loanBroker.address,
|
||||
Counterparty: borrower.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: '1000',
|
||||
InterestRate: 500,
|
||||
PaymentTotal: 12,
|
||||
PaymentInterval: 2592000,
|
||||
GracePeriod: 604800,
|
||||
LoanOriginationFee: '100',
|
||||
LoanServiceFee: '10'
|
||||
})
|
||||
|
||||
console.log(JSON.stringify(loanSetTx, null, 2))
|
||||
|
||||
// Loan broker signs first
|
||||
console.log(`\n=== Adding loan broker signature ===\n`)
|
||||
const loanBrokerSigned = loanBroker.sign(loanSetTx)
|
||||
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
|
||||
|
||||
console.log(`TxnSignature: ${loanBrokerSignedTx.TxnSignature}`)
|
||||
console.log(`SigningPubKey: ${loanBrokerSignedTx.SigningPubKey}\n`)
|
||||
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignedTx, null, 2)}`)
|
||||
|
||||
// Borrower signs second
|
||||
console.log(`\n=== Adding borrower signature ===\n`)
|
||||
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
|
||||
|
||||
console.log(`Borrower TxnSignature: ${fullySigned.tx.CounterpartySignature.TxnSignature}`)
|
||||
console.log(`Borrower SigningPubKey: ${fullySigned.tx.CounterpartySignature.SigningPubKey}`)
|
||||
|
||||
// Validate the transaction structure before submitting.
|
||||
xrpl.validate(fullySigned.tx)
|
||||
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(fullySigned.tx, null, 2)}`)
|
||||
|
||||
// Submit and wait for validation ----------------------
|
||||
console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
|
||||
|
||||
const submitResponse = await client.submitAndWait(fullySigned.tx)
|
||||
|
||||
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = submitResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to create loan:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan created successfully!')
|
||||
|
||||
// Extract loan information from the transaction result.
|
||||
console.log(`\n=== Loan Information ===\n`)
|
||||
const loanNode = submitResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'Loan'
|
||||
)
|
||||
console.log(JSON.stringify(loanNode.CreatedNode.NewFields, null, 2))
|
||||
|
||||
await client.disconnect()
|
||||
66
_code-samples/lending-protocol/js/createLoanBroker.js
Normal file
66
_code-samples/lending-protocol/js/createLoanBroker.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// IMPORTANT: This example creates a loan broker using an existing account
|
||||
// that has already created a PRIVATE vault.
|
||||
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
|
||||
// and loanBroker values with your own.
|
||||
|
||||
import fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and VaultID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
|
||||
const vaultID = setupData.vaultID
|
||||
|
||||
console.log(`\nLoan broker/vault owner address: ${loanBroker.address}`)
|
||||
console.log(`Vault ID: ${vaultID}`)
|
||||
|
||||
// Prepare LoanBrokerSet transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanBrokerSet transaction ===\n`)
|
||||
const loanBrokerSetTx = {
|
||||
TransactionType: 'LoanBrokerSet',
|
||||
Account: loanBroker.address,
|
||||
VaultID: vaultID,
|
||||
ManagementFeeRate: 1000
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(loanBrokerSetTx)
|
||||
console.log(JSON.stringify(loanBrokerSetTx, null, 2))
|
||||
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
console.log(`\n=== Submitting LoanBrokerSet transaction ===\n`)
|
||||
const submitResponse = await client.submitAndWait(loanBrokerSetTx, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = submitResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to create loan broker:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan broker created successfully!')
|
||||
|
||||
// Extract loan broker information from the transaction result
|
||||
console.log(`\n=== Loan Broker Information ===\n`)
|
||||
const loanBrokerNode = submitResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
|
||||
)
|
||||
console.log(`LoanBroker ID: ${loanBrokerNode.CreatedNode.LedgerIndex}`)
|
||||
console.log(`LoanBroker Psuedo-Account Address: ${loanBrokerNode.CreatedNode.NewFields.Account}`)
|
||||
|
||||
await client.disconnect()
|
||||
341
_code-samples/lending-protocol/js/lendingSetup.js
Normal file
341
_code-samples/lending-protocol/js/lendingSetup.js
Normal file
@@ -0,0 +1,341 @@
|
||||
// Setup script for lending protocol tutorials
|
||||
|
||||
import xrpl from 'xrpl'
|
||||
import fs from 'fs'
|
||||
|
||||
process.stdout.write('Setting up tutorial: 0/7\r')
|
||||
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Create and fund wallets
|
||||
const [
|
||||
{ wallet: loanBroker },
|
||||
{ wallet: borrower },
|
||||
{ wallet: depositor },
|
||||
{ wallet: credentialIssuer }
|
||||
] = await Promise.all([
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet()
|
||||
])
|
||||
|
||||
process.stdout.write('Setting up tutorial: 1/7\r')
|
||||
|
||||
// Create tickets for parallel transactions
|
||||
const extractTickets = (response) =>
|
||||
response.result.meta.AffectedNodes
|
||||
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
|
||||
.map(node => node.CreatedNode.NewFields.TicketSequence)
|
||||
|
||||
const [ciTicketResponse, lbTicketResponse, brTicketResponse, dpTicketResponse] = await Promise.all([
|
||||
client.submitAndWait({
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: credentialIssuer.address,
|
||||
TicketCount: 4
|
||||
}, { wallet: credentialIssuer, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: loanBroker.address,
|
||||
TicketCount: 4
|
||||
}, { wallet: loanBroker, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: borrower.address,
|
||||
TicketCount: 2
|
||||
}, { wallet: borrower, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: depositor.address,
|
||||
TicketCount: 2
|
||||
}, { wallet: depositor, autofill: true })
|
||||
])
|
||||
|
||||
const ciTickets = extractTickets(ciTicketResponse)
|
||||
const lbTickets = extractTickets(lbTicketResponse)
|
||||
const brTickets = extractTickets(brTicketResponse)
|
||||
const dpTickets = extractTickets(dpTicketResponse)
|
||||
|
||||
process.stdout.write('Setting up tutorial: 2/7\r')
|
||||
|
||||
// Issue MPT with depositor
|
||||
// Set up credentials and domain with credentialIssuer
|
||||
const credentialType = xrpl.convertStringToHex('KYC-Verified')
|
||||
const mptData = {
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
const [mptIssuanceResponse, domainSetResponse] = await Promise.all([
|
||||
client.submitAndWait({
|
||||
TransactionType: 'MPTokenIssuanceCreate',
|
||||
Account: depositor.address,
|
||||
MaximumAmount: '100000000',
|
||||
TransferFee: 0,
|
||||
Flags:
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanClawback |
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTrade,
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata(mptData)
|
||||
}, { wallet: depositor, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'PermissionedDomainSet',
|
||||
Account: credentialIssuer.address,
|
||||
AcceptedCredentials: [
|
||||
{
|
||||
Credential: {
|
||||
Issuer: credentialIssuer.address,
|
||||
CredentialType: credentialType
|
||||
}
|
||||
}
|
||||
],
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[0]
|
||||
}, { wallet: credentialIssuer, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: credentialIssuer.address,
|
||||
Subject: loanBroker.address,
|
||||
CredentialType: credentialType,
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[1]
|
||||
}, { wallet: credentialIssuer, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: credentialIssuer.address,
|
||||
Subject: borrower.address,
|
||||
CredentialType: credentialType,
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[2]
|
||||
}, { wallet: credentialIssuer, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: credentialIssuer.address,
|
||||
Subject: depositor.address,
|
||||
CredentialType: credentialType,
|
||||
Sequence: 0,
|
||||
TicketSequence: ciTickets[3]
|
||||
}, { wallet: credentialIssuer, autofill: true })
|
||||
])
|
||||
|
||||
// Extract MPT issuance ID
|
||||
const mptID = mptIssuanceResponse.result.meta.mpt_issuance_id
|
||||
|
||||
// Extract domain ID
|
||||
const domainID = domainSetResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
process.stdout.write('Setting up tutorial: 3/7\r')
|
||||
|
||||
// Accept credentials and authorize MPT for each account
|
||||
await Promise.all([
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: loanBroker.address,
|
||||
Issuer: credentialIssuer.address,
|
||||
CredentialType: credentialType,
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[0]
|
||||
}, { wallet: loanBroker, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: loanBroker.address,
|
||||
MPTokenIssuanceID: mptID,
|
||||
Sequence: 0,
|
||||
TicketSequence: lbTickets[1]
|
||||
}, { wallet: loanBroker, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: borrower.address,
|
||||
Issuer: credentialIssuer.address,
|
||||
CredentialType: credentialType,
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[0]
|
||||
}, { wallet: borrower, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: borrower.address,
|
||||
MPTokenIssuanceID: mptID,
|
||||
Sequence: 0,
|
||||
TicketSequence: brTickets[1]
|
||||
}, { wallet: borrower, autofill: true }),
|
||||
// Depositor only needs to accept credentials
|
||||
client.submitAndWait({
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: depositor.address,
|
||||
Issuer: credentialIssuer.address,
|
||||
CredentialType: credentialType
|
||||
}, { wallet: depositor, autofill: true })
|
||||
])
|
||||
|
||||
process.stdout.write('Setting up tutorial: 4/7\r')
|
||||
|
||||
// Create private vault and distribute MPT to accounts
|
||||
const [vaultCreateResponse] = await Promise.all([
|
||||
client.submitAndWait({
|
||||
TransactionType: 'VaultCreate',
|
||||
Account: loanBroker.address,
|
||||
Asset: {
|
||||
mpt_issuance_id: mptID
|
||||
},
|
||||
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
|
||||
DomainID: domainID
|
||||
}, { wallet: loanBroker, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'Payment',
|
||||
Account: depositor.address,
|
||||
Destination: loanBroker.address,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '5000'
|
||||
},
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[0]
|
||||
}, { wallet: depositor, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'Payment',
|
||||
Account: depositor.address,
|
||||
Destination: borrower.address,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '2500'
|
||||
},
|
||||
Sequence: 0,
|
||||
TicketSequence: dpTickets[1]
|
||||
}, { wallet: depositor, autofill: true })
|
||||
])
|
||||
|
||||
const vaultID = vaultCreateResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'Vault'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
process.stdout.write('Setting up tutorial: 5/7\r')
|
||||
|
||||
// Create LoanBroker and deposit MPT into vault
|
||||
const [loanBrokerSetResponse] = await Promise.all([
|
||||
client.submitAndWait({
|
||||
TransactionType: 'LoanBrokerSet',
|
||||
Account: loanBroker.address,
|
||||
VaultID: vaultID
|
||||
}, { wallet: loanBroker, autofill: true }),
|
||||
client.submitAndWait({
|
||||
TransactionType: 'VaultDeposit',
|
||||
Account: depositor.address,
|
||||
VaultID: vaultID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: '50000000'
|
||||
}
|
||||
}, { wallet: depositor, autofill: true })
|
||||
])
|
||||
|
||||
const loanBrokerID = loanBrokerSetResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
process.stdout.write('Setting up tutorial: 6/7\r')
|
||||
|
||||
// Create 2 identical loans with complete repayment due in 30 days
|
||||
|
||||
// Suppress unnecessary console warning from autofilling LoanSet.
|
||||
console.warn = () => {}
|
||||
|
||||
// Helper function to create, sign, and submit a LoanSet transaction
|
||||
async function createLoan (ticketSequence) {
|
||||
const loanSetTx = await client.autofill({
|
||||
TransactionType: 'LoanSet',
|
||||
Account: loanBroker.address,
|
||||
Counterparty: borrower.address,
|
||||
LoanBrokerID: loanBrokerID,
|
||||
PrincipalRequested: '1000',
|
||||
InterestRate: 500,
|
||||
PaymentTotal: 1,
|
||||
PaymentInterval: 2592000,
|
||||
LoanOriginationFee: '100',
|
||||
LoanServiceFee: '10',
|
||||
Sequence: 0,
|
||||
TicketSequence: ticketSequence
|
||||
})
|
||||
|
||||
const loanBrokerSigned = loanBroker.sign(loanSetTx)
|
||||
const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob)
|
||||
|
||||
const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx)
|
||||
const submitResponse = await client.submitAndWait(fullySigned.tx)
|
||||
|
||||
return submitResponse
|
||||
}
|
||||
|
||||
const [submitResponse1, submitResponse2] = await Promise.all([
|
||||
createLoan(lbTickets[2]),
|
||||
createLoan(lbTickets[3])
|
||||
])
|
||||
|
||||
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'Loan'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
const loanID2 = submitResponse2.result.meta.AffectedNodes.find(node =>
|
||||
node.CreatedNode?.LedgerEntryType === 'Loan'
|
||||
).CreatedNode.LedgerIndex
|
||||
|
||||
process.stdout.write('Setting up tutorial: 7/7\r')
|
||||
|
||||
// Write setup data to JSON file
|
||||
const setupData = {
|
||||
description: 'This file is auto-generated by lendingSetup.js. It stores XRPL account info for use in lending protocol tutorials.',
|
||||
loanBroker: {
|
||||
address: loanBroker.address,
|
||||
seed: loanBroker.seed
|
||||
},
|
||||
borrower: {
|
||||
address: borrower.address,
|
||||
seed: borrower.seed
|
||||
},
|
||||
depositor: {
|
||||
address: depositor.address,
|
||||
seed: depositor.seed
|
||||
},
|
||||
credentialIssuer: {
|
||||
address: credentialIssuer.address,
|
||||
seed: credentialIssuer.seed
|
||||
},
|
||||
domainID,
|
||||
mptID,
|
||||
vaultID,
|
||||
loanBrokerID,
|
||||
loanID1,
|
||||
loanID2
|
||||
}
|
||||
|
||||
fs.writeFileSync('lendingSetup.json', JSON.stringify(setupData, null, 2))
|
||||
|
||||
process.stdout.write('Setting up tutorial: Complete!\n')
|
||||
|
||||
await client.disconnect()
|
||||
144
_code-samples/lending-protocol/js/loanManage.js
Normal file
144
_code-samples/lending-protocol/js/loanManage.js
Normal file
@@ -0,0 +1,144 @@
|
||||
// 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 fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed)
|
||||
const loanID = setupData.loanID1
|
||||
|
||||
console.log(`\nLoan broker address: ${loanBroker.address}`)
|
||||
console.log(`LoanID: ${loanID}`)
|
||||
|
||||
// Check loan status before impairment ----------------------
|
||||
console.log(`\n=== Loan Status ===\n`)
|
||||
const loanStatus = await client.request({
|
||||
command: 'ledger_entry',
|
||||
index: loanID,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
|
||||
console.log(`Total Amount Owed: ${loanStatus.result.node.TotalValueOutstanding} TSTUSD.`)
|
||||
// Convert Ripple Epoch timestamp to local date and time
|
||||
let nextPaymentDueDate = loanStatus.result.node.NextPaymentDueDate
|
||||
let paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
|
||||
console.log(`Payment Due Date: ${paymentDue.toLocaleString()}`)
|
||||
|
||||
// Prepare LoanManage transaction to impair the loan ----------------------
|
||||
console.log(`\n=== Preparing LoanManage transaction to impair loan ===\n`)
|
||||
const loanManageImpair = {
|
||||
TransactionType: 'LoanManage',
|
||||
Account: loanBroker.address,
|
||||
LoanID: loanID,
|
||||
Flags: xrpl.LoanManageFlags.tfLoanImpair
|
||||
}
|
||||
|
||||
// Validate the impairment transaction before submitting
|
||||
xrpl.validate(loanManageImpair)
|
||||
console.log(JSON.stringify(loanManageImpair, null, 2))
|
||||
|
||||
// Sign, submit, and wait for impairment validation ----------------------
|
||||
console.log(`\n=== Submitting LoanManage impairment transaction ===\n`)
|
||||
const impairResponse = await client.submitAndWait(loanManageImpair, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (impairResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = impairResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to impair loan:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan impaired successfully!')
|
||||
|
||||
// Extract loan impairment info from transaction results ----------------------
|
||||
let loanNode = impairResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'Loan'
|
||||
)
|
||||
|
||||
// Check grace period and next payment due date
|
||||
const gracePeriod = loanNode.ModifiedNode.FinalFields.GracePeriod
|
||||
nextPaymentDueDate = loanNode.ModifiedNode.FinalFields.NextPaymentDueDate
|
||||
const defaultTime = nextPaymentDueDate + gracePeriod
|
||||
paymentDue = new Date((nextPaymentDueDate + 946684800) * 1000)
|
||||
|
||||
console.log(`New Payment Due Date: ${paymentDue.toLocaleString()}`)
|
||||
console.log(`Grace Period: ${gracePeriod} seconds`)
|
||||
|
||||
// Convert current time to Ripple Epoch timestamp
|
||||
const currentTime = Math.floor(Date.now() / 1000) - 946684800
|
||||
let secondsUntilDefault = defaultTime - currentTime
|
||||
|
||||
// Countdown until loan can be defaulted ----------------------
|
||||
console.log(`\n=== Countdown until loan can be defaulted ===\n`)
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const countdown = setInterval(() => {
|
||||
if (secondsUntilDefault <= 0) {
|
||||
clearInterval(countdown)
|
||||
process.stdout.write('\rGrace period expired. Loan can now be defaulted.\n')
|
||||
resolve()
|
||||
} else {
|
||||
process.stdout.write(`\r${secondsUntilDefault} seconds...`)
|
||||
secondsUntilDefault--
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
// Prepare LoanManage transaction to default the loan ----------------------
|
||||
console.log(`\n=== Preparing LoanManage transaction to default loan ===\n`)
|
||||
const loanManageDefault = {
|
||||
TransactionType: 'LoanManage',
|
||||
Account: loanBroker.address,
|
||||
LoanID: loanID,
|
||||
Flags: xrpl.LoanManageFlags.tfLoanDefault
|
||||
}
|
||||
|
||||
// Validate the default transaction before submitting
|
||||
xrpl.validate(loanManageDefault)
|
||||
console.log(JSON.stringify(loanManageDefault, null, 2))
|
||||
|
||||
// Sign, submit, and wait for default validation ----------------------
|
||||
console.log(`\n=== Submitting LoanManage default transaction ===\n`)
|
||||
const defaultResponse = await client.submitAndWait(loanManageDefault, {
|
||||
wallet: loanBroker,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (defaultResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = defaultResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to default loan:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan defaulted successfully!')
|
||||
|
||||
// Verify loan default status from transaction results ----------------------
|
||||
console.log(`\n=== Checking final loan status ===\n`)
|
||||
loanNode = defaultResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'Loan'
|
||||
)
|
||||
const loanFlags = loanNode.ModifiedNode.FinalFields.Flags
|
||||
console.log(`Final loan flags (parsed): ${JSON.stringify(xrpl.parseTransactionFlags({
|
||||
TransactionType: 'LoanManage',
|
||||
Flags: loanFlags
|
||||
}))}`)
|
||||
|
||||
await client.disconnect()
|
||||
134
_code-samples/lending-protocol/js/loanPay.js
Normal file
134
_code-samples/lending-protocol/js/loanPay.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// IMPORTANT: This example pays off an existing loan and then deletes it.
|
||||
|
||||
import fs from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import xrpl from 'xrpl'
|
||||
|
||||
// Connect to the network ----------------------
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// This step checks for the necessary setup data to run the lending protocol tutorials.
|
||||
// If missing, lendingSetup.js will generate the data.
|
||||
if (!fs.existsSync('lendingSetup.json')) {
|
||||
console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`)
|
||||
execSync('node lendingSetup.js', { stdio: 'inherit' })
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID.
|
||||
const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8'))
|
||||
|
||||
// You can replace these values with your own
|
||||
const borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
|
||||
const loanID = setupData.loanID2
|
||||
const mptID = setupData.mptID
|
||||
|
||||
console.log(`\nBorrower address: ${borrower.address}`)
|
||||
console.log(`LoanID: ${loanID}`)
|
||||
console.log(`MPT ID: ${mptID}`)
|
||||
|
||||
// Check initial loan status ----------------------
|
||||
console.log(`\n=== Loan Status ===\n`)
|
||||
const loanStatus = await client.request({
|
||||
command: 'ledger_entry',
|
||||
index: loanID,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
|
||||
const totalValueOutstanding = loanStatus.result.node.TotalValueOutstanding
|
||||
const loanServiceFee = loanStatus.result.node.LoanServiceFee
|
||||
const totalPayment = (BigInt(totalValueOutstanding) + BigInt(loanServiceFee)).toString()
|
||||
|
||||
console.log(`Amount Owed: ${totalValueOutstanding} TSTUSD`)
|
||||
console.log(`Loan Service Fee: ${loanServiceFee} TSTUSD`)
|
||||
console.log(`Total Payment Due (including fees): ${totalPayment} TSTUSD`)
|
||||
|
||||
// Prepare LoanPay transaction ----------------------
|
||||
console.log(`\n=== Preparing LoanPay transaction ===\n`)
|
||||
|
||||
const loanPayTx = {
|
||||
TransactionType: 'LoanPay',
|
||||
Account: borrower.address,
|
||||
LoanID: loanID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID,
|
||||
value: totalPayment
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(loanPayTx)
|
||||
console.log(JSON.stringify(loanPayTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for payment validation ----------------------
|
||||
console.log(`\n=== Submitting LoanPay transaction ===\n`)
|
||||
const payResponse = await client.submitAndWait(loanPayTx, {
|
||||
wallet: borrower,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (payResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = payResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to pay loan:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan paid successfully!')
|
||||
|
||||
// Extract updated loan info from transaction results ----------------------
|
||||
console.log(`\n=== Loan Status After Payment ===\n`)
|
||||
const loanNode = payResponse.result.meta.AffectedNodes.find(node =>
|
||||
node.ModifiedNode?.LedgerEntryType === 'Loan'
|
||||
)
|
||||
|
||||
const finalBalance = loanNode.ModifiedNode.FinalFields.TotalValueOutstanding
|
||||
? `${loanNode.ModifiedNode.FinalFields.TotalValueOutstanding} TSTUSD`
|
||||
: 'Loan fully paid off!'
|
||||
console.log(`Outstanding Loan Balance: ${finalBalance}`)
|
||||
|
||||
// Prepare LoanDelete transaction ----------------------
|
||||
// Either the loan broker or borrower can submit this transaction.
|
||||
console.log(`\n=== Preparing LoanDelete transaction ===\n`)
|
||||
const loanDeleteTx = {
|
||||
TransactionType: 'LoanDelete',
|
||||
Account: borrower.address,
|
||||
LoanID: loanID
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(loanDeleteTx)
|
||||
console.log(JSON.stringify(loanDeleteTx, null, 2))
|
||||
|
||||
// Sign, submit, and wait for deletion validation ----------------------
|
||||
console.log(`\n=== Submitting LoanDelete transaction ===\n`)
|
||||
const deleteResponse = await client.submitAndWait(loanDeleteTx, {
|
||||
wallet: borrower,
|
||||
autofill: true
|
||||
})
|
||||
|
||||
if (deleteResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
const resultCode = deleteResponse.result.meta.TransactionResult
|
||||
console.error('Error: Unable to delete loan:', resultCode)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log('Loan deleted successfully!')
|
||||
|
||||
// Verify loan deletion ----------------------
|
||||
console.log(`\n=== Verifying Loan Deletion ===\n`)
|
||||
try {
|
||||
await client.request({
|
||||
command: 'ledger_entry',
|
||||
index: loanID,
|
||||
ledger_index: 'validated'
|
||||
})
|
||||
console.log('Warning: Loan still exists in the ledger.')
|
||||
} catch (error) {
|
||||
if (error.data.error === 'entryNotFound') {
|
||||
console.log('Loan has been successfully removed from the XRP Ledger!')
|
||||
} else {
|
||||
console.error('Error checking loan status:', error)
|
||||
}
|
||||
}
|
||||
|
||||
await client.disconnect()
|
||||
8
_code-samples/lending-protocol/js/package.json
Normal file
8
_code-samples/lending-protocol/js/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "lending-protocol-examples",
|
||||
"description": "Example code for creating and managing loans.",
|
||||
"dependencies": {
|
||||
"xrpl": "^4.6.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
403
_code-samples/lending-protocol/py/README.md
Normal file
403
_code-samples/lending-protocol/py/README.md
Normal 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!
|
||||
```
|
||||
124
_code-samples/lending-protocol/py/cover_clawback.py
Normal file
124
_code-samples/lending-protocol/py/cover_clawback.py
Normal 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")
|
||||
@@ -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")
|
||||
89
_code-samples/lending-protocol/py/create_loan.py
Normal file
89
_code-samples/lending-protocol/py/create_loan.py
Normal 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))
|
||||
64
_code-samples/lending-protocol/py/create_loan_broker.py
Normal file
64
_code-samples/lending-protocol/py/create_loan_broker.py
Normal 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']}")
|
||||
416
_code-samples/lending-protocol/py/lending_setup.py
Normal file
416
_code-samples/lending-protocol/py/lending_setup.py
Normal file
@@ -0,0 +1,416 @@
|
||||
# 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 (
|
||||
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/7", 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/7", end="\r")
|
||||
|
||||
# Create tickets for parallel transactions
|
||||
def extract_tickets(response):
|
||||
return [
|
||||
node["CreatedNode"]["NewFields"]["TicketSequence"]
|
||||
for node in response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
|
||||
]
|
||||
|
||||
ci_ticket_response, lb_ticket_response, br_ticket_response, dp_ticket_response = (
|
||||
await asyncio.gather(
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=credential_issuer.address,
|
||||
ticket_count=4,
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=loan_broker.address,
|
||||
ticket_count=4,
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=borrower.address,
|
||||
ticket_count=2,
|
||||
),
|
||||
client,
|
||||
borrower,
|
||||
),
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=depositor.address,
|
||||
ticket_count=2,
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
ci_tickets = extract_tickets(ci_ticket_response)
|
||||
lb_tickets = extract_tickets(lb_ticket_response)
|
||||
br_tickets = extract_tickets(br_ticket_response)
|
||||
dp_tickets = extract_tickets(dp_ticket_response)
|
||||
|
||||
print("Setting up tutorial: 2/7", end="\r")
|
||||
|
||||
# Issue MPT with depositor
|
||||
# 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",
|
||||
},
|
||||
}
|
||||
|
||||
mpt_issuance_response, domain_set_response, *_ = await asyncio.gather(
|
||||
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(
|
||||
PermissionedDomainSet(
|
||||
account=credential_issuer.address,
|
||||
accepted_credentials=[
|
||||
Credential(
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
],
|
||||
sequence=0,
|
||||
ticket_sequence=ci_tickets[0],
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=loan_broker.address,
|
||||
credential_type=credential_type,
|
||||
sequence=0,
|
||||
ticket_sequence=ci_tickets[1],
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=borrower.address,
|
||||
credential_type=credential_type,
|
||||
sequence=0,
|
||||
ticket_sequence=ci_tickets[2],
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialCreate(
|
||||
account=credential_issuer.address,
|
||||
subject=depositor.address,
|
||||
credential_type=credential_type,
|
||||
sequence=0,
|
||||
ticket_sequence=ci_tickets[3],
|
||||
),
|
||||
client,
|
||||
credential_issuer,
|
||||
),
|
||||
)
|
||||
|
||||
# Extract MPT issuance ID
|
||||
mpt_id = mpt_issuance_response.result["meta"]["mpt_issuance_id"]
|
||||
|
||||
# Extract domain ID
|
||||
domain_id = next(
|
||||
node["CreatedNode"]["LedgerIndex"]
|
||||
for node in domain_set_response.result["meta"]["AffectedNodes"]
|
||||
if node.get("CreatedNode", {}).get("LedgerEntryType") == "PermissionedDomain"
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 3/7", end="\r")
|
||||
|
||||
# Accept credentials and authorize MPT for each account
|
||||
await asyncio.gather(
|
||||
submit_and_wait(
|
||||
CredentialAccept(
|
||||
account=loan_broker.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
sequence=0,
|
||||
ticket_sequence=lb_tickets[0],
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
MPTokenAuthorize(
|
||||
account=loan_broker.address,
|
||||
mptoken_issuance_id=mpt_id,
|
||||
sequence=0,
|
||||
ticket_sequence=lb_tickets[1],
|
||||
),
|
||||
client,
|
||||
loan_broker,
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialAccept(
|
||||
account=borrower.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
sequence=0,
|
||||
ticket_sequence=br_tickets[0],
|
||||
),
|
||||
client,
|
||||
borrower,
|
||||
),
|
||||
submit_and_wait(
|
||||
MPTokenAuthorize(
|
||||
account=borrower.address,
|
||||
mptoken_issuance_id=mpt_id,
|
||||
sequence=0,
|
||||
ticket_sequence=br_tickets[1],
|
||||
),
|
||||
client,
|
||||
borrower,
|
||||
),
|
||||
# Depositor only needs to accept credentials
|
||||
submit_and_wait(
|
||||
CredentialAccept(
|
||||
account=depositor.address,
|
||||
issuer=credential_issuer.address,
|
||||
credential_type=credential_type,
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
)
|
||||
|
||||
print("Setting up tutorial: 4/7", 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(
|
||||
Payment(
|
||||
account=depositor.address,
|
||||
destination=loan_broker.address,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
|
||||
sequence=0,
|
||||
ticket_sequence=dp_tickets[0],
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
),
|
||||
submit_and_wait(
|
||||
Payment(
|
||||
account=depositor.address,
|
||||
destination=borrower.address,
|
||||
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
|
||||
sequence=0,
|
||||
ticket_sequence=dp_tickets[1],
|
||||
),
|
||||
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: 5/7", 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: 6/7", 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(lb_tickets[2]),
|
||||
create_loan(lb_tickets[3]),
|
||||
)
|
||||
|
||||
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: 7/7", 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())
|
||||
130
_code-samples/lending-protocol/py/loan_manage.py
Normal file
130
_code-samples/lending-protocol/py/loan_manage.py
Normal 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}")
|
||||
117
_code-samples/lending-protocol/py/loan_pay.py
Normal file
117
_code-samples/lending-protocol/py/loan_pay.py
Normal 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')}")
|
||||
1
_code-samples/lending-protocol/py/requirements.txt
Normal file
1
_code-samples/lending-protocol/py/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
xrpl-py>=4.5.0
|
||||
3
_code-samples/vaults/README.md
Normal file
3
_code-samples/vaults/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Single Asset Vault Examples
|
||||
|
||||
Shows how to create, deposit into, and withdraw from single asset vaults on the XRP Ledger.
|
||||
187
_code-samples/vaults/js/README.md
Normal file
187
_code-samples/vaults/js/README.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Single Asset Vault Examples (JavaScript)
|
||||
|
||||
This directory contains JavaScript 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
|
||||
npm i
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Vault
|
||||
|
||||
```sh
|
||||
node createVault.js
|
||||
```
|
||||
|
||||
The script should output the VaultCreate transaction, vault ID, and complete vault information:
|
||||
|
||||
```sh
|
||||
Vault owner address: rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms
|
||||
MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
|
||||
Permissioned domain ID: 3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1
|
||||
|
||||
|
||||
=== VaultCreate transaction ===
|
||||
{
|
||||
"TransactionType": "VaultCreate",
|
||||
"Account": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
|
||||
},
|
||||
"Flags": 65536,
|
||||
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
|
||||
"Data": "50726976617465207661756C74",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"AssetsMaximum": "0",
|
||||
"WithdrawalPolicy": 1
|
||||
}
|
||||
|
||||
=== Submitting VaultCreate transaction... ===
|
||||
Vault created successfully!
|
||||
|
||||
Vault ID: 9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29
|
||||
Vault pseudo-account address: rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj
|
||||
Share MPT issuance ID: 000000012DF200D67FF9DA7686FF8B6F32097337D7765211
|
||||
|
||||
=== Getting vault_info... ===
|
||||
{
|
||||
"api_version": 2,
|
||||
"id": 12,
|
||||
"result": {
|
||||
"ledger_hash": "73B53C0608A9C87C2B97314F0BAD109F236C4A95FB53FE4E8CEAEFE826A1E7AB",
|
||||
"ledger_index": 597229,
|
||||
"validated": true,
|
||||
"vault": {
|
||||
"Account": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
|
||||
},
|
||||
"Data": "50726976617465207661756C74",
|
||||
"Flags": 65536,
|
||||
"LedgerEntryType": "Vault",
|
||||
"Owner": "rLXZNDSS7gWvQZKunRUFiaViSiHo1yd4Ms",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
|
||||
"PreviousTxnLgrSeq": 597229,
|
||||
"Sequence": 597228,
|
||||
"ShareMPTID": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211",
|
||||
"WithdrawalPolicy": 1,
|
||||
"index": "9D25282C143F0F7F71F0E6FC7ABB3BD6FB30B7DCF04DF4A1E31C701B1B332D29",
|
||||
"shares": {
|
||||
"DomainID": "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1",
|
||||
"Flags": 60,
|
||||
"Issuer": "rnBAKKEBBTqswakdeJJkZtBs9SRgpMkThj",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C742E222C2269223A226578616D706C652E636F6D2F61737365742D69636F6E2E706E67222C22696E223A22417373657420497373756572204E616D65222C226E223A225661756C7420736861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "8B64609225F802258250824B2C6C0A8B752AB8CBB6FAF64D433DC2F35C09E131",
|
||||
"PreviousTxnLgrSeq": 597229,
|
||||
"Sequence": 1,
|
||||
"index": "4C3CC0AF1FE27EBE364F02AFF889D73D1F6F7CB5ED6126D1CD605E8952E18302",
|
||||
"mpt_issuance_id": "000000012DF200D67FF9DA7686FF8B6F32097337D7765211"
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit into a Vault
|
||||
|
||||
```sh
|
||||
node deposit.js
|
||||
```
|
||||
|
||||
The script should output the vault state before and after the deposit, along with the depositor's share balance:
|
||||
|
||||
```sh
|
||||
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
|
||||
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
|
||||
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
|
||||
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
|
||||
|
||||
=== Getting initial vault state... ===
|
||||
- Total vault value: 1
|
||||
- Available assets: 1
|
||||
|
||||
=== Checking depositor's balance... ===
|
||||
Balance: 9937
|
||||
|
||||
=== VaultDeposit transaction ===
|
||||
{
|
||||
"TransactionType": "VaultDeposit",
|
||||
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
|
||||
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
|
||||
"value": "1"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting VaultDeposit transaction... ===
|
||||
Deposit successful!
|
||||
|
||||
=== Vault state after deposit ===
|
||||
- Total vault value: 2
|
||||
- Available assets: 2
|
||||
|
||||
=== Depositor's share balance ==
|
||||
Shares held: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Withdraw from a Vault
|
||||
|
||||
```sh
|
||||
node withdraw.js
|
||||
```
|
||||
|
||||
The script should output the vault state before and after the withdrawal, along with updated share and asset balances:
|
||||
|
||||
```sh
|
||||
Depositor address: rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU
|
||||
Vault ID: 6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF
|
||||
Asset MPT issuance ID: 0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4
|
||||
Vault share MPT issuance ID: 0000000152E7CD364F869E832EDB806C4A7AD8B3D0C151C5
|
||||
|
||||
=== Getting initial vault state... ===
|
||||
Initial vault state:
|
||||
Assets Total: 2
|
||||
Assets Available: 2
|
||||
|
||||
=== Checking depositor's share balance... ===
|
||||
Shares held: 2
|
||||
|
||||
=== Preparing VaultWithdraw transaction ===
|
||||
{
|
||||
"TransactionType": "VaultWithdraw",
|
||||
"Account": "rnEmvWahVbNXzs8zGjhEfkBwo41Zn5wDDU",
|
||||
"VaultID": "6AC4EC2D775C6275D314996D6ECDD16DCB9382A29FDB769951C42192FCED76EF",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4",
|
||||
"value": "1"
|
||||
}
|
||||
}
|
||||
|
||||
=== Submitting VaultWithdraw transaction... ===
|
||||
Withdrawal successful!
|
||||
|
||||
=== Vault state after withdrawal ===
|
||||
Assets Total: 1
|
||||
Assets Available: 1
|
||||
|
||||
=== Depositor's share balance ==
|
||||
Shares held: 1
|
||||
|
||||
=== Depositor's asset balance ==
|
||||
Balance: 9937
|
||||
```
|
||||
111
_code-samples/vaults/js/createVault.js
Normal file
111
_code-samples/vaults/js/createVault.js
Normal file
@@ -0,0 +1,111 @@
|
||||
import xrpl from "xrpl"
|
||||
import { execSync } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
// Auto-run setup if needed
|
||||
if (!fs.existsSync("vaultSetup.json")) {
|
||||
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
|
||||
execSync("node vaultSetup.js", { stdio: "inherit" })
|
||||
}
|
||||
|
||||
// Load setup data
|
||||
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
|
||||
|
||||
// Connect to the network
|
||||
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
|
||||
await client.connect()
|
||||
|
||||
// Create and fund vault owner account
|
||||
const { wallet: vaultOwner } = await client.fundWallet()
|
||||
|
||||
// You can replace these values with your own
|
||||
const mptIssuanceId = setupData.mptIssuanceId
|
||||
const domainId = setupData.domainId
|
||||
|
||||
console.log(`Vault owner address: ${vaultOwner.address}`)
|
||||
console.log(`MPT issuance ID: ${mptIssuanceId}`)
|
||||
console.log(`Permissioned domain ID: ${domainId}\n`)
|
||||
|
||||
// Prepare VaultCreate transaction ----------------------
|
||||
console.log(`\n=== VaultCreate transaction ===`)
|
||||
const vaultCreateTx = {
|
||||
TransactionType: "VaultCreate",
|
||||
Account: vaultOwner.address,
|
||||
Asset: { mpt_issuance_id: mptIssuanceId },
|
||||
Flags: xrpl.VaultCreateFlags.tfVaultPrivate, // Omit tfVaultPrivate flag for public vaults
|
||||
// To make vault shares non-transferable add the tfVaultShareNonTransferable flag:
|
||||
// Flags: xrpl.VaultCreateFlags.tfVaultPrivate | xrpl.VaultCreateFlags.tfVaultShareNonTransferable
|
||||
DomainID: domainId, // Omit for public vaults
|
||||
// Convert Vault data to a string (without excess whitespace), then string to hex.
|
||||
Data: xrpl.convertStringToHex(JSON.stringify(
|
||||
{ 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
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
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",
|
||||
},
|
||||
}),
|
||||
AssetsMaximum: "0", // No cap
|
||||
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe,
|
||||
};
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(vaultCreateTx)
|
||||
console.log(JSON.stringify(vaultCreateTx, null, 2))
|
||||
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
console.log("\n=== Submitting VaultCreate transaction... ===")
|
||||
const submit_response = await client.submitAndWait(vaultCreateTx, {
|
||||
wallet: vaultOwner,
|
||||
autofill: true,
|
||||
})
|
||||
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
|
||||
const result_code = submit_response.result.meta.TransactionResult;
|
||||
console.error("Error: Unable to create vault:", result_code)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log("Vault created successfully!")
|
||||
|
||||
// Extract vault information from the transaction result
|
||||
const affectedNodes = submit_response.result.meta.AffectedNodes || []
|
||||
const vaultNode = affectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === "Vault"
|
||||
)
|
||||
if (vaultNode) {
|
||||
console.log(`\nVault ID: ${vaultNode.CreatedNode.LedgerIndex}`)
|
||||
console.log(`Vault pseudo-account address: ${vaultNode.CreatedNode.NewFields.Account}`)
|
||||
console.log(`Share MPT issuance ID: ${vaultNode.CreatedNode.NewFields.ShareMPTID}`)
|
||||
}
|
||||
|
||||
// Call vault_info method to retrieve the vault's information
|
||||
console.log("\n=== Getting vault_info... ===")
|
||||
const vaultID = vaultNode.CreatedNode.LedgerIndex
|
||||
const vault_info_response = await client.request({
|
||||
command: "vault_info",
|
||||
vault_id: vaultID,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
console.log(JSON.stringify(vault_info_response, null, 2))
|
||||
|
||||
await client.disconnect()
|
||||
147
_code-samples/vaults/js/deposit.js
Normal file
147
_code-samples/vaults/js/deposit.js
Normal file
@@ -0,0 +1,147 @@
|
||||
// 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 vaultID and shareMPTIssuanceId
|
||||
// values with your own.
|
||||
|
||||
import xrpl from "xrpl"
|
||||
import { execSync } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
// Auto-run setup if needed
|
||||
if (!fs.existsSync("vaultSetup.json")) {
|
||||
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
|
||||
execSync("node vaultSetup.js", { stdio: "inherit" })
|
||||
}
|
||||
|
||||
// Load setup data
|
||||
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
|
||||
|
||||
// Connect to the network
|
||||
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
|
||||
await client.connect()
|
||||
|
||||
// You can replace these values with your own
|
||||
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
|
||||
const vaultID = setupData.vaultID
|
||||
const assetMPTIssuanceId = setupData.mptIssuanceId
|
||||
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
|
||||
|
||||
console.log(`Depositor address: ${depositor.address}`)
|
||||
console.log(`Vault ID: ${vaultID}`)
|
||||
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
|
||||
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
|
||||
|
||||
const depositAmount = 1
|
||||
|
||||
// Get initial vault state ----------------------
|
||||
console.log("\n=== Getting initial vault state... ===")
|
||||
const initialVaultInfo = await client.request({
|
||||
command: "vault_info",
|
||||
vault_id: vaultID,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
|
||||
console.log(` - Total vault value: ${initialVaultInfo.result.vault.AssetsTotal}`)
|
||||
console.log(` - Available assets: ${initialVaultInfo.result.vault.AssetsAvailable}`)
|
||||
|
||||
// Check depositor's asset balance ----------------------
|
||||
console.log("\n=== Checking depositor's balance... ===")
|
||||
try {
|
||||
// Use ledger_entry to get specific MPT issuance balance
|
||||
const ledgerEntryResult = await client.request({
|
||||
command: "ledger_entry",
|
||||
mptoken: {
|
||||
mpt_issuance_id: assetMPTIssuanceId,
|
||||
account: depositor.address
|
||||
},
|
||||
ledger_index: "validated"
|
||||
})
|
||||
|
||||
const balance = ledgerEntryResult.result.node?.MPTAmount
|
||||
console.log(`Balance: ${balance}`)
|
||||
|
||||
// Check if balance is sufficient
|
||||
if (balance < depositAmount) {
|
||||
console.error(`Error: Insufficient balance! Have ${balance}, need ${depositAmount}`)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.data?.error === 'entryNotFound') {
|
||||
console.error(`Error: The depositor doesn't hold any assets with ID: ${assetMPTIssuanceId}`)
|
||||
} else {
|
||||
console.error(`Error checking MPT: ${error}`)
|
||||
}
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Prepare VaultDeposit transaction ----------------------
|
||||
console.log(`\n=== VaultDeposit transaction ===`)
|
||||
const vaultDepositTx = {
|
||||
TransactionType: "VaultDeposit",
|
||||
Account: depositor.address,
|
||||
VaultID: vaultID,
|
||||
Amount: {
|
||||
mpt_issuance_id: assetMPTIssuanceId,
|
||||
value: depositAmount.toString()
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(vaultDepositTx)
|
||||
console.log(JSON.stringify(vaultDepositTx, null, 2))
|
||||
|
||||
// Submit VaultDeposit transaction ----------------------
|
||||
console.log("\n=== Submitting VaultDeposit transaction... ===")
|
||||
const depositResult = await client.submitAndWait(vaultDepositTx, {
|
||||
wallet: depositor,
|
||||
autofill: true,
|
||||
})
|
||||
if (depositResult.result.meta.TransactionResult !== "tesSUCCESS") {
|
||||
const result_code = depositResult.result.meta.TransactionResult
|
||||
console.error("Error: Unable to deposit:", result_code)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log("Deposit successful!")
|
||||
|
||||
// Extract vault state from transaction metadata ----------------------
|
||||
console.log("\n=== Vault state after deposit ===")
|
||||
const affectedNodes = depositResult.result.meta.AffectedNodes
|
||||
const vaultNode = affectedNodes.find(
|
||||
(node) => {
|
||||
return (
|
||||
node.ModifiedNode &&
|
||||
node.ModifiedNode.LedgerEntryType === "Vault" &&
|
||||
node.ModifiedNode.LedgerIndex === vaultID
|
||||
)
|
||||
}
|
||||
)
|
||||
if (vaultNode) {
|
||||
const vaultFields = vaultNode.ModifiedNode.FinalFields
|
||||
console.log(` - Total vault value: ${vaultFields.AssetsTotal}`)
|
||||
console.log(` - Available assets: ${vaultFields.AssetsAvailable}`)
|
||||
}
|
||||
|
||||
// Get the depositor's share balance ----------------------
|
||||
console.log("\n=== Depositor's share balance ==")
|
||||
const depositorShareNode = affectedNodes.find((node) => {
|
||||
const shareNode = node.ModifiedNode || node.CreatedNode
|
||||
const fields = shareNode?.FinalFields || shareNode?.NewFields
|
||||
return (
|
||||
shareNode &&
|
||||
shareNode.LedgerEntryType === "MPToken" &&
|
||||
fields?.Account === depositor.address &&
|
||||
fields?.MPTokenIssuanceID === shareMPTIssuanceId
|
||||
)
|
||||
})
|
||||
if (depositorShareNode) {
|
||||
const shareNode = depositorShareNode.ModifiedNode || depositorShareNode.CreatedNode
|
||||
const shareFields = shareNode.FinalFields || shareNode.NewFields
|
||||
console.log(`Shares held: ${shareFields.MPTAmount}`)
|
||||
}
|
||||
|
||||
await client.disconnect()
|
||||
|
||||
8
_code-samples/vaults/js/package.json
Normal file
8
_code-samples/vaults/js/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "vault-examples",
|
||||
"description": "Example code for creating and managing vaults",
|
||||
"dependencies": {
|
||||
"xrpl": "^4.5.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
291
_code-samples/vaults/js/vaultSetup.js
Normal file
291
_code-samples/vaults/js/vaultSetup.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import xrpl from 'xrpl'
|
||||
import fs from 'fs'
|
||||
|
||||
// Helper function to extract ticket sequences from a TicketCreate transaction result
|
||||
function getTicketSequences(ticketCreateResult) {
|
||||
return ticketCreateResult.result.meta.AffectedNodes
|
||||
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
|
||||
.map(node => node.CreatedNode.NewFields.TicketSequence)
|
||||
}
|
||||
|
||||
// Setup script for vault tutorials
|
||||
|
||||
process.stdout.write('Setting up tutorial: 0/6\r')
|
||||
|
||||
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
|
||||
await client.connect()
|
||||
|
||||
// Create and fund all wallets
|
||||
const [
|
||||
{ wallet: mptIssuer },
|
||||
{ wallet: domainOwner },
|
||||
{ wallet: depositor },
|
||||
{ wallet: vaultOwner }
|
||||
] = await Promise.all([
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet(),
|
||||
client.fundWallet()
|
||||
])
|
||||
|
||||
// Step 1: Create tickets for domain owner and depositor to submit transactions concurrently
|
||||
process.stdout.write('Setting up tutorial: 1/6\r')
|
||||
|
||||
const credType = 'VaultAccess'
|
||||
const [domainOwnerTicketCreateResult, depositorTicketCreateResult] = await Promise.all([
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: domainOwner.address,
|
||||
TicketCount: 2
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true }
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'TicketCreate',
|
||||
Account: depositor.address,
|
||||
TicketCount: 2
|
||||
},
|
||||
{ wallet: depositor, autofill: true }
|
||||
)
|
||||
])
|
||||
|
||||
// Get the ticket sequences from transaction results
|
||||
const domainOwnerTicketSequences = getTicketSequences(domainOwnerTicketCreateResult)
|
||||
const depositorTicketSequences = getTicketSequences(depositorTicketCreateResult)
|
||||
|
||||
// Step 2: Create MPT issuance, permissioned domain, and credentials in parallel
|
||||
process.stdout.write('Setting up tutorial: 2/6\r')
|
||||
|
||||
const [mptCreateResult, domainSetResult] = await Promise.all([
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: "MPTokenIssuanceCreate",
|
||||
Account: mptIssuer.address,
|
||||
Flags:
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
|
||||
xrpl.MPTokenIssuanceCreateFlags.tfMPTCanLock,
|
||||
AssetScale: 2,
|
||||
TransferFee: 0,
|
||||
MaximumAmount: "1000000000000",
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: "USTST",
|
||||
name: "USTST Stablecoin",
|
||||
desc: "A test stablecoin token",
|
||||
icon: "example.org/ustst-icon.png",
|
||||
asset_class: "rwa",
|
||||
asset_subclass: "stablecoin",
|
||||
issuer_name: "Test Stablecoin Inc",
|
||||
uris: [
|
||||
{
|
||||
uri: "example.org/ustst",
|
||||
category: "website",
|
||||
title: "USTST Official Website",
|
||||
},
|
||||
{
|
||||
uri: "example.org/ustst/reserves",
|
||||
category: "attestation",
|
||||
title: "Reserve Attestation Reports",
|
||||
},
|
||||
{
|
||||
uri: "example.org/ustst/docs",
|
||||
category: "docs",
|
||||
title: "USTST Documentation",
|
||||
},
|
||||
],
|
||||
additional_info: {
|
||||
backing: "USD",
|
||||
reserve_ratio: "1:1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
{ wallet: mptIssuer, autofill: true },
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: "PermissionedDomainSet",
|
||||
Account: domainOwner.address,
|
||||
AcceptedCredentials: [
|
||||
{
|
||||
Credential: {
|
||||
Issuer: domainOwner.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType),
|
||||
},
|
||||
},
|
||||
],
|
||||
TicketSequence: domainOwnerTicketSequences[0],
|
||||
Sequence: 0
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true },
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: "CredentialCreate",
|
||||
Account: domainOwner.address,
|
||||
Subject: depositor.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType),
|
||||
TicketSequence: domainOwnerTicketSequences[1],
|
||||
Sequence: 0
|
||||
},
|
||||
{ wallet: domainOwner, autofill: true },
|
||||
),
|
||||
]);
|
||||
|
||||
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
|
||||
|
||||
// Get domain ID from transaction result
|
||||
const domainNode = domainSetResult.result.meta.AffectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
|
||||
)
|
||||
const domainId = domainNode.CreatedNode.LedgerIndex
|
||||
|
||||
// Step 3: Depositor accepts credential, authorizes MPT, and creates vault in parallel
|
||||
process.stdout.write('Setting up tutorial: 3/6\r')
|
||||
|
||||
const [, , vaultCreateResult] = await Promise.all([
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: depositor.address,
|
||||
Issuer: domainOwner.address,
|
||||
CredentialType: xrpl.convertStringToHex(credType),
|
||||
TicketSequence: depositorTicketSequences[0],
|
||||
Sequence: 0
|
||||
},
|
||||
{ wallet: depositor, autofill: true }
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: depositor.address,
|
||||
MPTokenIssuanceID: mptIssuanceId,
|
||||
TicketSequence: depositorTicketSequences[1],
|
||||
Sequence: 0
|
||||
},
|
||||
{ wallet: depositor, autofill: true }
|
||||
),
|
||||
client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'VaultCreate',
|
||||
Account: vaultOwner.address,
|
||||
Asset: {
|
||||
mpt_issuance_id: mptIssuanceId
|
||||
},
|
||||
Flags: xrpl.VaultCreateFlags.tfVaultPrivate,
|
||||
DomainID: domainId,
|
||||
Data: xrpl.convertStringToHex(
|
||||
JSON.stringify({ n: "LATAM Fund II", w: "examplefund.com" })
|
||||
),
|
||||
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
|
||||
ticker: 'SHARE1',
|
||||
name: 'Vault Shares',
|
||||
desc: 'Proportional ownership shares of the vault',
|
||||
icon: 'example.com/vault-shares-icon.png',
|
||||
asset_class: 'defi',
|
||||
issuer_name: 'Vault Owner',
|
||||
uris: [
|
||||
{
|
||||
uri: 'example.com/asset',
|
||||
category: 'website',
|
||||
title: 'Asset Website'
|
||||
},
|
||||
{
|
||||
uri: 'example.com/docs',
|
||||
category: 'docs',
|
||||
title: 'Docs'
|
||||
}
|
||||
],
|
||||
additional_info: {
|
||||
example_info: 'test'
|
||||
}
|
||||
}),
|
||||
AssetsMaximum: '0',
|
||||
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe
|
||||
},
|
||||
{ wallet: vaultOwner, autofill: true }
|
||||
)
|
||||
])
|
||||
|
||||
const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
|
||||
(node) => node.CreatedNode?.LedgerEntryType === 'Vault'
|
||||
)
|
||||
const vaultID = vaultNode.CreatedNode.LedgerIndex
|
||||
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
|
||||
|
||||
// Step 4: Issuer sends payment to depositor
|
||||
process.stdout.write('Setting up tutorial: 4/6\r')
|
||||
|
||||
const paymentResult = await client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'Payment',
|
||||
Account: mptIssuer.address,
|
||||
Destination: depositor.address,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptIssuanceId,
|
||||
value: '10000'
|
||||
}
|
||||
},
|
||||
{ wallet: mptIssuer, autofill: true }
|
||||
)
|
||||
|
||||
if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error('\nPayment failed:', paymentResult.result.meta.TransactionResult)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Step 5: Make an initial deposit so withdraw example has shares to work with
|
||||
process.stdout.write('Setting up tutorial: 5/6\r')
|
||||
|
||||
const initialDepositResult = await client.submitAndWait(
|
||||
{
|
||||
TransactionType: 'VaultDeposit',
|
||||
Account: depositor.address,
|
||||
VaultID: vaultID,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptIssuanceId,
|
||||
value: '1000'
|
||||
}
|
||||
},
|
||||
{ wallet: depositor, autofill: true }
|
||||
)
|
||||
|
||||
if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
|
||||
console.error('\nInitial deposit failed:', initialDepositResult.result.meta.TransactionResult)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Step 6: Save setup data to file
|
||||
process.stdout.write('Setting up tutorial: 6/6\r')
|
||||
|
||||
const setupData = {
|
||||
mptIssuer: {
|
||||
address: mptIssuer.address,
|
||||
seed: mptIssuer.seed
|
||||
},
|
||||
mptIssuanceId,
|
||||
domainOwner: {
|
||||
address: domainOwner.address,
|
||||
seed: domainOwner.seed
|
||||
},
|
||||
domainId,
|
||||
credentialType: credType,
|
||||
depositor: {
|
||||
address: depositor.address,
|
||||
seed: depositor.seed
|
||||
},
|
||||
vaultOwner: {
|
||||
address: vaultOwner.address,
|
||||
seed: vaultOwner.seed
|
||||
},
|
||||
vaultID,
|
||||
vaultShareMPTIssuanceId
|
||||
}
|
||||
|
||||
fs.writeFileSync('vaultSetup.json', JSON.stringify(setupData, null, 2))
|
||||
|
||||
process.stdout.write('Setting up tutorial: Complete!\n')
|
||||
|
||||
await client.disconnect()
|
||||
161
_code-samples/vaults/js/withdraw.js
Normal file
161
_code-samples/vaults/js/withdraw.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import xrpl from "xrpl"
|
||||
import { execSync } from "child_process"
|
||||
import fs from "fs"
|
||||
|
||||
// Auto-run setup if needed
|
||||
if (!fs.existsSync("vaultSetup.json")) {
|
||||
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
|
||||
execSync("node vaultSetup.js", { stdio: "inherit" })
|
||||
}
|
||||
|
||||
// Load setup data
|
||||
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
|
||||
|
||||
// Connect to the network
|
||||
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
|
||||
await client.connect()
|
||||
|
||||
// You can replace these values with your own
|
||||
const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed)
|
||||
const vaultID = setupData.vaultID
|
||||
const assetMPTIssuanceId = setupData.mptIssuanceId
|
||||
const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId
|
||||
|
||||
console.log(`Depositor address: ${depositor.address}`)
|
||||
console.log(`Vault ID: ${vaultID}`)
|
||||
console.log(`Asset MPT issuance ID: ${assetMPTIssuanceId}`)
|
||||
console.log(`Vault share MPT issuance ID: ${shareMPTIssuanceId}`)
|
||||
|
||||
const withdrawAmount = "1"
|
||||
|
||||
// Get initial vault state ----------------------
|
||||
console.log("\n=== Getting initial vault state... ===")
|
||||
const initialVaultInfo = await client.request({
|
||||
command: "vault_info",
|
||||
vault_id: vaultID,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
|
||||
console.log(`Initial vault state:`)
|
||||
console.log(` Assets Total: ${initialVaultInfo.result.vault.AssetsTotal}`)
|
||||
console.log(` Assets Available: ${initialVaultInfo.result.vault.AssetsAvailable}`)
|
||||
|
||||
// Check depositor's share balance ----------------------
|
||||
console.log("\n=== Checking depositor's share balance... ===")
|
||||
try {
|
||||
const shareBalanceResult = await client.request({
|
||||
command: "ledger_entry",
|
||||
mptoken: {
|
||||
mpt_issuance_id: shareMPTIssuanceId,
|
||||
account: depositor.address
|
||||
},
|
||||
ledger_index: "validated"
|
||||
})
|
||||
|
||||
const shareBalance = shareBalanceResult.result.node?.MPTAmount
|
||||
console.log(`Shares held: ${shareBalance}`)
|
||||
} catch (error) {
|
||||
if (error.data?.error === 'entryNotFound') {
|
||||
console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`)
|
||||
} else {
|
||||
console.error(`Error checking MPT: ${error}`)
|
||||
}
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Prepare VaultWithdraw transaction ----------------------
|
||||
console.log(`\n=== Preparing VaultWithdraw transaction ===`)
|
||||
const vaultWithdrawTx = {
|
||||
TransactionType: "VaultWithdraw",
|
||||
Account: depositor.address,
|
||||
VaultID: vaultID,
|
||||
Amount: {
|
||||
mpt_issuance_id: assetMPTIssuanceId,
|
||||
value: withdrawAmount
|
||||
},
|
||||
// Optional: Add Destination field to send assets to a different account
|
||||
// Destination: "rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
|
||||
}
|
||||
|
||||
// Validate the transaction structure before submitting
|
||||
xrpl.validate(vaultWithdrawTx)
|
||||
console.log(JSON.stringify(vaultWithdrawTx, null, 2))
|
||||
|
||||
// Submit VaultWithdraw transaction ----------------------
|
||||
console.log("\n=== Submitting VaultWithdraw transaction... ===")
|
||||
const withdrawResult = await client.submitAndWait(vaultWithdrawTx, {
|
||||
wallet: depositor,
|
||||
autofill: true,
|
||||
})
|
||||
if (withdrawResult.result.meta.TransactionResult !== "tesSUCCESS") {
|
||||
const result_code = withdrawResult.result.meta.TransactionResult
|
||||
console.error("Error: Unable to withdraw from vault:", result_code)
|
||||
await client.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
console.log("Withdrawal successful!")
|
||||
|
||||
// Extract vault state from transaction metadata ----------------------
|
||||
console.log("\n=== Vault state after withdrawal ===")
|
||||
const affectedNodes = withdrawResult.result.meta.AffectedNodes
|
||||
const vaultNode = affectedNodes.find(
|
||||
(node) => {
|
||||
const modifiedNode = node.ModifiedNode || node.DeletedNode
|
||||
return (
|
||||
modifiedNode &&
|
||||
modifiedNode.LedgerEntryType === "Vault" &&
|
||||
modifiedNode.LedgerIndex === vaultID
|
||||
)
|
||||
}
|
||||
)
|
||||
if (vaultNode) {
|
||||
if (vaultNode.DeletedNode) {
|
||||
console.log(` Vault empty (all assets withdrawn)`)
|
||||
} else {
|
||||
const vaultFields = vaultNode.ModifiedNode.FinalFields
|
||||
console.log(` Assets Total: ${vaultFields.AssetsTotal}`)
|
||||
console.log(` Assets Available: ${vaultFields.AssetsAvailable}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the depositor's share balance ----------------------
|
||||
console.log("\n=== Depositor's share balance ==")
|
||||
const depositorShareNode = affectedNodes.find((node) => {
|
||||
const modifiedNode = node.ModifiedNode || node.DeletedNode
|
||||
return (
|
||||
modifiedNode &&
|
||||
modifiedNode.LedgerEntryType === "MPToken" &&
|
||||
modifiedNode.FinalFields?.Account === depositor.address &&
|
||||
modifiedNode.FinalFields?.MPTokenIssuanceID === shareMPTIssuanceId
|
||||
)
|
||||
})
|
||||
if (depositorShareNode) {
|
||||
if (depositorShareNode.DeletedNode) {
|
||||
console.log(`No more shares held (redeemed all shares)`)
|
||||
} else {
|
||||
const shareFields = depositorShareNode.ModifiedNode.FinalFields
|
||||
console.log(`Shares held: ${shareFields.MPTAmount}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the depositor's asset balance ----------------------
|
||||
console.log("\n=== Depositor's asset balance ==")
|
||||
const depositorAssetNode = affectedNodes.find((node) => {
|
||||
const assetNode = node.ModifiedNode || node.CreatedNode
|
||||
const fields = assetNode?.FinalFields || assetNode?.NewFields
|
||||
return (
|
||||
assetNode &&
|
||||
assetNode.LedgerEntryType === "MPToken" &&
|
||||
fields?.Account === depositor.address &&
|
||||
fields?.MPTokenIssuanceID === assetMPTIssuanceId
|
||||
)
|
||||
})
|
||||
if (depositorAssetNode) {
|
||||
const assetNode = depositorAssetNode.ModifiedNode || depositorAssetNode.CreatedNode
|
||||
const assetFields = assetNode.FinalFields || assetNode.NewFields
|
||||
console.log(`Balance: ${assetFields.MPTAmount}`)
|
||||
}
|
||||
|
||||
await client.disconnect()
|
||||
|
||||
187
_code-samples/vaults/py/README.md
Normal file
187
_code-samples/vaults/py/README.md
Normal 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
|
||||
```
|
||||
115
_code-samples/vaults/py/create_vault.py
Normal file
115
_code-samples/vaults/py/create_vault.py
Normal 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([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# Create and fund vault owner account
|
||||
vault_owner = generate_faucet_wallet(client)
|
||||
|
||||
# You can replace these values with your own
|
||||
mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
domain_id = setup_data["domain_id"]
|
||||
|
||||
print(f"Vault owner address: {vault_owner.address}")
|
||||
print(f"MPT issuance ID: {mpt_issuance_id}")
|
||||
print(f"Permissioned domain ID: {domain_id}\n")
|
||||
|
||||
# Prepare VaultCreate transaction ----------------------
|
||||
print("\n=== VaultCreate transaction ===")
|
||||
vault_create_tx = VaultCreate(
|
||||
account=vault_owner.address,
|
||||
asset={"mpt_issuance_id": mpt_issuance_id},
|
||||
flags=VaultCreateFlag.TF_VAULT_PRIVATE, # Omit TF_VAULT_PRIVATE flag for public vaults
|
||||
# To make vault shares non-transferable add the TF_VAULT_SHARE_NON_TRANSFERABLE flag:
|
||||
# flags=VaultCreateFlag.TF_VAULT_PRIVATE | VaultCreateFlag.TF_VAULT_SHARE_NON_TRANSFERABLE,
|
||||
domain_id=domain_id, # Omit for public vaults
|
||||
# Convert Vault data to a string (without excess whitespace), then string to hex.
|
||||
data=str_to_hex(json.dumps(
|
||||
{"n": "LATAM Fund II", "w": "examplefund.com"}
|
||||
)),
|
||||
# Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
|
||||
# See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
|
||||
mptoken_metadata=encode_mptoken_metadata({
|
||||
"ticker": "SHARE1",
|
||||
"name": "Vault shares",
|
||||
"desc": "Proportional ownership shares of the vault.",
|
||||
"icon": "example.com/asset-icon.png",
|
||||
"asset_class": "defi",
|
||||
"issuer_name": "Asset Issuer Name",
|
||||
"uris": [
|
||||
{
|
||||
"uri": "example.com/asset",
|
||||
"category": "website",
|
||||
"title": "Asset Website",
|
||||
},
|
||||
{
|
||||
"uri": "example.com/docs",
|
||||
"category": "docs",
|
||||
"title": "Docs",
|
||||
},
|
||||
],
|
||||
"additional_info": {
|
||||
"example_info": "test",
|
||||
},
|
||||
}),
|
||||
assets_maximum="0", # No cap
|
||||
withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE,
|
||||
)
|
||||
|
||||
print(json.dumps(vault_create_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit, sign, and wait for validation ----------------------
|
||||
print("\n=== Submitting VaultCreate transaction... ===")
|
||||
submit_response = submit_and_wait(vault_create_tx, client, vault_owner, autofill=True)
|
||||
|
||||
if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = submit_response.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to create vault: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Vault created successfully!")
|
||||
|
||||
# Extract vault information from the transaction result
|
||||
affected_nodes = submit_response.result["meta"].get("AffectedNodes", [])
|
||||
vault_node = next(
|
||||
(node for node in affected_nodes
|
||||
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"),
|
||||
None
|
||||
)
|
||||
|
||||
if vault_node:
|
||||
print(f"\nVault ID: {vault_node['CreatedNode']['LedgerIndex']}")
|
||||
print(f"Vault pseudo-account address: {vault_node['CreatedNode']['NewFields']['Account']}")
|
||||
print(f"Share MPT issuance ID: {vault_node['CreatedNode']['NewFields']['ShareMPTID']}")
|
||||
|
||||
# Call vault_info method to retrieve the vault's information
|
||||
print("\n=== Getting vault_info... ===")
|
||||
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
|
||||
vault_info_response = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
print(json.dumps(vault_info_response.result, indent=2))
|
||||
|
||||
149
_code-samples/vaults/py/deposit.py
Normal file
149
_code-samples/vaults/py/deposit.py
Normal 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([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# You can replace these values with your own
|
||||
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
|
||||
vault_id = setup_data["vault_id"]
|
||||
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
|
||||
|
||||
print(f"Depositor address: {depositor.address}")
|
||||
print(f"Vault ID: {vault_id}")
|
||||
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
|
||||
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
|
||||
|
||||
deposit_amount = 1
|
||||
|
||||
# Get initial vault state
|
||||
print("\n=== Getting initial vault state... ===")
|
||||
initial_vault_info = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
print(f" - Total vault value: {initial_vault_info.result['vault']['AssetsTotal']}")
|
||||
print(f" - Available assets: {initial_vault_info.result['vault']['AssetsAvailable']}")
|
||||
|
||||
# Check depositor's asset balance
|
||||
print("\n=== Checking depositor's balance... ===")
|
||||
try:
|
||||
# Use ledger_entry to get specific MPT issuance balance
|
||||
ledger_entry_result = client.request(
|
||||
LedgerEntry(
|
||||
mptoken={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"account": depositor.address
|
||||
},
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
balance = ledger_entry_result.result["node"]["MPTAmount"]
|
||||
print(f"Balance: {balance}")
|
||||
|
||||
# Check if balance is sufficient
|
||||
if int(balance) < deposit_amount:
|
||||
print(f"Error: Insufficient balance! Have {balance}, need {deposit_amount}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as error:
|
||||
error_data = getattr(error, 'data', {})
|
||||
if 'error' in error_data and error_data['error'] == 'entryNotFound':
|
||||
print(f"Error: The depositor doesn't hold any assets with ID: {asset_mpt_issuance_id}", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error checking MPT: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare VaultDeposit transaction
|
||||
print("\n=== VaultDeposit transaction ===")
|
||||
vault_deposit_tx = VaultDeposit(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"value": str(deposit_amount)
|
||||
}
|
||||
)
|
||||
|
||||
print(json.dumps(vault_deposit_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit VaultDeposit transaction
|
||||
print("\n=== Submitting VaultDeposit transaction... ===")
|
||||
deposit_result = submit_and_wait(vault_deposit_tx, client, depositor, autofill=True)
|
||||
|
||||
if deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = deposit_result.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to deposit: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Deposit successful!")
|
||||
|
||||
# Extract vault state from transaction metadata
|
||||
print("\n=== Vault state after deposit ===")
|
||||
affected_nodes = deposit_result.result["meta"]["AffectedNodes"]
|
||||
vault_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
modified = node["ModifiedNode"]
|
||||
if modified["LedgerEntryType"] == "Vault" and modified["LedgerIndex"] == vault_id:
|
||||
vault_node = node
|
||||
break
|
||||
|
||||
if vault_node:
|
||||
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
|
||||
print(f" - Total vault value: {vault_fields['AssetsTotal']}")
|
||||
print(f" - Available assets: {vault_fields['AssetsAvailable']}")
|
||||
|
||||
# Get the depositor's share balance
|
||||
print("\n=== Depositor's share balance ===")
|
||||
depositor_share_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
share_node = node["ModifiedNode"]
|
||||
fields = share_node["FinalFields"]
|
||||
elif "CreatedNode" in node:
|
||||
share_node = node["CreatedNode"]
|
||||
fields = share_node["NewFields"]
|
||||
else:
|
||||
continue
|
||||
|
||||
if (share_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
|
||||
depositor_share_node = node
|
||||
break
|
||||
|
||||
if depositor_share_node:
|
||||
if "ModifiedNode" in depositor_share_node:
|
||||
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
|
||||
else:
|
||||
share_fields = depositor_share_node["CreatedNode"]["NewFields"]
|
||||
print(f"Shares held: {share_fields['MPTAmount']}")
|
||||
|
||||
2
_code-samples/vaults/py/requirements.txt
Normal file
2
_code-samples/vaults/py/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
xrpl-py==4.5.0
|
||||
|
||||
310
_code-samples/vaults/py/vault_setup.py
Normal file
310
_code-samples/vaults/py/vault_setup.py
Normal file
@@ -0,0 +1,310 @@
|
||||
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 (
|
||||
CredentialAccept, CredentialCreate, MPTokenAuthorize,
|
||||
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
|
||||
PermissionedDomainSet, TicketCreate, VaultDeposit
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
def get_ticket_sequences(ticket_create_result):
|
||||
"""Extract ticket sequences from a TicketCreate transaction result."""
|
||||
return [
|
||||
node["CreatedNode"]["NewFields"]["TicketSequence"]
|
||||
for node in ticket_create_result.result["meta"]["AffectedNodes"]
|
||||
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Ticket"
|
||||
]
|
||||
|
||||
async def main():
|
||||
# Setup script for vault tutorials
|
||||
print("Setting up tutorial: 0/6", 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 tickets for domain owner and depositor to submit transactions concurrently
|
||||
print("Setting up tutorial: 1/6", end="\r")
|
||||
|
||||
cred_type = "VaultAccess"
|
||||
domain_owner_ticket_create_result, depositor_ticket_create_result = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=domain_owner.address,
|
||||
ticket_count=2
|
||||
),
|
||||
client,
|
||||
domain_owner,
|
||||
autofill=True
|
||||
),
|
||||
submit_and_wait(
|
||||
TicketCreate(
|
||||
account=depositor.address,
|
||||
ticket_count=2
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
autofill=True
|
||||
)
|
||||
)
|
||||
|
||||
# Get the ticket sequences from transaction results
|
||||
domain_owner_ticket_sequences = get_ticket_sequences(domain_owner_ticket_create_result)
|
||||
depositor_ticket_sequences = get_ticket_sequences(depositor_ticket_create_result)
|
||||
|
||||
# Step 2: Create MPT issuance, permissioned domain, and credentials in parallel
|
||||
print("Setting up tutorial: 2/6", end="\r")
|
||||
|
||||
mpt_create_result, domain_set_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(
|
||||
PermissionedDomainSet(
|
||||
account=domain_owner.address,
|
||||
accepted_credentials=[
|
||||
Credential(
|
||||
issuer=domain_owner.address,
|
||||
credential_type=str_to_hex(cred_type)
|
||||
)
|
||||
],
|
||||
ticket_sequence=domain_owner_ticket_sequences[0],
|
||||
sequence=0
|
||||
),
|
||||
client,
|
||||
domain_owner,
|
||||
autofill=True
|
||||
),
|
||||
submit_and_wait(
|
||||
CredentialCreate(
|
||||
account=domain_owner.address,
|
||||
subject=depositor.address,
|
||||
credential_type=str_to_hex(cred_type),
|
||||
ticket_sequence=domain_owner_ticket_sequences[1],
|
||||
sequence=0
|
||||
),
|
||||
client,
|
||||
domain_owner,
|
||||
autofill=True
|
||||
)
|
||||
)
|
||||
|
||||
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
|
||||
|
||||
# Get domain ID from transaction result
|
||||
domain_node = next(
|
||||
node for node in domain_set_result.result["meta"]["AffectedNodes"]
|
||||
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "PermissionedDomain"
|
||||
)
|
||||
domain_id = domain_node["CreatedNode"]["LedgerIndex"]
|
||||
|
||||
# Step 3: Depositor accepts credential, authorizes MPT, and creates vault in parallel
|
||||
print("Setting up tutorial: 3/6", end="\r")
|
||||
|
||||
_, _, vault_create_result = await asyncio.gather(
|
||||
submit_and_wait(
|
||||
CredentialAccept(
|
||||
account=depositor.address,
|
||||
issuer=domain_owner.address,
|
||||
credential_type=str_to_hex(cred_type),
|
||||
ticket_sequence=depositor_ticket_sequences[0],
|
||||
sequence=0
|
||||
),
|
||||
client,
|
||||
depositor,
|
||||
autofill=True
|
||||
),
|
||||
submit_and_wait(
|
||||
MPTokenAuthorize(
|
||||
account=depositor.address,
|
||||
mptoken_issuance_id=mpt_issuance_id,
|
||||
ticket_sequence=depositor_ticket_sequences[1],
|
||||
sequence=0
|
||||
),
|
||||
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 4: Issuer sends payment to depositor
|
||||
print("Setting up tutorial: 4/6", 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 5: Make an initial deposit so withdraw example has shares to work with
|
||||
print("Setting up tutorial: 5/6", 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 6: Save setup data to file
|
||||
print("Setting up tutorial: 6/6", 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())
|
||||
164
_code-samples/vaults/py/withdraw.py
Normal file
164
_code-samples/vaults/py/withdraw.py
Normal 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([sys.executable, "vault_setup.py"], check=True)
|
||||
|
||||
# Load setup data
|
||||
with open("vault_setup.json", "r") as f:
|
||||
setup_data = json.load(f)
|
||||
|
||||
# Connect to the network
|
||||
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
|
||||
|
||||
# You can replace these values with your own
|
||||
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
|
||||
vault_id = setup_data["vault_id"]
|
||||
asset_mpt_issuance_id = setup_data["mpt_issuance_id"]
|
||||
share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"]
|
||||
|
||||
print(f"Depositor address: {depositor.address}")
|
||||
print(f"Vault ID: {vault_id}")
|
||||
print(f"Asset MPT issuance ID: {asset_mpt_issuance_id}")
|
||||
print(f"Vault share MPT issuance ID: {share_mpt_issuance_id}")
|
||||
|
||||
withdraw_amount = 1
|
||||
|
||||
# Get initial vault state
|
||||
print("\n=== Getting initial vault state... ===")
|
||||
initial_vault_info = client.request(
|
||||
VaultInfo(
|
||||
vault_id=vault_id,
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
print("Initial vault state:")
|
||||
print(f" Assets Total: {initial_vault_info.result['vault']['AssetsTotal']}")
|
||||
print(f" Assets Available: {initial_vault_info.result['vault']['AssetsAvailable']}")
|
||||
|
||||
# Check depositor's share balance
|
||||
print("\n=== Checking depositor's share balance... ===")
|
||||
try:
|
||||
share_balance_result = client.request(
|
||||
LedgerEntry(
|
||||
mptoken={
|
||||
"mpt_issuance_id": share_mpt_issuance_id,
|
||||
"account": depositor.address
|
||||
},
|
||||
ledger_index="validated"
|
||||
)
|
||||
)
|
||||
|
||||
share_balance = share_balance_result.result["node"]["MPTAmount"]
|
||||
print(f"Shares held: {share_balance}")
|
||||
except Exception as error:
|
||||
error_data = getattr(error, 'data', {})
|
||||
if 'error' in error_data and error_data['error'] == 'entryNotFound':
|
||||
print(f"Error: The depositor doesn't hold any vault shares with ID: {share_mpt_issuance_id}.", file=sys.stderr)
|
||||
else:
|
||||
print(f"Error checking MPT: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare VaultWithdraw transaction
|
||||
print("\n=== Preparing VaultWithdraw transaction ===")
|
||||
vault_withdraw_tx = VaultWithdraw(
|
||||
account=depositor.address,
|
||||
vault_id=vault_id,
|
||||
amount={
|
||||
"mpt_issuance_id": asset_mpt_issuance_id,
|
||||
"value": str(withdraw_amount)
|
||||
}
|
||||
# Optional: Add destination field to send assets to a different account
|
||||
# destination="rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY"
|
||||
)
|
||||
|
||||
print(json.dumps(vault_withdraw_tx.to_xrpl(), indent=2))
|
||||
|
||||
# Submit VaultWithdraw transaction
|
||||
print("\n=== Submitting VaultWithdraw transaction... ===")
|
||||
withdraw_result = submit_and_wait(vault_withdraw_tx, client, depositor, autofill=True)
|
||||
|
||||
if withdraw_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
|
||||
result_code = withdraw_result.result["meta"]["TransactionResult"]
|
||||
print(f"Error: Unable to withdraw from vault: {result_code}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("Withdrawal successful!")
|
||||
|
||||
# Extract vault state from transaction metadata
|
||||
print("\n=== Vault state after withdrawal ===")
|
||||
affected_nodes = withdraw_result.result["meta"]["AffectedNodes"]
|
||||
vault_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node or "DeletedNode" in node:
|
||||
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
|
||||
if modified_node["LedgerEntryType"] == "Vault" and modified_node["LedgerIndex"] == vault_id:
|
||||
vault_node = node
|
||||
break
|
||||
|
||||
if vault_node:
|
||||
if "DeletedNode" in vault_node:
|
||||
print(" Vault empty (all assets withdrawn)")
|
||||
else:
|
||||
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
|
||||
print(f" Assets Total: {vault_fields['AssetsTotal']}")
|
||||
print(f" Assets Available: {vault_fields['AssetsAvailable']}")
|
||||
|
||||
# Get the depositor's share balance
|
||||
print("\n=== Depositor's share balance ===")
|
||||
depositor_share_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node or "DeletedNode" in node:
|
||||
modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"]
|
||||
if "FinalFields" in modified_node:
|
||||
fields = modified_node["FinalFields"]
|
||||
if (modified_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
|
||||
depositor_share_node = node
|
||||
break
|
||||
|
||||
if depositor_share_node:
|
||||
if "DeletedNode" in depositor_share_node:
|
||||
print("No more shares held (redeemed all shares)")
|
||||
else:
|
||||
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
|
||||
print(f"Shares held: {share_fields['MPTAmount']}")
|
||||
|
||||
# Get the depositor's asset balance
|
||||
print("\n=== Depositor's asset balance ===")
|
||||
depositor_asset_node = None
|
||||
for node in affected_nodes:
|
||||
if "ModifiedNode" in node:
|
||||
asset_node = node["ModifiedNode"]
|
||||
fields = asset_node["FinalFields"]
|
||||
elif "CreatedNode" in node:
|
||||
asset_node = node["CreatedNode"]
|
||||
fields = asset_node["NewFields"]
|
||||
else:
|
||||
continue
|
||||
|
||||
if (asset_node["LedgerEntryType"] == "MPToken" and
|
||||
fields["Account"] == depositor.address and
|
||||
fields["MPTokenIssuanceID"] == asset_mpt_issuance_id):
|
||||
depositor_asset_node = node
|
||||
break
|
||||
|
||||
if depositor_asset_node:
|
||||
if "ModifiedNode" in depositor_asset_node:
|
||||
asset_fields = depositor_asset_node["ModifiedNode"]["FinalFields"]
|
||||
else:
|
||||
asset_fields = depositor_asset_node["CreatedNode"]["NewFields"]
|
||||
print(f"Balance: {asset_fields['MPTAmount']}")
|
||||
|
||||
@@ -26,7 +26,7 @@ const logos = {
|
||||
],
|
||||
developer_tooling: ["cryptum", "evernode", "threezy", "tokenize"],
|
||||
interoperability: ["multichain"],
|
||||
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet"],
|
||||
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet", "bifrost-wallet", "bitget-wallet"],
|
||||
nfts: [
|
||||
"aesthetes",
|
||||
"audiotarky",
|
||||
@@ -413,6 +413,24 @@ const cardsData = [
|
||||
category_name: "Wallet",
|
||||
link: "https://xaman.app/#team",
|
||||
},
|
||||
{
|
||||
id: "bifrost-wallet",
|
||||
title: "Bifrost Wallet",
|
||||
description:
|
||||
"Bifrost Wallet is a secure, independently audited self-custodial wallet for the XRP Ledger with multi-chain support. Purpose-built for XRPFi: a wallet where your XRP works for you and earns yield.",
|
||||
category_id: "wallet",
|
||||
category_name: "Wallet",
|
||||
link: "https://bifrostwallet.com/",
|
||||
},
|
||||
{
|
||||
id: "bitget-wallet",
|
||||
title: "Bitget Wallet",
|
||||
description:
|
||||
"Bitget Wallet is a non-custodial wallet designed to make crypto simple and secure for everyone.",
|
||||
category_id: "wallet",
|
||||
category_name: "Wallet",
|
||||
link: "https://web3.bitget.com/",
|
||||
},
|
||||
];
|
||||
|
||||
const featured_categories = {
|
||||
@@ -459,7 +477,7 @@ const uses = [
|
||||
{
|
||||
id: "wallet",
|
||||
title: "Wallet",
|
||||
number: 5,
|
||||
number: 7,
|
||||
description:
|
||||
"Build digital wallets to store passwords and interact with various blockchains to send and receive digital assets, including XRP."
|
||||
},
|
||||
|
||||
@@ -18,19 +18,15 @@ const links = [
|
||||
];
|
||||
|
||||
const softwallets = [
|
||||
{ href: "https://towolabs.com/", id: "wallet-towo", alt: "Towo" },
|
||||
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" },
|
||||
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
|
||||
{
|
||||
href: "https://gatehub.net/",
|
||||
id: "wallet-gatehub",
|
||||
alt: "Gatehub",
|
||||
imgclasses: "invertible-img",
|
||||
},
|
||||
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
|
||||
{ href: "https://bifrostwallet.com/", id: "wallet-bifrost", alt: "Bifrost Wallet" },
|
||||
{ href: "https://web3.bitget.com/", id: "wallet-bitget", alt: "Bitget Wallet" },
|
||||
{ href: "https://coin.space/", id: "wallet-coin", alt: "Coin Space" },
|
||||
{ href: "https://crossmark.io/", id: "wallet-crossmark", alt: "Crossmark Wallet" },
|
||||
{ href: "https://gatehub.net/", id: "wallet-gatehub", alt: "Gatehub", imgclasses: "invertible-img" },
|
||||
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
|
||||
{ href: "https://joeywallet.xyz/", id: "wallet-joey", alt: "Joey Wallet" },
|
||||
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
|
||||
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" }
|
||||
];
|
||||
|
||||
const hardwallets = [
|
||||
@@ -61,16 +57,8 @@ const exchanges = [
|
||||
{ href: "https://www.liquid.com/", id: "exch-liquid", alt: "Liquid" },
|
||||
{ href: "https://www.lmax.com/", id: "exch-lmax", alt: "LMAX" },
|
||||
{ href: "https://www.bitfinex.com/", id: "exch-bitfinex", alt: "Bitfinex" },
|
||||
{
|
||||
href: "https://www.etoro.com/crypto/exchange/",
|
||||
id: "exch-etoro",
|
||||
alt: "eToro",
|
||||
},
|
||||
{
|
||||
href: "https://currency.com",
|
||||
id: "exch-currency-com",
|
||||
alt: "Currency.com",
|
||||
},
|
||||
{ href: "https://www.etoro.com/crypto/exchange/", id: "exch-etoro", alt: "eToro"},
|
||||
{ href: "https://currency.com", id: "exch-currency-com", alt: "Currency.com"},
|
||||
{ href: "https://bittrex.com/", id: "exch-bittrex", alt: "Bittrex" },
|
||||
];
|
||||
|
||||
|
||||
192
blog/2026/clio-2.7.0.md
Normal file
192
blog/2026/clio-2.7.0.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-01-27"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
title: Introducing Clio version 2.7.0
|
||||
description: Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
|
||||
labels:
|
||||
- Clio Release Notes
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# Introducing Clio version 2.7.0
|
||||
|
||||
Version 2.7.0 of Clio, an XRP Ledger API server optimized for HTTP and WebSocket API calls, is now available. This release adds new features and bug fixes.
|
||||
|
||||
## Install / Upgrade
|
||||
|
||||
| Package |
|
||||
| :------- |
|
||||
| [Clio Server Linux Release (GCC)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_Linux_Release_gcc.zip) |
|
||||
| [Clio Server Linux Debian Release (amd64)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_2.7.0_amd64.deb) |
|
||||
| [Clio Server macOS Release (Apple Clang 17)](https://github.com/XRPLF/clio/releases/download/2.7.0/clio_server_macOS_Release_apple-clang.zip) |
|
||||
|
||||
For other platforms, please [build from source](https://github.com/XRPLF/clio/releases/tag/2.7.0). The most recent commit in the git log should be:
|
||||
|
||||
```text
|
||||
Author: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
|
||||
Date: Thu Jan 15 19:03:47 2026 +0000
|
||||
|
||||
ci: Restart colima on macOS (#2923)
|
||||
```
|
||||
|
||||
## What's Changed
|
||||
|
||||
See the [Full Changelog on GitHub](https://github.com/XRPLF/clio/compare/2.6.0...2.7.0).
|
||||
|
||||
### Features
|
||||
|
||||
- Adds `account_mptoken_issuances` API method to retrieve all `MPTokenIssuances` created by a specified account, and `account_mptokens` API method to retrieve all `MPTokens` held by a specified account. ([#2680](https://github.com/XRPLF/clio/pull/2680))
|
||||
- Adds DynamicMPT support to `account_mptoken_issuances` handler. ([#2820](https://github.com/XRPLF/clio/pull/2820))
|
||||
|
||||
### Improvements
|
||||
|
||||
- Removed old ETL implementation and enabled ETLng by default. ([#2752](https://github.com/XRPLF/clio/pull/2752))
|
||||
- Added async framework `submit` method for running one-shot tasks that don't require a handle to retrieve the result. ([#2751](https://github.com/XRPLF/clio/pull/2751))
|
||||
- Updated the Ledger Publisher to use async framework instead of directly using `io_context`. ([#2756](https://github.com/XRPLF/clio/pull/2756))
|
||||
- Added support for normal/high priority to `WorkQueue`. ([#2721](https://github.com/XRPLF/clio/pull/2721))
|
||||
- Added ability to read and write `LedgerCache` to file. ([#2761](https://github.com/XRPLF/clio/pull/2761))
|
||||
- Added graceful shutdown for old web server. ([#2786](https://github.com/XRPLF/clio/pull/2786))
|
||||
- Prometheus requests are now handled in `WorkQueue`. ([#2790](https://github.com/XRPLF/clio/pull/2790))
|
||||
- Added observable value utility to enable reactive approach across the codebase. ([#2831](https://github.com/XRPLF/clio/pull/2831))
|
||||
- Added option to save cache asynchronously. ([#2883](https://github.com/XRPLF/clio/pull/2883))
|
||||
- Added basic support for channels. ([#2859](https://github.com/XRPLF/clio/pull/2859))
|
||||
- Added build information to `clio_server --version` command. ([#2893](https://github.com/XRPLF/clio/pull/2893))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fixed an issue where `account_info` was omitting the `signer_lists` field when requested for accounts with no signer lists. ([#2746](https://github.com/XRPLF/clio/pull/2746))
|
||||
- Fixed an issue where `ledger_entry` error codes didn't match with `rippled`. ([#2549](https://github.com/XRPLF/clio/pull/2549))
|
||||
- Enhanced cache saving error to include more information. ([#2794](https://github.com/XRPLF/clio/pull/2794))
|
||||
- Fixed `WorkQueue` contention issues. ([#2866](https://github.com/XRPLF/clio/pull/2866))
|
||||
- Fixed issue where failed asserts in tests produced no output. ([#2905](https://github.com/XRPLF/clio/pull/2905))
|
||||
- Added workaround for an edge case exception in `AmendmentCenter`. ([#2897](https://github.com/XRPLF/clio/pull/2897))
|
||||
- Fixed `WorkQueue` performance. ([#2887](https://github.com/XRPLF/clio/pull/2887))
|
||||
|
||||
### Refactor
|
||||
|
||||
- Refactored duplicate `ledger_index` pattern in RPC handlers into a common function. ([#2755](https://github.com/XRPLF/clio/pull/2755))
|
||||
- Refactored `getLedgerIndex` to return `std::expected` instead of throwing exceptions. ([#2788](https://github.com/XRPLF/clio/pull/2788))
|
||||
- Added writing command to `etl::SystemState`. ([#2842](https://github.com/XRPLF/clio/pull/2842))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Removed `logging.md` from README. ([#2710](https://github.com/XRPLF/clio/pull/2710))
|
||||
- Fixed `graceful_period` description. ([#2791](https://github.com/XRPLF/clio/pull/2791))
|
||||
|
||||
### Styling
|
||||
|
||||
- Fixed pre-commit style issues. ([#2743](https://github.com/XRPLF/clio/pull/2743))
|
||||
- Fixed comment in `pre-commit-autoupdate.yml`. ([#2750](https://github.com/XRPLF/clio/pull/2750))
|
||||
- Fixed hadolint issues. ([#2777](https://github.com/XRPLF/clio/pull/2777))
|
||||
- Added black pre-commit hook. ([#2811](https://github.com/XRPLF/clio/pull/2811))
|
||||
- Updated pre-commit hooks. ([#2825](https://github.com/XRPLF/clio/pull/2825), [#2875](https://github.com/XRPLF/clio/pull/2875))
|
||||
- Used `shfmt` for shell scripts. ([#2841](https://github.com/XRPLF/clio/pull/2841))
|
||||
- Fixed clang-tidy error. ([#2901](https://github.com/XRPLF/clio/pull/2901))
|
||||
|
||||
### Testing
|
||||
|
||||
- Fixed flaky `DeadlineIsHandledCorrectly` test. ([#2716](https://github.com/XRPLF/clio/pull/2716))
|
||||
- Fixed flaky test. ([#2729](https://github.com/XRPLF/clio/pull/2729))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- Pinned all GitHub actions. ([#2712](https://github.com/XRPLF/clio/pull/2712))
|
||||
- Updated CI to use intermediate environment variables for improved security. ([#2713](https://github.com/XRPLF/clio/pull/2713))
|
||||
- Updated CI to save full logs for failed sanitizer tests. ([#2715](https://github.com/XRPLF/clio/pull/2715))
|
||||
- Enabled clang asan builds. ([#2717](https://github.com/XRPLF/clio/pull/2717))
|
||||
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0. ([#2722](https://github.com/XRPLF/clio/pull/2722))
|
||||
- [DEPENDABOT] bump actions/upload-artifact from 4.6.2 to 5.0.0 in /.github/actions/code-coverage. ([#2725](https://github.com/XRPLF/clio/pull/2725))
|
||||
- [DEPENDABOT] bump actions/download-artifact from 5.0.0 to 6.0.0. ([#2723](https://github.com/XRPLF/clio/pull/2723))
|
||||
- Improved pre-commit failure message. ([#2720](https://github.com/XRPLF/clio/pull/2720))
|
||||
- Updated CI to use XRPLF/get-nproc Github action. ([#2727](https://github.com/XRPLF/clio/pull/2727))
|
||||
- [DEPENDABOT] bump actions/checkout from 4.3.0 to 5.0.0. ([#2724](https://github.com/XRPLF/clio/pull/2724))
|
||||
- Added date to nightly release version. ([#2731](https://github.com/XRPLF/clio/pull/2731))
|
||||
- Fixed nightly commits link. ([#2738](https://github.com/XRPLF/clio/pull/2738))
|
||||
- Updated CI to use new prepare-runner. ([#2742](https://github.com/XRPLF/clio/pull/2742))
|
||||
- Updated tooling in Docker images. ([#2737](https://github.com/XRPLF/clio/pull/2737))
|
||||
- Installed pre-commit in the main CI image. ([#2744](https://github.com/XRPLF/clio/pull/2744))
|
||||
- Updated docker images. ([#2745](https://github.com/XRPLF/clio/pull/2745))
|
||||
- Added date to nightly release title. ([#2748](https://github.com/XRPLF/clio/pull/2748))
|
||||
- Updated prepare-runner to fix `ccache` on macOS. ([#2749](https://github.com/XRPLF/clio/pull/2749))
|
||||
- Removed backticks from release date. ([#2754](https://github.com/XRPLF/clio/pull/2754))
|
||||
- Specified apple-clang 17.0 in Conan profile. ([#2757](https://github.com/XRPLF/clio/pull/2757))
|
||||
- Fixed pre-commit hook failing on empty file. ([#2766](https://github.com/XRPLF/clio/pull/2766))
|
||||
- [DEPENDABOT] bump docker/setup-qemu-action from 3.6.0 to 3.7.0 in /.github/actions/build-docker-image. ([#2763](https://github.com/XRPLF/clio/pull/2763))
|
||||
- [DEPENDABOT] bump docker/metadata-action from 5.8.0 to 5.9.0 in /.github/actions/build-docker-image. ([#2762](https://github.com/XRPLF/clio/pull/2762))
|
||||
- Changed default `max_queue_size` to 1000. ([#2771](https://github.com/XRPLF/clio/pull/2771))
|
||||
- Specified bash as default shell in Github workflows. ([#2772](https://github.com/XRPLF/clio/pull/2772))
|
||||
- Updated CI to use `ucontext` in ASAN builds. ([#2775](https://github.com/XRPLF/clio/pull/2775))
|
||||
- Updated `xrpl` version to 3.0.0-rc1. ([#2776](https://github.com/XRPLF/clio/pull/2776))
|
||||
- Forced usage of `ucontext` with ASAN. ([#2774](https://github.com/XRPLF/clio/pull/2774))
|
||||
- Removed redundant silencing of ASAN errors in CI. ([#2779](https://github.com/XRPLF/clio/pull/2779))
|
||||
- Updated CI to use environment variables instead of input. ([#2781](https://github.com/XRPLF/clio/pull/2781))
|
||||
- Improved cache implementation. ([#2780](https://github.com/XRPLF/clio/pull/2780))
|
||||
- Updated nudb recipe to remove linker warnings. ([#2787](https://github.com/XRPLF/clio/pull/2787))
|
||||
- Updated CI to use environment variables instead of input in cache-key. ([#2789](https://github.com/XRPLF/clio/pull/2789))
|
||||
- Added defines for asan/tsan to Conan profile. ([#2784](https://github.com/XRPLF/clio/pull/2784))
|
||||
- Enabled TSAN in CI. ([#2785](https://github.com/XRPLF/clio/pull/2785))
|
||||
- Updated CI to stop downloading `ccache` on develop branch. ([#2792](https://github.com/XRPLF/clio/pull/2792))
|
||||
- Updated CI to always upload cache on develop. ([#2793](https://github.com/XRPLF/clio/pull/2793))
|
||||
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.8 to 7.0.9. ([#2805](https://github.com/XRPLF/clio/pull/2805))
|
||||
- [DEPENDABOT] bump actions/checkout from 5.0.0 to 6.0.0. ([#2806](https://github.com/XRPLF/clio/pull/2806))
|
||||
- Updated `spdlog` and `fmt` libraries. ([#2804](https://github.com/XRPLF/clio/pull/2804))
|
||||
- Ran clang-tidy multiple times to ensure all issues were resolved. ([#2803](https://github.com/XRPLF/clio/pull/2803))
|
||||
- Fixed Repeat-based tests TSAN issues. ([#2810](https://github.com/XRPLF/clio/pull/2810))
|
||||
- Fixed `WebServerAdminTestsSuit` TSAN issues. ([#2809](https://github.com/XRPLF/clio/pull/2809))
|
||||
- Used `boost::asio::ssl::stream` instead of `boost::beast::ssl_stream`. ([#2814](https://github.com/XRPLF/clio/pull/2814))
|
||||
- Installed latest Ninja in images. ([#2813](https://github.com/XRPLF/clio/pull/2813))
|
||||
- Updated images to use latest Ninja. ([#2817](https://github.com/XRPLF/clio/pull/2817))
|
||||
- Updated lockfile. ([#2818](https://github.com/XRPLF/clio/pull/2818))
|
||||
- Added mathbunnyru to maintainers. ([#2823](https://github.com/XRPLF/clio/pull/2823))
|
||||
- Fixed TSAN async-signal-unsafe issue. ([#2824](https://github.com/XRPLF/clio/pull/2824))
|
||||
- [DEPENDABOT] bump docker/metadata-action from 5.9.0 to 5.10.0 in /.github/actions/build-docker-image. ([#2826](https://github.com/XRPLF/clio/pull/2826))
|
||||
- [DEPENDABOT] bump actions/checkout from 6.0.0 to 6.0.1. ([#2837](https://github.com/XRPLF/clio/pull/2837))
|
||||
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.9 to 7.0.11. ([#2836](https://github.com/XRPLF/clio/pull/2836))
|
||||
- [DEPENDABOT] bump ytanikin/pr-conventional-commits from 1.4.2 to 1.5.1. ([#2835](https://github.com/XRPLF/clio/pull/2835))
|
||||
- Reduced delay in ETL taskman. ([#2802](https://github.com/XRPLF/clio/pull/2802))
|
||||
- Added systemd file to the Debian package. ([#2844](https://github.com/XRPLF/clio/pull/2844))
|
||||
- Switched to `xrpl` version 3.0.0. ([#2843](https://github.com/XRPLF/clio/pull/2843))
|
||||
- Added a Debian package to the Github release. ([#2850](https://github.com/XRPLF/clio/pull/2850))
|
||||
- Added a script to regenerate the Conan lockfile. ([#2849](https://github.com/XRPLF/clio/pull/2849))
|
||||
- [DEPENDABOT] bump tj-actions/changed-files from 46.0.5 to 47.0.1. ([#2853](https://github.com/XRPLF/clio/pull/2853))
|
||||
- [DEPENDABOT] bump peter-evans/create-pull-request from 7.0.11 to 8.0.0. ([#2854](https://github.com/XRPLF/clio/pull/2854))
|
||||
- [DEPENDABOT] bump actions/download-artifact from 6.0.0 to 7.0.0. ([#2855](https://github.com/XRPLF/clio/pull/2855))
|
||||
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0. ([#2856](https://github.com/XRPLF/clio/pull/2856))
|
||||
- [DEPENDABOT] bump codecov/codecov-action from 5.5.1 to 5.5.2. ([#2857](https://github.com/XRPLF/clio/pull/2857))
|
||||
- [DEPENDABOT] bump actions/upload-artifact from 5.0.0 to 6.0.0 in /.github/actions/code-coverage. ([#2858](https://github.com/XRPLF/clio/pull/2858))
|
||||
- Updated shared Github actions. ([#2852](https://github.com/XRPLF/clio/pull/2852))
|
||||
- Removed unnecessary creation of build directory in CI. ([#2867](https://github.com/XRPLF/clio/pull/2867))
|
||||
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0 in /.github/actions/build-docker-image. ([#2872](https://github.com/XRPLF/clio/pull/2872))
|
||||
- [DEPENDABOT] bump docker/setup-buildx-action from 3.11.1 to 3.12.0. ([#2870](https://github.com/XRPLF/clio/pull/2870))
|
||||
- [DEPENDABOT] bump actions/cache from 4.3.0 to 5.0.1. ([#2871](https://github.com/XRPLF/clio/pull/2871))
|
||||
- Updated prepare-runner in Github actions and workflows. ([#2889](https://github.com/XRPLF/clio/pull/2889))
|
||||
- Fixed branch name and commit SHA for GitHub PRs. ([#2888](https://github.com/XRPLF/clio/pull/2888))
|
||||
- Updated CI to show `ccache` stats. ([#2902](https://github.com/XRPLF/clio/pull/2902))
|
||||
- Changed build process to pass version explicitly instead of relying on tags. ([#2904](https://github.com/XRPLF/clio/pull/2904))
|
||||
- Updated `gtest` and `spdlog`. ([#2908](https://github.com/XRPLF/clio/pull/2908))
|
||||
- Updated tooling in Docker images. ([#2907](https://github.com/XRPLF/clio/pull/2907))
|
||||
- Updated CI workflows to use new Docker images and GitHub actions. ([#2909](https://github.com/XRPLF/clio/pull/2909))
|
||||
- Updated CI to use actual build date instead of date of last commit. ([#2911](https://github.com/XRPLF/clio/pull/2911))
|
||||
- Updated CI to use environment variable for `BUILD_TYPE` in `reusable-build.yml`. ([#2913](https://github.com/XRPLF/clio/pull/2913))
|
||||
- Changed build date format. ([#2914](https://github.com/XRPLF/clio/pull/2914))
|
||||
- Updated CI to restart colima on macOS. ([#2923](https://github.com/XRPLF/clio/pull/2923))
|
||||
- Reverted "refactor: Add writing command to etl::SystemState". ([#2860](https://github.com/XRPLF/clio/pull/2860))
|
||||
|
||||
## Contributors
|
||||
|
||||
The following people contributed directly to this release:
|
||||
|
||||
- [@godexsoft](https://github.com/godexsoft)
|
||||
- [@mathbunnyru](https://github.com/mathbunnyru)
|
||||
- [@kuznetsss](https://github.com/kuznetsss)
|
||||
- [@yinyiqian1](https://github.com/yinyiqian1)
|
||||
- [@emreariyurek](https://github.com/emreariyurek)
|
||||
- [@PeterChen13579](https://github.com/PeterChen13579)
|
||||
- [@bthomee](https://github.com/bthomee)
|
||||
|
||||
## Feedback
|
||||
|
||||
To report an issue or propose a new idea, please [open an issue](https://github.com/XRPLF/clio/issues).
|
||||
60
blog/2026/gpg-key-rotation.md
Normal file
60
blog/2026/gpg-key-rotation.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-02-18"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
title: GPG Key Rotation
|
||||
description: Ripple has rotated the GPG key used to sign rippled packages.
|
||||
labels:
|
||||
- Advisories
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# GPG Key Rotation
|
||||
|
||||
Ripple has rotated the GPG key used to sign `rippled` packages. If you have an existing installation, you should download and trust the new key to prevent issues upgrading in the future. **Automatic upgrades will not work** until you have trusted the new key.
|
||||
|
||||
## Action Needed
|
||||
|
||||
Add Ripple's package-signing GPG key, then verify the fingerprint of the newly-added key.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Red Hat / CentOS" %}
|
||||
|
||||
```bash
|
||||
sudo rpm --import https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
|
||||
rpm -qi gpg-pubkey-ab06faa6 | gpg --show-keys --fingerprint
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Ubuntu / Debian" %}
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings && \
|
||||
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/ripple.gpg > /dev/null
|
||||
gpg --show-keys --fingerprint /etc/apt/keyrings/ripple.gpg
|
||||
```
|
||||
|
||||
Ensure the `signed-by` path in your Ripple source list refers to the location the key was downloaded. For example, on an Ubuntu 22.04 Jammy installation, `/etc/apt/sources.list.d/ripple.list` would contain:
|
||||
|
||||
```
|
||||
deb [signed-by=/etc/apt/keyrings/ripple.gpg] https://repos.ripple.com/repos/rippled-deb jammy stable
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The output should include an entry for Ripple such as the following:
|
||||
|
||||
```
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
{% admonition type="danger" name="Warning" %}
|
||||
Only trust this key if its fingerprint exactly matches: `E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6`.
|
||||
{% /admonition %}
|
||||
76
blog/2026/rippled-3.1.0.md
Normal file
76
blog/2026/rippled-3.1.0.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-01-28"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
title: Introducing XRP Ledger version 3.1.0
|
||||
description: rippled version 3.1.0 is now available. This version introduces new amendments and bug fixes.
|
||||
labels:
|
||||
- rippled Release Notes
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# Introducing XRP Ledger version 3.1.0
|
||||
|
||||
Version 3.1.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release introduces Single Asset Vaults, the Lending Protocol, and bug fixes.
|
||||
|
||||
|
||||
## Action Required
|
||||
|
||||
If you run an XRP Ledger server, upgrade to version 3.1.0 as soon as possible to ensure service continuity.
|
||||
|
||||
|
||||
## Install / Upgrade
|
||||
|
||||
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
|
||||
|
||||
| Package | SHA-256 |
|
||||
|:--------|:--------|
|
||||
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.0-1.el9.x86_64.rpm) | `8ac8c529718566e6ebef3cb177d170fda1efc81ee08c4ea99d7e8fa3db0a2c70` |
|
||||
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.0-1_amd64.deb) | `58574a2299db2edf567e09efa25504677cdc66e4fa26f8a84322ab05f3a02996` |
|
||||
|
||||
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
|
||||
|
||||
```text
|
||||
commit d325f20c76fa798d0286d25e80b126ec0a2ee679
|
||||
Author: Ed Hennis <ed@ripple.com>
|
||||
Date: Tue Jan 27 21:13:06 2026 -0400
|
||||
|
||||
Set version to 3.1.0 (#6284)
|
||||
```
|
||||
|
||||
|
||||
## Full Changelog
|
||||
|
||||
|
||||
### Amendments
|
||||
|
||||
- **SingleAssetVault**: Adds vaults, which pool a single asset for use with the Lending Protocol. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
|
||||
- **LendingProtocol**: Adds the ability to create loans on the XRP Ledger. Loan brokers can create fixed-term, uncollateralized loans using the pooled funds from a Single Asset Vault. The protocol is highly configurable, enabling loan brokers to tune risk appetite, depostitor protections, and economic incentives. ([#5632](https://github.com/XRPLF/rippled/pull/5632))
|
||||
- **fixBatchInnerSigs**: Fixes an issue where inner transactions of a `Batch` transaction would be flagged as having valid signatures. Inner transactions never have valid signatures. ([#6069](https://github.com/XRPLF/rippled/pull/6069))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Expand `Number` to support full integer range. ([#6192](https://github.com/XRPLF/rippled/pull/6192))
|
||||
- Fix: Reorder Batch Preflight Errors. ([#6176](https://github.com/XRPLF/rippled/pull/6176))
|
||||
- Fix dependencies so clio can use libxrpl. ([#6251](https://github.com/XRPLF/rippled/pull/6251))
|
||||
- Fix: Remove DEFAULT fields that change to the default in associateAsset (was Add Vault creation tests for showing valid range for AssetsMaximum). ([#6259](https://github.com/XRPLF/rippled/pull/6259))
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
The following RippleX teams and GitHub users contributed to this release:
|
||||
|
||||
- RippleX Engineering
|
||||
- RippleX Docs
|
||||
- RippleX Product
|
||||
- @dangell7
|
||||
|
||||
|
||||
## Bug Bounties and Responsible Disclosures
|
||||
|
||||
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
|
||||
|
||||
To report a bug, please send a detailed report to: <bugs@xrpl.org>
|
||||
142
blog/2026/rippled-3.1.1.md
Normal file
142
blog/2026/rippled-3.1.1.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-02-23"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
title: Introducing XRP Ledger version 3.1.1 and upcoming Devnet reset
|
||||
description: rippled version 3.1.1 is now available. This version disables the Batch and fixBatchInnerSigs amendments. Devnet is also scheduled to reset on Tuesday, March 3, 2026 to prevent validators from becoming amendment blocked.
|
||||
labels:
|
||||
- rippled Release Notes
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# Introducing XRP Ledger version 3.1.1 and Upcoming Devnet Reset
|
||||
|
||||
Version 3.1.1 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release supersedes version 3.1.0, disabling the `Batch` and `fixBatchInnerSigs` amendments due to a severe bug.
|
||||
|
||||
|
||||
## Upcoming Devnet Reset
|
||||
|
||||
Devnet is scheduled for a reset on **Tuesday, March 3, 2026**. The `Batch` amendment requires more development and is now set to unsupported in version 3.1.1. To prevent validators that upgrade to this version from becoming amendment blocked, Devnet must be reset.
|
||||
|
||||
### Impact
|
||||
|
||||
This reset affects Devnet only. Other networks will continue to operate as usual, including XRPL Mainnet, XRPL Testnet, Xahau, and the Hooks Testnet.
|
||||
|
||||
The reset will delete all ledger data in Devnet, including all accounts, transactions, balances, settings, offers, AMMs, escrows, and other data. This means all balances will be reset to zero and the block number will start at one again. No changes are anticipated to services such as Devnet APIs, faucets, Explorers, access rights, and wallet integrations; these services usually manage resets without issues.
|
||||
|
||||
Any existing accounts or other data will need new test XRP from the faucet and will need to be re-created.
|
||||
|
||||
If code relies on specific addresses, a request to the faucet can fund the same address again. However, any AMMs or Vaults that are re-created after the reset will have different pseudo-account addresses. As a reminder, it's best not to use the same addresses or key pairs on Mainnet and any developer networks.
|
||||
|
||||
|
||||
## Action Required
|
||||
|
||||
If you run an XRP Ledger server, upgrade to version 3.1.1 as soon as possible to ensure service continuity.
|
||||
|
||||
### Add New GPG Key
|
||||
|
||||
As a reminder, [Ripple rotated the GPG key](./gpg-key-rotation.md) used to sign `rippled` packages. You must download and trust the new key before upgrading to version 3.1.1.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Red Hat / CentOS" %}
|
||||
|
||||
```bash
|
||||
sudo rpm --import https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
|
||||
rpm -qi gpg-pubkey-ab06faa6 | gpg --show-keys --fingerprint
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Ubuntu / Debian" %}
|
||||
|
||||
```bash
|
||||
sudo install -d -m 0755 /etc/apt/keyrings && \
|
||||
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public \
|
||||
| gpg --dearmor \
|
||||
| sudo tee /etc/apt/keyrings/ripple.gpg > /dev/null
|
||||
gpg --show-keys --fingerprint /etc/apt/keyrings/ripple.gpg
|
||||
```
|
||||
|
||||
Ensure the `signed-by` path in your Ripple source list refers to the location the key was downloaded. For example, on an Ubuntu 22.04 Jammy installation, `/etc/apt/sources.list.d/ripple.list` would contain:
|
||||
|
||||
```
|
||||
deb [signed-by=/etc/apt/keyrings/ripple.gpg] https://repos.ripple.com/repos/rippled-deb jammy stable
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The output should include an entry for Ripple such as the following:
|
||||
|
||||
```
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
{% admonition type="danger" name="Warning" %}
|
||||
Only trust this key if its fingerprint exactly matches: `E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6`.
|
||||
{% /admonition %}
|
||||
|
||||
### Install / Upgrade
|
||||
|
||||
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
|
||||
|
||||
| Package | SHA-256 |
|
||||
|:--------|:--------|
|
||||
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.1-1.el9.x86_64.rpm) | `c6d028db1e2a4da898df68e5a92a893bebf1d167a0539d15ae27435f2155ccb2` |
|
||||
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.1-1_amd64.deb) | `cc30c33012bd83ed793b38738870cf931a96ae106fde60b71685c766da1d22e3` |
|
||||
|
||||
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
|
||||
|
||||
```text
|
||||
commit c5988233d05bedddac28866ed37607f4869855f9
|
||||
Author: Ed Hennis <ed@ripple.com>
|
||||
Date: Mon Feb 23 16:47:09 2026 -0400
|
||||
|
||||
Set version to 3.1.1 (#6410)
|
||||
```
|
||||
|
||||
### Delete Devnet Database
|
||||
|
||||
If you run a `rippled` server that is connected to Devnet, after the reset you should delete your database data and restart the server. Database files and folders are defined in your config file in the `[database_path]` and `[node_db]` stanzas. If you use the default config, you can run the following commands:
|
||||
|
||||
```sh
|
||||
rm -r /var/lib/rippled/db/*
|
||||
|
||||
systemctl restart rippled.service
|
||||
```
|
||||
|
||||
|
||||
## Full Changelog
|
||||
|
||||
### Amendments
|
||||
|
||||
- **Batch** - A bug was discovered in `Batch`, and the amendment was disabled. The fix for this feature will be included in a future release as `BatchV1_1`. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
|
||||
- **fixBatchInnerSigs** - A bug was discovered in `Batch`, so this amendment was also disabled. This fix will be included in a future release as part of `BatchV1_1`. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
|
||||
|
||||
### CI/Build
|
||||
|
||||
- CI: Update `prepare-runner` action to fix macOS build environment. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
Thanks to Pranamya Keshkamat and Cantina AI for discovering and responsibly disclosing the `Batch` issue.
|
||||
|
||||
The following RippleX teams contributed to this release:
|
||||
|
||||
- RippleX Engineering
|
||||
- RippleX Docs
|
||||
- RippleX Product
|
||||
|
||||
|
||||
## Bug Bounties and Responsible Disclosures
|
||||
|
||||
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
|
||||
|
||||
For more information, see:
|
||||
|
||||
- [Ripple's Bug Bounty Program](https://ripple.com/legal/bug-bounty/)
|
||||
- [rippled Security Policy](https://github.com/XRPLF/rippled/blob/develop/SECURITY.md)
|
||||
113
blog/2026/vulnerabilitydisclosurereport-bug-feb2026.md
Normal file
113
blog/2026/vulnerabilitydisclosurereport-bug-feb2026.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
category: 2026
|
||||
date: "2026-02-26"
|
||||
template: '../../@theme/templates/blogpost'
|
||||
seo:
|
||||
description: This vulnerability disclosure report contains technical details of the XRP Ledger bug reported on February 19, 2026.
|
||||
labels:
|
||||
- Advisories
|
||||
markdown:
|
||||
editPage:
|
||||
hide: true
|
||||
---
|
||||
# Vulnerability Disclosure Report: XRPL Batch Amendment – Unauthorized Inner Transaction Execution
|
||||
|
||||
_By XRPL Labs_
|
||||
|
||||
This vulnerability disclosure report contains technical details of the XRPL `Batch` amendment bug reported on February 19, 2026.
|
||||
|
||||
**Date Reported:** February 19, 2026
|
||||
|
||||
**Affected Version(s):** rippled 3.1.0 (with the `Batch` amendment enabled)
|
||||
|
||||
## Summary of Vulnerability
|
||||
|
||||
On February 19, 2026, **Pranamya Keshkamat** and **Cantina AI** identified a critical logic flaw in the signature-validation logic of the XRPL `Batch` amendment. The bug allowed an attacker to execute inner transactions on behalf of arbitrary victim accounts without their private keys, enabling unauthorized fund transfers and ledger state changes. The amendment was in its voting phase and had not been activated on mainnet; no funds were at risk. Note that even before this bug was reported, the `Batch` amendment was disabled due to extra caution around ensuring the `fixBatchInnerSigs` amendment activated first.
|
||||
|
||||
UNL validators were immediately advised to vote "No" on the amendment. The emergency release **rippled 3.1.1** marks both `Batch` and `fixBatchInnerSigs` as unsupported, preventing activation. A corrected replacement, `BatchV1_1`, has been implemented and is currently under review; no release date has been set.
|
||||
|
||||
|
||||
## Impact
|
||||
|
||||
If the `Batch` amendment had activated before this bug was caught, an attacker could have:
|
||||
|
||||
- **Stolen funds:** executed inner `Payment` transactions draining victim accounts down to the reserve, without access to victim private keys.
|
||||
- **Modified ledger state:** submitted `AccountSet`, `TrustSet`, and potentially `AccountDelete` transactions from victim accounts without authorization.
|
||||
- **Destabilized the ecosystem:** a successful large-scale exploit could have caused substantial loss of confidence in XRPL, with potentially significant disruption for the broader ecosystem.
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Discovery
|
||||
|
||||
**Pranamya Keshkamat** and the autonomous AI security tool **Apex** (developed by **Cantina AI**) identified the vulnerability via static analysis of the rippled codebase and submitted a responsible disclosure report. Ripple engineering teams promptly validated the report with an independent proof-of-concept and a full unit-test reproduction. Remediation began the same evening.
|
||||
|
||||
### Root Cause
|
||||
|
||||
When the `Batch` amendment is enabled, inner transactions in a batch are intentionally unsigned; authorization is delegated entirely to the outer batch's list of batch signers. The function responsible for validating those signers contained a critical loop error: when it encountered a signer whose account did not yet exist on the ledger and whose signing key matched their own account (the normal case for a brand-new account), it immediately declared success and exited, skipping validation of all remaining signers entirely.
|
||||
|
||||
### Exploit Path
|
||||
|
||||
1. The attacker constructs a batch transaction containing three inner transactions: one that creates a new account they control (account B), one simple transaction from that new account (making it a required signer), and one payment from the victim account to the attacker.
|
||||
2. The attacker provides two batch signer entries: a legitimate one for account B signed with B's own key, and a forged one claiming to authorize the victim account but signed with the attacker's own key.
|
||||
3. Because account B does not yet exist at validation time, the signer check exits successfully after the first entry and never validates the second.
|
||||
4. The victim's payment executes without the victim's keys ever being involved.
|
||||
|
||||
## Remediation
|
||||
|
||||
- UNL validators were contacted and advised to vote "No" on the `Batch` amendment.
|
||||
- **rippled 3.1.1** was published on February 23, 2026. This release marks `Batch` and `fixBatchInnerSigs` as unsupported, preventing them from receiving validator votes or being activated on the network. This is the immediate remediation and does not contain the underlying logic fix.
|
||||
- The full logic fix removing the early-exit, adding additional authorization guards, and tightening the scope of the signing check has been implemented and is undergoing a thorough review process prior to release under the `BatchV1_1` amendment. Given the sensitivity of the changes, no timeline has been set.
|
||||
|
||||
## Security Enhancements Roadmap
|
||||
|
||||
- Adding AI-assisted code audit pipelines as a standard step in the review process.
|
||||
- Extending static analysis coverage to flag premature success returns inside signer-iteration loops.
|
||||
- Adding explicit comments and invariant assertions documenting expected behavior for uncreated accounts at validation time.
|
||||
- Reviewing all other locations in the codebase where early success returns occur inside loops to confirm no similar patterns exist.
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
1. Fund two attacker-controlled accounts and leave a third unfunded. Construct a batch transaction containing a payment that creates the unfunded account, a simple transaction from that account, and a payment from the victim account to the attacker.
|
||||
2. Sign the batch message with the unfunded account's master key for its signer entry, and with the attacker's own key for a second signer entry that falsely claims to authorize the victim account.
|
||||
3. Submit the batch transaction to the network.
|
||||
|
||||
**Pre-fix behavior:**
|
||||
|
||||
- The batch transaction succeeds.
|
||||
- The victim account's balance is reduced by the payment amount without the victim's keys being used.
|
||||
- The attacker's balance increases by the net amount minus the batch fee.
|
||||
|
||||
**Expected post-fix behavior:**
|
||||
|
||||
- The batch transaction is rejected with an authorization error.
|
||||
- No account balances change.
|
||||
|
||||
## Fixes / Patches Available
|
||||
|
||||
[**rippled 3.1.1**](https://xrpl.org/blog/2026/rippled-3.1.1) is available now. The corrected `BatchV1_1` amendment will be included in a future release following completion of its review; no date has been set.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Thanks to **Pranamya Keshkamat** and **Cantina AI** (whose autonomous security tool *Apex* identified the vulnerability) for responsible and thorough disclosure. Their detailed bug report, proof-of-concept, and constructive collaboration throughout the remediation process were invaluable.
|
||||
|
||||
Thanks also to the UNL validators who moved swiftly to vote against the affected amendment, the Ripple and XRPL Foundation engineering teams for the expedited review and emergency release, and the broader community of validators, developers, and contributors who keep the XRP Ledger safe and secure.
|
||||
|
||||
## References
|
||||
|
||||
- [rippled 3.1.1 release](https://xrpl.org/blog/2026/rippled-3.1.1)
|
||||
- [Community post on X](https://x.com/hrkrshnn/status/2025336360010387613)
|
||||
|
||||
## Contact
|
||||
|
||||
To report security issues, contact [security@ripple.com](mailto:security@ripple.com).
|
||||
|
||||
## Incident Response Timeline
|
||||
|
||||
| Key Action | Timestamp | Description |
|
||||
|---|---|---|
|
||||
| Initial Discovery | February 19, 2026 | Bug reported by Pranamya Keshkamat & Cantina AI. Engineering teams validated the report and opened an emergency response channel. |
|
||||
| Validator Notification | February 19, 2026 | UNL validators advised to vote "No" on `Batch`. Several validators applied vetoes the same evening. |
|
||||
| Unit Test Reproduction | February 19, 2026 | Independent unit-test reproduction confirmed. Proof-of-concept script validated by multiple engineers. |
|
||||
| Patch In Review | February 21, 2026 | Fix created in a private repository and currently under review. |
|
||||
| Emergency Release | February 23, 2026 | rippled 3.1.1 published. Operators advised to upgrade. |
|
||||
| Report Published | February 25, 2026 | Public vulnerability disclosure report published. |
|
||||
@@ -7,6 +7,14 @@
|
||||
page: index.page.tsx
|
||||
expanded: true
|
||||
items:
|
||||
- group: '2026'
|
||||
expanded: false
|
||||
items:
|
||||
- page: 2026/vulnerabilitydisclosurereport-bug-feb2026.md
|
||||
- page: 2026/rippled-3.1.1.md
|
||||
- page: 2026/gpg-key-rotation.md
|
||||
- page: 2026/rippled-3.1.0.md
|
||||
- page: 2026/clio-2.7.0.md
|
||||
- group: '2025'
|
||||
expanded: false
|
||||
items:
|
||||
|
||||
@@ -1317,6 +1317,61 @@ const events = [
|
||||
image: require("../static/img/events/hackathon-kaigi.png"),
|
||||
end_date: "December 06, 2025",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day EMEA",
|
||||
description:
|
||||
"Join the EMEA XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/w118fmkh?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 11, 2026",
|
||||
image: require("../static/img/events/emea_xrplorg.png"),
|
||||
end_date: "February 11, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day Americas",
|
||||
description:
|
||||
"Join the Americas XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/1powvqnc?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 11, 2026",
|
||||
image: require("../static/img/events/amer_xrplorg.png"),
|
||||
end_date: "February 11, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day APAC",
|
||||
description:
|
||||
"Join the APAC XRP community on February 12 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/ckzg3l3r?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 12, 2026",
|
||||
image: require("../static/img/events/apac_xrplorg.png"),
|
||||
end_date: "February 12, 2026",
|
||||
},
|
||||
{
|
||||
name: "Building On The XRP Ledger",
|
||||
description:
|
||||
"This 2-day intensive hands-on training is designed for developers who are curious to learn about XRP Ledger. Meet your peers, share insights, and join a community of builders.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/lxb5ttsc",
|
||||
location: "Paris, France",
|
||||
date: "January 26 - 27, 2026",
|
||||
image: require("../static/img/events/building-xrpl.png"),
|
||||
end_date: "January 27, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRPL Meetup in London",
|
||||
description:
|
||||
"Calling all blockchain and XRP Ledger enthusiasts in London! Join XRPL Meetups to share knowledge, build real-life connections, and foster communities centered around blockchain and XRP Ledger. We're establishing local “XRPL Hubs” across Europe, and we want you to be a part of it!",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/xshnm19t",
|
||||
location: "London, UK",
|
||||
date: "February 18, 2026",
|
||||
image: require("../static/img/events/meetup-london.png"),
|
||||
end_date: "February 18, 2026",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1398,33 +1453,33 @@ export default function Events() {
|
||||
<div className="pr-2 col">
|
||||
<img
|
||||
alt="xrp ledger events hero"
|
||||
src={require("../static/img/events/xrp-community-night.png")}
|
||||
src={require("../static/img/events/xrpl-hero.png")}
|
||||
className="w-100"
|
||||
/>
|
||||
</div>
|
||||
<div className="pt-5 pr-2 col">
|
||||
<div className="d-flex flex-column-reverse">
|
||||
<h2 className="mb-8 h4 h2-sm">
|
||||
{translate("XRP Community Night NYC")}
|
||||
{translate("XRP Community Night Denver")}
|
||||
</h2>
|
||||
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
|
||||
</div>
|
||||
<p className="mb-4">
|
||||
{translate(
|
||||
"Join the XRP community in NYC—meet builders, users, and projects innovating on the XRP Ledger."
|
||||
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
|
||||
)}
|
||||
</p>
|
||||
<div className=" my-3 event-small-gray">
|
||||
{translate("Location: New York, NY")}
|
||||
{translate("Location: Denver, CO")}
|
||||
</div>
|
||||
<div className="py-2 my-3 event-small-gray">
|
||||
{translate("November 5, 2025")}
|
||||
{translate("February 18, 2026")}
|
||||
</div>
|
||||
<div className="d-lg-block">
|
||||
<a
|
||||
className="btn btn-primary btn-arrow-out"
|
||||
target="_blank"
|
||||
href="https://lu.ma/g5uja58m?utm_source=xrpleventspage"
|
||||
href="https://luma.com/chz145tf?utm_source=xprlorg"
|
||||
>
|
||||
{translate("Register Now")}
|
||||
</a>
|
||||
|
||||
@@ -406,6 +406,78 @@ const events = [
|
||||
start_date: "November 14, 2025",
|
||||
end_date: "December 06, 2025",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day EMEA",
|
||||
description:
|
||||
"Join the EMEA XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/w118fmkh?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 11, 2026",
|
||||
image: require("../static/img/events/emea_xrplorg.png"),
|
||||
start_date: "February 11, 2026",
|
||||
end_date: "February 11, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day Americas",
|
||||
description:
|
||||
"Join the Americas XRP community on February 11, 2026 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/1powvqnc?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 11, 2026",
|
||||
image: require("../static/img/events/amer_xrplorg.png"),
|
||||
start_date: "February 11, 2026",
|
||||
end_date: "February 11, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Day APAC",
|
||||
description:
|
||||
"Join the APAC XRP community on February 12 for XRP Community Day, a global virtual event celebrating innovation, utility, and growth across the XRP ecosystem.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/ckzg3l3r?utm_source=xrplorg",
|
||||
location: "Virtual - X Spaces",
|
||||
date: "February 12, 2026",
|
||||
image: require("../static/img/events/apac_xrplorg.png"),
|
||||
start_date: "February 12, 2026",
|
||||
end_date: "February 12, 2026",
|
||||
},
|
||||
{
|
||||
name: "Building On The XRP Ledger",
|
||||
description:
|
||||
"This 2-day intensive hands-on training is designed for developers who are curious to learn about XRP Ledger. Meet your peers, share insights, and join a community of builders.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/lxb5ttsc",
|
||||
location: "Paris, France",
|
||||
date: "January 26 - 27, 2026",
|
||||
image: require("../static/img/events/building-xrpl.png"),
|
||||
start_date: "January 26, 2026",
|
||||
end_date: "January 27, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRPL Meetup in London",
|
||||
description:
|
||||
"Calling all blockchain and XRP Ledger enthusiasts in London! Join XRPL Meetups to share knowledge, build real-life connections, and foster communities centered around blockchain and XRP Ledger. We're establishing local “XRPL Hubs” across Europe, and we want you to be a part of it!",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/xshnm19t",
|
||||
location: "London, UK",
|
||||
date: "February 18, 2026",
|
||||
image: require("../static/img/events/meetup-london.png"),
|
||||
start_date: "February 18, 2026",
|
||||
end_date: "February 18, 2026",
|
||||
},
|
||||
{
|
||||
name: "XRP Community Night Denver",
|
||||
description:
|
||||
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP.",
|
||||
type: "meetup",
|
||||
link: "https://luma.com/chz145tf?utm_source=xprlorg",
|
||||
location: "Denver, CO",
|
||||
date: "February 18, 2026",
|
||||
image: require("../static/img/events/denver_xrplorg.png"),
|
||||
start_date: "February 18, 2026",
|
||||
end_date: "February 18, 2026",
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
[AccountSet transactions]: /docs/references/protocol/transactions/types/accountset.md
|
||||
[AccountSet]: /docs/references/protocol/transactions/types/accountset.md
|
||||
[Address]: /docs/references/protocol/data-types/basic-data-types.md#addresses
|
||||
[Amendments entry]: /docs/concepts/networks-and-servers/amendments.md
|
||||
[Amendments object]: /docs/concepts/networks-and-servers/amendments.md
|
||||
[Amendments entry]: /docs/references/protocol/ledger-data/ledger-entry-types/amendments.md
|
||||
[Amendments object]: /docs/references/protocol/ledger-data/ledger-entry-types/amendments.md
|
||||
[Batch amendment]: /resources/known-amendments.md#batch
|
||||
[Batch]: /docs/references/protocol/transactions/types/batch.md
|
||||
[Batch transaction]: /docs/references/protocol/transactions/types/batch.md
|
||||
@@ -128,6 +128,15 @@
|
||||
[LedgerStateFix transaction]: /docs/references/protocol/transactions/types/ledgerstatefix.md
|
||||
[LedgerStateFix transactions]: /docs/references/protocol/transactions/types/ledgerstatefix.md
|
||||
[LedgerStateFix]: /docs/references/protocol/transactions/types/ledgerstatefix.md
|
||||
[LoanBrokerCoverClawback transaction]: /docs/references/protocol/transactions/types/loanbrokercoverclawback.md
|
||||
[LoanBrokerCoverDeposit transaction]: /docs/references/protocol/transactions/types/loanbrokercoverdeposit.md
|
||||
[LoanBrokerCoverWithdraw transaction]: /docs/references/protocol/transactions/types/loanbrokercoverwithdraw.md
|
||||
[LoanBrokerDelete transaction]: /docs/references/protocol/transactions/types/loanbrokerdelete.md
|
||||
[LoanBrokerSet transaction]: /docs/references/protocol/transactions/types/loanbrokerset.md
|
||||
[LoanDelete transaction]: /docs/references/protocol/transactions/types/loandelete.md
|
||||
[LoanManage transaction]: /docs/references/protocol/transactions/types/loanmanage.md
|
||||
[LoanPay transaction]: /docs/references/protocol/transactions/types/loanpay.md
|
||||
[LoanSet transaction]: /docs/references/protocol/transactions/types/loanset.md
|
||||
[Marker]: /docs/references/http-websocket-apis/api-conventions/markers-and-pagination.md
|
||||
[MPTokenIssuanceSet]: /docs/references/protocol/transactions/types/mptokenissuanceset.md
|
||||
[MPTokenIssuanceSet transaction]: /docs/references/protocol/transactions/types/mptokenissuanceset.md
|
||||
@@ -135,6 +144,7 @@
|
||||
[MPTokensV1 amendment]: /resources/known-amendments.md#mptokensv1
|
||||
[MultiSign amendment]: /resources/known-amendments.md#multisign
|
||||
[MultiSignReserve amendment]: /resources/known-amendments.md#multisignreserve
|
||||
[NFT]: /docs/references/protocol/data-types/nftoken.md
|
||||
[NFTokenAcceptOffer transaction]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
|
||||
[NFTokenAcceptOffer transactions]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
|
||||
[NFTokenAcceptOffer]: /docs/references/protocol/transactions/types/nftokenacceptoffer.md
|
||||
@@ -155,6 +165,7 @@
|
||||
[NFTokenPage entry]: /docs/references/protocol/ledger-data/ledger-entry-types/nftokenpage.md
|
||||
[NFTokenPage object]: /docs/references/protocol/ledger-data/ledger-entry-types/nftokenpage.md
|
||||
[NFToken]: /docs/references/protocol/data-types/nftoken.md
|
||||
[Negative UNL]: /docs/concepts/consensus-protocol/negative-unl.md
|
||||
[NegativeUNL amendment]: /resources/known-amendments.md#negativeunl
|
||||
[NegativeUNL entry]: /docs/references/protocol/ledger-data/ledger-entry-types/negativeunl.md
|
||||
[NegativeUNL object]: /docs/references/protocol/ledger-data/ledger-entry-types/negativeunl.md
|
||||
@@ -248,6 +259,13 @@
|
||||
[UNLModify pseudo-transaction]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
|
||||
[UNLModify pseudo-transactions]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
|
||||
[UNLModify]: /docs/references/protocol/transactions/pseudo-transaction-types/unlmodify.md
|
||||
[Vault entry]: /docs/references/protocol/ledger-data/ledger-entry-types/vault.md
|
||||
[VaultCreate transaction]: /docs/references/protocol/transactions/types/vaultcreate.md
|
||||
[VaultDelete transaction]: /docs/references/protocol/transactions/types/vaultdelete.md
|
||||
[VaultDeposit transaction]: /docs/references/protocol/transactions/types/vaultdeposit.md
|
||||
[VaultSet transaction]: /docs/references/protocol/transactions/types/vaultset.md
|
||||
[VaultWithdraw transaction]: /docs/references/protocol/transactions/types/vaultwithdraw.md
|
||||
[VaultClawback transaction]: /docs/references/protocol/transactions/types/vaultclawback.md
|
||||
[XChainAddAccountCreateAttestation transaction]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
|
||||
[XChainAddAccountCreateAttestation transactions]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
|
||||
[XChainAddAccountCreateAttestation]: /docs/references/protocol/transactions/types/xchainaddaccountcreateattestation.md
|
||||
@@ -266,6 +284,7 @@
|
||||
[XChainOwnedClaimID entry]: /docs/references/protocol/ledger-data/ledger-entry-types/xchainownedclaimid
|
||||
[XRP, in drops]: /docs/references/protocol/data-types/basic-data-types.md#specifying-currency-amounts
|
||||
[XRPFees amendment]: /resources/known-amendments.md#xrpfees
|
||||
[AccountID]: /docs/references/protocol/binary-format/#accountid-fields
|
||||
[account_channels command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels.md
|
||||
[account_channels method]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_channels.md
|
||||
[account_currencies command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/account_currencies.md
|
||||
@@ -295,6 +314,7 @@
|
||||
[channel_verify command]: /docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify.md
|
||||
[channel_verify method]: /docs/references/http-websocket-apis/public-api-methods/payment-channel-methods/channel_verify.md
|
||||
[common fields]: /docs/references/protocol/transactions/common-fields.md
|
||||
[common ledger entry fields]: /docs/references/protocol/ledger-data/common-fields/
|
||||
[connect command]: /docs/references/http-websocket-apis/admin-api-methods/peer-management-methods/connect.md
|
||||
[connect method]: /docs/references/http-websocket-apis/admin-api-methods/peer-management-methods/connect.md
|
||||
[consensus_info command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/consensus_info.md
|
||||
@@ -309,6 +329,7 @@
|
||||
[fee command]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/fee.md
|
||||
[fee levels]: /docs/concepts/transactions/transaction-cost.md#fee-levels
|
||||
[fee method]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/fee.md
|
||||
[fee voting]: /docs/concepts/consensus-protocol/fee-voting.md
|
||||
[fetch_info command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/fetch_info.md
|
||||
[fetch_info method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/fetch_info.md
|
||||
[fix1201 amendment]: /resources/known-amendments.md#fix1201
|
||||
@@ -341,17 +362,21 @@
|
||||
[fixRemoveNFTokenAutoTrustLine amendment]: /resources/known-amendments.md#fixremovenftokenautotrustline
|
||||
[fixTakerDryOfferRemoval amendment]: /resources/known-amendments.md#fixtakerdryofferremoval
|
||||
[fixTrustLinesToSelf amendment]: /resources/known-amendments.md#fixtrustlinestoself
|
||||
[flags field]: /docs/references/protocol/transactions/common-fields#flags-field
|
||||
[get_aggregate_price command]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/get_aggregate_price.md
|
||||
[get_aggregate_price method]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/get_aggregate_price.md
|
||||
[gateway_balances command]: /docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances.md
|
||||
[gateway_balances method]: /docs/references/http-websocket-apis/public-api-methods/account-methods/gateway_balances.md
|
||||
[get_counts command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||
[get_counts method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/get_counts.md
|
||||
[Get Started Using JavaScript]: /docs/tutorials/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
|
||||
[identifying hash]: /docs/concepts/transactions/index.md#identifying-transactions
|
||||
[json command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
|
||||
[json method]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/json.md
|
||||
[ledger command]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger.md
|
||||
[ledger entry ID]: /docs/references/protocol/ledger-data/common-fields.md#ledger-entry-id
|
||||
[ledger format]: /docs/references/protocol/ledger-data/ledger-entry-types/index.md
|
||||
[ledger index]: /docs/references/protocol/data-types/basic-data-types.md#ledger-index
|
||||
[ledger method]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger.md
|
||||
@@ -369,6 +394,13 @@
|
||||
[ledger_entry method]: /docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry.md
|
||||
[ledger_request command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/ledger_request.md
|
||||
[ledger_request method]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/ledger_request.md
|
||||
[Lending Protocol]: /docs/concepts/tokens/lending-protocol.md
|
||||
[Loan]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
|
||||
[Loan entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
|
||||
[Loan ledger entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loan.md
|
||||
[LoanBroker]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
|
||||
[LoanBroker entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
|
||||
[LoanBroker ledger entry]: /docs/references/protocol/ledger-data/ledger-entry-types/loanbroker.md
|
||||
[log_level command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/log_level.md
|
||||
[log_level method]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/log_level.md
|
||||
[logrotate command]: /docs/references/http-websocket-apis/admin-api-methods/logging-and-data-management-methods/logrotate.md
|
||||
@@ -404,10 +436,12 @@
|
||||
[public servers]: /docs/tutorials/public-servers.md
|
||||
[random command]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/random.md
|
||||
[random method]: /docs/references/http-websocket-apis/public-api-methods/utility-methods/random.md
|
||||
[reserves]: /docs/concepts/accounts/reserves.md
|
||||
[result code]: /docs/references/protocol/transactions/transaction-results/index.md
|
||||
[ripple-lib]: https://github.com/XRPLF/xrpl.js
|
||||
[ripple_path_find command]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/ripple_path_find.md
|
||||
[ripple_path_find method]: /docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods/ripple_path_find.md
|
||||
[Ripple Epoch]: /docs/references/protocol/data-types/basic-data-types.md#specifying-time
|
||||
[seconds since the Ripple Epoch]: /docs/references/protocol/data-types/basic-data-types.md#specifying-time
|
||||
[server_definitions method]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/server_definitions.md
|
||||
[server_info command]: /docs/references/http-websocket-apis/public-api-methods/server-info-methods/server_info.md
|
||||
@@ -432,6 +466,7 @@
|
||||
[transaction cost]: /docs/concepts/transactions/transaction-cost.md
|
||||
[transaction_entry command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/transaction_entry.md
|
||||
[transaction_entry method]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/transaction_entry.md
|
||||
[transaction result codes]: /docs/references/protocol/transactions/transaction-results
|
||||
[tx command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx.md
|
||||
[tx method]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx.md
|
||||
[tx_history command]: /docs/references/http-websocket-apis/public-api-methods/transaction-methods/tx_history.md
|
||||
@@ -447,5 +482,9 @@
|
||||
[validator_list_sites method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validator_list_sites.md
|
||||
[validators command]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validators.md
|
||||
[validators method]: /docs/references/http-websocket-apis/admin-api-methods/status-and-debugging-methods/validators.md
|
||||
[vault_info command]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
|
||||
[vault_info method]: /docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info.md
|
||||
[wallet_propose command]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||
[wallet_propose method]: /docs/references/http-websocket-apis/admin-api-methods/key-generation-methods/wallet_propose.md
|
||||
[xrpl.js library]: https://github.com/XRPLF/xrpl.js
|
||||
[xrpl-py library]: https://github.com/XRPLF/xrpl-py
|
||||
|
||||
@@ -10,7 +10,7 @@ status: not_enabled
|
||||
|
||||
Permission delegation is the function of granting various permissions to another account to send permissions on behalf of your account. You can use permission delegation to enable flexible security paradigms such as role-based access control, instead of or alongside techniques such as [multi-signing](./multi-signing.md).
|
||||
|
||||
{% amendment-disclaimer name="PermissionDelegation" /%}
|
||||
{% amendment-disclaimer name="PermissionDelegation" /%}
|
||||
|
||||
|
||||
## Background: The Need for Permission Delegation
|
||||
|
||||
33
docs/concepts/accounts/pseudo-accounts.md
Normal file
33
docs/concepts/accounts/pseudo-accounts.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
seo:
|
||||
description: A pseudo-account is a special type of XRPL account that holds assets on behalf of an on-chain protocol.
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
- AMM
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Pseudo-Accounts
|
||||
|
||||
The XRP Ledger is an account-based blockchain where assets like XRP, trust line tokens, and Multi-Purpose Tokens (MPTs) are held by accounts, and are represented on-chain by an [AccountRoot](../../references/protocol/ledger-data/ledger-entry-types/accountroot) ledger entry. However, certain use cases require assets to be transferable to and from an object, which is why a pseudo-account is needed.
|
||||
|
||||
A pseudo-account is a special type of account that holds assets on behalf of an on-chain protocol. Use cases for pseudo-accounts include:
|
||||
|
||||
- **Automated Market Makers (AMM)**: The [XLS-30 amendment](../../../resources/known-amendments#amm) introduced pseudo-accounts for AMMs by adding the `AMMID` field to the `AccountRoot` ledger entry. This field links a pseudo-account to an AMM instance, allowing it to track XRP and token balances in the pool and issue `LPTokens` on behalf of the AMM instance.
|
||||
|
||||
- **Single Asset Vaults**: A single asset vault pseudo-account is used to store deposited funds and issue MPT shares. A new `VaultID` field is introduced in the `AccountRoot` ledger entry, which links the pseudo-account with the vault.
|
||||
|
||||
- **Lending Protocol**: The Lending Protocol also uses the single asset vault's pseudo-account, with each `LoanBroker` tracked in the pseudo-account's owner directory. The pseudo-account holds first-loss capital that protects vault depositors from loan defaults, as well as the loan funds themselves.
|
||||
|
||||
A pseudo-account has strict limitations. It cannot receive payments from other accounts, cannot send transactions since it has no signing authority, and exists solely to store or issue assets.
|
||||
|
||||
## Reserve Requirements
|
||||
|
||||
The cost of creating a pseudo-account depends on whether it is owned and controlled by another account:
|
||||
|
||||
- **Owned pseudo-accounts**: For objects like a `Vault` where a single account owns and controls the associated pseudo-account, the creation transaction increases the owner's XRP reserve by one [incremental owner reserve](../accounts/reserves#base-reserve-and-owner-reserve) (currently {% $env.PUBLIC_OWNER_RESERVE %}). This is in addition to any other reserve requirements of the transaction (for example, the Vault object itself).
|
||||
|
||||
- **Unowned pseudo-accounts**: For objects like an `AMM` that are not owned by any account, the creation transaction must charge a special, higher-than-normal transaction fee. This fee must be at least the value of one incremental owner reserve. This amount is burned, compensating for the permanent ledger space without tying the reserve to a specific owner.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
202
docs/concepts/tokens/lending-protocol.md
Normal file
202
docs/concepts/tokens/lending-protocol.md
Normal file
@@ -0,0 +1,202 @@
|
||||
---
|
||||
seo:
|
||||
description: The XRPL Lending Protocol enables on-chain, uncollateralized fixed-term loans.
|
||||
labels:
|
||||
- Decentralized Finance
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# Lending Protocol
|
||||
|
||||
The Lending Protocol is an XRP Ledger DeFi primitive that enables on-chain, fixed-term, uncollateralized loans using pooled funds from a [Single Asset Vault](./single-asset-vaults.md). The protocol is highly configurable, enabling loan brokers to easily tune risk appetite, depostitor protections, and economic incentives.
|
||||
|
||||
The implementation relies on off-chain underwriting and risk management to assess the creditworthiness of borrowers, but offers peer-to-peer loans without intermediaries like banks or financial institutions. First-loss capital protection is used to help offset losses from loan defaults.
|
||||
|
||||
The current implementation of the lending protocol doesn't include automated on-chain collateral and liquidation management, instead focusing on on-chain credit origination.
|
||||
|
||||
To ensure compliance needs are met, asset issuers can [claw back](../../references/protocol/transactions/types/clawback.md) funds from the vault associated with the lending protocol. Issuers can also [freeze](./fungible-tokens/freezes.md) individual accounts or issue a global freeze.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Protocol Flow
|
||||
|
||||
There are three parties involved in the process of creating a loan:
|
||||
|
||||
- **Loan Brokers**: Create asset vaults and manage associated loans.
|
||||
- **Depositors**: Add assets to vaults.
|
||||
- **Borrowers**: Receive loans, making repayments as defined by their loan terms.
|
||||
|
||||
[{% inline-svg file="../../img/lending-protocol-diagram.svg" /%}](../../img/lending-protocol-diagram.svg "Diagram: The lifecycle of a loan.")
|
||||
|
||||
The lifecycle of a loan is as follows:
|
||||
|
||||
1. A loan broker creates a vault.
|
||||
2. Depositors add assets to the vault.
|
||||
3. (Optional) The loan broker deposits first-loss capital.
|
||||
4. A loan broker and borrower create a loan, defining the terms of the loan, and the requested principal (excluding fees) is transferred to the borrower.
|
||||
5. If payments are missed, the loan enters a grace period. Once the grace period expires, the loan broker has the option to default the loan.
|
||||
6. The loan is deleted when matured or defaulted.
|
||||
7. (Optional) The loan broker can withdraw first-loss capital.
|
||||
8. After all loans are paid, the loan broker can delete the `LoanBroker` ledger entry, and then the corresponding `Vault` ledger entry.
|
||||
|
||||
|
||||
## Accounting
|
||||
|
||||
### Risk Management
|
||||
|
||||
#### First-Loss Capital
|
||||
|
||||
First-Loss Capital is an optional mechanism to mitigate the risks associated with lending. To protect investors' assets, a loan broker can deposit assets as first-loss capital, which acts as a buffer in the event of loan defaults. The first-loss capital is placed into the vault to cover a percentage of losses from missed payments.
|
||||
|
||||
Three parameters control the First-Loss Capital:
|
||||
|
||||
- `CoverAvailable`: The total amount of cover deposited by the lending protocol owner.
|
||||
- `CoverRateMinimum`: The percentage of debt that must be covered by `CoverAvailable`.
|
||||
- `CoverRateLiquidation`: The maximum percentage of the minimum required cover _(DebtTotal x CoverRateMinimum)_ that will be placed in the asset vault to cover a loan default.
|
||||
|
||||
Whenever the available cover falls below the minimum required:
|
||||
|
||||
- The loan broker can't issue new loans.
|
||||
- The loan broker can't receive fees. All fees are added to the First-Loss Capital to cover the deficit.
|
||||
|
||||
Below is an example of how first-loss capital is used to cover a loan default:
|
||||
|
||||
```
|
||||
** Initial States **
|
||||
|
||||
-- Vault --
|
||||
AssetsTotal = 100,090 Tokens
|
||||
AssetsAvailable = 99,000 Tokens
|
||||
SharesTotal = 100,000 Tokens
|
||||
|
||||
-- Lending Protocol --
|
||||
DebtTotal = 1,090 Tokens
|
||||
CoverRateMinimum = 0.1 (10%)
|
||||
CoverRateLiquidation = 0.1 (10%)
|
||||
CoverAvailable = 1,000 Tokens
|
||||
|
||||
-- Loan --
|
||||
PrincipleOutstanding = 1,000 Tokens
|
||||
InterestOutstanding = 90 Tokens
|
||||
|
||||
|
||||
# First-Loss Capital liquidation maths
|
||||
|
||||
DefaultAmount = PrincipleOutstanding + InterestOutstanding
|
||||
= 1,000 + 90
|
||||
= 1,090
|
||||
|
||||
# The amount of the default that the first-loss capital scheme will cover
|
||||
DefaultCovered = min((DebtTotal x CoverRateMinimum) x CoverRateLiquidation, DefaultAmount)
|
||||
= min((1,090 * 0.1) * 0.1, 1,090) = min(10.9, 1,090)
|
||||
= 10.9 Tokens
|
||||
|
||||
Loss = DefaultAmount - DefaultCovered
|
||||
= 1,090 - 10.9
|
||||
= 1,079.1 Tokens
|
||||
|
||||
FundsReturned = DefaultCovered
|
||||
= 10.9
|
||||
|
||||
# Note, Loss + FundsReturned MUST be equal to PrincipleOutstanding + InterestOutstanding
|
||||
|
||||
** State Changes **
|
||||
|
||||
-- Vault --
|
||||
AssetsTotal = AssetsTotal - Loss
|
||||
= 100,090 - 1,079.1
|
||||
= 99,010.9 Tokens
|
||||
|
||||
AssetsAvailable = AssetsAvailable + FundsReturned
|
||||
= 99,000 + 10.9
|
||||
= 99,010.9 Tokens
|
||||
|
||||
SharesTotal = (UNCHANGED)
|
||||
|
||||
-- Lending Protocol --
|
||||
DebtTotal = DebtTotal - PrincipleOutstanding + InterestOutstanding
|
||||
= 1,090 - (1,000 + 90)
|
||||
= 0 Tokens
|
||||
|
||||
CoverAvailable = CoverAvailable - DefaultCovered
|
||||
= 1,000 - 10.9
|
||||
= 989.1 Tokens
|
||||
```
|
||||
|
||||
#### Impairment
|
||||
|
||||
If the loan broker discovers a borrower can't make an upcoming payment, impairment allows the loan broker to register a "paper loss" with the vault. The impairment mechanism moves the due date of the next payment to the time the loan is impaired, allowing the loan to default more quickly. However, if the borrower makes a payment before that date, the impairment status is automatically cleared.
|
||||
|
||||
|
||||
### Compliance
|
||||
|
||||
#### Clawback
|
||||
|
||||
Issuers (trust line token or MPT, not XRP) can claw back funds from First-Loss Capital. To ensure there is always a minimum amount of capital available to protect depositors, issuers can't claw back the entire available amount. Instead, they can claw back up to a minimum amount of First-Loss Capital that the loan broker must maintain for the lending protocol; the minimum amount is calculated as `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum`.
|
||||
|
||||
#### Freeze
|
||||
|
||||
Freezing is a mechanism by which an asset issuer (trust line token or MPT, not XRP) prevents an account from sending their issued asset. _Deep freeze_ takes this a step further by preventing an account from sending _and_ receiving issued assets. Issuers can also enact a _global freeze_, which prevents everyone from sending or receiving their issued asset.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
In all freeze scenarios, assets can be sent back to the issuer.
|
||||
{% /admonition %}
|
||||
|
||||
If a borrower has their account frozen or deep frozen, they can't make loan payments. This doesn't absolve a borrower of their repayment obligations, and they will eventually default on their loan.
|
||||
|
||||
Freezing a borrower's account won't affect a loan broker's functions, but it will prevent them from receiving any lending protocol fees. However, issuers can freeze a loan broker's _pseudo-account_ and prevent the loan broker from creating new loans; existing loans won't be affected. A deep freeze on a loan broker's _pseudo-account_ also prevents loans from being repaid.
|
||||
|
||||
|
||||
### Interest Rates
|
||||
|
||||
There are three interest rates associated with a loan:
|
||||
|
||||
- **Interest Rate**: The regular interest rate based on the principal amount. It is the cost of borrowing funds.
|
||||
- **Late Interest Rate**: A higher interest rate charged for a late payment.
|
||||
- **Full Payment Rate**: An interest rate charged for repaying the total loan early.
|
||||
|
||||
|
||||
### Fees
|
||||
|
||||
The lending protocol charges a number of fees that the loan broker can configure. The protocol won't charge these fees if the loan broker hasn't deposited enough first-loss capital.
|
||||
|
||||
- **Management Fee**: This is a percentage of interest charged by the loan broker. Vault depositors pay this fee.
|
||||
- **Loan Origination Fee**: A fee paid to the loan broker, taken from the principal amount loaned out.
|
||||
- **Loan Service Fee**: A fee charged on top of each loan payment.
|
||||
- **Late Payment Fee**: A fee paid on top of a late payment.
|
||||
- **Early Payment Fee**: A fee paid on top of an early payment.
|
||||
|
||||
|
||||
## Loan Payment Processing
|
||||
|
||||
Loan payments are evaluated and processed around three criteria: amount, timing, and specified flags. The combination of these criteria determine how funds are applied to the loan's principal, interest, and associated fees.
|
||||
|
||||
Each payment consists of four components:
|
||||
|
||||
- **Principal**: The portion that reduces the outstanding loan principle.
|
||||
- **Interest**: The portion that covers the cost of borrowing for the period.
|
||||
- **Fees**: The portion that covers any applicable service fees, management fees, late payment fees, or other charges.
|
||||
- **ValueChange**: The amount by which the payment changes the loan balance.
|
||||
|
||||
When the loan payment is submitted, the lending protocol then checks these parameters:
|
||||
|
||||
- **Timing**: Is the payment on time or late?
|
||||
- **Amount**: Does the payment amount meet the minimum required amount, or exceed it?
|
||||
|
||||
Based on the timing and transaction flags, the lending protocol processes the payment as one of four types:
|
||||
|
||||
- **On-Time Payments**: If the payment is on-time, it's further classified into these payment scenarios:
|
||||
- **Sequential Periodic Payments**: The payment is applied to as many complete payment cycles as possible; cycles are calculated as the amount due each payment period (including fees).
|
||||
- **Overpayments**: After all possible cycles are fully paid, any remaining amount is treated as an overpayment and applied to the principal. This type of payment requires the `lsfLoanOverpayment` flag to be enabled on the `Loan` ledger entry, as well as the `tfLoanOverpayment` flag to be enabled on the `LoanPay` transaction. If these flags are missing, the excess amount is ignored.
|
||||
- **Full Early Repayment**: The payment has the `tfLoanFullPayment` flag set, and the amount covers the remainder of the loan (including fees).
|
||||
- **Late Payments**: The payment is late on a payment cycle. Late payments must be for an exact amount, calculated as:
|
||||
|
||||
`totalDue = periodicPayment + loanServiceFee + latePaymentFee + latePaymentInterest`
|
||||
|
||||
Overpayments aren't permitted on late payments; any excess amount is ignored.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
In scenarios where excess payment amounts are "ignored", the transaction succeeds, but the borrower is only charged on the expected amount.
|
||||
{% /admonition %}
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
251
docs/concepts/tokens/single-asset-vaults.md
Normal file
251
docs/concepts/tokens/single-asset-vaults.md
Normal file
@@ -0,0 +1,251 @@
|
||||
---
|
||||
seo:
|
||||
description: Single asset vaults aggregate assets from multiple depositors and make them available to other on-chain protocols.
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Single Asset Vaults
|
||||
|
||||
A single asset vault is an XRP Ledger primitive that aggregates assets from multiple depositors and makes them available to other on-chain protocols, such as the Lending Protocol. A vault asset can be [XRP](../../introduction/what-is-xrp.md), a [trust line token](../tokens/fungible-tokens/index.md), or an [MPT (Multi-Purpose Token)](../tokens/fungible-tokens/multi-purpose-tokens.md).
|
||||
|
||||
A Vault Owner account manages the vault and can create, update, or delete it as needed. When creating a vault, the Vault Owner can also specify whether shares are transferable or non-transferable. Non-transferable shares cannot be transferred to any other account, and can only be redeemed.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Public vs. Private Vaults
|
||||
|
||||
A vault can be **public** or **private**, depending on the required level of access control.
|
||||
|
||||
In a public vault, anyone can deposit or redeem liquidity as long as they hold sufficient shares. In contrast, a private vault restricts access, allowing only depositors with the necessary [Credentials](../../concepts/decentralized-storage/credentials.md), managed through [Permissioned Domains](./decentralized-exchange/permissioned-domains.md), to deposit assets.
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
If a depositor's credentials expire, they can no longer deposit assets in a private vault, but can always redeem their existing shares.
|
||||
{% /admonition %}
|
||||
|
||||
To prevent the Vault Owner from locking funds away, any shareholder in a private vault can redeem their shares for assets.
|
||||
|
||||
Choosing between a public or private vault depends on your use case. For example, if depositor identity verification is required, use a private vault and issue credentials only to verified accounts.
|
||||
|
||||
## Vault Share Distribution and Redemption
|
||||
|
||||
Depositors can deposit assets to receive shares, which represent their proportional ownership of the vault, or redeem shares for assets.
|
||||
|
||||
[{% inline-svg file="../../img/single-asset-vault-img.svg" /%}](../../img/single-asset-vault-img.svg "Diagram: an example of an asset being deposited into the vault and shares being redeemed.")
|
||||
|
||||
Since the XRP Ledger is an account-based blockchain, all assets must be held by an account. A `Vault` ledger entry cannot hold assets directly, so a [pseudo-account](../accounts/pseudo-accounts.md) is created to hold assets on its behalf. This stand-alone account cannot receive funds or send transactions, and exists solely to store assets and issue shares.
|
||||
|
||||
Each share is represented on-chain as an MPT, issued by the vault's pseudo-account. Since MPTs can only exist as whole number units, the vault uses a `Scale` setting to convert fractional asset amounts into whole number shares.
|
||||
|
||||
The scale behavior varies based on the type of asset held by the vault:
|
||||
|
||||
- **XRP**: Uses a fixed scale that aligns with XRP's native structure, where one share represents one drop.
|
||||
- **Trust Line Token**: Allows configurable precision (default preserves 6 decimal places).
|
||||
- **MPT**: Uses a 1-to-1 relationship between MPT units and shares.
|
||||
|
||||
Depending on the connected protocol, vault shares may be yield-bearing, meaning shareholders could redeem shares for more or less liquidity than they originally deposited. This is because the total asset balance in the vault can grow or shrink over time, affecting the value of each share. However, the vault asset (e.g., USDC, XRP) does not generate yield on its own.
|
||||
|
||||
The value of each share depends on the total assets in the vault:
|
||||
|
||||
- If the vault earns yield over time, shares represent a larger claim, allowing depositors to redeem them for more assets.
|
||||
- If the vault incurs losses, shares hold less value, resulting in lower redemptions.
|
||||
|
||||
A vault could generate yield through mechanisms like lending or staking, with yield paid in the same asset deposited. The specific logic for this depends on how the connected on-chain protocol generates yield. For example, if a vault is used by a lending protocol, it could earn yield from interest paid by borrowers.
|
||||
|
||||
### Exchange Algorithm
|
||||
|
||||
A single asset vault uses an **exchange algorithm** to define how assets convert into shares during deposits and how shares convert back into assets during redemptions.
|
||||
|
||||
A vault's total value can fluctuate due to factors like _unrealized losses_, which impact the exchange rate for deposits and redemptions. To ensure fairness, the algorithm adjusts the exchange rate dynamically, so depositors receive shares or redeem them for assets at a rate that accurately reflects the vault’s true value.
|
||||
|
||||
#### Unrealized Loss
|
||||
|
||||
To prevent depositors from exploiting potential losses by redeeming shares early and shifting the full loss onto the remaining depositors, the vault tracks unrealized losses (or paper loss) using the `LossUnrealized` attribute in the `Vault` ledger entry.
|
||||
|
||||
Because the unrealized loss temporarily decreases the vault's value, a malicious depositor may take advantage of this by depositing assets at a lowered price and redeeming shares once the price increases.
|
||||
|
||||
For example, consider a vault with a total value of $1.0m and total shares of 1.0m. Let's assume the unrealized loss for the vault is $900k:
|
||||
|
||||
1. The new exchange rate is calculated as:
|
||||
|
||||
```js
|
||||
// ExchangeRate = (AssetsTotal - LossUnrealized) / SharesTotal
|
||||
exchangeRate = (1,000,000 - 900,000) / 1,000,000
|
||||
```
|
||||
|
||||
The exchange rate value is now **0.1**.
|
||||
|
||||
2. After the unrealized loss is cleared, the new effective exchange rate would be:
|
||||
|
||||
```js
|
||||
// ExchangeRate = AssetsTotal / SharesTotal
|
||||
exchangeRate = 1,000,000 / 1,000,000
|
||||
```
|
||||
|
||||
The exchange rate is now **1.0**.
|
||||
|
||||
A depositor could deposit $100k assets at a 0.1 exchange rate and get 1.0m shares. Once the unrealized loss is cleared, their shares would be worth $1.0m.
|
||||
|
||||
To mitigate this, the vault uses separate exchange rates for deposits and redemptions.
|
||||
|
||||
#### Exchange Rates
|
||||
|
||||
A single asset vault uses **two distinct exchange rates**:
|
||||
|
||||
- **Deposit Exchange Rate**: Protects new depositors from prior losses and ensures fair share allocation.
|
||||
- **Withdrawal Exchange Rate**: Ensures all shareholders share losses proportionally. Whether redeeming shares or withdrawing assets, the vault always calculates payouts using the actual current value (total assets minus losses), so depositors get their fair share of what's actually in the vault.
|
||||
- **Redemptions**: The vault burns shares so the depositor can receive proportional assets.
|
||||
- **Withdrawals**: The vault determines the shares to burn based on the requested asset amount.
|
||||
|
||||
These exchange rates ensure fairness and prevent manipulation, maintaining the integrity of deposits and redemptions.
|
||||
|
||||
To understand how the exchange rates are applied, here are the key variables used in the calculations:
|
||||
|
||||
- `Γ_assets`: The total balance of assets held within the vault.
|
||||
- `Γ_shares`: The total number of shares currently issued by the vault.
|
||||
- `Δ_assets`: The amount of assets being deposited, withdrawn, or redeemed.
|
||||
- `Δ_shares`: The number of shares being issued or burned.
|
||||
- `l`: The vault's total unrealized loss.
|
||||
- `σ`: The scaling factor (σ = 10<sup>Scale</sup>) used to convert fractional assets into whole number shares.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Deposit" %}
|
||||
The vault computes the number of shares a depositor will receive as follows:
|
||||
|
||||
- **Initial Deposit (Empty Vault)**: For the first deposit into an empty vault, shares are calculated using the scaling factor to properly represent fractional assets as whole numbers.
|
||||
|
||||
```js
|
||||
Δ_shares = Δ_assets * σ // σ = 10^Scale
|
||||
```
|
||||
|
||||
- **Subsequent Deposits**: For all other deposits, shares are calculated proportionally. The resulting share value is rounded **down** to the nearest whole number.
|
||||
|
||||
```js
|
||||
Δ_shares = (Δ_assets * Γ_shares) / Γ_assets
|
||||
```
|
||||
Because the share amount is rounded down, the actual assets taken from the depositor are recalculated. This ensures the depositor isn't overcharged and that new shares are valued against the vault's true value, accounting for any unrealized loss:
|
||||
|
||||
```js
|
||||
Δ_assets = (Δ_shares * (Γ_assets - l)) / Γ_shares
|
||||
```
|
||||
|
||||
After a successful deposit, the _total assets_ and _total shares_ values are updated like so:
|
||||
|
||||
```js
|
||||
Γ_assets = Γ_assets + Δ_assets // New balance of assets in the vault.
|
||||
Γ_shares = Γ_shares + Δ_shares // New share balance in the vault.
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Redeem" %}
|
||||
The vault computes the number of assets returned by burning shares as follows:
|
||||
|
||||
```js
|
||||
Δ_assets = (Δ_shares * (Γ_assets - l)) / Γ_shares
|
||||
```
|
||||
|
||||
After a successful redemption, the _total assets_ and _total shares_ values are updated like so:
|
||||
|
||||
```js
|
||||
Γ_assets = Γ_assets - Δ_assets // New balance of assets in the vault.
|
||||
Γ_shares = Γ_shares - Δ_shares // New share balance in the vault.
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Withdraw" %}
|
||||
When a depositor requests a specific asset amount, the vault uses a two-step process to determine the final payout:
|
||||
|
||||
1. The requested asset amount (`Δ_assets_requested`) is converted into shares.
|
||||
|
||||
```js
|
||||
Δ_shares = (Δ_assets_requested * Γ_shares) / (Γ_assets - l)
|
||||
```
|
||||
|
||||
The calculated share amount is rounded to the **nearest** whole number.
|
||||
|
||||
2. The rounded number of shares is used to calculate the final asset payout (`Δ_assets_out`), using the same logic as a redemption.
|
||||
|
||||
```js
|
||||
Δ_assets_out = (Δ_shares * (Γ_assets - l)) / Γ_shares
|
||||
```
|
||||
|
||||
Due to rounding in step 1, the final payout may differ slightly from the requested amount.
|
||||
|
||||
After a successful withdrawal, the _total asset_ and _total share_ values are updated like so:
|
||||
|
||||
```js
|
||||
Γ_assets = Γ_assets - Δ_assets_out // New balance of assets in the vault.
|
||||
Γ_shares = Γ_shares - Δ_shares // New share balance in the vault.
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### Can a Depositor Transfer Shares to Another Account?
|
||||
|
||||
Vault shares are a first-class asset, meaning that they can be transferred and used in other on-ledger protocols that support MPTs. However, the payee (or the receiver) must have permission to hold both the shares and the underlying asset.
|
||||
|
||||
For example, if a private vault holds USDC, the destination account must belong to the vault’s Permissioned Domain and have permission to hold USDC. Any compliance mechanisms applied to USDC also apply to the shares. If the USDC issuer freezes the payee’s trust line, the payee cannot receive shares representing USDC.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
It is important to remember that a vault must be **configured** to allow share transfers, or this will not be possible.
|
||||
{% /admonition %}
|
||||
|
||||
A depositor can transfer vault shares to another account by making a [Payment](../../references/protocol/transactions/types/payment) transaction. Nothing changes in the way the payment transaction is submitted for transferring vault shares. However, there are new failure scenarios to watch out for if the transaction fails:
|
||||
|
||||
- The vault is private and the payee lacks credentials in the vault's permissioned domain.
|
||||
- The vault shares are configured as non-transferable.
|
||||
- There is a global freeze (trust line tokens) or lock (MPTs) on the underlying asset.
|
||||
- The underlying asset is an MPT and is locked for the payer, payee, or vault pseudo-account.
|
||||
- The underlying asset is a trust line token and the trust line is frozen between the issuer and the payer, payee, or vault pseudo-account.
|
||||
|
||||
If the transfer succeeds and the payee already holds vault shares, their balance increases. Otherwise, a new MPT entry is created for their account.
|
||||
|
||||
## Compliance
|
||||
|
||||
### Frozen Assets
|
||||
|
||||
The issuer of a vault asset can enact a [freeze](./fungible-tokens/freezes) for trust line tokens or [lock an MPT](./fungible-tokens/deep-freeze#how-does-mpt-freeze/lock-behavior-differ-from-iou). When a vault asset is frozen:
|
||||
|
||||
1. Withdrawals can only be made to the asset’s issuer.
|
||||
2. The asset cannot be deposited into the vault.
|
||||
3. Its corresponding shares also cannot be transferred.
|
||||
|
||||
### Clawback
|
||||
|
||||
An asset issuer can perform a [Clawback](../../use-cases/tokenization/stablecoin-issuer#clawback) on vault assets by forcing redemption of shares held by an account. This exchanges the holder's shares for the underlying assets, which are sent directly to the issuer. This mechanism allows asset issuers to recover their issued assets from vault depositors when necessary for fraud prevention or regulatory compliance.
|
||||
|
||||
## Why Use a Single Asset Vault?
|
||||
|
||||
With a single asset vault you don't have to manage liquidity at the protocol level. Instead, you can use the vault to handle deposits, redemptions, and asset tracking separately.
|
||||
|
||||
Vaults handle asset-to-share conversion, ensure accurate pricing, and eliminate the need to add custom logic to calculate exchange rates or account for unrealized losses.
|
||||
|
||||
Depending on the connected on-chain protocol, vaults can be applied to various use cases, such as:
|
||||
|
||||
- Lending markets
|
||||
- Aggregators
|
||||
- Yield-bearing tokens
|
||||
- Asset management
|
||||
|
||||
The only supported use cases right now are _asset management_ and [_lending markets_](./lending-protocol.md).
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Credentials](../../concepts/decentralized-storage/credentials.md) - Define access requirements for private vaults.
|
||||
- [Permissioned Domains](../tokens/decentralized-exchange/permissioned-domains.md) - Control access to private vaults.
|
||||
- [Pseudo-Accounts](../accounts/pseudo-accounts.md) - Special accounts that hold assets on behalf of on-chain protocols.
|
||||
- **References:**
|
||||
- [Vault entry][] - Data structure on the ledger that records vault information.
|
||||
- [VaultClawback transaction][] - Allow asset issuers to recover assets from the vault.
|
||||
- [VaultCreate transaction][] - Create a new vault for aggregating assets.
|
||||
- [VaultDelete transaction][] - Delete an existing vault entry.
|
||||
- [VaultDeposit transaction][] - Add assets to a vault in exchange for shares.
|
||||
- [VaultSet transaction][] - Update the configuration of an existing vault.
|
||||
- [VaultWithdraw transaction][] - Redeem liquidity from a vault.
|
||||
- [vault_info method][] - Retrieve information about a vault and its shares.
|
||||
@@ -10,6 +10,8 @@ status: not_enabled
|
||||
|
||||
XRPL Batch Transactions let you package multiple [transactions](/docs/concepts/transactions) together and execute them as a single unit. It eliminates the risk of partial completion and unexpected outcomes, giving you a more reliable and predictable experience for complex operations. Up to eight transactions can be submitted in a single batch.
|
||||
|
||||
{% amendment-disclaimer name="Batch" /%}
|
||||
|
||||
## XRPL Batch Use Cases
|
||||
|
||||
Some potential uses for `Batch` include the following.
|
||||
|
||||
31
docs/img/lending-protocol-diagram.svg
Normal file
31
docs/img/lending-protocol-diagram.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 100 KiB |
1
docs/img/single-asset-vault-img.svg
Normal file
1
docs/img/single-asset-vault-img.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 39 KiB |
@@ -13,7 +13,7 @@ In its default configuration, the `rippled` server automatically deletes outdate
|
||||
|
||||
## Warnings
|
||||
|
||||
Storing full history is expensive. As of 2023-07-19, the full history of the XRP Ledger occupies approximately **26 terabytes** of disk space, which must be entirely stored on fast solid state disk drives for proper server performance. Such a large amount of solid state storage is not cheap, and the total amount of history you must store increases by approximately 12 GB per day.
|
||||
Storing full history is expensive. As of 2026-01-25, the full history of the XRP Ledger occupies approximately **39 terabytes** of disk space, which must be entirely stored on fast solid state disk drives for proper server performance. Such a large amount of solid state storage is not cheap, and the total amount of history you must store increases by approximately 12 GB per day.
|
||||
|
||||
Additionally, storing full history in NuDB requires single files that are larger than the 16 TB limit of ext4 filesystems, which is the default on many Linux distributions. You must use a filesystem with a larger single-file limit, such as XFS (recommended) or ZFS.
|
||||
|
||||
|
||||
@@ -81,10 +81,10 @@ Before you install Clio, you must meet the following requirements.
|
||||
|
||||
```
|
||||
gpg: WARNING: no command supplied. Trying to guess what you mean ...
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@ Before you install `rippled`, you must meet the [System Requirements](system-req
|
||||
The output should include an entry for Ripple such as the following:
|
||||
|
||||
```
|
||||
pub rsa3072 2019-02-14 [SC] [expires: 2026-02-17]
|
||||
C0010EC205B35A3310DC90DE395F97FFCCAFD9A2
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub rsa3072 2019-02-14 [E] [expires: 2026-02-17]
|
||||
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
|
||||
E057C1CF72B0DF1A4559E8577DEE9236AB06FAA6
|
||||
uid TechOps Team at Ripple <techops+rippled@ripple.com>
|
||||
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
|
||||
```
|
||||
|
||||
In particular, make sure that the fingerprint matches. (In the above example, the fingerprint is on the second line, starting with `C001`.)
|
||||
|
||||
@@ -87,6 +87,7 @@ The request includes the following parameters:
|
||||
| `Field` | Type | Description |
|
||||
|:---------------|:--------|:--------------------------------------------------|
|
||||
| `tx_json` | Object | [Transaction definition](../../../protocol/transactions/index.md) in JSON format |
|
||||
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
|
||||
| `secret` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
|
||||
| `seed` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
|
||||
| `seed_hex` | String | _(Optional)_ The secret seed of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |
|
||||
|
||||
@@ -94,6 +94,7 @@ The request includes the following parameters:
|
||||
|:-------------|:---------------------|:---------------------------------------|
|
||||
| `account` | String - [Address][] | The address which is providing the signature. |
|
||||
| `tx_json` | Object | The [Transaction](../../../protocol/transactions/index.md) to sign. Unlike using the [sign method][], all fields of the transaction must be provided, including `Fee` and `Sequence`. The transaction must include the field `SigningPubKey` with an empty string as the value. The object may optionally contain a `Signers` array with previously-collected signatures. |
|
||||
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
|
||||
| `secret` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
|
||||
| `seed` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
|
||||
| `seed_hex` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |
|
||||
|
||||
@@ -115,6 +115,11 @@ Use these methods to perform convenient tasks, such as ping and random number ge
|
||||
* **[`ping`](utility-methods/ping.md)** - Confirm connectivity with the server.
|
||||
* **[`random`](utility-methods/random.md)** - Generate a random number.
|
||||
|
||||
## [Vault Methods](vault-methods/index.md)
|
||||
|
||||
Use these methods to retrieve vault information.
|
||||
|
||||
* **[`vault_info`](vault-methods/vault_info.md)** - Get information about a specific vault.
|
||||
|
||||
## Deprecated Methods
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -81,6 +81,7 @@ The request includes the following parameters:
|
||||
| `Field` | Type | Description |
|
||||
|:---------------|:--------|:--------------------------------------------------|
|
||||
| `tx_json` | Object | [Transaction definition](../../../protocol/transactions/index.md) in JSON format, optionally omitting any auto-fillable fields. |
|
||||
| `signature_target` | String | _(Optional)_ Specifies where in the transaction metadata the signature information should be stored. Currently, the only valid value is `CounterpartySignature`. |
|
||||
| `secret` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Do not send your secret to untrusted servers or through unsecured network connections. Cannot be used with `key_type`, `seed`, `seed_hex`, or `passphrase`. |
|
||||
| `seed` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in the XRP Ledger's [base58][] format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed_hex`, or `passphrase`. |
|
||||
| `seed_hex` | String | _(Optional)_ Secret key of the account supplying the transaction, used to sign it. Must be in hexadecimal format. If provided, you must also specify the `key_type`. Cannot be used with `secret`, `seed`, or `passphrase`. |
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
metadata:
|
||||
indexPage: true
|
||||
---
|
||||
# Vault Methods
|
||||
|
||||
A `Vault` object in the XRP Ledger represents the state of a tokenized vault. Use these methods to interact with vaults.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -0,0 +1,274 @@
|
||||
---
|
||||
seo:
|
||||
description: Retrieve information about a vault, its owner, available assets, and details on issued shares.
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
---
|
||||
|
||||
# vault_info
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/master/src/xrpld/rpc/handlers/VaultInfo.cpp "Source")
|
||||
|
||||
The `vault_info` command retrieves information about a vault, its owner, available assets, and details on issued shares. All information retrieved is relative to a particular version of the ledger. {% badge href="https://github.com/XRPLF/rippled/releases/tag/3.1.0" %}New in: rippled 3.1.0{% /badge %}
|
||||
|
||||
## Request Format
|
||||
|
||||
An example of the request format:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"command": "vault_info",
|
||||
"vault_id": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
{
|
||||
"method": "vault_info",
|
||||
"params": [
|
||||
{
|
||||
"vault_id": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```sh
|
||||
#Syntax: vault_info [<vault_id>]
|
||||
rippled vault_info 9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% try-it method="vault_info" server="devnet" /%}
|
||||
|
||||
The request includes the following parameters:
|
||||
|
||||
| Field | Type | Required? | Description |
|
||||
| :--------- | :------------------- | :-------- | :------------------------------------------------------ |
|
||||
| `vault_id` | String | No | The [ledger entry ID][] of the `Vault` to be returned. |
|
||||
| `owner` | String - [Address][] | No | The account address of the `Vault` owner. |
|
||||
| `seq` | Number | No | The transaction sequence number that created the vault. |
|
||||
|
||||
You can provide either the `vault_id`, or both `owner` and `seq` values in the request.
|
||||
|
||||
## Response Format
|
||||
|
||||
An example of a successful response:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="WebSocket" %}
|
||||
```json
|
||||
{
|
||||
"result": {
|
||||
"ledger_current_index": 3280200,
|
||||
"validated": false,
|
||||
"vault": {
|
||||
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
|
||||
},
|
||||
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
|
||||
"Flags": 65536,
|
||||
"LedgerEntryType": "Vault",
|
||||
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 3113728,
|
||||
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
|
||||
"WithdrawalPolicy": 1,
|
||||
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
|
||||
"shares": {
|
||||
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
|
||||
"Flags": 60,
|
||||
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 1,
|
||||
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
|
||||
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": "success",
|
||||
"type": "response"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="JSON-RPC" %}
|
||||
```json
|
||||
200 OK
|
||||
|
||||
{
|
||||
"result": {
|
||||
"ledger_hash": "DC8D26A6DC85C70A112F5D798A3B5AF599A730098AFCC20CE18BFE6ADA5E66F9",
|
||||
"ledger_index": 3279463,
|
||||
"status": "success",
|
||||
"validated": true,
|
||||
"vault": {
|
||||
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
|
||||
},
|
||||
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
|
||||
"Flags": 65536,
|
||||
"LedgerEntryType": "Vault",
|
||||
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 3113728,
|
||||
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
|
||||
"WithdrawalPolicy": 1,
|
||||
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
|
||||
"shares": {
|
||||
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
|
||||
"Flags": 60,
|
||||
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 1,
|
||||
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
|
||||
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Commandline" %}
|
||||
```json
|
||||
Loading: "/etc/rippled.cfg"
|
||||
Connecting to 127.0.0.1:5005
|
||||
|
||||
{
|
||||
"result": {
|
||||
"ledger_current_index": 3280200,
|
||||
"validated": false,
|
||||
"vault": {
|
||||
"Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"Asset": {
|
||||
"mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
|
||||
},
|
||||
"Data": "50726976617465207661756C7420666F72207475746F7269616C73",
|
||||
"Flags": 65536,
|
||||
"LedgerEntryType": "Vault",
|
||||
"Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 3113728,
|
||||
"ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
|
||||
"WithdrawalPolicy": 1,
|
||||
"index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
|
||||
"shares": {
|
||||
"DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
|
||||
"Flags": 60,
|
||||
"Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
|
||||
"LedgerEntryType": "MPTokenIssuance",
|
||||
"MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
|
||||
"OutstandingAmount": "0",
|
||||
"OwnerNode": "0",
|
||||
"PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
|
||||
"PreviousTxnLgrSeq": 3113735,
|
||||
"Sequence": 1,
|
||||
"index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
|
||||
"mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": "success"
|
||||
}
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The response follows the [standard format][], with a successful result containing the following fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| :--------------------- | :--------------- | :---------- |
|
||||
| `ledger_hash` | [Hash][] | _(Omitted if either `ledger_current_index` or `ledger_index` is provided instead)_ The identifying hash of the ledger version that was used when retrieving this data. |
|
||||
| `ledger_current_index` | [Ledger Index][] | _(Omitted if `ledger_index` is provided instead)_ The [ledger index][] of the current in-progress ledger, which was used when retrieving this information. |
|
||||
| `ledger_index` | [Ledger Index][] | _(Omitted if `ledger_current_index` is provided instead)_ The [ledger index][] of the ledger version used when retrieving this information. |
|
||||
| `validated` | Boolean | True if this data is from a validated ledger version; if omitted or set to false, this data is not final. |
|
||||
| `vault` | Object | The [**Vault Description Object**](#vault-description-object) that represents the current status of the vault. |
|
||||
|
||||
### Vault Description Object
|
||||
|
||||
The `vault` field is an object describing the current status of a `Vault` entry in the ledger, and contains the following fields:
|
||||
|
||||
| `Field` | Type | Description |
|
||||
| :--------------------- | :------------------- | :---------- |
|
||||
| `Account` | String - [Address][] | The address of the vault's pseudo-account. |
|
||||
| `Asset` | Object | The [**Asset**](#asset-object) of the vault. An asset can be XRP, a trust line token, or an MPT. |
|
||||
| `AssetsAvailable` | Number | The asset amount that is available in the vault. |
|
||||
| `AssetsMaximum` | Number | The maximum asset amount that can be held in the vault. If set to 0, this indicates there is no cap. |
|
||||
| `AssetsTotal` | Number | The total value of the vault. |
|
||||
| `Flags` | String | Set of bit-flags for this ledger object. |
|
||||
| `LossUnrealized` | Number | The potential loss amount that is not yet realized, expressed as the vault's asset. |
|
||||
| `ShareMPTID` | String | The identifier of the share `MPTokenIssuance` object. |
|
||||
| `WithdrawalPolicy` | String | Indicates the withdrawal strategy used by the vault. |
|
||||
| `index` | String | The unique index of the vault ledger entry. |
|
||||
| `shares` | Object | A [**Shares Object**](#shares-object) containing details about the vault's issued shares. |
|
||||
| `Scale` | Number | Specifies decimal precision for share calculations. Assets are multiplied by 10<sup>Scale</sup > to convert fractional amounts into whole number shares. For example, with a `Scale` of `6`, depositing 20.3 units creates 20,300,000 shares (20.3 × 10<sup>Scale</sup >). For **trust line tokens** this can be configured at vault creation, and valid values are between 0-18, with the default being `6`. For **XRP** and **MPTs**, this is fixed at `0`. |
|
||||
|
||||
### Asset Object
|
||||
|
||||
The `asset` object contains the following nested fields:
|
||||
|
||||
| `Field` | Type | Description |
|
||||
| :--------------------- | :------------------- | :---------- |
|
||||
| `currency` | String | _(Omitted if the asset is an MPT)_ The currency code of the asset stored in the vault. |
|
||||
| `issuer` | String - [Address][] | _(Omitted if the asset is XRP or an MPT)_ The address of the asset issuer. |
|
||||
| `mpt_issuance_id` | String | _(Omitted if the asset is XRP or a trust line token)_ The identifier of the asset's `MPTokenIssuance` object. |
|
||||
|
||||
### Shares Object
|
||||
|
||||
The `shares` object contains the following nested fields:
|
||||
|
||||
| `Field` | Type | Description |
|
||||
| :--------------------- | :--------------- | :---------- |
|
||||
| `DomainID` | String | _(Omitted if the vault is public)_ The permissioned domain associated with the vault's shares. |
|
||||
| `Flags` | Number | Set of bit-flags for this ledger object. |
|
||||
| `Issuer` | String | The address issuing the shares. This is always the vault's pseudo-account. |
|
||||
| `LedgerEntryType` | String | The ledger object type (i.e., `MPTokenIssuance`). |
|
||||
| `OutstandingAmount` | String | The total outstanding shares issued. |
|
||||
| `OwnerNode` | String | Identifies the page where this item is referenced in the owner's directory. |
|
||||
| `PreviousTxnID` | String | Identifies the transaction ID that most recently modified this object. |
|
||||
| `PreviousTxnLgrSeq` | Number | The sequence of the ledger that contains the transaction that most recently modified this object. |
|
||||
| `Sequence` | Number | The transaction sequence number that created the shares. |
|
||||
| `index` | String | The unique index of the shares ledger entry. |
|
||||
| `mpt_issuance_id` | String | The identifier of the `MPTokenIssuance` object. This is always equal to the vault's `ShareMPTID`. |
|
||||
| `AssetScale` | Number | The decimal precision for share calculations. |
|
||||
|
||||
## Possible Errors
|
||||
|
||||
- Any of the [universal error types][].
|
||||
- `invalidParams` - One or more fields are specified incorrectly, or one or more required fields are missing.
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -39,7 +39,7 @@ In addition to the [common fields](../common-fields.md), {% code-page-name /%} e
|
||||
|:------------------------------|:----------|:------------------|:----------|:-------------|
|
||||
| `Account` | String | AccountID | Yes | The identifying (classic) address of this [account](../../../../concepts/accounts/index.md). |
|
||||
| `AccountTxnID` | String | UInt256 | No | The identifying hash of the transaction most recently sent by this account. This field must be enabled to use the [`AccountTxnID` transaction field](../../transactions/common-fields.md#accounttxnid). To enable it, send an [AccountSet transaction with the `asfAccountTxnID` flag enabled](../../transactions/types/accountset.md#accountset-flags). |
|
||||
| `AMMID` | String | UInt256 | No | {% amendment-disclaimer name="AMM" /%} The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified. If present, indicates that this is a special AMM AccountRoot; always omitted on non-AMM accounts. |
|
||||
| `AMMID` | String | UInt256 | No | {% amendment-disclaimer name="AMM" /%} The ledger entry ID of the corresponding AMM ledger entry. Set during account creation; cannot be modified. If present, indicates that this is a special AMM [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md) AccountRoot; always omitted on non-AMM accounts. |
|
||||
| `Balance` | String | Amount | No | The account's current [XRP balance in drops][XRP, in drops], represented as a string. |
|
||||
| `BurnedNFTokens` | Number | UInt32 | No | How many total of this account's issued [non-fungible tokens](../../../../concepts/tokens/nfts/index.md) have been burned. This number is always equal or less than `MintedNFTokens`. |
|
||||
| `Domain` | String | Blob | No | A domain associated with this account. In JSON, this is the hexadecimal for the ASCII representation of the domain. [Cannot be more than 256 bytes in length.](https://github.com/XRPLF/rippled/blob/70d5c624e8cf732a362335642b2f5125ce4b43c1/include/xrpl/protocol/Protocol.h#L98) |
|
||||
@@ -53,15 +53,18 @@ In addition to the [common fields](../common-fields.md), {% code-page-name /%} e
|
||||
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes |The [index of the ledger][Ledger Index] that contains the transaction that most recently modified this object. |
|
||||
| `RegularKey` | String | AccountID | No | The address of a [key pair](../../../../concepts/accounts/cryptographic-keys.md) that can be used to sign transactions for this account instead of the master key. Use a [SetRegularKey transaction][] to change this value. |
|
||||
| `Sequence` | Number | UInt32 | Yes | The [sequence number](../../data-types/basic-data-types.md#account-sequence) of the next valid transaction for this account. |
|
||||
| `TicketCount` | Number | UInt32 | No | How many [Tickets](../../../../concepts/accounts/tickets.md) this account owns in the ledger. This is updated automatically to ensure that the account stays within the hard limit of 250 Tickets at a time. This field is omitted if the account has zero Tickets. {% amendment-disclaimer name="TicketBatch" /%} |
|
||||
| `TickSize` | Number | UInt8 | No | How many significant digits to use for exchange rates of Offers involving currencies issued by this address. Valid values are `3` to `15`, inclusive. {% amendment-disclaimer name="TickSize" /%} |
|
||||
| `TicketCount` | Number | UInt32 | No | How many [Tickets](../../../../concepts/accounts/tickets.md) this account owns in the ledger. This is updated automatically to ensure that the account stays within the hard limit of 250 Tickets at a time. This field is omitted if the account has zero Tickets. |
|
||||
| `TickSize` | Number | UInt8 | No | How many significant digits to use for exchange rates of Offers involving currencies issued by this address. Valid values are `3` to `15`, inclusive. |
|
||||
| `TransferRate` | Number | UInt32 | No | A [transfer fee](../../../../concepts/tokens/fungible-tokens/transfer-fees.md) to charge other users for sending currency issued by this account to each other. |
|
||||
| `VaultID` | String | UInt256 | No | {% amendment-disclaimer name="SingleAssetVault" /%} The ID of the `Vault` entry associated with this account. Set during account creation; cannot be modified. If present, indicates that this is a special Vault [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md) AccountRoot; always omitted on non-Vault accounts. |
|
||||
| `WalletLocator` | String | UInt256 | No | An arbitrary 256-bit value that users can set. |
|
||||
| `WalletSize` | Number | UInt32 | No | Unused. (The code supports this field but there is no way to set it.) |
|
||||
|
||||
## Special AMM AccountRoot Entries
|
||||
## Special AMM AccountRoot (Pseudo-Account)
|
||||
|
||||
Automated Market Makers use an AccountRoot ledger entry to issue their LP Tokens and hold the assets in the AMM pool, and an [AMM ledger entry](amm.md) for tracking some of the details of the AMM. The address of an AMM's AccountRoot is randomized so that users cannot identify and fund the address in advance of the AMM being created. Unlike normal accounts, AMM AccountRoot objects are created with the following settings:
|
||||
{% amendment-disclaimer name="AMM" /%}
|
||||
|
||||
Automated Market Makers use an AccountRoot ledger entry (pseudo-account) to issue their LP Tokens and hold the assets in the AMM pool, and an [AMM ledger entry](amm.md) for tracking some of the details of the AMM. The address of an AMM's AccountRoot is randomized so that users cannot identify and fund the address in advance of the AMM being created. Unlike normal accounts, AMM AccountRoot objects are created with the following settings:
|
||||
|
||||
- `lsfDisableMaster` **enabled** and no means of authorizing transactions. This ensures no one can control the account directly, and it cannot send transactions.
|
||||
- `lsfDepositAuth` **enabled** and no accounts preauthorized. This ensures that the only way to add money to the AMM Account is using the [AMMDeposit transaction][].
|
||||
@@ -76,7 +79,21 @@ In addition, the following special rules apply to an AMM's AccountRoot entry:
|
||||
|
||||
Other than those exceptions, these accounts are like ordinary accounts; the LP Tokens they issue behave like other [tokens](../../../../concepts/tokens/index.md) except that those tokens can also be used in AMM-related transactions. You can check an AMM's balances and the history of transactions that affected it the same way you would with a regular account.
|
||||
|
||||
{% amendment-disclaimer name="AMM" /%}
|
||||
## Special Vault AccountRoot (Pseudo-Account)
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
Vaults use an AccountRoot ledger entry (pseudo-account) to issue their shares and hold the assets deposited into the vault, and a [Vault entry][] for tracking the vault's configuration and state. The address of a vault's AccountRoot is randomized so that users cannot identify and fund the address in advance of the vault being created. Unlike normal accounts, vault AccountRoot objects are created with the following settings:
|
||||
|
||||
- `lsfDisableMaster` **enabled** and no means of authorizing transactions. This ensures no one can control the account directly, and it cannot send transactions.
|
||||
- `lsfDepositAuth` **enabled** and no accounts pre-authorized. This ensures that the only way to add money to the vault's AccountRoot is using the [VaultDeposit transaction][].
|
||||
- `lsfDefaultRipple` **enabled**. This enables rippling for the vault's pseudo-account.
|
||||
|
||||
In addition, the following special rules apply to a Vault's AccountRoot entry:
|
||||
|
||||
- The vault owner account must pay one [incremental owner reserve](../../../../concepts/accounts/reserves#base-reserve-and-owner-reserve) (currently {% $env.PUBLIC_OWNER_RESERVE %}) when creating the vault to cover the pseudo-account.
|
||||
- The `Sequence` number is always `0` and never changes, preventing the pseudo-account from submitting transactions.
|
||||
- A pseudo-account is automatically deleted when the vault is deleted, and cannot exist independently of a Vault entry.
|
||||
|
||||
## AccountRoot Flags
|
||||
|
||||
@@ -104,7 +121,7 @@ AccountRoot objects can have the following flags combined in the `Flags` field:
|
||||
|
||||
## {% $frontmatter.seo.title %} Reserve
|
||||
|
||||
The [reserve](../../../../concepts/accounts/reserves.md) for an AccountRoot entry is the base reserve, currently {% $env.PUBLIC_BASE_RESERVE %}, except in the case of a special AMM AccountRoot.
|
||||
The [reserve](../../../../concepts/accounts/reserves.md) for an AccountRoot entry is the base reserve, currently {% $env.PUBLIC_BASE_RESERVE %}, except in the case of a special AMM or Vault AccountRoot.
|
||||
|
||||
This XRP cannot be sent to others but it can be burned as part of the [transaction cost][].
|
||||
|
||||
@@ -117,6 +134,9 @@ The ID of an AccountRoot entry is the [SHA-512Half][] of the following values, c
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [Pseudo-Accounts](../../../../concepts/accounts/pseudo-accounts.md)
|
||||
|
||||
- **Transactions:**
|
||||
- [AccountSet transaction][]
|
||||
- [AccountDelete transaction][]
|
||||
|
||||
121
docs/references/protocol/ledger-data/ledger-entry-types/loan.md
Normal file
121
docs/references/protocol/ledger-data/ledger-entry-types/loan.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
seo:
|
||||
description: A Loan ledger entry represents the terms of a loan between a borrower and loan issuer.
|
||||
labels:
|
||||
- Decentralized Finance
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Loan
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L543-L623 "Source")
|
||||
|
||||
A `Loan` ledger entry defines the state of an on-chain loan agreement between a _Loan Broker_ and a _Borrower_. It contains all the details of the loan, such as fees and interest rates. You can create a `Loan` ledger entry with the [LoanSet transaction][].
|
||||
|
||||
The `Loan` ledger entry is tracked in two [Owner Directories](./directorynode.md):
|
||||
1. The owner directory of the _Borrower_ on the loan.
|
||||
2. The owner directory of the `LoanBroker` pseudo-account.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"LedgerEntryType": "Loan",
|
||||
"LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Flags": "0",
|
||||
"PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
|
||||
"PreviousTxnLgrSeq": 12345678,
|
||||
"LoanSequence": 1,
|
||||
"OwnerNode": 2,
|
||||
"LoanBrokerNode": 1,
|
||||
"LoanBrokerID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
|
||||
"Borrower": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"LoanOriginationFee": 100,
|
||||
"LoanServiceFee": 10,
|
||||
"LatePaymentFee": 5,
|
||||
"ClosePaymentFee": 20,
|
||||
"OverpaymentFee": 5,
|
||||
"InterestRate": 500,
|
||||
"LateInterestRate": 1000,
|
||||
"CloseInterestRate": 200,
|
||||
"OverpaymentInterestRate": 5,
|
||||
"StartDate": 1234567890,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"PreviousPaymentDueDate": 1234587890,
|
||||
"NextPaymentDueDate": 1234597890,
|
||||
"PaymentRemaining": 12,
|
||||
"PrincipalOutstanding": 10000,
|
||||
"TotalValueOutstanding": 12000,
|
||||
"ManagementFeeOutstanding": 2000,
|
||||
"PeriodicPayment": 1000
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common ledger entry fields][], {% code-page-name /%} entries have the following fields:
|
||||
|
||||
| Name | JSON Type | Internal Type | Required? | Description |
|
||||
| :-------------------- | :-------- | :------------ | :-------- | :-----------|
|
||||
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
|
||||
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
|
||||
| `LoanSequence` | Number | UInt32 | Yes | The sequence number of the loan. |
|
||||
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's 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. |
|
||||
| `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. |
|
||||
| `LoanOriginationFee` | String | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
|
||||
| `LoanServiceFee` | String | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
|
||||
| `LatePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
|
||||
| `ClosePaymentFee` | String | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
|
||||
| `OverpaymentFee` | Number | UInt32 | Yes | The fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `InterestRate` | Number | UInt32 | Yes | The annualized interest rate of the loan, in 1/10th basis points. |
|
||||
| `LateInterestRate` | Number | UInt32 | Yes | The premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `CloseInterestRate` | Number | UInt32 | Yes | The interest rate 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 | Yes | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `StartDate` | Number | UInt32 | Yes | The timestamp of when the loan started, in [seconds since the Ripple Epoch][]. |
|
||||
| `PaymentInterval` | Number | UInt32 | Yes | The number of seconds between loan payments. |
|
||||
| `GracePeriod` | Number | UInt32 | Yes | The number of seconds after a loan payment is due before the loan defaults. |
|
||||
| `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. |
|
||||
| `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. |
|
||||
| `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. |
|
||||
| `PrincipalOutstanding` | String | Number | Yes | The principal amount still owed on the loan. |
|
||||
| `TotalValueOutstanding` | String | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
|
||||
| `ManagementFeeOutstanding` | String | Number | Yes | The remaining management fee owed to the loan broker. |
|
||||
| `PeriodicPayment` | String | Number | Yes | The amount due for each payment interval. |
|
||||
| `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" %}
|
||||
When the loan broker discovers that the borrower can't make an upcoming payment, they can impair the loan to register a "paper loss" with the vault. The impairment mechanism moves up the `NextPaymentDueDate` to the time the loan is impaired, allowing the loan to default quicker. However, if the borrower makes a payment in the subsequent `GracePeriod`, the impairment status is removed.
|
||||
{% /admonition %}
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
{% code-page-name /%} entries can have the following flags:
|
||||
|
||||
| Field Name | Hex Value | Decimal Value | Description |
|
||||
|:---------------------|:-------------|:--------------|:------------|
|
||||
| `lsfLoanDefault` | `0x00010000` | `65536` | Indicates the loan is defaulted. |
|
||||
| `lsfLoanImpaired` | `0x00020000` | `131072` | Indicates the loan is impaired. |
|
||||
| `lsfLoanOverpayment` | `0x00040000` | `262144` | Indicates the loan supports overpayments. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Reserve
|
||||
|
||||
`Loan` entries incur one owner reserve from the borrower.
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} ID Format
|
||||
|
||||
The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order:
|
||||
|
||||
- The `Loan` space key `0x004C`.
|
||||
- The `LoanBrokerID` of the associated `LoanBroker` ledger entry.
|
||||
- The `LoanSequence` number of the `LoanBroker` ledger entry.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
seo:
|
||||
description: A LoanBroker ledger entry represents the configuration and state of a lending protocol instance.
|
||||
labels:
|
||||
- Decentralized Finance
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBroker
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L519-L537 "Source")
|
||||
|
||||
A `LoanBroker` ledger entry defines the configuration and state of a lending protocol instance. It tracks details such as fees and first-loss capital cover. You can create a `LoanBroker` object with the [LoanBrokerSet transaction][].
|
||||
|
||||
The `LoanBroker` entry is tracked in an [Owner Directory](./directorynode.md) owned by the account that submitted the `LoanBrokerSet` transaction. To facilitate lookup, it is also tracked in the `OwnerDirectory` of the associated vault's _pseudo-account_.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
The lending protocol uses the pseudo-account of the associated `Vault` entry to hold the first-loss capital.
|
||||
{% /admonition %}
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"LedgerEntryType": "LoanBroker",
|
||||
"LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Flags": "0",
|
||||
"PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
|
||||
"PreviousTxnLgrSeq": 12345678,
|
||||
"Sequence": 1,
|
||||
"LoanSequence": 2,
|
||||
"OwnerNode": 2,
|
||||
"VaultNode": 1,
|
||||
"VaultID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
|
||||
"Account": "rBROKER9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Owner": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865206C6F616E62726F6B65722E",
|
||||
"ManagementFeeRate": 100,
|
||||
"OwnerCount": 3,
|
||||
"DebtTotal": 50000,
|
||||
"DebtMaximum": 100000,
|
||||
"CoverAvailable": 10000,
|
||||
"CoverRateMinimum": 1000,
|
||||
"CoverRateLiquidation": 500
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common ledger entry fields][], {% code-page-name /%} entries have the following fields:
|
||||
|
||||
| Name | JSON Type | Internal Type | Required? | Description |
|
||||
| :-------------------- | :-------- | :------------ | :-------- | :-----------|
|
||||
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
|
||||
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
|
||||
| `Sequence` | Number | UInt32 | Yes | The transaction sequence number that created the LoanBroker. |
|
||||
| `LoanSequence` | Number | UInt32 | Yes | A sequential identifier for `Loan` ledger entires, incremented each time a new loan is created by this `LoanBroker`. |
|
||||
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's directory. |
|
||||
| `VaultNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `Vault` pseudo-account owner's directory. |
|
||||
| `VaultID` | String | Hash256 | Yes | The ID of the vault that provides the loaned assets. |
|
||||
| `Account` | String | AccountID | Yes | The address of the `LoanBroker` pseudo-account. |
|
||||
| `Owner` | String | AccountID | Yes | The account address of the vault owner. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. |
|
||||
| `ManagementFeeRate` | Number | UInt16 | No | The fee charged by the lending protocol, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `OwnerCount` | Number | UInt32 | Yes | The number of active loans issued by the LoanBroker. |
|
||||
| `DebtTotal` | String | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
|
||||
| `DebtMaximum` | String | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
|
||||
| `CoverAvailable` | String | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
|
||||
| `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%. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} ledger entries.
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Reserve
|
||||
|
||||
`Loan` entries incur one owner reserve from the account that creates it.
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} ID Format
|
||||
|
||||
The ID of a `LoanBroker` entry is the [SHA512-Half][] of the following values, concatenated in order:
|
||||
|
||||
- The `LoanBroker` space key `0x006C`.
|
||||
- The [AccountID][] of the account submitting the `LoanBrokerSet` transaction.
|
||||
- The transaction `Sequence` number. If the transaction used a [Ticket][], the `TicketSequence` value is used instead.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
150
docs/references/protocol/ledger-data/ledger-entry-types/vault.md
Normal file
150
docs/references/protocol/ledger-data/ledger-entry-types/vault.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
seo:
|
||||
description: A Vault object defines the state of a tokenized vault.
|
||||
labels:
|
||||
- Vault
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Vault
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/include/xrpl/protocol/detail/ledger_entries.macro#L493-L511 "Source")
|
||||
|
||||
A {% code-page-name /%} object defines the state of a tokenized vault. It contains key details such as available assets, shares, total value, and other relevant information. You can create a {% code-page-name /%} object with the [VaultCreate](../../transactions/types/vaultcreate.md) transaction.
|
||||
|
||||
The {% code-page-name /%} object is tracked in an [Owner Directory](../../../protocol/ledger-data/ledger-entry-types/directorynode) owned by the Vault Owner account.
|
||||
Additionally, to facilitate `Vault` object lookup, the object is tracked in the owner directory of the vault's [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md).
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example Vault JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"LedgerEntryType": "Vault",
|
||||
"Account": "rwCNM7SeUHTajEBQDiNqxDG8p1Mreizw85",
|
||||
"Asset": {
|
||||
"currency": "USD",
|
||||
"issuer": "rXJSJiZMxaLuH3kQBUV5DLipnYtrE6iVb"
|
||||
},
|
||||
"AssetsAvailable": "0",
|
||||
"AssetsMaximum": "1000000",
|
||||
"AssetsTotal": "0",
|
||||
"Data": "5661756C74206D65746164617461",
|
||||
"Flags": 0,
|
||||
"LossUnrealized": "0",
|
||||
"Owner": "rNGHoQwNG753zyfDrib4qDvvswbrtmV8Es",
|
||||
"OwnerNode": "0",
|
||||
"Scale": 6,
|
||||
"Sequence": 200370,
|
||||
"ShareMPTID": "0000000169F415C9F1AB6796AB9224CE635818AFD74F8175",
|
||||
"WithdrawalPolicy": 1,
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common ledger entry fields](../../../protocol/ledger-data/common-fields), {% code-page-name /%} entries have the following fields:
|
||||
|
||||
| Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :------------------ | :------------ | :---------------- | :-------- | -----------------|
|
||||
| `PreviousTxnID` | String | Hash256 | Yes | Identifies the transaction ID that most recently modified this object. |
|
||||
| `PreviousTxnLgrSeq` | Number | UInt32 | Yes | The sequence of the ledger that contains the transaction that most recently modified this object. |
|
||||
| `Sequence` | Number | UInt32 | Yes | The transaction sequence number that created the vault. |
|
||||
| `OwnerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the owner's directory. |
|
||||
| `Owner` | String | AccountID | Yes | The account address of the Vault Owner. |
|
||||
| `Account` | String | AccountID | Yes | The address of the vault's pseudo-account. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata, in hex format, about the vault. Limited to 256 bytes. See [Data Field Format](#data-field-format) for more information. |
|
||||
| `Asset` | Object | Issue | Yes | The asset of the vault. The vault supports XRP, trust line tokens, and MPTs. |
|
||||
| `AssetsTotal` | Number | Number | Yes | The total value of the vault. |
|
||||
| `AssetsAvailable` | Number | Number | Yes | The asset amount that is available in the vault. |
|
||||
| `AssetsMaximum` | Number | Number | No | The maximum asset amount that can be held in the vault. If set to 0, this indicates there is no cap. |
|
||||
| `LossUnrealized` | Number | Number | Yes | The potential loss amount that is not yet realized, expressed as the vault's asset. Only a protocol connected to the vault can modify this attribute. |
|
||||
| `ShareMPTID` | String | UInt192 | Yes | The identifier of the share `MPTokenIssuance` object. |
|
||||
| `WithdrawalPolicy` | String | UInt8 | Yes | Indicates the withdrawal strategy used by the vault. |
|
||||
| `Scale` | Number | UInt8 | No | Specifies decimal precision for share calculations. Assets are multiplied by 10<sup>Scale</sup > to convert fractional amounts into whole number shares. For example, with a `Scale` of `6`, depositing 20.3 units creates 20,300,000 shares (20.3 × 10<sup>Scale</sup >). For **trust line tokens** this can be configured at vault creation, and valid values are between 0-18, with the default being `6`. For **XRP** and **MPTs**, this is fixed at `0`. See [Scaling Factor](#scaling-factor) for more information. |
|
||||
|
||||
### Data Field Format
|
||||
|
||||
While any data structure is allowed in the `Data` field, the following format is recommended:
|
||||
|
||||
| Field Name | Key | Type | Description |
|
||||
| ---------- | --- | ------ | ------------------------------------------------------------------------------------------ |
|
||||
| Name | n | String | Human-readable name of the vault. Should clearly reflect the vault's strategy or mandate. |
|
||||
| Website | w | String | Website associated with the vault. Omit protocol (`https://`) and `www` to conserve space. |
|
||||
|
||||
To fit within the 256-byte limit, vault metadata should use the _compressed_ JSON keys.
|
||||
|
||||
Following this format helps XRPL explorers and other tools parse and display vault information in a standardized way, improving discoverability and user experience.
|
||||
|
||||
#### Example JSON
|
||||
|
||||
For a vault named "LATAM Fund II" with website "examplefund.com":
|
||||
|
||||
```json
|
||||
{
|
||||
"n": "LATAM Fund II",
|
||||
"w": "examplefund.com"
|
||||
}
|
||||
```
|
||||
|
||||
1. Remove any whitespace from the JSON:
|
||||
|
||||
`{"n":"LATAM Fund II","w":"examplefund.com"}`
|
||||
|
||||
2. Hex-encode the JSON. For example:
|
||||
|
||||
```sh
|
||||
# Using xxd (macOS/Linux)
|
||||
echo -n '{"n":"LATAM Fund II","w":"examplefund.com"}' | xxd -p | tr -d '\n'
|
||||
```
|
||||
|
||||
You should see this result: `7b226e223a224c4154414d2046756e64204949222c2277223a226578616d706c6566756e642e636f6d227d`
|
||||
|
||||
### Scaling Factor
|
||||
|
||||
The **`Scale`** field enables the vault to accurately represent fractional asset values using integer-only MPT shares, which prevents the loss of value from decimal truncation. It defines a scaling factor, calculated as 10<sup>Scale</sup>, that converts a decimal asset amount into a corresponding whole number of shares.
|
||||
|
||||
The scaling factor behavior varies by asset type:
|
||||
|
||||
- **Trust line token**: When a vault holds a trust line token, the `Scale` is configurable by the Vault Owner when creating the vault. The value can range from **0** to a maximum of **18**, with a default of **6**. This flexibility allows issuers to set a level of precision appropriate for their specific token.
|
||||
|
||||
- **XRP**: When a vault holds XRP, the `Scale` is fixed at **0**. This aligns with XRP's native structure, where one share represents one drop, and one XRP equals 1,000,000 drops. Therefore, a deposit of 10 XRP to an empty vault will result in the issuance of 10,000,000 shares.
|
||||
|
||||
- **MPT**: When a vault holds an MPT, its `Scale` is fixed at **0**. This creates a 1-to-1 relationship between deposited MPT units and the shares issued. For example, depositing 10 MPTs to an empty vault issues 10 shares. The value of a single MPT is determined at the issuer's discretion.
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
If an MPT is set to represent a large value, the vault owner and the depositor must be cautious. Since only whole MPT units are used in calculations, any value that is not a multiple of a single MPT's value may be lost due to rounding during a transaction.
|
||||
{% /admonition %}
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
{% code-page-name /%} entries can have the following flags:
|
||||
|
||||
| Flag Name | Flag Value | Description |
|
||||
| :---------------- | :----------- | :---------------------------|
|
||||
| `lsfVaultPrivate` | `0x00010000` | If set, indicates that the vault is private. This flag can only be set when _creating_ the vault. |
|
||||
|
||||
## Vault ID Format
|
||||
|
||||
The ID of a {% code-page-name /%} entry is the [SHA-512Half][] of the following values, concatenated in order:
|
||||
|
||||
- The {% code-page-name /%} space key `0x0056` (capital V).
|
||||
- The [AccountID](../../../protocol/binary-format/#accountid-fields) of the account submitting the transaction (for example, the vault owner).
|
||||
- The transaction `Sequence` number. If the transaction used a [Ticket](../../../../concepts/accounts/tickets), use the `TicketSequence` value.
|
||||
|
||||
## See Also
|
||||
|
||||
**API Methods**:
|
||||
- [vault_info method][]
|
||||
|
||||
**Transactions**:
|
||||
- [VaultClawback transaction][]
|
||||
- [VaultCreate transaction][]
|
||||
- [VaultDelete transaction][]
|
||||
- [VaultDeposit transaction][]
|
||||
- [VaultSet transaction][]
|
||||
- [VaultWithdraw transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -170,7 +170,7 @@ A transaction is considered successful if it receives a `tesSUCCESS` result.
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:--------------------------------------------------|
|
||||
| `temARRAY_EMPTY` | The batch transaction contains zero or one inner transaction. You must submit at least two inner transactions. |
|
||||
| `temINVALID_INNER_BATCH` | An inner transaction is malformed. |
|
||||
| `temINVALID_INNER_BATCH` | - An inner transaction is malformed.<br>- You are attempting to submit a lending or vault-related transaction, which are currently disabled. Invalid transactions include: `LoanBrokerCoverClawback`, `LoanBrokerCoverDeposit`, `LoanBrokerCoverWithdraw`, `LoanBrokerDelete`, `LoanBrokerSet`, `LoanDelete`, `LoanManage`, `LoanPay`, `LoanSet`, `VaultCreate`, `VaultSet`, `VaultDelete`, `VaultDeposit`, `VaultWithdraw`, and `VaultClawback`. |
|
||||
| `temSEQ_AND_TICKET` | The transaction contains both a `TicketSequence` field and a non-zero `Sequence` value. A transaction can't include both fields, but must have at least one. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
seo:
|
||||
description: Claw back first-loss capital from a `LoanBroker` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBrokerCoverClawback
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerCoverClawback.cpp "Source")
|
||||
|
||||
The `LoanBrokerCoverClawback` transaction claws back first-loss capital from a `LoanBroker` ledger entry. The transaction can only be submitted by the issuer of the asset used in the lending protocol, and can't clawback an amount that would cause the available first-loss capital to drop below the minimum amount defined by the `LoanBroker.CoverRateMinimum` value.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverClawback",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Amount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rIssuer1234567890abcdef1234567890abcdef",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | No | The ID of the `LoanBroker` ledger entry to clawback first-loss capital. Must be provided if `Amount` is an MPT, or `Amount` is an IOU and the specified `issuer` matches the `Account` submitting the transaction. |
|
||||
| `Amount` | [Currency Amount][] | Amount | No | The amount of first-loss capital to claw back. If the value is `0` or empty, claw back all assets down to the minimum cover (`DebtTotal * CoverRateMinimum`). |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
| :----------------------- | :----------------------------------|
|
||||
| `temINVALID` | - The transaction is missing the `LoanBrokerID` and `Amount` fields.<br>- `LoanBrokerID` is invalid.<br>- `Amount` is an MPT and `LoanBrokerID` is missing.<br>- Amount is an IOU and its `issuer` matches `Account`. |
|
||||
| `temBAD_AMOUNT` | - `Amount` is less than or equal to zero.<br>- `Amount` specifies the native XRP. |
|
||||
| `tecNO_ENTRY` | - The `LoanBroker` ledger entry doesn't exist, or the asset `issuer` doesn't exist. |
|
||||
| `tecNO_PERMISSION` | - The asset doesn't support clawback, or the asset can't be frozen.<br>- The transaction is attempting to clawback native XRP.<br>- The `Account` isn't the asset issuer. |
|
||||
| `tecWRONG_ASSET` | The specified asset to clawback doesn't match the asset in the lending protocol vault. |
|
||||
| `tecINTERNAL` | The balance of the trust line doesn't match the `CoverAvailable` field. |
|
||||
| `tecINSUFFICIENT_FUNDS` | Clawing back the specified `Amount` puts the available cover below the minimum required. You can also receive this error if the issuer of the asset has frozen the account or placed a global freeze. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
seo:
|
||||
description: Deposits first-loss capital into a `LoanBroker` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBrokerCoverDeposit
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerCoverDeposit.cpp "Source")
|
||||
|
||||
Deposits first-loss capital into a `LoanBroker` ledger entry to provide protection for vault depositors.
|
||||
|
||||
Only the owner of the associated `LoanBroker` entry can initiate this transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverDeposit",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Amount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rIssuer1234567890abcdef1234567890abcdef",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to deposit the first-loss capital. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to deposit. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
| :----------------------- | :----------------------------------|
|
||||
| `temINVALID` | The `LoanBrokerID` field is invalid. |
|
||||
| `temBAD_AMOUNT` | The `Amount` field is less than or equal to zero. |
|
||||
| `tecNO_ENTRY` | The `LoanBroker` ledger entry doesn't exist. |
|
||||
| `tecNO_PERMISSION` | The account sending the transaction isn't the owner of the `LoanBroker` ledger entry. |
|
||||
| `tecWRONG_ASSET` | The asset being deposited doesn't match the asset in the `LoanBroker` vault. |
|
||||
| `tecINSUFFICIENT_FUNDS` | The account depositing first-loss capital doesn't hold enough of the asset. You can also receive this error if the issuer of the asset has frozen the account or placed a global freeze. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
seo:
|
||||
description: Withdraws first-loss capital from a `LoanBroker` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBrokerCoverWithdraw
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerCoverWithdraw.cpp "Source")
|
||||
|
||||
Withdraws first-loss capital from a `LoanBroker` ledger entry.
|
||||
|
||||
Only the owner of the associated `LoanBroker` entry can initiate this transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanBrokerCoverWithdraw",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Amount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rIssuer1234567890abcdef1234567890abcdef",
|
||||
"value": "1000"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to withdraw. |
|
||||
| `Destination` | String | AccountID | No | An account to receive the assets. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
| :------------------------ | :----------------------------------|
|
||||
| `temINVALID` | The `LoanBrokerID` field is invalid. |
|
||||
| `temBAD_AMOUNT` | The amount to withdraw is lass than or equal to `0`. |
|
||||
| `temMALFORMED` | The `Destination` account is empty or `0`. You can also receive this error if the destination tag is set, but `Destination` isn't. |
|
||||
| `tecNO_ENTRY` | The specified `LoanBroker` ledger entry doesn't exist. |
|
||||
| `tecWRONG_ASSET` | The withdrawal asset doesn't match the asset in the vault. |
|
||||
| `tecNO_DST` | The `Destination` provided doesn't exist on the ledger. |
|
||||
| `tecDST_TAG_NEEDED` | The `Destination` account requires a destination tag. |
|
||||
| `tecINSUFFICIENT_FUNDS` | There isn't enough first-loss capital to withdraw. You can also receive this error if the issuer of the asset has frozen the account or placed a global freeze. |
|
||||
| `tecNO_PERMISSION` | The account sending the transaction isn't the owner of the `LoanBroker` ledger entry. |
|
||||
| `tecPATH_DRY` | The XRP Ledger failed to send the funds to the `Destination`. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
seo:
|
||||
description: Deletes a `LoanBroker` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBrokerDelete
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerDelete.cpp "Source")
|
||||
|
||||
Deletes a `LoanBroker` ledger entry. Only the owner of the `LoanBroker` entry can delete it.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanBrokerDelete",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to delete. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
| :--------------------| :----------------------------------|
|
||||
| `tecNO_ENTRY` | The `LoanBroker` ledger entry specified doesn't exist. |
|
||||
| `tec_NO_PERMISSION` | The transaction submitter is not the owner of the `LoanBroker` ledger entry. |
|
||||
| `tecHAS_OBLIGATIONS` | The `OwnerCount` field is greater than zero (active loans exist). This error can also occur if the loan broker's pseudo-account has a balance, owns other ledger entries, or has an owner directory. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
66
docs/references/protocol/transactions/types/loanbrokerset.md
Normal file
66
docs/references/protocol/transactions/types/loanbrokerset.md
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
seo:
|
||||
description: Creates or updates an existing `LoanBroker` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanBrokerSet
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanBrokerSet.cpp "Source")
|
||||
|
||||
Creates or updates a `LoanBroker` ledger entry, configuring protocol parameters and associating it with a `Vault`.
|
||||
|
||||
Only the owner of the associated vault can initiate this transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"VaultID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
|
||||
"LoanBrokerID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
|
||||
"Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865206C6F616E62726F6B65722E",
|
||||
"ManagementFeeRate": 100,
|
||||
"DebtMaximum": 100000,
|
||||
"CoverRateMinimum": 1000,
|
||||
"CoverRateLiquidation": 500
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-----------------------|:----------|:--------------|:----------|:------------|
|
||||
| `VaultID` | String | Hash256 | Yes | The ID of the vault that the lending protocol will use to access liquidity. |
|
||||
| `LoanBrokerID` | String | Hash256 | No | The loan broker ID that the transaction is modifying. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata in hex format--limited to 256 bytes. |
|
||||
| `ManagementFeeRate` | Number | UInt16 | No | The 1/10th basis point fee charged by the lending protocol owner. Valid values range from `0` to `10000` (inclusive), representing 0% to 10%. |
|
||||
| `DebtMaximum` | String | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
|
||||
| `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%. |
|
||||
|
||||
When this transaction modifies an existing `LoanBroker` ledger entry, you can only modify `Flags`, `Data`, and `DebtMaximum`.
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:-----------------------------------|
|
||||
| `temINVALID` | The transaction is trying to modify a fixed field. You can only update the values for `Flags`, `Data`, or `DebtMaximum`. |
|
||||
| `tecNO_PERMISSION` | The account submitting the transaction doesn't own the associated `Vault` ledger entry. You can also receive this error if the transaction tries to modify the `VaultID` of an existing `LoanBroker` ledger entry. |
|
||||
| `tecNO_ENTRY` | A `LoanBroker` entry with the specified ID does not exist. You can also receive this if the specified `VaultID` doesn't exist. |
|
||||
| `tecINSUFFICIENT_RESERVE` | The owner's account doesn't have enough to cover the reserve requirement for the new `LoanBroker` ledger entry. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
51
docs/references/protocol/transactions/types/loandelete.md
Normal file
51
docs/references/protocol/transactions/types/loandelete.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
seo:
|
||||
description: Delete a `Loan` ledger entry.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanDelete
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanDelete.cpp "Source")
|
||||
|
||||
Deletes a `Loan` ledger entry. Only the loan broker or borrower can submit this transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanDelete",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:--------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to delete. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
|:---------------------|:------------|
|
||||
| `temINVALID` | The `LoanID` is missing or set to zero. |
|
||||
| `tecNO_ENTRY` | The loan specified by `LoanID` doesn't exist. |
|
||||
| `tecHAS_OBLIGATIONS` | The loan can't be deleted because it still has outstanding payments due. |
|
||||
| `tecNO_PERMISSION` | The account submitting the transaction is neither the borrower of the `Loan` ledger entry nor the owner of the `LoanBroker` ledger entry. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
67
docs/references/protocol/transactions/types/loanmanage.md
Normal file
67
docs/references/protocol/transactions/types/loanmanage.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
seo:
|
||||
description: Manages the state of a loan, including defaulting, impairing, or unimpairing a loan.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanManage
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanManage.cpp "Source")
|
||||
|
||||
Manages the state of a `Loan` ledger entry, including defaulting, impairing, or unimpairing a loan.
|
||||
|
||||
Only the `LoanBroker` ledger entry owner can initiate this transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanManage",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 65536,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"LoanID": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:-------------- |:----------|:-------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to manage. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
|
||||
|
||||
| Field Name | Hex Value | Decimal Value | Description |
|
||||
|:----------------|:-------------|:--------------|:------------|
|
||||
| `tfLoanDefault` | `0x00010000` | `65536` | Indicates the loan should be defaulted. |
|
||||
| `tfLoanImpair` | `0x00020000` | `131072` | Indicates the the loan should be impaired. |
|
||||
| `tfLoanUnimpair`| `0x00040000` | `262144` | Indicates the the loan should be unimpaired. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
| :------------------ | :----------------------------------|
|
||||
| `temINVALID` | the `LoanID` field is missing or set to zero. |
|
||||
| `temINVALID_FLAG` | Multiple management flags have been set. Only one can be set at a time. |
|
||||
| `tecNO_ENTRY` | The loan specified by `LoanID` doesn't exist. |
|
||||
| `tecNO_PERMISSION` | - The transaction is attempting to modify a defaulted loan, or a fully paid loan.<br>- The transaction is attempting to change the loan's impairment status to the one it already has. |
|
||||
| `tecTOO_SOON` | The loan can't be marked as defaulted before its payment due date and grace period have passed. |
|
||||
| `tecLIMIT_EXCEEDED` | Marking the loan as impaired creates a loss greater than the vault's oustanding assets, which would put the vault in an invalid state. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
78
docs/references/protocol/transactions/types/loanpay.md
Normal file
78
docs/references/protocol/transactions/types/loanpay.md
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
seo:
|
||||
description: Make a payment on an active loan.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanPay
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanPay.cpp "Source")
|
||||
|
||||
Makes a payment on an active loan. Only the borrower on the loan can make payments, and payments must meet the minimum amount required for that period.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
A loan payment has four types, depending on the amount and timing of the payment:
|
||||
|
||||
- **Regular Payment**: A payment made on time, where the payment size and schedule are calculated with a standard [amortization formula](https://en.wikipedia.org/wiki/Amortization_calculator).
|
||||
- **Late Payment**: A payment made after the `NextPaymentDueDate` in the `Loan` ledger entry. Late payments include a `LatePaymentFee` and `LateInterestRate`.
|
||||
- **Early Full Payment**: A payment that covers the outstanding principal of the loan. A `CloseInterestRate` is charged on the outstanding principal.
|
||||
- **Overpayment**: A payment that exceeds the required minimum payment amount.
|
||||
|
||||
To see how loan payment transactions are calculated, see [transaction pseudo-code](https://github.com/Tapanito/XRPL-Standards/tree/xls-66-lending-protocol/XLS-0066-lending-protocol#a-3-loanpay-implementation-reference).
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanPay",
|
||||
"Account": "rBORROWER9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LoanID": "ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890",
|
||||
"Amount": 1000,
|
||||
"Sequence": 10,
|
||||
"LastLedgerSequence": 7108701
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:--------------- |:--------------------|:--------------|:----------|:------------|
|
||||
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. |
|
||||
| `Amount` | [Currency Amount][] | Amount | Yes | The amount to pay toward the loan. |
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [flags field], as follows:
|
||||
|
||||
| Flag Name | Hex Value | Decimal Value | Description |
|
||||
|:--------------------|:-------------|:--------------|:------------|
|
||||
| `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the remaining payment amount should be treated as an overpayment. |
|
||||
| `tfLoanFullPayment` | `0x00020000` | 131072 | Indicates that the borrower is making a full early repayment. |
|
||||
| `tfLoanLatePayment` | `0x00040000` | 262144 | Indicates that the borrower is making a late loan payment. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
|:-----------|:------------|
|
||||
| `temINVALID` | The `LoanID` field is missing or set to zero. |
|
||||
| `temBAD_AMOUNT` | The `Amount` field must specify a positive value. |
|
||||
| `tecNO_ENTRY` | The loan specified by `LoanID` doesn't exist. |
|
||||
| `tecNO_PERMISSION` | The account submitting the transaction isn't the borrower on the loan. |
|
||||
| `tecTOO_SOON` | The loan hasn't started yet. |
|
||||
| `tecKILLED` | The loan is already fully paid. |
|
||||
| `tecWRONG_ASSET` | The asset specified by `Amount` doesn't match the asset of the loan. |
|
||||
| `tecFROZEN` | The borrower's account is frozen for the specified asset, or the loan broker's pseudo-account is deep-frozen and can't receive funds. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
122
docs/references/protocol/transactions/types/loanset.md
Normal file
122
docs/references/protocol/transactions/types/loanset.md
Normal file
@@ -0,0 +1,122 @@
|
||||
---
|
||||
seo:
|
||||
description: Creates a new `Loan` ledger entry to represent a loan agreement between a Loan Broker and Borrower.
|
||||
labels:
|
||||
- Transactions
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
# LoanSet
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/LoanSet.cpp "Source")
|
||||
|
||||
Creates a new `Loan` ledger entry, representing a loan agreement between a _Loan Broker_ and _Borrower_.
|
||||
|
||||
The `LoanSet` transaction is a mutual agreement between the _Loan Broker_ and _Borrower_, and must be signed by both parties. The following multi-signature flow can be initiated by either party:
|
||||
|
||||
1. The borrower or loan broker creates the transaction with the preagreed terms of the loan. They sign the transaction and set the `SigningPubKey`, `TxnSignature`, `Signers`, `Account`, `Fee`, `Sequence`, and `Counterparty` fields.
|
||||
2. The counterparty verifies the loan terms and signature before signing and submitting the transaction.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "LoanSet",
|
||||
"Account": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"Data": "546869732069732061726269747261727920646174612061626F757420746865206C6F616E2E",
|
||||
"Counterparty": "rCOUNTER9AbCdEfGhIjKlMnOpQrStUvWxYz",
|
||||
"LoanOriginationFee": 100,
|
||||
"LoanServiceFee": 10,
|
||||
"LatePaymentFee": 5,
|
||||
"ClosePaymentFee": 20,
|
||||
"OverpaymentFee": 5,
|
||||
"InterestRate": 500,
|
||||
"LateInterestRate": 1000,
|
||||
"CloseInterestRate": 200,
|
||||
"OverpaymentInterestRate": 5,
|
||||
"PrincipalRequested": 10000,
|
||||
"PaymentTotal": 12,
|
||||
"PaymentInterval": 2592000,
|
||||
"GracePeriod": 604800,
|
||||
"SigningPubKey": "03C040CAC1E164B0E385D31E41447FE6B8960E0D202811CFDA08B55BA29E08C6B0",
|
||||
"TxnSignature": "30440220549D359F792E155D20B5E8B3423F0F844CCF7C86986EB85BE482908A55A7157D02207B464FFE57E75D9693BAC445540CF078E9E0B6452C917DE4D66F27918D32A170",
|
||||
"hash": "831EEFF19C980FC348E984625FE41AEB27301B0B072D4239A980E78B86B2515C"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:--------------------------|:----------|:--------------|:----------|:------------|
|
||||
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry. |
|
||||
| `Data` | String | Blob | No | Arbitrary metadata in hex format (max 256 bytes). |
|
||||
| `Counterparty` | String | AccountID | No | The address of the counterparty of the loan. |
|
||||
| `LoanOriginationFee` | String | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. |
|
||||
| `LoanServiceFee` | String | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. |
|
||||
| `LatePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for late payments. |
|
||||
| `ClosePaymentFee` | String | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. |
|
||||
| `OverpaymentFee` | Number | UInt32 | No | A fee charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `InterestRate` | Number | UInt32 | No | The annualized interest rate of the loan, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `LateInterestRate` | Number | UInt32 | No | A premium added to the interest rate for late payments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `CloseInterestRate` | Number | UInt32 | No | A fee charged for repaying the loan early, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `OverpaymentInterestRate` | Number | UInt32 | No | The interest rate charged on overpayments, in units of 1/10th basis points. Valid values are 0 to 100000 (inclusive), representing 0% to 100%. |
|
||||
| `PrincipalRequested` | String | Number | Yes | The principal loan amount requested by the borrower. |
|
||||
| `PaymentTotal` | Number | UInt32 | No | The total number of payments to be made against the loan. |
|
||||
| `PaymentInterval` | Number | UInt32 | No | The number of seconds between loan payments. |
|
||||
| `GracePeriod` | Number | UInt32 | No | The number of seconds after the loan's payment due date when it can be defaulted. |
|
||||
| `SigningPubKey` | String | Blob | Yes | The public key used to verify the validity of the first signer's signature. |
|
||||
| `TxnSignature` | String | Blob | Yes | The hex encoding of the digital signature for the first signing. |
|
||||
| `hash` | String | Hash256 | Yes | The unique identifying hash of the partially-signed transaction. |
|
||||
|
||||
### CounterpartySignature Fields
|
||||
|
||||
An inner object that contains the signatures of the counterparty of the transaction. The object contains the following fields:
|
||||
|
||||
| Field Name | JSON Type | Internal Type | Required? | Description |
|
||||
|:----------------|:----------|:--------------|:----------|:------------|
|
||||
| `SigningPubKey` | String | STBlob | No | The public key used to verify the validity of the signature. |
|
||||
| `TxnSignature` | String | STBlob | No | The signature over all signing fields. |
|
||||
| `Signers` | List | STArray | No | An array of transaction signatures from the counterparty. |
|
||||
|
||||
The final transaction must include either:
|
||||
- Both the `SigningPubKey` and `TxnSignature` fields.
|
||||
- The `Signers` field.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
This field isn't included in the `LoanSet` JSON the first party signs, instead it's added by the counterparty when they sign.
|
||||
{% /admonition %}
|
||||
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
Transactions of the {% code-page-name /%} type support additional values in the [`flags` field], as follows:
|
||||
|
||||
| Flag Name | Hex Value | Decimal Value | Description |
|
||||
|:----------|:----------|:--------------|:------------|
|
||||
| `tfLoanOverpayment` | `0x00010000` | 65536 | Indicates that the loan supports overpayments. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes][]:
|
||||
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:-----------------------------------|
|
||||
| `temBAD_SIGNER` | - The transaction is missing a `CounterpartySignature` field.<br>- This transaction is part of a `Batch` transaction, but didn't specify a `Counterparty`. |
|
||||
| `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval` or less than `60` seconds. |
|
||||
| `tecNO_ENTRY` | The `LoanBroker` doesn't exist. |
|
||||
| `tecNO_PERMISSION` | Neither the transaction sender's `Account` or the `Counterparty` field owns the associated `LoanBroker` ledger entry. |
|
||||
| `tecINSUFFICIENT_FUNDS` | - The `Vault` associated with the `LoanBroker` doesn't have enough assets to fund the loan.<br>- The `LoanBroker` ledger entry doesn't have enough first-loss capital to meet the minimum coverage requirement for the new total debt. |
|
||||
| `tecLIMIT_EXCEEDED` | The requested loan would cause the `LoanBroker` ledger entry to exceed it's maximum allowed debt. |
|
||||
| `tecINSUFFICIENT_RESERVE` | The borrower's account doesn't have enough XRP to meet the reserve requirements. |
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -13,6 +13,7 @@ If the transaction is successful, it creates an [MPTokenIssuance entry][] where
|
||||
|
||||
{% amendment-disclaimer name="MPTokensV1" /%}
|
||||
|
||||
|
||||
## Example MPTokenIssuanceCreate JSON
|
||||
|
||||
This example assumes that the issuer of the token is the signer of the transaction.
|
||||
@@ -24,6 +25,7 @@ This example assumes that the issuer of the token is the signer of the transacti
|
||||
"AssetScale": 4,
|
||||
"TransferFee": 0,
|
||||
"MaximumAmount": "50000000",
|
||||
"Flags": 83659,
|
||||
"MPTokenMetadata": "7B2274223A225442494C4C222C226E223A22542D42696C6C205969656C6420546F6B656E222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A226578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C226163223A22727761222C226173223A227472656173757279222C22696E223A224578616D706C65205969656C6420436F2E222C227573223A5B7B2275223A226578616D706C657969656C642E636F2F7462696C6C222C2263223A2277656273697465222C2274223A2250726F647563742050616765227D2C7B2275223A226578616D706C657969656C642E636F2F646F6373222C2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373227D5D2C226169223A7B22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73222C226D617475726974795F64617465223A22323034352D30362D3330222C226375736970223A22393132373936525830227D7D",
|
||||
"Fee": "12",
|
||||
"Flags": 122,
|
||||
@@ -38,6 +40,7 @@ This example assumes that the issuer of the token is the signer of the transacti
|
||||
| Field | JSON Type | [Internal Type][] | Required? | Description |
|
||||
|:------------------|:---------------------|:------------------|:----------|:------------|
|
||||
| `AssetScale` | Number | UInt8 | No | Where to put the decimal place when displaying amounts of this MPT. More formally, the asset scale is a non-negative integer (0, 1, 2, …) such that one standard unit equals 10^(-scale) of a corresponding fractional unit. For example, if a US Dollar Stablecoin has an asset scale of _2_, then 1 unit of that MPT would equal 0.01 US Dollars. This indicates to how many decimal places the MPT can be subdivided. If omitted, the default is 0, meaning that the MPT cannot be divided into smaller than 1 unit. |
|
||||
| `DomainID` | String - [Hash][] | UInt256 | No | The ledger entry ID of a permissioned domain that grants access to the MPT. You must enable the `tfMPTRequireAuth` flag to use permissioned domains. {% amendment-disclaimer name="PermissionedDomains" /%} {% amendment-disclaimer name="SingleAssetVault" /%} |
|
||||
| `TransferFee` | Number | UInt16 | No | The value specifies the fee to charged by the issuer for secondary sales of the Token, if such sales are allowed. Valid values for this field are between 0 and 50,000 inclusive, allowing transfer rates of between 0.000% and 50.000% in increments of 0.001. The field _must not_ be present if the tfMPTCanTransfer flag is not set. If it is, the transaction should fail and a fee should be claimed. |
|
||||
| `MaximumAmount` | String - Number | UInt64 | No | The maximum asset amount of this token that can ever be issued, as a base-10 number encoded as a string. The current default maximum limit is 9,223,372,036,854,775,807 (2^63-1). _This limit may increase in the future. If an upper limit is required, you must specify this field._ |
|
||||
| `MPTokenMetadata` | String - Hexadecimal | Blob | No | Arbitrary metadata about this issuance. The limit for this field is 1024 bytes. By convention, the metadata should decode to JSON data describing what the MPT represents. The [XLS-89 specification](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0089-multi-purpose-token-metadata-schema) defines a recommended format for metadata. |
|
||||
@@ -46,6 +49,7 @@ This example assumes that the issuer of the token is the signer of the transacti
|
||||
For an example of how to encode metadata for the `MPTokenMetadata` field, see {% repo-link path="_code-samples/issue-mpt-with-metadata/" %}Code Sample: Issue MPT with Metadata{% /repo-link %}.
|
||||
{% /admonition %}
|
||||
|
||||
|
||||
## MPTokenIssuanceCreate Flags
|
||||
|
||||
Transactions of the MPTokenIssuanceCreate type support additional values in the [`Flags` field](../common-fields.md#flags-field), as follows:
|
||||
@@ -59,6 +63,20 @@ Transactions of the MPTokenIssuanceCreate type support additional values in the
|
||||
| `tfMPTCanTransfer` | `0x00000020` | `32` | If set, indicates that tokens can be transferred to other accounts that are not the issuer. |
|
||||
| `tfMPTCanClawback` | `0x00000040` | `64` | If set, indicates that the issuer can use the `Clawback` transaction to claw back value from individual holders. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% $frontmatter.seo.title %} transactions can result in the following [transaction result codes](../transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:------------|
|
||||
| `tecDIR_FULL` | The owner directory of the account creating the `MPTokenIssuance` ledger entry is full. |
|
||||
| `temBAD_TRANSFER_FEE` | The transfer fee specified is greater than the maximum allowed value of 50,000. |
|
||||
| `temDISABLED` | The `MPTokensV1` amendment is disabled. You will also receive this error if you include a `DomainID` field in the transaction, but the `PermissionedDomains` and `SingleAssetVault` amendments are both disabled. |
|
||||
| `tecINSUFFICIENT_RESERVE` | The account creating the `MPTokenIssuance` ledger entry doesn't have enough XRP to meet the owner reserve. |
|
||||
| `temMALFORMED` | Besides generally malformed transactions, you can receive this error if:<br>- A non-zero transfer fee is set, but the `tfMPTCanTransfer` flag is _not_ set.<br>- The `DomainID` points to an invalid permissioned domain; you can also receive this error if you include a `DomainID` without setting the `tfMPTRequireAuth` flag.<br>- The `MPTokenMetadata` field is an invalid length (0 or exceeds 1024 bytes).<br>- The `MaximumAmount` field is 0 or exceeds 9,223,372,036,854,775,807 (2^63-1). |
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- [MPTokenIssuance entry][]
|
||||
|
||||
@@ -11,6 +11,7 @@ Update a mutable property of a [Multi-purpose Token (MPT)](../../../../concepts/
|
||||
|
||||
{% amendment-disclaimer name="MPTokensV1" /%}
|
||||
|
||||
|
||||
## Example MPTokenIssuanceSet JSON
|
||||
|
||||
This example locks the balances of all holders of the specified MPT issuance.
|
||||
@@ -32,9 +33,11 @@ This example locks the balances of all holders of the specified MPT issuance.
|
||||
|
||||
| Field | JSON Type | [Internal Type][] | Required? | Description |
|
||||
|:--------------------|:---------------------|:------------------|:----------|-------------|
|
||||
| `DomainID` | String - [Hash][] | UInt256 | No | The ledger entry ID of a permissioned domain that grants access to the MPT. An empty value or `0` removes the permissioned domain from the MPT issuance so that only users who are explicitly approved by the issuer can send and receive the MPT. You can only set a `DomainID` if the MPT issuance has [**Require Auth**](/docs/concepts/tokens/fungible-tokens/multi-purpose-tokens#transferability-controls) enabled. {% amendment-disclaimer name="PermissionedDomains" /%} {% amendment-disclaimer name="SingleAssetVault" /%} |
|
||||
| `MPTokenIssuanceID` | String - Hexadecimal | UInt192 | Yes | The identifier of the `MPTokenIssuance` to update. |
|
||||
| `Holder` | String - [Address][] | AccountID | No | An individual token holder. If provided, apply changes to the given holder's balance of the given MPT issuance. If omitted, apply to all accounts holding the given MPT issuance. |
|
||||
|
||||
|
||||
### MPTokenIssuanceSet Flags
|
||||
|
||||
Transactions of the `MPTokenIssuanceSet` type support additional values in the `Flags` field, as follows:
|
||||
@@ -44,6 +47,20 @@ Transactions of the `MPTokenIssuanceSet` type support additional values in the `
|
||||
| `tfMPTLock` | `0x00000001` | 1 | Enable to lock balances of this MPT issuance. |
|
||||
| `tfMPTUnlock` | `0x00000002` | 2 | Enable to unlock balances of this MPT issuance. |
|
||||
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% $frontmatter.seo.title %} transactions can result in the following [transaction result codes](../transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
|:--------------------------|:------------|
|
||||
| `temDISABLED` | The `MPTokensV1` amendment is disabled. You will also receive this error if you include a `DomainID` field in the transaction, but the `PermissionedDomains` and `SingleAssetVault` amendments are both disabled. |
|
||||
| `tecNO_DST` | The account specified in the `Holder` field doesn't exist. |
|
||||
| `tecNO_PERMISSION` | - The `lsfMPTCanLock` flag isn't enabled, but you are attempting to lock or unlock an MPT.<br>- The `SingleAssetVault` amendment is disabled and you're trying to modify a `DomainID` field. |
|
||||
| `temMALFORMED` | Besides generally malformed transactions, you can receive this error if:<br>- You specified a `DomainID` and `Holder` value; only one can be set in a single transaction.<br>- You specified the same account for both `Account` and `Holder`.<br>- The transaction isn't changing anything; it must either update a flag or modify the `DomainID`. |
|
||||
| `tecOBJECT_NOT_FOUND` | The specified `MPToken`, `MPTokenIssuance`, or `PermissionedDomain` ledger entry doesn't exist. |
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- [MPTokenIssuance entry][]
|
||||
|
||||
71
docs/references/protocol/transactions/types/vaultclawback.md
Normal file
71
docs/references/protocol/transactions/types/vaultclawback.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
seo:
|
||||
description: Allows the issuer of a trust line token or MPT to claw back funds from the vault.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# VaultClawback
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultClawback.cpp "Source")
|
||||
|
||||
Performs a [Clawback](../../../../use-cases/tokenization/stablecoin-issuer#clawback) from the vault, exchanging the shares of an account for assets.
|
||||
|
||||
Under the hood, the transaction performs a [VaultWithdraw](./vaultwithdraw.md) on behalf of the account from which assets are clawed back, converting its shares into assets and transferring the funds to the asset’s issuing account. Because of this, {% code-page-name /%} must respect any applicable fees or penalties (e.g., unrealized loss).
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
Clawbacks cannot be performed on native XRP.
|
||||
{% /admonition %}
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultClawback",
|
||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"VaultID": "77D6234D074E505024D39C04C3F262997B773719AB29ACFA83119E4210328776",
|
||||
"Holder": "ruazs5h1qEsqpke88pcqnaseXdm6od2xc",
|
||||
"Amount" : "10000"
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :--------- | :-------- | :---------------- | :-------- | :---------- |
|
||||
| `VaultID` | String | Hash256 | Yes | The unique identifier of the vault from which assets are withdrawn. |
|
||||
| `Holder` | String | AccountID | Yes | The unique identifier of the account from which to claw back the assets. |
|
||||
| `Amount` | Number | Number | No | The asset amount to claw back. When this field is set to 0, the transaction claws back all funds, up to the total shares the `Holder` owns. |
|
||||
|
||||
If the requested amount exceeds the vault’s available assets, the transaction claws back only up to the vault's `AssetsAvailable` balance. Otherwise, it retrieves the exact asset amount specified in the transaction.
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} transactions.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes](../../../protocol/transactions/transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :---------------------- | :---------- |
|
||||
| `tecNO_ENTRY` | The `Vault` object with the specified `VaultID` does not exist on the ledger. |
|
||||
| `tecNO_PERMISSION` | The transaction attempts to claw back XRP, or the asset is a trust line token or MPT and the transaction isn't submitted by the issuing account. |
|
||||
| `tecWRONG_ASSET` | The asset in the transaction does not match the vault's asset type. |
|
||||
| `tecINSUFFICIENT_FUNDS` | The `MPToken` object for the vault share of the `Holder` account does not exist, or the `MPToken.MPTAmount` is 0. |
|
||||
| `temDISABLED` | The Single Asset Vault amendment is not enabled. |
|
||||
| `temMALFORMED` | The transaction was not validly formatted. For example, if the `VaultID` is not provided. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
97
docs/references/protocol/transactions/types/vaultcreate.md
Normal file
97
docs/references/protocol/transactions/types/vaultcreate.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
seo:
|
||||
description: Creates a new vault object in the ledger.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
---
|
||||
|
||||
# VaultCreate
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultCreate.cpp "Source")
|
||||
|
||||
Creates a new `Vault` ledger entry, an `MPTokenIssuance` ledger entry for the vault’s shares, and an `AccountRoot` for the vault’s [pseudo-account](../../../../concepts/accounts/pseudo-accounts.md).
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
Currently, the account that creates the vault must also create other protocols that utilize the vault, though this may change in the future.
|
||||
{% /admonition %}
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultCreate",
|
||||
"Account": "rNGHoQwNG753zyfDrib4qDvvswbrtmV8Es",
|
||||
"Asset": {
|
||||
"currency": "USD",
|
||||
"issuer": "rXJSJiZMxaLuH3kQBUV5DLipnYtrE6iVb"
|
||||
},
|
||||
"AssetsMaximum": "1000000",
|
||||
"Data": "5661756C74206D65746164617461",
|
||||
"Fee": "5000000",
|
||||
"Flags": 0,
|
||||
"MPTokenMetadata": "7B2274223A225473745368617265222C226E223A2254657374205661756C74205368617265222C2264223A22412074657374207661756C742073686172652E222C2269223A226578616D706C652E6F72672F73686172652D69636F6E2E706E67222C226163223A22727761222C226173223A22657175697479222C22696E223A224D53205465737420497373756572222C227573223A5B7B2275223A226578616D706C657969656C642E636F2F7473747368617265222C2263223A2277656273697465222C2274223A2250726F647563742050616765227D2C7B2275223A226578616D706C657969656C642E636F2F646F6373222C2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373227D5D2C226169223A7B22766F6C6174696C697479223A226C6F77227D7D",
|
||||
"Scale": 6,
|
||||
"Sequence": 200370,
|
||||
"WithdrawalPolicy": 1
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields](../../../../references/protocol/transactions/common-fields#transaction-common-fields), {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? |Description |
|
||||
|:-------------------|:--------------|:------------------|:----------|:------------------|
|
||||
| `Data` | String | Blob | No | Arbitrary vault metadata, in hex format, limited to 256 bytes. See [Data Field Format](../../ledger-data/ledger-entry-types/vault.md#data-field-format) for the recommended format. |
|
||||
| `Asset` | Object | Issue | Yes | The asset to be held in the vault. This can be XRP, a trust line token, or an MPT. If the asset is a trust line token, the transaction creates a [trust line](../../../../concepts/tokens/fungible-tokens/trust-line-tokens.md#structure) between the vault's pseudo-account and the issuer of the asset. If the asset is an MPT, the transaction creates an `MPToken` object for the vault's pseudo-account. |
|
||||
| `AssetsMaximum` | Number | UInt64 | No | The maximum asset amount that can be held in a vault. |
|
||||
| `MPTokenMetadata` | String | Blob | No | Arbitrary metadata about the shares issued by the vault, in hex format, limited to 1024 bytes. |
|
||||
| `WithdrawalPolicy` | Number | UInt8 | No | Indicates the withdrawal strategy used by the vault. The default value is `0x0001`, mapped to the string `vaultStrategyFirstComeFirstServe`. See [WithdrawalPolicy](#withdrawalpolicy). |
|
||||
| `DomainID` | String | Hash256 | No | The [PermissionedDomain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md) object ID associated with the shares of this vault. If provided, the transaction creates a private vault, which restricts access to accounts with [credentials](../../../../concepts/decentralized-storage/credentials.md) in the specified Permissioned Domain. |
|
||||
| `Scale` | Number | UInt8 | No | _(Trust line tokens only)_ Specifies decimal precision for share calculations. Assets are multiplied by 10<sup>Scale</sup > to convert fractional amounts into whole number shares. For example, with a `Scale` of `6`, depositing 20.3 units creates 20,300,000 shares (20.3 × 10<sup>Scale</sup >). For **trust line tokens** this can be configured at vault creation, and valid values are between 0-18, with the default being `6`. For **XRP** and **MPTs**, this is fixed at `0`.|
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
{% code-page-name /%} transactions support additional values in the `Flags` field, as follows:
|
||||
|
||||
| Flag Name | Value | Description |
|
||||
| :---------------------------- | :------------| -------------------------|
|
||||
| `tfVaultPrivate` | `0x00010000` | Indicates that the vault is private. This flag can only be set when _creating_ the vault. |
|
||||
| `tfVaultShareNonTransferable` | `0x00020000` | Indicates the vault share is non-transferable. This flag can only be set when _creating_ the vault. |
|
||||
|
||||
## WithdrawalPolicy
|
||||
|
||||
A `WithdrawalPolicy` defines the strategy for processing withdrawal requests from a vault. This policy governs how liquidity is removed. Currently, only one strategy is supported:
|
||||
|
||||
| Policy Name | Value | Description |
|
||||
| :--------------------------------- | :------- | -------------------------|
|
||||
| `vaultStrategyFirstComeFirstServe` | `0x0001` | Requests are processed on a first-come, first-served basis. With this strategy, a depositor can redeem any amount of assets, provided they hold a sufficient number of shares. |
|
||||
|
||||
## Transaction Cost
|
||||
|
||||
Since the {% code-page-name /%} transaction creates a new `AccountRoot` object for a vault’s pseudo-account, it incurs a higher than usual [transaction cost](../../../../concepts/transactions/transaction-cost) to deter ledger spam. Instead of the standard minimum of 0.00001 XRP, {% code-page-name /%} must destroy an [incremental owner reserve](../../../../concepts/accounts/reserves#base-reserve-and-owner-reserve), currently 0.2 XRP.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes](../transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :------------------------ | :----------------------------------|
|
||||
| `tecNO_AUTH` | The asset is an MPT and the `lsfMPTCanTransfer` flag is not set in the `MPTokenIssuance` object, meaning the vault cannot be created with a non-transferable MPT. |
|
||||
| `tecLOCKED` | The asset is an MPT and the `lsfMPTLocked` flag is set in the `MPTokenIssuance` object, meaning the asset is locked. |
|
||||
| `tecFROZEN` | The issuer has frozen the asset to be held in the vault. |
|
||||
| `tecOBJECT_NOT_FOUND` | A ledger entry specified in the transaction does not exist. For example, the provided `DomainID` does not exist. |
|
||||
| `temMALFORMED` | The transaction was not validly formatted. For example, the `Data` field is larger than 256 bytes. |
|
||||
| `tecINSUFFICIENT_RESERVE` | There is insufficient `AccountRoot.Balance` for the Owner Reserve. |
|
||||
| `terNO_RIPPLE` | The issuer of the asset has not enabled the [Default Ripple flag](../../../../concepts/tokens/fungible-tokens/stablecoins/configuration#default-ripple). |
|
||||
| `terNO_ACCOUNT` | The issuer account of the vault's asset does not exist. |
|
||||
| `temDISABLED` | Either the Single Asset Vault amendment is not enabled, a `DomainID` is provided and the Permissioned Domains amendment is not enabled, or the MPTokensV1 amendment is not enabled. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
60
docs/references/protocol/transactions/types/vaultdelete.md
Normal file
60
docs/references/protocol/transactions/types/vaultdelete.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
seo:
|
||||
description: Deletes an existing Vault object from the ledger.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# VaultDelete
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultDelete.cpp "Source")
|
||||
|
||||
Permanently deletes an existing `Vault` object from the ledger, removes all associated ledger entries, and frees up the reserve requirement for the Vault Owner.
|
||||
|
||||
Only the Vault Owner can initiate this transaction, and the vault must be completely empty before deletion.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultDelete",
|
||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"VaultID": "77D6234D074E505024D39C04C3F262997B773719AB29ACFA83119E4210328776"
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields](https://xrpl.org/docs/references/protocol/transactions/common-fields#transaction-common-fields), {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :----------------- | :-------- | :---------------- | :-------- | :------------|
|
||||
| `VaultID` | String | Hash256 | Yes | The unique identifier of the vault that needs to be deleted. |
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} transactions.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, VaultCreate transactions can result in the following [transaction result codes](../../../protocol/transactions/transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :------------------------ | :----------------------------------|
|
||||
| `tecNO_ENTRY` | The `Vault` object with the provided `VaultID` does not exist on the ledger. |
|
||||
| `tecNO_PERMISSION` | The account submitting the transaction is not the `Owner` of the vault. |
|
||||
| `tecHAS_OBLIGATIONS` | The vault to be deleted is connected to objects that cannot be deleted in the ledger. For example, the owner directory of the vault's pseudo-account contains references to any objects other than the vault, shares, or assets. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
96
docs/references/protocol/transactions/types/vaultdeposit.md
Normal file
96
docs/references/protocol/transactions/types/vaultdeposit.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
seo:
|
||||
description: Deposits a specified number of assets into a vault in exchange for shares.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# VaultDeposit
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultDeposit.cpp "Source")
|
||||
|
||||
Deposits a specified number of assets into a vault in exchange for shares.
|
||||
|
||||
For private vaults, the depositor must be authorized to interact with the vault’s shares and have [Credentials](../../../../concepts/decentralized-storage/credentials.md) in the [Permissioned Domain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md) of the share.
|
||||
|
||||
Public vaults require no authorization, and anyone can deposit as long as they meet the asset type requirement and have sufficient funds.
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
A depositor cannot deposit assets into the vault if:
|
||||
|
||||
- The asset is frozen for the depositor.
|
||||
- The trust line between the pseudo-account and the issuer is frozen, or the `MPToken` is locked.
|
||||
- The vault is private and the depositor's credentials have expired.
|
||||
{% /admonition %}
|
||||
|
||||
If successful, the transaction moves the assets from the depositor's account to the vault's pseudo-account, issues the corresponding vault shares, and updates the vault’s balance.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultDeposit",
|
||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"VaultID": "77D6234D074E505024D39C04C3F262997B773719AB29ACFA83119E4210328776",
|
||||
"Amount" : {
|
||||
"currency" : "TST",
|
||||
"issuer" : "rP9jPyP5kyvFRb6ZiRghAGw5u8SGAmU4bd",
|
||||
"value" : "2.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields](../../../protocol/transactions/common-fields#transaction-common-fields), {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :-----------------------| :------------ | :---------------- | :-------- | :-------------------|
|
||||
| `VaultID` | String | Hash256 | Yes | The unique identifier of the vault to which the asset is deposited. |
|
||||
| `Amount` | Object | Amount | Yes | The asset and quantity to be deposited into the vault.|
|
||||
|
||||
The deposited asset must match the vault’s designated asset for the transaction to succeed. Depending on the asset type, the following changes occur:
|
||||
|
||||
- **XRP**: The vault’s pseudo-account balance increases, and the depositor’s balance decreases.
|
||||
- **Trust line token**: The [trust line](../../../../concepts/tokens/fungible-tokens/trust-line-tokens.md#structure) balance between the vault's pseudo-account and the asset issuer is adjusted.
|
||||
- **MPT**: The `MPToken.MPTAmount` of both the depositor and the vault's pseudo-account is updated.
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} transactions.
|
||||
|
||||
## Transfer Fees
|
||||
|
||||
A single asset vault does not apply the [transfer fee](../../../../concepts/tokens/fungible-tokens/transfer-fees) to {% code-page-name /%} transactions. Additionally, whenever a protocol moves assets from or to a vault, the transfer fee isn't charged.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes](../../../protocol/transactions/transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :---------------------- | :----------------------------------|
|
||||
| `tecNO_ENTRY` | The `Vault` object with the provided `VaultID` does not exist on the ledger. |
|
||||
| `tecOBJECT_NOT_FOUND` | A ledger entry specified in the transaction does not exist. |
|
||||
| `tecWRONG_ASSET` | The asset of the vault does not match the asset being deposited. |
|
||||
| `tecINSUFFICIENT_FUNDS` | The depositor does not have sufficient funds to make a deposit. |
|
||||
| `tecLIMIT_EXCEEDED` | Adding the provided `Amount` to the `AssetsTotal` exceeds the `AssetsMaximum` value. |
|
||||
| `tecNO_AUTH` | Either the vault is private and the depositing account does not have credentials in the share's Permissioned Domain, or the asset is a non-transferable MPT. |
|
||||
| `tecFROZEN` | Either the trust line between the issuer and the depositor is frozen, or the asset is globally frozen. |
|
||||
| `tecLOCKED` | Either the MPT asset is locked for the depositor, or if the asset is globally locked. |
|
||||
| `temMALFORMED` | The transaction was not validly formatted. For example, if the `VaultID` is not provided. |
|
||||
| `temDISABLED` | The Single Asset Vault amendment is not enabled. |
|
||||
| `temBAD_AMOUNT` | The `Amount` field of the transaction is invalid. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
71
docs/references/protocol/transactions/types/vaultset.md
Normal file
71
docs/references/protocol/transactions/types/vaultset.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
seo:
|
||||
description: Modifies a single asset vault that you own.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# VaultSet
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultSet.cpp "Source")
|
||||
|
||||
Modifies a single asset vault that you own. This transaction allows the Vault Owner to update certain mutable fields, including vault metadata and the maximum asset amount.
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
Once a vault is created, its public or private status is permanent and cannot be changed. The [tfVaultPrivate](../../ledger-data/ledger-entry-types/vault.md#vault-flags) flag determines this status, and once set, it cannot be updated.
|
||||
{% /admonition %}
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultSet",
|
||||
"Account": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"LastLedgerSequence": 7108682,
|
||||
"Sequence": 8,
|
||||
"VaultID": "77D6234D074E505024D39C04C3F262997B773719AB29ACFA83119E4210328776",
|
||||
"Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865207661756C742E",
|
||||
"AssetsMaximum": 5,
|
||||
"DomainID": "77D6234D074E505024D39C04C3F262997B773719AB29ACFA83119E4210328776"
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields](../../../protocol/transactions/common-fields#transaction-common-fields), {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :---------------- | :-------- | :---------------- | :-------- | :-------------------|
|
||||
| `VaultID` | String | Hash256 | Yes | The unique identifier of the vault that needs to be updated. |
|
||||
| `Data` | String | Blob | No | Arbitrary vault metadata, limited to 256 bytes. See [Data Field Format](../../ledger-data/ledger-entry-types/vault.md#data-field-format) for the recommended format. |
|
||||
| `AssetsMaximum` | Number | Number | No | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal`, unless the value is 0. |
|
||||
| `DomainID` | String | Hash256 | No | The [PermissionedDomain](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md) object ID associated with the shares of this vault. The `DomainID` is only required when updating a private vault. |
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} transactions.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes](../../../protocol/transactions/transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :-------------------- | :-------------------------------------|
|
||||
| `tecNO_ENTRY` | The transaction attempted to modify a vault that does not exist. Check the `VaultID` field of the transaction. |
|
||||
| `tecOBJECT_NOT_FOUND` | The `PermissionedDomain` object with the provided `DomainID` does not exist. |
|
||||
| `tecNO_PERMISSION` | The account submitting the transaction is not the `Owner` of the vault, or is trying to set a `DomainID` for a public vault. |
|
||||
| `temMALFORMED` | The transaction was not validly formatted. For example, the `Data` field is larger than 256 bytes. |
|
||||
| `tecLIMIT_EXCEEDED` | The _new_ `AssetsMaximum` value is **lower** than the vault's _current_ `AssetsTotal`. |
|
||||
| `temDISABLED` | Either the Single Asset Vault amendment is not enabled, or a `DomainID` is provided and the Permissioned Domains amendment is not enabled. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
90
docs/references/protocol/transactions/types/vaultwithdraw.md
Normal file
90
docs/references/protocol/transactions/types/vaultwithdraw.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
seo:
|
||||
description: Redeem vault shares for assets.
|
||||
labels:
|
||||
- Transactions
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# VaultWithdraw
|
||||
|
||||
[[Source]](https://github.com/XRPLF/rippled/blob/release-3.1/src/xrpld/app/tx/detail/VaultWithdraw.cpp "Source")
|
||||
|
||||
Redeem vault shares for assets. The amount of assets received depends on the [exchange rate](../../../../concepts/tokens/single-asset-vaults.md#exchange-algorithm), which adjusts based on the vault’s total assets and any [unrealized losses](../../../../concepts/tokens/single-asset-vaults.md#unrealized-loss).
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
The `VaultWithdraw` transaction does not respect the Permissioned Domain rules. In other words, any account that holds the shares of the vault can redeem them. This is to avoid a situation where a depositor deposits assets to a private vault to then have their access revoked by invalidating their credentials, and thus losing access to their funds.
|
||||
{% /admonition %}
|
||||
|
||||
A depositor cannot redeem liquidity if the trust line between the pseudo-account and the issuer of the vault asset is frozen, or the `MPToken` is locked.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Example {% $frontmatter.seo.title %} JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "VaultWithdraw",
|
||||
"Account": "rGFBE8WA2ZKfqGGB7CFkLusVt7hsVT4r8H",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "000000016E1417CA9DFD23400B05E43FDE5BB8D8FFA817CA",
|
||||
"value": "5"
|
||||
},
|
||||
"Destination": "rGFBE8WA2ZKfqGGB7CFkLusVt7hsVT4r8H",
|
||||
"Fee": "12",
|
||||
"Flags": 0,
|
||||
"Sequence": 200380,
|
||||
"VaultID": "A7B7B3ED3F5BD8E58C9064278EB29519CD6475D87A4517707DE108E65AE9C08C",
|
||||
}
|
||||
```
|
||||
|
||||
## {% $frontmatter.seo.title %} Fields
|
||||
|
||||
In addition to the [common fields](../../../protocol/transactions/common-fields#transaction-common-fields), {% code-page-name /%} transactions use the following fields:
|
||||
|
||||
| Field Name | JSON Type | [Internal Type][] | Required? | Description |
|
||||
| :-----------------------| :------------ | :---------------- | :-------- | :-------------------|
|
||||
| `VaultID` | String | Hash256 | Yes | The unique identifier of the vault to which the assets are deposited. |
|
||||
| `Amount` | Number | Amount | Yes | The exact amount of vault asset to withdraw or vault share to redeem. |
|
||||
| `Destination` | String | AccountID | No | An account to receive the assets. This account must be able to receive the vault asset or the transaction fails. |
|
||||
| `DestinationTag` | Number | UInt32 | No | Arbitrary tag identifying the reason for the withdrawal to the destination. |
|
||||
|
||||
There are two ways to specify the transaction `Amount` field:
|
||||
|
||||
<!-- Added extra column to avoid first column text from being automatically bolded -->
|
||||
| |Specify Assets | Specify Shares |
|
||||
|:--- |:-------------- |:--------------- |
|
||||
| |<ul><li>If the `Amount` field specifies an **asset amount** (e.g., 100 XRP), the transaction burns the necessary number of shares to provide the requested amount.</li><li>If the vault has an **unrealized loss**, withdrawing the same amount of assets requires burning more shares.</li></ul> | <ul><li>If the `Amount` field specifies a **share amount** (e.g., 500 vault shares), the transaction converts those shares into the corresponding amount of assets.</li><li>If the vault has an **unrealized loss**, each share is worth less, meaning fewer assets are received.</li></ul> |
|
||||
|
||||
## {% $frontmatter.seo.title %} Flags
|
||||
|
||||
There are no flags defined for {% code-page-name /%} transactions.
|
||||
|
||||
## Transfer Fees
|
||||
|
||||
A single asset vault does not apply the [transfer fee](../../../../concepts/tokens/fungible-tokens/transfer-fees) to {% code-page-name /%} transactions. Additionally, whenever a protocol moves assets from or to a vault, the Transfer Fee must not be charged.
|
||||
|
||||
## Error Cases
|
||||
|
||||
Besides errors that can occur for all transactions, {% code-page-name /%} transactions can result in the following [transaction result codes](../../../protocol/transactions/transaction-results/index.md):
|
||||
|
||||
| Error Code | Description |
|
||||
| :---------------------- | :----------------------------------|
|
||||
| `tecNO_ENTRY` | The `Vault` object with the provided `VaultID` does not exist on the ledger. |
|
||||
| `tecOBJECT_NOT_FOUND` | A ledger entry specified in the transaction does not exist. |
|
||||
| `tecNO_PERMISSION` | The destination account specified does not have permission to receive the asset. |
|
||||
| `tecWRONG_ASSET` | The unit of `Amount` is neither a share or asset of the vault. |
|
||||
| `tecINSUFFICIENT_FUNDS` | There is insufficient liquidity in the vault to fill the request. |
|
||||
| `tecFROZEN` | Either the trust line between the issuer and the destination account is frozen, or the asset is globally frozen. |
|
||||
| `tecLOCKED` | The MPT asset is locked for the depositor, destination account, or if the asset is globally locked. |
|
||||
| `temMALFORMED` | The transaction is not validly formatted. For example, the `VaultID` is not provided. |
|
||||
| `temDISABLED` | The Single Asset Vault amendment is not enabled. |
|
||||
| `temBAD_AMOUNT` | The `Amount` field of the transaction is invalid. For example, the provided amount is set to 0. |
|
||||
| `tecNO_AUTH` | The asset is a non-transferable MPT. |
|
||||
|
||||
## See Also
|
||||
|
||||
- [Vault entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
14
docs/tutorials/how-tos/set-up-lending/index.md
Normal file
14
docs/tutorials/how-tos/set-up-lending/index.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
seo:
|
||||
description: Pool funds in a single asset vault and enable on-chain, fixed-term, uncollateralized loans.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
- Single Asset Vault
|
||||
---
|
||||
# Set Up Lending
|
||||
|
||||
Pool funds in a single asset vault and enable on-chain, fixed-term, uncollateralized loans. The lending protocol is highly configurable, enabling loan brokers to easily tune risk appetite, depostitor protections, and economic incentives.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -0,0 +1,190 @@
|
||||
---
|
||||
seo:
|
||||
description: Create a single asset vault on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Create a Single Asset Vault
|
||||
|
||||
This tutorial shows you how to create a [single asset vault](../../../../concepts/tokens/single-asset-vaults.md) on the XRP Ledger. Vaults can only hold a single type of asset, such as XRP, a trust line token, or a Multi-Purpose Token (MPT).
|
||||
|
||||
You can create either a:
|
||||
|
||||
- **Public vault**: Anyone can deposit assets.
|
||||
- **Private vault**: Only users with valid [Credentials](../../../../concepts/decentralized-storage/credentials) can deposit, managed through [Permissioned Domains](../../../../concepts/tokens/decentralized-exchange/permissioned-domains).
|
||||
|
||||
The tutorial demonstrates how a financial institution could use a **private vault** to pool lender assets for uncollateralized lending while maintaining regulatory compliance through credential-based access control.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Create a **private** vault.
|
||||
- Configure vault parameters such as the asset type, maximum deposit amount, and withdrawal policy.
|
||||
- Configure whether depositors can transfer their vault shares to other accounts.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/vaults/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 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:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" before="// Create and fund" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" before="# Create and fund" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, fund a vault owner account, define the MPT issuance ID for the vault's asset, and provide a permissioned domain ID to control who can deposit into the vault.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Create and fund" before="// Prepare VaultCreate" /%}
|
||||
|
||||
The example uses an existing MPT issuance and permissioned domain data from the `vaultSetup.js` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domainID`.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Create and fund" before="# Prepare VaultCreate" /%}
|
||||
|
||||
The example uses an existing MPT issuance and permissioned domain data from the `vault_setup.py` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domain_id`.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare VaultCreate transaction
|
||||
|
||||
Create the [VaultCreate transaction][] object:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Prepare VaultCreate" before="// Submit, sign" /%}
|
||||
|
||||
The `tfVaultPrivate` flag and `DomainID` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
|
||||
|
||||
The `Data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
|
||||
|
||||
The `AssetsMaximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Prepare VaultCreate" before="# Submit, sign" /%}
|
||||
|
||||
The `tfVaultPrivate` flag and `domain_id` field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
|
||||
|
||||
The `data` field contains hex-encoded metadata about the vault itself, such as its name (`n`) and website (`w`). While any data structure is allowed, it's recommended to follow the [defined data schema](../../../../references/protocol/ledger-data/ledger-entry-types/vault.md#data-field-format) for better discoverability in the XRPL ecosystem.
|
||||
|
||||
The `assets_maximum` is set to `0` to indicate no cap on how much of the asset the vault can hold, but you can adjust as needed.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Vault shares are **transferable** by default, meaning depositors can transfer their shares to other accounts. If you don't want the vault's shares to be transferable, enable the `tfVaultShareNonTransferable` flag.
|
||||
|
||||
### 4. Submit VaultCreate transaction
|
||||
|
||||
Sign and submit the `VaultCreate` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Submit, sign" before="// Extract vault information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Submit, sign" before="# Extract vault information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 5. Get vault information
|
||||
|
||||
Retrieve the vault's information from the transaction result by checking for the `Vault` object in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Extract vault information" before="// Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Extract vault information" before="# Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
You can also use the [vault_info method][] to retrieve the vault's details:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/createVault.js" language="js" from="// Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/create_vault.py" language="python" from="# Call vault_info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This confirms that you have successfully created an empty single asset vault.
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Single Asset Vaults](../../../../concepts/tokens/single-asset-vaults.md)
|
||||
- [Credentials](../../../../concepts/decentralized-storage/credentials)
|
||||
- [Permissioned Domains](../../../../concepts/tokens/decentralized-exchange/permissioned-domains)
|
||||
|
||||
**Tutorials**:
|
||||
- [Issue Credentials](../../../javascript/build-apps/credential-issuing-service.md)
|
||||
- [Create Permissioned Domain](../../../javascript/compliance/create-permissioned-domains.md)
|
||||
- [Deposit Assets into a Vault](./deposit-into-a-vault.md)
|
||||
|
||||
**References**:
|
||||
- [VaultCreate transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,224 @@
|
||||
---
|
||||
seo:
|
||||
description: Deposit assets into a vault and receive shares.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Deposit into a Vault
|
||||
|
||||
This tutorial shows you how to deposit assets into a [single asset vault](../../../../concepts/tokens/single-asset-vaults.md). The example demonstrates depositing into a private vault with credential-based access control, however you can easily use the same code to deposit into a public vault.
|
||||
|
||||
When you deposit into a vault, you receive shares that represent your proportional ownership of the vault's assets. For example, in an institutional lending context, depositing into a vault allows you to pool your assets with other depositors to participate in larger lending markets.
|
||||
|
||||
{% admonition type="warning" name="Warning" %}
|
||||
Anyone can create a public vault, and malicious vault owners can drain your assets. Always verify that the vault owner and vault settings meet your standards before depositing assets.
|
||||
{% /admonition %}
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Deposit assets into a private/public vault.
|
||||
- Check the depositing account's share balance and the vault's state after a successful deposit.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have access to an existing vault. This tutorial uses a preconfigured vault. To create your own vault, see [Create a Single Asset Vault](./create-a-single-asset-vault.md).
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/vaults/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 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:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" before="// You can replace" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" before="# You can replace" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Provide the depositing account and specify the vault details. The depositor must have a balance of the vault's asset to deposit.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// You can replace" before="// Get initial vault" /%}
|
||||
|
||||
This example uses an existing vault, depositor account, and MPT from the `vaultSetup.js` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# You can replace" before="# Get initial vault" /%}
|
||||
|
||||
This example uses an existing vault, depositor account, and MPT from the `vault_setup.py` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The preconfigured depositor account has:
|
||||
|
||||
- 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.
|
||||
|
||||
### 3. Check initial vault state
|
||||
|
||||
Use the [vault_info method][] to retrieve the vault's current state, including its total value and available liquidity.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get initial vault" before="// Check depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get initial vault" before="# Check depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check depositor's asset balance
|
||||
|
||||
Before depositing, verify that the depositor has sufficient balance of the vault's asset. If the depositor doesn't have enough funds, the transaction will fail with a `tecINSUFFICIENT_FUNDS` error.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Check depositor's asset balance" before="// Prepare VaultDeposit" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Check depositor's asset balance" before="# Prepare VaultDeposit" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Prepare VaultDeposit transaction
|
||||
|
||||
Create a [VaultDeposit transaction][] object to deposit assets into the vault.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Prepare VaultDeposit" before="// Submit VaultDeposit" /%}
|
||||
|
||||
The transaction specifies the depositing account, the vault's unique identifier (`VaultID`), and the amount to deposit. The asset in the `Amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Prepare VaultDeposit" before="# Submit VaultDeposit" /%}
|
||||
|
||||
The transaction specifies the depositing account, the vault's unique identifier (`vault_id`), and the amount to deposit. The asset in the `amount` field must match the vault's asset type, otherwise the transaction will fail with a `tecWRONG_ASSET` error.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 6. Submit VaultDeposit transaction
|
||||
|
||||
Submit the `VaultDeposit` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Submit VaultDeposit" before="// Extract vault state" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Submit VaultDeposit" before="# Extract vault state" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
When depositing into a private vault, the transaction verifies that the depositor has valid credentials in the vault's permissioned domain. Without valid credentials, the `VaultDeposit` transaction fails with a `tecNO_AUTH` error.
|
||||
|
||||
If the transaction succeeds, the vault:
|
||||
|
||||
- Transfers the assets from the depositing account to the vault's pseudo-account.
|
||||
- Issues vault shares to the depositor.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
Transfer fees are not charged on `VaultDeposit` transactions.
|
||||
{% /admonition %}
|
||||
|
||||
### 7. Verify deposit and check share balance
|
||||
|
||||
After depositing, verify the vault's updated state. You can extract this information directly from the transaction metadata without making additional API calls:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Extract vault state" before="// Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Extract vault state" before="# Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, check that the depositing account has received the shares.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/deposit.js" language="js" from="// Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/deposit.py" language="py" from="# Get the depositor's" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The code checks for both `ModifiedNode` and `CreatedNode` because on the first deposit, a new MPToken entry is created for the depositor's shares (`CreatedNode`). On subsequent deposits, the depositor's existing share balance is updated (`ModifiedNode`).
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Single Asset Vaults](../../../../concepts/tokens/single-asset-vaults.md)
|
||||
- [Credentials](../../../../concepts/decentralized-storage/credentials.md)
|
||||
- [Permissioned Domains](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md)
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Single Asset Vault](./create-a-single-asset-vault.md)
|
||||
- [Withdraw from a Vault](./withdraw-from-a-vault.md)
|
||||
|
||||
**References**:
|
||||
- [VaultDeposit transaction][]
|
||||
- [vault_info method][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
seo:
|
||||
description: Create, deposit into, and withdraw from single asset vaults.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
---
|
||||
# Use Single Asset Vaults
|
||||
|
||||
Single asset vaults aggregate assets from multiple depositors and make them available to other on-chain protocols.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -0,0 +1,235 @@
|
||||
---
|
||||
seo:
|
||||
description: Withdraw assets from a single asset vault.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Single Asset Vault
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Withdraw from a Vault
|
||||
|
||||
This tutorial shows you how to withdraw assets from a [single asset vault](../../../../concepts/tokens/single-asset-vaults.md). You can withdraw by specifying either how many assets you want to receive or how many shares you want to redeem. The vault burns the necessary shares and transfers the corresponding assets to your account.
|
||||
|
||||
{% amendment-disclaimer name="SingleAssetVault" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Withdraw assets from a private/public vault.
|
||||
- Check the vault's state after a successful withdrawal.
|
||||
- Check the depositor account's state after the withdrawal.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have previously deposited into a vault. This tutorial uses an account that has already deposited into a vault. To deposit your own asset, see [Deposit into a Vault](./deposit-into-a-vault.md).
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/vaults/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies:
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies:
|
||||
|
||||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 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:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
- `fs` and `child_process`: Used to run tutorial setup scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" before="// You can replace" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, `sys`: Used for file handling and running setup scripts.
|
||||
- `xrpl`: Used for XRPL client connection and transaction handling.
|
||||
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" before="# You can replace" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Provide the depositor account and specify the vault details.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// You can replace" before="console.log" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# You can replace" before="print" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `vault_setup.py` script, but you can replace these values with your own.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check initial vault state
|
||||
|
||||
Before withdrawing, check the vault's current state to see its total assets and available liquidity.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get initial vault" before="// Check depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get initial vault" before="# Check depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Check share balance
|
||||
|
||||
Verify that the depositor account has vault shares to redeem. If not, the transaction will fail with a `tecINSUFFICIENT_FUNDS` error.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Check depositor's share balance" before="// Prepare VaultWithdraw" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Check depositor's share balance" before="# Prepare VaultWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Prepare VaultWithdraw transaction
|
||||
|
||||
Create a [VaultWithdraw transaction][] to withdraw assets from the vault.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Prepare VaultWithdraw" before="// Submit VaultWithdraw" /%}
|
||||
|
||||
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways:
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Prepare VaultWithdraw" before="# Submit VaultWithdraw" /%}
|
||||
|
||||
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`vault_id`), and the amount to withdraw or redeem. You can specify the `amount` field in two ways:
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
- **Asset amount**: When you specify an asset amount, the vault burns the necessary shares to provide that amount.
|
||||
- **Share amount**: When you specify a share amount, the vault converts those shares into the corresponding asset amount.
|
||||
|
||||
While not required, you can provide a destination account to receive the assets; if omitted, assets go to the account submitting the transaction.
|
||||
|
||||
{% 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.
|
||||
{% /admonition %}
|
||||
|
||||
### 6. Submit VaultWithdraw transaction
|
||||
|
||||
Submit the `VaultWithdraw` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Submit VaultWithdraw " before="// Extract vault state" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Submit VaultWithdraw" before="# Extract vault state" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
When the transaction succeeds:
|
||||
|
||||
- The vault calculates how many shares need to be burned to provide the requested asset amount.
|
||||
- The vault transfers the assets from its pseudo-account to the depositor account (or the destination account if specified).
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
Transfer fees are not charged on `VaultWithdraw` transactions.
|
||||
{% /admonition %}
|
||||
|
||||
### 7. Verify withdrawal
|
||||
|
||||
After withdrawing, check the vault's state. You can extract this information directly from the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Extract vault state" before="// Get the depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Extract vault state" before="# Get the depositor's share balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Then, check the depositor's share balance:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's share balance" before="// Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's share balance" before="# Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Finally, verify the correct asset amount has been received by the depositor account:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's asset balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
|
||||
- [Single Asset Vaults](../../../../concepts/tokens/single-asset-vaults.md)
|
||||
- [Credentials](../../../../concepts/decentralized-storage/credentials.md)
|
||||
- [Permissioned Domains](../../../../concepts/tokens/decentralized-exchange/permissioned-domains.md)
|
||||
|
||||
**Tutorials**:
|
||||
|
||||
- [Create a Single Asset Vault](./create-a-single-asset-vault.md)
|
||||
- [Deposit into a Vault](./deposit-into-a-vault.md)
|
||||
|
||||
**References**:
|
||||
|
||||
- [VaultWithdraw transaction][]
|
||||
- [vault_info method][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,231 @@
|
||||
---
|
||||
seo:
|
||||
description: Claw back tokens that have been deposited into a LoanBroker entry as first-loss capital.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Claw Back First-Loss Capital
|
||||
|
||||
This tutorial shows you how to claw back tokens from a [LoanBroker][] on the XRP Ledger. The clawback feature enables token issuers (Multi-Purpose or Trust Line) to meet regulatory standards and claw back funds even if those funds have been deposited as [first-loss capital](../../../../concepts/tokens/lending-protocol.md#risk-management).
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Deposit an MPT as first-loss capital into a `LoanBroker` entry.
|
||||
- Verify the MPT issuer.
|
||||
- Claw back all first-loss capital from the `LoanBroker` entry.
|
||||
- Check the cover available for the `LoanBroker` entry.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// This step checks" before="// Check cover available" /%}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# This step checks" before="# Check cover available" /%}
|
||||
|
||||
This example uses preconfigured accounts, MPTs, and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `mpt_issuer`, `loan_broker_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check initial cover available
|
||||
|
||||
Check the initial cover (first-loss capital) available using the [ledger_entry method][].
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Check cover available" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Check cover available" before="# Prepare LoanBrokerCoverDeposit" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited.
|
||||
|
||||
### 4. Prepare LoanBrokerCoverDeposit transaction
|
||||
|
||||
Create the [LoanBrokerCoverDeposit transaction][] object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit validation" /%}
|
||||
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit validation" /%}
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
|
||||
### 5. Submit LoanBrokerCoverDeposit transaction
|
||||
|
||||
Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for deposit validation" before="// Extract updated cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for deposit validation" before="# Extract updated cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 6. Check updated cover available
|
||||
|
||||
Retrieve the cover available from the transaction result by checking the `LoanBroker` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract updated cover available" before="// Verify issuer of cover asset" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract updated cover available" before="# Verify issuer of cover asset" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Verify the asset issuer
|
||||
|
||||
Before executing a clawback, verify that the account submitting the transaction is the same as the asset issuer.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Verify issuer of cover asset" before="// Prepare LoanBrokerCoverClawback" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Verify issuer of cover asset" before="# Prepare LoanBrokerCoverClawback" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Clawback functionality is disabled by default. In the case of MPTs, the `tfMPTCanClawback` flag must be enabled when the [MPTokenIssuanceCreate transaction][] is submitted. This tutorial uses an MPT that is already configured for clawback.
|
||||
|
||||
### 8. Prepare LoanBrokerCoverClawback transaction
|
||||
|
||||
Create the [LoanBrokerCoverClawback transaction][] object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Prepare LoanBrokerCoverClawback" before="// Sign, submit, and wait for clawback validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Prepare LoanBrokerCoverClawback" before="# Sign, submit, and wait for clawback validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
In this example we claw back the entire amount, but you can specify any amount so long as it doesn't exceed the available cover or reduce the cover below the minimum required by the `LoanBroker`.
|
||||
|
||||
### 9. Submit LoanBrokerCoverClawback transaction
|
||||
|
||||
Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Sign, submit, and wait for clawback validation" before="// Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Sign, submit, and wait for clawback validation" before="# Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 10. Check final cover available
|
||||
|
||||
Retrieve the final cover available from the transaction result.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverClawback.js" language="js" from="// Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_clawback.py" language="py" from="# Extract final cover available" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
- [Clawing Back Tokens](../../../../concepts/tokens/fungible-tokens/clawing-back-tokens.md)
|
||||
|
||||
**Tutorials**:
|
||||
- [Issue a Multi-Purpose Token (MPT)](../../use-tokens/issue-a-multi-purpose-token.md)
|
||||
|
||||
**References**:
|
||||
- [LoanBrokerCoverDeposit transaction][]
|
||||
- [LoanBrokerCoverClawback transaction][]
|
||||
- [MPTokenIssuanceCreate transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,155 @@
|
||||
---
|
||||
seo:
|
||||
description: Create a loan broker on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Create a Loan Broker
|
||||
|
||||
This tutorial shows you how to create a [LoanBroker][] on the XRP Ledger using a private vault. A loan broker creates and manages loans, and also manages the first-loss capital for a connected single asset vault.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Create a **loan broker** linked to a private single asset vault.
|
||||
- Configure loan broker parameters, such as a management fee rate.
|
||||
- Retrieve the loan broker ID and pseudo-account.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the vault owner account and vault ID. The vault owner will also be the loan broker.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// This step checks" before="// Prepare LoanBrokerSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lendingSetup.js` script, but you can replace `loanBroker` and `vaultID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# This step checks" before="# Prepare LoanBrokerSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and vault data from the `lending_setup.py` script, but you can replace `loan_broker` and `vault_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanBrokerSet transaction
|
||||
|
||||
Create the [LoanBrokerSet transaction][] object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Prepare LoanBrokerSet" before="// Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Prepare LoanBrokerSet" before="# Submit, sign" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The management fee rate is set in 1/10th basis points. A value of `1000` equals 1% (100 basis points).
|
||||
|
||||
### 4. Submit LoanBrokerSet transaction
|
||||
|
||||
Sign and submit the `LoanBrokerSet` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Submit, sign" before="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Submit, sign" before="# Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 5. Get loan broker information
|
||||
|
||||
Retrieve the loan broker's information from the transaction result by checking for the `LoanBroker` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoanBroker.js" language="js" from="// Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan_broker.py" language="py" from="# Extract loan broker" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan broker pseudo-account is a special account that holds first-loss capital.
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
- [Single Asset Vault](../../../../concepts/tokens/single-asset-vaults.md)
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Single Asset Vault](../use-single-asset-vaults/create-a-single-asset-vault.md)
|
||||
- [Deposit and Withdraw First-Loss Capital](./deposit-and-withdraw-cover.md)
|
||||
- [Create a Loan](./create-a-loan.md)
|
||||
|
||||
**References**:
|
||||
- [LoanBrokerSet transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
seo:
|
||||
description: Create a loan on the XRP Ledger using the Lending Protocol.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Create a Loan
|
||||
|
||||
This tutorial shows you how to create a [Loan][] on the XRP Ledger. A loan requires signatures from both the loan broker and the borrower to be created.
|
||||
|
||||
This tutorial demonstrates how a loan broker and a borrower can cosign the terms of a loan and create that loan on the XRPL.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Create a **LoanSet transaction** with loan terms.
|
||||
- Sign and add the loan broker's signature to the transaction.
|
||||
- Sign and add the borrower's signature to the transaction.
|
||||
- Submit the cosigned transaction to create a loan.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, borrower account, and loan broker ID.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// This step checks" before="// Prepare LoanSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `borrower`, and `loanBrokerID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# This step checks" before="# Prepare LoanSet" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `borrower`, and `loan_broker_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanSet transaction
|
||||
|
||||
Create the [LoanSet transaction][] object with the loan terms.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Prepare LoanSet" before="// Loan broker signs first" /%}
|
||||
|
||||
The `Account` field is the loan broker, and the `Counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `Account` signs first, and the `Counterparty` signs second.
|
||||
|
||||
The loan terms include:
|
||||
- `PrincipalRequested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.
|
||||
- `InterestRate`: The annualized interest rate in 1/10th basis points (500 = 0.5%).
|
||||
- `PaymentTotal`: The number of payments to be made.
|
||||
- `PaymentInterval`: The number of seconds between payments (2592000 = 30 days).
|
||||
- `GracePeriod`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).
|
||||
- `LoanOriginationFee`: A one-time fee charged when the loan is created, paid in the borrowed asset.
|
||||
- `LoanServiceFee`: A fee charged with every loan payment, paid in the borrowed asset.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Prepare LoanSet" before="# Loan broker signs first" /%}
|
||||
|
||||
The `account` field is the loan broker, and the `counterparty` field is the borrower. These fields can be swapped, but determine the signing order: the `account` signs first, and the `counterparty` signs second.
|
||||
|
||||
The loan terms include:
|
||||
- `principal_requested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.
|
||||
- `interest_rate`: The annualized interest rate in 1/10th basis points (500 = 0.5%).
|
||||
- `payment_total`: The number of payments to be made.
|
||||
- `payment_interval`: The number of seconds between payments (2592000 = 30 days).
|
||||
- `grace_period`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).
|
||||
- `loan_origination_fee`: A one-time fee charged when the loan is created, paid in the borrowed asset.
|
||||
- `loan_service_fee`: A fee charged with every loan payment, paid in the borrowed asset.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 4. Add loan broker signature
|
||||
|
||||
The loan broker (the `Account`) signs the transaction first, adding their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Loan broker signs first" before="// Borrower signs second" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Loan broker signs first" before="# Borrower signs second" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Add borrower signature
|
||||
|
||||
The borrower (the `Counterparty`) signs the transaction second. Their `TxnSignature` and `SigningPubKey` are stored in a `CounterpartySignature` field, which is added to the `LoanSet` transaction object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Borrower signs second" before="// Submit and wait" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Borrower signs second" before="# Submit and wait" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 6. Submit LoanSet transaction
|
||||
|
||||
Submit the fully signed `LoanSet` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Submit and wait" before="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Submit and wait" before="# Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 7. Get loan information
|
||||
|
||||
Retrieve the loan's information from the transaction result by checking for the `Loan` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/createLoan.js" language="js" from="// Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/create_loan.py" language="py" from="# Extract loan information" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Loan Broker](./create-a-loan-broker.md)
|
||||
- [Manage a Loan](./manage-a-loan.md)
|
||||
|
||||
**References**:
|
||||
- [LoanSet transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,202 @@
|
||||
---
|
||||
seo:
|
||||
description: Deposit and withdraw first-loss capital from a LoanBroker entry on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Deposit and Withdraw First-Loss Capital
|
||||
|
||||
This tutorial shows you how to deposit and withdraw first-loss capital from a [LoanBroker][] on the XRP Ledger. First-loss capital helps protect vault depositor's assets, acting as a buffer in the event of loan defaults.
|
||||
|
||||
The tutorial demonstrates how a loan broker can manage risk by depositing XRP as first-loss capital, and how they can withdraw it when needed.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Deposit an MPT as first-loss capital into a `LoanBroker` entry.
|
||||
- Check the available cover balance in the loan broker's pseudo-account.
|
||||
- Withdraw first-loss capital from a `LoanBroker` entry.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account, loan broker ID, and MPT issuance ID.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// This step checks" before="// Prepare LoanBrokerCoverDeposit" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `loanBrokerID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# This step checks" before="# Prepare LoanBrokerCoverDeposit" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `loan_broker_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Prepare LoanBrokerCoverDeposit transaction
|
||||
|
||||
Create the [LoanBrokerCoverDeposit transaction][] object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverDeposit" before="// Sign, submit, and wait for deposit" /%}
|
||||
|
||||
The `Amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverDeposit" before="# Sign, submit, and wait for deposit" /%}
|
||||
|
||||
The `amount` field specifies the MPT and amount to deposit as first-loss capital.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry.
|
||||
|
||||
### 4. Submit LoanBrokerCoverDeposit transaction
|
||||
|
||||
Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for deposit" before="// Extract cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for deposit" before="# Extract cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 5. Check cover balance after deposit
|
||||
|
||||
Retrieve the cover balance from the transaction result by checking the `LoanBroker` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract cover balance" before="// Prepare LoanBrokerCoverWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract cover balance" before="# Prepare LoanBrokerCoverWithdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `LoanBroker` pseudo-account address is the `Account` field, and `CoverAvailable` shows the cover balance.
|
||||
|
||||
### 6. Prepare LoanBrokerCoverWithdraw transaction
|
||||
|
||||
Create the [LoanBrokerCoverWithdraw transaction][] object.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Prepare LoanBrokerCoverWithdraw" before="// Sign, submit, and wait for withdraw" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Prepare LoanBrokerCoverWithdraw" before="# Sign, submit, and wait for withdraw" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 7. Submit LoanBrokerCoverWithdraw transaction
|
||||
|
||||
Sign and submit the `LoanBrokerCoverWithdraw` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Sign, submit, and wait for withdraw" before="// Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Sign, submit, and wait for withdraw" before="# Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 8. Check cover balance after withdrawal
|
||||
|
||||
Retrieve the updated cover balance from the transaction result.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/coverDepositAndWithdraw.js" language="js" from="// Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/cover_deposit_and_withdraw.py" language="py" from="# Extract updated cover balance" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `CoverAvailable` field now shows the reduced balance after the withdrawal.
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Loan Broker](./create-a-loan-broker.md)
|
||||
|
||||
**References**:
|
||||
- [LoanBrokerCoverDeposit transaction][]
|
||||
- [LoanBrokerCoverWithdraw transaction][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
seo:
|
||||
description: Create and manage loans on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
---
|
||||
# Use the Lending Protocol
|
||||
|
||||
The Lending Protocol enables you to create highly configurable loans on the XRP Ledger.
|
||||
|
||||
{% child-pages /%}
|
||||
@@ -0,0 +1,226 @@
|
||||
---
|
||||
seo:
|
||||
description: Impair and default a loan on the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Manage a Loan
|
||||
|
||||
This tutorial shows you how to manage a [Loan][] on the XRP Ledger. Loan management includes marking loans as impaired when payments are missed, defaulting loans after the grace period expires, and deleting repaid or defaulted loans.
|
||||
|
||||
The tutorial demonstrates how a loan broker can manually impair a loan before a payment due date passes (in cases where you suspect a borrower can't make a payment) and default the loan after the grace period expires.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Check the status of an existing loan.
|
||||
- Manually impair a loan to speed up the default process.
|
||||
- Wait through the loan's grace period.
|
||||
- Default a loan after the grace period expires.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
- `time` and `datetime`: Used for grace period countdown and date formatting.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the loan broker account and loan ID.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// This step checks" before="// Check loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# This step checks" before="# Check loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `loan_broker` and `loan_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check loan status
|
||||
|
||||
Check the current status of the loan using the [ledger_entry method][].
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Check loan status" before="// Prepare LoanManage transaction to impair" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Check loan status" before="# Prepare LoanManage transaction to impair" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
This shows the total amount owed and the next payment due date. The [Ripple Epoch][] timestamp is converted to a readable date format.
|
||||
|
||||
### 4. Prepare LoanManage transaction to impair the loan
|
||||
|
||||
Create the [LoanManage transaction][] with the `tfLoanImpair` flag.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to impair" before="// Sign, submit, and wait for impairment" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to impair" before="# Sign, submit, and wait for impairment" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanManage impairment transaction
|
||||
|
||||
Sign and submit the `LoanManage` transaction to impair the loan.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for impairment" before="// Extract loan impairment info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for impairment" before="# Extract loan impairment info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 6. Get loan impairment information
|
||||
|
||||
Retrieve the loan's grace period and updated payment due date from the transaction result by checking for the `Loan` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Extract loan impairment info" before="// Countdown until loan can be defaulted" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Extract loan impairment info" before="# Countdown until loan can be defaulted" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan can only be defaulted after the grace period expires. The example calculates when the grace period ends and displays a countdown.
|
||||
|
||||
### 7. Wait for grace period to expire
|
||||
|
||||
This countdown displays the remaining seconds in real-time. Once the grace period expires, the loan can be defaulted.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Countdown until loan can be defaulted" before="// Prepare LoanManage transaction to default" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Countdown until loan can be defaulted" before="# Prepare LoanManage transaction to default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 8. Prepare LoanManage transaction to default the loan
|
||||
|
||||
After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Prepare LoanManage transaction to default" before="// Sign, submit, and wait for default" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Prepare LoanManage transaction to default" before="# Sign, submit, and wait for default" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 9. Submit LoanManage default transaction
|
||||
|
||||
Sign and submit the `LoanManage` transaction to default the loan.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Sign, submit, and wait for default" before="// Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Sign, submit, and wait for default" before="# Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 10. Verify loan default status
|
||||
|
||||
Confirm the loan has been defaulted by checking the loan flags.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanManage.js" language="js" from="// Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_manage.py" language="py" from="# Verify loan default status" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The loan flags are parsed to confirm the `tfLoanDefault` flag is now set.
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Loan](./create-a-loan.md)
|
||||
|
||||
**References**:
|
||||
- [LoanManage transaction][]
|
||||
- [Loan entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,219 @@
|
||||
---
|
||||
seo:
|
||||
description: Pay off a loan and delete it from the XRP Ledger.
|
||||
metadata:
|
||||
indexPage: true
|
||||
labels:
|
||||
- Lending Protocol
|
||||
status: not_enabled
|
||||
---
|
||||
|
||||
# Pay Off a Loan
|
||||
|
||||
This tutorial shows you how to pay off a [Loan][] and delete it. Loans can only be deleted after they are fully paid off, or if they've been defaulted by the loan broker.
|
||||
|
||||
The tutorial demonstrates how to calculate the final payment due, which includes the loan balance and any additional fees, and then pay off the loan. After the loan is fully paid off, the loan is deleted, completely removing it from the XRP Ledger.
|
||||
|
||||
{% amendment-disclaimer name="LendingProtocol" /%}
|
||||
|
||||
## Goals
|
||||
|
||||
By the end of this tutorial, you will be able to:
|
||||
|
||||
- Check the outstanding balance on a loan.
|
||||
- Calculate the total payment due, including additional fees.
|
||||
- Submit a loan payment.
|
||||
- Delete a paid off loan from the XRP Ledger.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To complete this tutorial, you should:
|
||||
|
||||
- Have a basic understanding of the XRP Ledger.
|
||||
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
|
||||
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
|
||||
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for this tutorial's examples in the {% repo-link path="_code-samples/lending-protocol/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Install dependencies
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
From the code sample folder, use `npm` to install dependencies.
|
||||
|
||||
```bash
|
||||
npm install xrpl
|
||||
```
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
From the code sample folder, set up a virtual environment and use `pip` to install dependencies.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 2. Set up client and accounts
|
||||
|
||||
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `fs` and `child_process`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" before="// This step checks" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
|
||||
- `json`: Used for loading and formatting JSON data.
|
||||
- `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts.
|
||||
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" before="# This step checks" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Next, load the borrower account, loan ID, and MPT issuance ID.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// This step checks" before="// Check initial loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values.
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# This step checks" before="# Check initial loan status" /%}
|
||||
|
||||
This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `borrower`, `loan_id`, and `mpt_id` with your own values.
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 3. Check loan status
|
||||
|
||||
Check the current status of the loan using the [ledger_entry method][].
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Check initial loan status" before="// Prepare LoanPay transaction" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Check initial loan status" before="# Prepare LoanPay transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
The `TotalValueOutstanding` field contains the remaining principal plus accrued interest; the `LoanServiceFee` is an additional fee charged per payment. Add these together to calculate the total payment.
|
||||
|
||||
{% admonition type="info" name="Note" %}
|
||||
Other fees can be charged on a loan, such as late or early payment fees. These additional fees must be accounted for when calculating payment amounts.
|
||||
{% /admonition %}
|
||||
|
||||
### 4. Prepare LoanPay transaction
|
||||
|
||||
Create the [LoanPay transaction][] with the total payment amount.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanPay transaction" before="// Sign, submit, and wait for payment validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanPay transaction" before="# Sign, submit, and wait for payment validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
### 5. Submit LoanPay transaction
|
||||
|
||||
Sign and submit the `LoanPay` transaction to the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for payment validation" before="// Extract updated loan info" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for payment validation" before="# Extract updated loan info" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 6. Check loan balance
|
||||
|
||||
Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Extract updated loan info" before="// Prepare LoanDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Extract updated loan info" before="# Prepare LoanDelete transaction" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If `TotalValueOutstanding` is absent from the loan metadata, the loan has been fully paid off and is ready for deletion.
|
||||
|
||||
### 7. Prepare LoanDelete transaction
|
||||
|
||||
Create a [LoanDelete transaction][] to remove the paid loan from the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Prepare LoanDelete transaction" before="// Sign, submit, and wait for deletion validation" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Prepare LoanDelete transaction" before="# Sign, submit, and wait for deletion validation" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Either the loan broker or the borrower can submit a `LoanDelete` transaction. In this example, the borrower deletes their own paid off loan.
|
||||
|
||||
### 8. Submit LoanDelete transaction
|
||||
|
||||
Sign and submit the `LoanDelete` transaction.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Sign, submit, and wait for deletion validation" before="// Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Sign, submit, and wait for deletion validation" before="# Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
Verify that the transaction succeeded by checking for a `tesSUCCESS` result code.
|
||||
|
||||
### 9. Verify loan deletion
|
||||
|
||||
Confirm that the loan has been removed from the XRP Ledger.
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/js/loanPay.js" language="js" from="// Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/lending-protocol/py/loan_pay.py" language="py" from="# Verify loan deletion" /%}
|
||||
{% /tab %}
|
||||
{% /tabs %}
|
||||
|
||||
If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted.
|
||||
|
||||
## See Also
|
||||
|
||||
**Concepts**:
|
||||
- [Lending Protocol][]
|
||||
|
||||
**Tutorials**:
|
||||
- [Create a Loan](./create-a-loan.md)
|
||||
|
||||
**References**:
|
||||
- [LoanDelete transaction][]
|
||||
- [LoanPay transaction][]
|
||||
- [Loan entry][]
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user