Compare commits

...

6 Commits

Author SHA1 Message Date
Maria Shodunke
681e55dd18 Speed up vault_setup.py 2026-02-13 21:41:32 +00:00
Maria Shodunke
0d960fa0d3 Add Python SAV code examples 2026-02-13 21:33:54 +00:00
oeggert
5ad87dee4b Merge pull request #3487 from XRPLF/lending-use-case
Lending Protocol doc updates
2026-02-10 10:19:59 -08:00
oeggert
66caa79289 Update docs/use-cases/defi/institutional-credit-facilities.md
Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com>
2026-02-10 10:10:32 -08:00
Oliver Eggert
ff75fc0af9 update reference docs to latest spec 2026-02-09 22:09:26 -08:00
Oliver Eggert
1e2f9ea0b0 add user journeys section 2026-02-09 20:52:27 -08:00
19 changed files with 1159 additions and 88 deletions

View File

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

View File

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

View File

@@ -0,0 +1,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 vault_id and share_mpt_issuance_id
# values with your own.
import json
import os
import subprocess
import sys
from xrpl.clients import JsonRpcClient
from xrpl.models import VaultDeposit
from xrpl.models.requests import VaultInfo, LedgerEntry
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet
# Auto-run setup if needed
if not os.path.exists("vault_setup.json"):
print("\n=== Vault setup data doesn't exist. Running setup script... ===\n")
subprocess.run(["python", "vault_setup.py"], check=True)
# Load setup data
with open("vault_setup.json", "r") as f:
setup_data = json.load(f)
# Connect to the network
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
# You can replace these values with your own
depositor = Wallet.from_seed(setup_data["depositor"]["seed"])
vault_id = setup_data["vaultID"]
asset_mpt_issuance_id = setup_data["mptIssuanceId"]
share_mpt_issuance_id = setup_data["vaultShareMPTIssuanceId"]
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)
sys.exit(1)
# Prepare VaultDeposit transaction
print("\n=== VaultDeposit transaction ===")
vault_deposit_tx = VaultDeposit(
account=depositor.address,
vault_id=vault_id,
amount={
"mpt_issuance_id": asset_mpt_issuance_id,
"value": str(deposit_amount)
}
)
print(json.dumps(vault_deposit_tx.to_xrpl(), indent=2))
# Submit VaultDeposit transaction
print("\n=== Submitting VaultDeposit transaction... ===")
deposit_result = submit_and_wait(vault_deposit_tx, client, depositor, autofill=True)
if deposit_result.result["meta"]["TransactionResult"] != "tesSUCCESS":
result_code = deposit_result.result["meta"]["TransactionResult"]
print(f"Error: Unable to deposit: {result_code}", file=sys.stderr)
sys.exit(1)
print("Deposit successful!")
# Extract vault state from transaction metadata
print("\n=== Vault state after deposit ===")
affected_nodes = deposit_result.result["meta"]["AffectedNodes"]
vault_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
modified = node["ModifiedNode"]
if modified["LedgerEntryType"] == "Vault" and modified["LedgerIndex"] == vault_id:
vault_node = node
break
if vault_node:
vault_fields = vault_node["ModifiedNode"]["FinalFields"]
print(f" - Total vault value: {vault_fields['AssetsTotal']}")
print(f" - Available assets: {vault_fields['AssetsAvailable']}")
# Get the depositor's share balance
print("\n=== Depositor's share balance ===")
depositor_share_node = None
for node in affected_nodes:
if "ModifiedNode" in node:
share_node = node["ModifiedNode"]
fields = share_node["FinalFields"]
elif "CreatedNode" in node:
share_node = node["CreatedNode"]
fields = share_node["NewFields"]
else:
continue
if (share_node["LedgerEntryType"] == "MPToken" and
fields["Account"] == depositor.address and
fields["MPTokenIssuanceID"] == share_mpt_issuance_id):
depositor_share_node = node
break
if depositor_share_node:
if "ModifiedNode" in depositor_share_node:
share_fields = depositor_share_node["ModifiedNode"]["FinalFields"]
else:
share_fields = depositor_share_node["CreatedNode"]["NewFields"]
print(f"Shares held: {share_fields['MPTAmount']}")

View File

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

View File

