# Pay Off a Loan This tutorial shows you how to pay off a [Loan](/docs/references/protocol/ledger-data/ledger-entry-types/loan) and delete it. Loans can only be deleted after they are fully paid off, or if they've been defaulted by the loan broker. The tutorial demonstrates how to calculate the final payment due, which includes the loan balance and any additional fees, and then pay off the loan. After the loan is fully paid off, the loan is deleted, completely removing it from the XRP Ledger. LendingProtocol ## Goals By the end of this tutorial, you will be able to: - Check the outstanding balance on a loan. - Calculate the total payment due, including additional fees. - Submit a loan payment. - Delete a paid off loan from the XRP Ledger. ## Prerequisites To complete this tutorial, you should: - Have a basic understanding of the XRP Ledger. - Have an XRP Ledger client library set up in your development environment. This page provides examples for the following: - **JavaScript** with the [xrpl.js library](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 pays off an existing loan and then deletes it. import fs from 'fs' import { execSync } from 'child_process' import xrpl from 'xrpl' // Connect to the network ---------------------- const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233') await client.connect() 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. # IMPORTANT: This example pays off an existing loan and then deletes it. import json import os import subprocess import sys from xrpl.clients import JsonRpcClient from xrpl.models import LedgerEntry, LoanDelete, LoanPay, MPTAmount from xrpl.transaction import submit_and_wait from xrpl.wallet import Wallet # Set up client ---------------------- client = JsonRpcClient("https://s.devnet.rippletest.net:51234") Next, load the borrower account, loan ID, and MPT issuance 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 borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed) const loanID = setupData.loanID2 const mptID = setupData.mptID console.log(`\nBorrower address: ${borrower.address}`) console.log(`LoanID: ${loanID}`) console.log(`MPT ID: ${mptID}`) This example uses preconfigured accounts and loan data from the `lendingSetup.js` script, but you can replace `borrower`, `loanID`, and `mptID` with your own values. 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, loan_id, and mpt_id. with open("lending_setup.json") as f: setup_data = json.load(f) # You can replace these values with your own. borrower = Wallet.from_seed(setup_data["borrower"]["seed"]) loan_id = setup_data["loan_id_2"] mpt_id = setup_data["mpt_id"] print(f"\nBorrower address: {borrower.address}") print(f"LoanID: {loan_id}") print(f"MPT ID: {mpt_id}") This example uses preconfigured accounts and loan data from the `lending_setup.py` script, but you can replace `borrower`, `loan_id`, and `mpt_id` with your own values. ### 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 initial loan status ---------------------- console.log(`\n=== Loan Status ===\n`) const loanStatus = await client.request({ command: 'ledger_entry', index: loanID, ledger_index: 'validated' }) const totalValueOutstanding = loanStatus.result.node.TotalValueOutstanding const loanServiceFee = loanStatus.result.node.LoanServiceFee const totalPayment = (BigInt(totalValueOutstanding) + BigInt(loanServiceFee)).toString() console.log(`Amount Owed: ${totalValueOutstanding} TSTUSD`) console.log(`Loan Service Fee: ${loanServiceFee} TSTUSD`) console.log(`Total Payment Due (including fees): ${totalPayment} TSTUSD`) Python # Check initial loan status ---------------------- print("\n=== Loan Status ===\n") loan_status = client.request(LedgerEntry( index=loan_id, ledger_index="validated", )) total_value_outstanding = loan_status.result["node"]["TotalValueOutstanding"] loan_service_fee = loan_status.result["node"]["LoanServiceFee"] total_payment = str(int(total_value_outstanding) + int(loan_service_fee)) print(f"Amount Owed: {total_value_outstanding} TSTUSD") print(f"Loan Service Fee: {loan_service_fee} TSTUSD") print(f"Total Payment Due (including fees): {total_payment} TSTUSD") The `TotalValueOutstanding` field contains the remaining principal plus accrued interest; the `LoanServiceFee` is an additional fee charged per payment. Add these together to calculate the total payment. Note Other fees can be charged on a loan, such as late or early payment fees. These additional fees must be accounted for when calculating payment amounts. ### 4. Prepare LoanPay transaction Create the [LoanPay transaction](/docs/references/protocol/transactions/types/loanpay) with the total payment amount. JavaScript // Prepare LoanPay transaction ---------------------- console.log(`\n=== Preparing LoanPay transaction ===\n`) const loanPayTx = { TransactionType: 'LoanPay', Account: borrower.address, LoanID: loanID, Amount: { mpt_issuance_id: mptID, value: totalPayment } } // Validate the transaction structure before submitting xrpl.validate(loanPayTx) console.log(JSON.stringify(loanPayTx, null, 2)) Python # Prepare LoanPay transaction ---------------------- print("\n=== Preparing LoanPay transaction ===\n") loan_pay_tx = LoanPay( account=borrower.address, loan_id=loan_id, amount=MPTAmount(mpt_issuance_id=mpt_id, value=total_payment), ) print(json.dumps(loan_pay_tx.to_xrpl(), indent=2)) ### 5. Submit LoanPay transaction Sign and submit the `LoanPay` transaction to the XRP Ledger. JavaScript // Sign, submit, and wait for payment validation ---------------------- console.log(`\n=== Submitting LoanPay transaction ===\n`) const payResponse = await client.submitAndWait(loanPayTx, { wallet: borrower, autofill: true }) if (payResponse.result.meta.TransactionResult !== 'tesSUCCESS') { const resultCode = payResponse.result.meta.TransactionResult console.error('Error: Unable to pay loan:', resultCode) await client.disconnect() process.exit(1) } console.log('Loan paid successfully!') Python # Sign, submit, and wait for payment validation ---------------------- print("\n=== Submitting LoanPay transaction ===\n") pay_response = submit_and_wait(loan_pay_tx, client, borrower) if pay_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = pay_response.result["meta"]["TransactionResult"] print(f"Error: Unable to pay loan: {result_code}") sys.exit(1) print("Loan paid successfully!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 6. Check loan balance Retrieve the loan balance from the transaction result by checking for the `Loan` entry in the transaction metadata. JavaScript // Extract updated loan info from transaction results ---------------------- console.log(`\n=== Loan Status After Payment ===\n`) const loanNode = payResponse.result.meta.AffectedNodes.find(node => node.ModifiedNode?.LedgerEntryType === 'Loan' ) const finalBalance = loanNode.ModifiedNode.FinalFields.TotalValueOutstanding ? `${loanNode.ModifiedNode.FinalFields.TotalValueOutstanding} TSTUSD` : 'Loan fully paid off!' console.log(`Outstanding Loan Balance: ${finalBalance}`) Python # Extract updated loan info from transaction results ---------------------- print("\n=== Loan Status After Payment ===\n") loan_node = next( node for node in pay_response.result["meta"]["AffectedNodes"] if node.get("ModifiedNode", {}).get("LedgerEntryType") == "Loan" ) final_balance = loan_node["ModifiedNode"]["FinalFields"].get("TotalValueOutstanding") if final_balance: print(f"Outstanding Loan Balance: {final_balance} TSTUSD") else: print("Outstanding Loan Balance: Loan fully paid off!") If `TotalValueOutstanding` is absent from the loan metadata, the loan has been fully paid off and is ready for deletion. ### 7. Prepare LoanDelete transaction Create a [LoanDelete transaction](/docs/references/protocol/transactions/types/loandelete) to remove the paid loan from the XRP Ledger. JavaScript // Prepare LoanDelete transaction ---------------------- // Either the loan broker or borrower can submit this transaction. console.log(`\n=== Preparing LoanDelete transaction ===\n`) const loanDeleteTx = { TransactionType: 'LoanDelete', Account: borrower.address, LoanID: loanID } // Validate the transaction structure before submitting xrpl.validate(loanDeleteTx) console.log(JSON.stringify(loanDeleteTx, null, 2)) Python # Prepare LoanDelete transaction ---------------------- # Either the loan broker or borrower can submit this transaction. print("\n=== Preparing LoanDelete transaction ===\n") loan_delete_tx = LoanDelete( account=borrower.address, loan_id=loan_id, ) print(json.dumps(loan_delete_tx.to_xrpl(), indent=2)) Either the loan broker or the borrower can submit a `LoanDelete` transaction. In this example, the borrower deletes their own paid off loan. ### 8. Submit LoanDelete transaction Sign and submit the `LoanDelete` transaction. JavaScript // Sign, submit, and wait for deletion validation ---------------------- console.log(`\n=== Submitting LoanDelete transaction ===\n`) const deleteResponse = await client.submitAndWait(loanDeleteTx, { wallet: borrower, autofill: true }) if (deleteResponse.result.meta.TransactionResult !== 'tesSUCCESS') { const resultCode = deleteResponse.result.meta.TransactionResult console.error('Error: Unable to delete loan:', resultCode) await client.disconnect() process.exit(1) } console.log('Loan deleted successfully!') Python # Sign, submit, and wait for deletion validation ---------------------- print("\n=== Submitting LoanDelete transaction ===\n") delete_response = submit_and_wait(loan_delete_tx, client, borrower) if delete_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = delete_response.result["meta"]["TransactionResult"] print(f"Error: Unable to delete loan: {result_code}") sys.exit(1) print("Loan deleted successfully!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 9. Verify loan deletion Confirm that the loan has been removed from the XRP Ledger. JavaScript // Verify loan deletion ---------------------- console.log(`\n=== Verifying Loan Deletion ===\n`) try { await client.request({ command: 'ledger_entry', index: loanID, ledger_index: 'validated' }) console.log('Warning: Loan still exists in the ledger.') } catch (error) { if (error.data.error === 'entryNotFound') { console.log('Loan has been successfully removed from the XRP Ledger!') } else { console.error('Error checking loan status:', error) } } await client.disconnect() Python # Verify loan deletion ---------------------- print("\n=== Verifying Loan Deletion ===\n") verify_response = client.request(LedgerEntry( index=loan_id, ledger_index="validated", )) if verify_response.is_successful(): print("Warning: Loan still exists in the ledger.") elif verify_response.result.get("error") == "entryNotFound": print("Loan has been successfully removed from the XRP Ledger!") else: print(f"Error checking loan status: {verify_response.result.get('error')}") If the `ledger_entry` method returns an `entryNotFound` error, the loan has been successfully deleted. ## See Also **Concepts**: - [Lending Protocol](/docs/concepts/tokens/lending-protocol) **Tutorials**: - [Create a Loan](/es-es/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan) **References**: - [LoanDelete transaction](/docs/references/protocol/transactions/types/loandelete) - [LoanPay transaction](/docs/references/protocol/transactions/types/loanpay) - [Loan entry](/docs/references/protocol/ledger-data/ledger-entry-types/loan)