# Manage a Loan This tutorial shows you how to manage a [Loan](/docs/references/protocol/ledger-data/ledger-entry-types/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. 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](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](/docs/tutorials/get-started/get-started-javascript) for setup steps. - **Python** with the [xrpl-py library](https://github.com/XRPLF/xrpl-py). See [Get Started Using Python](/docs/tutorials/get-started/get-started-python) for setup steps. ## Source Code You can find the complete source code for this tutorial's examples in the code samples section of this website's repository. ## Steps ### 1. Install dependencies JavaScript From the code sample folder, use `npm` to install dependencies. ```bash npm install xrpl ``` 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 ``` ### 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: JavaScript - `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling. - `fs` and `child_process`: Used to run tutorial set up scripts. // 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() 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. # 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") Next, load the loan broker account and loan ID. JavaScript // 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}`) This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `loanBroker` and `loanID` with your own values. Python # 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}") 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. ### 3. Check loan status Check the current status of the loan using the [ledger_entry method](/docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry). JavaScript // 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()}`) Python # 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}") This shows the total amount owed and the next payment due date. The [Ripple Epoch](/docs/references/protocol/data-types/basic-data-types#specifying-time) timestamp is converted to a readable date format. ### 4. Prepare LoanManage transaction to impair the loan Create the [LoanManage transaction](/docs/references/protocol/transactions/types/loanmanage) with the `tfLoanImpair` flag. JavaScript // 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)) Python # 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)) ### 5. Submit LoanManage impairment transaction Sign and submit the `LoanManage` transaction to impair the loan. JavaScript // 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!') Python # 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!") 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. JavaScript // 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 Python # 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 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. JavaScript // 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) }) Python # 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.") ### 8. Prepare LoanManage transaction to default the loan After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag. JavaScript // 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)) Python # 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)) ### 9. Submit LoanManage default transaction Sign and submit the `LoanManage` transaction to default the loan. JavaScript // 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!') Python # 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 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. JavaScript // 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() Python # 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}") The loan flags are parsed to confirm the `tfLoanDefault` flag is now set. ## See Also **Concepts**: - [Lending Protocol](/docs/concepts/tokens/lending-protocol) **Tutorials**: - [Create a Loan](/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan) **References**: - [LoanManage transaction](/docs/references/protocol/transactions/types/loanmanage) - [Loan entry](/docs/references/protocol/ledger-data/ledger-entry-types/loan)