@@ -0,0 +1,271 @@
import asyncio
import json
import sys
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.transaction import submit_and_wait
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.models import (
Batch, BatchFlag, CredentialAccept, CredentialCreate, MPTokenAuthorize,
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
PermissionedDomainSet, VaultDeposit
)
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.models.transactions.vault_create import (
VaultCreate, VaultCreateFlag, WithdrawalPolicy
)
from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
# Setup script for vault tutorials
print("Setting up tutorial: 0/7", end="\r")
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
# Create and fund all wallets concurrently
mpt_issuer, domain_owner, depositor, vault_owner = await asyncio.gather(
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
generate_faucet_wallet(client),
)
# Step 1: Create MPT issuance
print("Setting up tutorial: 1/7", end="\r")
mpt_create_result = await 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
)
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
# Step 2: Create Permissioned Domain
print("Setting up tutorial: 2/7", end="\r")
cred_type = "VaultAccess"
domain_result = await submit_and_wait(
PermissionedDomainSet(
account=domain_owner.address,
accepted_credentials=[
Credential(
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type)
)
],
),
client,
domain_owner,
autofill=True
)
domain_node = next(
node for node in domain_result.result["meta"]["AffectedNodes"]
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "PermissionedDomain"
)
domain_id = domain_node["CreatedNode"]["LedgerIndex"]
# Step 3: Create depositor account with credentials and MPT balance
print("Setting up tutorial: 3/7", end="\r")
# Create credential for depositor and depositor accepts credential concurrently
await submit_and_wait(
CredentialCreate(
account=domain_owner.address,
subject=depositor.address,
credential_type=str_to_hex(cred_type),
),
client,
domain_owner,
autofill=True
)
# Depositor accepts credential and authorizes MPT in a batch transaction
await submit_and_wait(
Batch(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=depositor.address,
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type),
),
MPTokenAuthorize(
account=depositor.address,
mptoken_issuance_id=mpt_issuance_id,
),
],
),
client,
depositor,
autofill=True
)
print("Setting up tutorial: 4/7", 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: Create a vault for deposit/withdraw examples
print("Setting up tutorial: 5/7", end="\r")
vault_create_result = await 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
)
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 6: Make an initial deposit so withdraw example has shares to work with
print("Setting up tutorial: 6/7", 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 7: Save setup data to file
print("Setting up tutorial: 7/7", end="\r")
setup_data = {
"mptIssuer": {
"address": mpt_issuer.address,
"seed": mpt_issuer.seed,
},
"mptIssuanceId": mpt_issuance_id,
"domainOwner": {
"address": domain_owner.address,
"seed": domain_owner.seed,
},
"domainId": domain_id,
"credentialType": cred_type,
"depositor": {
"address": depositor.address,
"seed": depositor.seed,
},
"vaultOwner": {
"address": vault_owner.address,
"seed": vault_owner.seed,
},
"vaultID": vault_id,
"vaultShareMPTIssuanceId": vault_share_mpt_issuance_id,
}
with open("vault_setup.json", "w") as f:
json.dump(setup_data, f, indent=2)
print("Setting up tutorial: Complete!")
if __name__ == "__main__":
asyncio.run(main())

View File

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

View File

@@ -68,10 +68,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `LoanBrokerNode` | Number | UInt64 | Yes | Identifies the page where this item is referenced in the `LoanBroker` owner directory. |
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the _Loan Broker_ associated with this loan. |
| `Borrower` | String | AccountID | Yes | The account address of the _Borrower_. |
| `LoanOriginationFee` | Number | Number | Yes | The amount paid to the _Loan Broker_, taken from the principal loan at creation. |
| `LoanServiceFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ with each loan payment. |
| `LatePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ for each late payment. |
| `ClosePaymentFee` | Number | Number | Yes | The amount paid to the _Loan Broker_ when a full early payment is made. |
| `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%. |
@@ -83,10 +83,10 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `PreviousPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the previous payment was made, in [seconds since the Ripple Epoch][]. |
| `NextPaymentDueDate` | Number | UInt32 | Yes | The timestamp of when the next payment is due, in [seconds since the Ripple Epoch][]. |
| `PaymentRemaining` | Number | UInt32 | Yes | The number of payments remaining on the loan. |
| `PrincipalOutstanding` | Number | Number | Yes | The principal amount still owed on the loan. |
| `TotalValueOutstanding` | Number | Number | Yes | The total amount owed on the loan, including remaining principal and fees. |
| `ManagementFeeOutstanding` | Number | Number | Yes | The remaining management fee owed to the loan broker. |
| `PeriodicPayment` | Number | Number | Yes | The amount due for each payment interval. |
| `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" %}
@@ -115,7 +115,6 @@ When the loan broker discovers that the borrower can't make an upcoming payment,
The ID of a `Loan` ledger entry is the [SHA-512Half][] of the following values, concatenated in order:
- The `Loan` space key `0x004C`.
- The [AccountID][] of the Borrower account.
- The `LoanBrokerID` of the associated `LoanBroker` ledger entry.
- The `LoanSequence` number of the `LoanBroker` ledger entry.

View File

@@ -65,9 +65,9 @@ In addition to the [common ledger entry fields][], {% code-page-name /%} entries
| `Data` | String | Blob | No | Arbitrary metadata about the vault. Limited to 256 bytes. |
| `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` | Number | Number | Yes | The total asset amount the protocol owes the vault, including interest. |
| `DebtMaximum` | Number | Number | Yes | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. |
| `CoverAvailable` | Number | Number | Yes | The total amount of first-loss capital deposited into the lending protocol. |
| `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%. |

View File

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

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| `LoanBrokerID` | String | Hash256 | No | The loan broker ID that the transaction is modifying. |
| `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` | Number | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
| `DebtMaximum` | String | Number | No | The maximum amount the protocol can owe the vault. The default value of `0` means there is no limit to the debt. Must be a positive value. |
| `CoverRateMinimum` | Number | UInt32 | No | The 1/10th basis point `DebtTotal` that the first-loss capital must cover. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |
| `CoverRateLiquidation` | Number | UInt32 | No | The 1/10th basis point of minimum required first-loss capital that is moved to an asset vault to cover a loan default. Valid values range from `0` to `100000` (inclusive), representing 0% to 100%. |

View File

@@ -38,7 +38,6 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:----------|:-------------|:----------|:------------|
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to manage. |
| `Flags` | String | UInt32 | No | The flag to modify the loan. |
## {% $frontmatter.seo.title %} Flags

View File

@@ -43,20 +43,21 @@ To see how loan payment transactions are calculated, see [transaction pseudo-cod
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description |
|:--------------- |:----------|:-------------|:----------|:------------|
| `LoanID` | String | Hash256 | Yes | The ID of the `Loan` ledger entry to repay. |
| `Amount` | Number | Amount | Yes | The amount to pay toward the loan. |
| 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. |
| 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

View File

@@ -58,19 +58,18 @@ In addition to the [common fields][], {% code-page-name /%} transactions use the
| Field Name | JSON Type | Internal Type | Required? | Description |
|:--------------------------|:----------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry. |
| `Flags` | String | UInt32 | No | Flags for the loan. |
| `Data` | String | Blob | No | Arbitrary metadata in hex format (max 256 bytes). |
| `Counterparty` | String | AccountID | No | The address of the counterparty of the loan. |
| `LoanOriginationFee` | Number | Number | No | The amount paid to the `LoanBroker` owner when the loan is created. |
| `LoanServiceFee` | Number | Number | No | The amount paid to the `LoanBroker` owner with each loan payment. |
| `LatePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for late payments. |
| `ClosePaymentFee` | Number | Number | No | The amount paid to the `LoanBroker` owner for early full repayment. |
| `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` | Number | Number | Yes | The principal loan amount requested by the borrower. |
| `PrincipalRequested` | String | Number | Yes | The principal loan amount requested by the borrower. |
| `PaymentTotal` | Number | UInt32 | No | The total number of payments to be made against the loan. |
| `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. |
@@ -113,7 +112,7 @@ Besides errors that can occur for all transactions, {% code-page-name /%} transa
| Error Code | Description |
|:--------------------------|:-----------------------------------|
| `temBAD_SIGNER` | - The transaction is missing a `CounterpartySignature` field.<br>- This transaction is part of a `Batch` transaction, but didn't specify a `Counterparty`. |
| `temINVALID` | One or more of the numeric fields are outside their valid ranges. For example, the `GracePeriod` can't be longer than the `PaymentInterval`. |
| `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. |

View File

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

View File

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

View File

@@ -29,7 +29,8 @@ 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](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](../../../../tutorials/javascript/build-apps/get-started.md) for setup steps.
- **JavaScript** with the [xrpl.js library][]. See [Get Started Using JavaScript][] for setup steps.
- **Python** with the [xrpl-py library][]. See [Get Started Using Python][] for setup steps.
## Source Code
@@ -41,38 +42,65 @@ You can find the complete source code for this tutorial's examples in the {% rep
{% tabs %}
{% tab label="JavaScript" %}
From the code sample folder, use npm to install dependencies:
From the code sample folder, use `npm` to install dependencies:
```bash
npm install xrpl
```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, use `pip` to install dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install xrpl-py
```
{% /tab %}
{% /tabs %}
### 2. Set up client and accounts
To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
- `fs` and `child_process`: Used to run tutorial setup scripts.
To get started, import the necessary libraries and instantiate a client to connect to the XRPL.
{% tabs %}
{% tab label="JavaScript" %}
This example imports:
- `xrpl`: Used for XRPL client connection and transaction handling.
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" before="// You can replace" /%}
{% /tab %}
{% tab label="Python" %}
This example imports:
- `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="// Get initial vault" /%}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// You can replace" before="console.log" /%}
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace these values with your own.
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# You can replace" before="print" /%}
This example uses preconfigured accounts and vault data from the `vault_setup.py` script, but you can replace these values with your own.
{% /tab %}
{% /tabs %}
This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace `depositor`, `vaultID`, `assetMPTIssuanceId`, and `shareMPTIssuanceId` with your own values.
### 3. Check initial vault state
Before withdrawing, check the vault's current state to see its total assets and available liquidity.
@@ -81,6 +109,10 @@ Before withdrawing, check the vault's current state to see its total assets and
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get initial vault" before="// Check depositor's share balance" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get initial vault" before="# Check depositor's share balance" /%}
{% /tab %}
{% /tabs %}
### 4. Check share balance
@@ -91,6 +123,10 @@ Verify that the depositor account has vault shares to redeem. If not, the transa
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Check depositor's share balance" before="// Prepare VaultWithdraw" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Check depositor's share balance" before="# Prepare VaultWithdraw" /%}
{% /tab %}
{% /tabs %}
### 5. Prepare VaultWithdraw transaction
@@ -100,15 +136,21 @@ 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" /%}
{% /tab %}
{% /tabs %}
The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways:
{% /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 specified in the `Account` field.
While not required, you can provide a destination account to receive the assets; if omitted, assets go to the account submitting the transaction.
{% admonition type="info" name="Note" %}
You can withdraw from a vault regardless of whether it's private or public. If you hold vault shares, you can always redeem them, even if your credentials in a private vault's permissioned domain have expired or been revoked. This prevents you from being locked out of your funds.
@@ -122,6 +164,10 @@ Submit the `VaultWithdraw` transaction to the XRP Ledger.
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Submit VaultWithdraw " before="// Extract vault state" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Submit VaultWithdraw" before="# Extract vault state" /%}
{% /tab %}
{% /tabs %}
When the transaction succeeds:
@@ -141,6 +187,10 @@ After withdrawing, check the vault's state. You can extract this information dir
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Extract vault state" before="// Get the depositor's share balance" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Extract vault state" before="# Get the depositor's share balance" /%}
{% /tab %}
{% /tabs %}
Then, check the depositor's share balance:
@@ -149,6 +199,10 @@ Then, check the depositor's share balance:
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's share balance" before="// Get the depositor's asset balance" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's share balance" before="# Get the depositor's asset balance" /%}
{% /tab %}
{% /tabs %}
Finally, verify the correct asset amount has been received by the depositor account:
@@ -157,6 +211,10 @@ Finally, verify the correct asset amount has been received by the depositor acco
{% tab label="JavaScript" %}
{% code-snippet file="/_code-samples/vaults/js/withdraw.js" language="js" from="// Get the depositor's asset balance" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/vaults/py/withdraw.py" language="py" from="# Get the depositor's asset balance" /%}
{% /tab %}
{% /tabs %}
## See Also

View File

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