Files
2026-02-12 22:04:27 -08:00

367 lines
13 KiB
Python

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