# 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/javascript/build-apps/get-started) 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 ``` ### 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. JavaScript // 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() 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. ### 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`) 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)) ### 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!') 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}`) 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)) 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!') 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() 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](/docs/tutorials/how-tos/set-up-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)