# 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.
  - **Go** with the [xrpl-go library](https://github.com/XRPLF/xrpl-go). See [Get Started Using Go](/docs/tutorials/get-started/get-started-go) 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
```

Go
From the code sample folder, use `go` to install dependencies.


```bash
go mod tidy
```

### 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.



```js
// 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.



```py
# 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")
```

Go
- `xrpl-go`: Used for XRPL client connection, transaction submission, and wallet handling.
- `encoding/json` and `fmt`: Used for formatting and printing results to the console.
- `math/big`: Used for calculating the total payment amount.
- `os` and `os/exec`: Used to run tutorial set up scripts.



```go
// IMPORTANT: This example pays off an existing loan and then deletes it.

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
	"os"
	"os/exec"

	"github.com/Peersyst/xrpl-go/xrpl/queries/common"
	"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
	"github.com/Peersyst/xrpl-go/xrpl/transaction"
	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
	"github.com/Peersyst/xrpl-go/xrpl/wallet"
	"github.com/Peersyst/xrpl-go/xrpl/websocket"
	wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
)

func main() {
	// Connect to the network ----------------------
	client := websocket.NewClient(
		websocket.NewClientConfig().
			WithHost("wss://s.devnet.rippletest.net:51233"),
	)
	defer client.Disconnect()

	if err := client.Connect(); err != nil {
		panic(err)
	}
```

Next, load the borrower account, loan ID, and MPT issuance ID.

JavaScript

```js
// 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

```py
# 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.

Go

```go
	// Check for setup data; run lending-setup if missing
	if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
		fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
		cmd := exec.Command("go", "run", "./lending-setup")
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			panic(err)
		}
	}

	// Load preconfigured accounts and LoanID
	data, err := os.ReadFile("lending-setup.json")
	if err != nil {
		panic(err)
	}
	var setup map[string]any
	if err := json.Unmarshal(data, &setup); err != nil {
		panic(err)
	}

	// You can replace these values with your own
	borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
	if err != nil {
		panic(err)
	}
	loanID := setup["loanID2"].(string)
	mptID := setup["mptID"].(string)

	fmt.Printf("\nBorrower address: %s\n", borrowerWallet.ClassicAddress)
	fmt.Printf("LoanID: %s\n", loanID)
	fmt.Printf("MPT ID: %s\n", mptID)
```

This example uses preconfigured accounts and loan data from the `lending-setup` script, but you can replace `borrowerWallet`, `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

```js
// 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

```py
# 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")
```

Go

```go
	// Check initial loan status ----------------------
	fmt.Printf("\n=== Loan Status ===\n\n")
	loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
		Index:       loanID,
		LedgerIndex: common.Validated,
	})
	if err != nil {
		panic(err)
	}

	totalValueOutstanding := loanStatus.Node["TotalValueOutstanding"].(string)
	loanServiceFee := loanStatus.Node["LoanServiceFee"].(string)

	outstanding, _ := new(big.Int).SetString(totalValueOutstanding, 10)
	serviceFee, _ := new(big.Int).SetString(loanServiceFee, 10)
	totalPayment := new(big.Int).Add(outstanding, serviceFee).String()

	fmt.Printf("Amount Owed: %s TSTUSD\n", totalValueOutstanding)
	fmt.Printf("Loan Service Fee: %s TSTUSD\n", loanServiceFee)
	fmt.Printf("Total Payment Due (including fees): %s TSTUSD\n", totalPayment)
```

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

```js
// 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

```py
# 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))
```

Go

```go
	// Prepare LoanPay transaction ----------------------
	fmt.Printf("\n=== Preparing LoanPay transaction ===\n\n")
	loanPayTx := transaction.LoanPay{
		BaseTx: transaction.BaseTx{
			Account: borrowerWallet.ClassicAddress,
		},
		LoanID: loanID,
		Amount: types.MPTCurrencyAmount{
			MPTIssuanceID: mptID,
			Value:         totalPayment,
		},
	}

	// Flatten() converts the struct to a map and adds the TransactionType field
	flatLoanPayTx := loanPayTx.Flatten()
	loanPayTxJSON, _ := json.MarshalIndent(flatLoanPayTx, "", "  ")
	fmt.Printf("%s\n", string(loanPayTxJSON))
```

### 5. Submit LoanPay transaction

Sign and submit the `LoanPay` transaction to the XRP Ledger.

JavaScript

```js
// 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

```py
# 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!")
```

Go

```go
	// Sign, submit, and wait for payment validation ----------------------
	fmt.Printf("\n=== Submitting LoanPay transaction ===\n\n")
	payResponse, err := client.SubmitTxAndWait(flatLoanPayTx, &wstypes.SubmitOptions{
		Autofill: true,
		Wallet:   &borrowerWallet,
	})
	if err != nil {
		panic(err)
	}

	if payResponse.Meta.TransactionResult != "tesSUCCESS" {
		fmt.Printf("Error: Unable to pay loan: %s\n", payResponse.Meta.TransactionResult)
		os.Exit(1)
	}
	fmt.Printf("Loan paid successfully!\n")
```

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

```js
// 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

```py
# 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!")
```

Go

```go
	// Extract updated loan info from transaction results ----------------------
	fmt.Printf("\n=== Loan Status After Payment ===\n\n")
	for _, node := range payResponse.Meta.AffectedNodes {
		if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
			if balance, ok := node.ModifiedNode.FinalFields["TotalValueOutstanding"].(string); ok {
				fmt.Printf("Outstanding Loan Balance: %s TSTUSD\n", balance)
			} else {
				fmt.Printf("Outstanding Loan Balance: Loan fully paid off!\n")
			}
			break
		}
	}
```

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

```js
// 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

```py
# 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))
```

Go

```go
	// Prepare LoanDelete transaction ----------------------
	// Either the loan broker or borrower can submit this transaction.
	fmt.Printf("\n=== Preparing LoanDelete transaction ===\n\n")
	loanDeleteTx := transaction.LoanDelete{
		BaseTx: transaction.BaseTx{
			Account: borrowerWallet.ClassicAddress,
		},
		LoanID: loanID,
	}

	flatLoanDeleteTx := loanDeleteTx.Flatten()
	loanDeleteTxJSON, _ := json.MarshalIndent(flatLoanDeleteTx, "", "  ")
	fmt.Printf("%s\n", string(loanDeleteTxJSON))
```

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

```js
// 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

```py
# 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!")
```

Go

```go
	// Sign, submit, and wait for deletion validation ----------------------
	fmt.Printf("\n=== Submitting LoanDelete transaction ===\n\n")
	deleteResponse, err := client.SubmitTxAndWait(flatLoanDeleteTx, &wstypes.SubmitOptions{
		Autofill: true,
		Wallet:   &borrowerWallet,
	})
	if err != nil {
		panic(err)
	}

	if deleteResponse.Meta.TransactionResult != "tesSUCCESS" {
		fmt.Printf("Error: Unable to delete loan: %s\n", deleteResponse.Meta.TransactionResult)
		os.Exit(1)
	}
	fmt.Printf("Loan deleted successfully!\n")
```

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

```js
// 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

```py
# 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')}")
```

Go

```go
	// Verify loan deletion ----------------------
	fmt.Printf("\n=== Verifying Loan Deletion ===\n\n")
	_, err = client.GetLedgerEntry(&ledger.EntryRequest{
		Index:       loanID,
		LedgerIndex: common.Validated,
	})
	if err != nil {
		if err.Error() == "entryNotFound" {
			fmt.Printf("Loan has been successfully removed from the XRP Ledger!\n")
		} else {
			panic(err)
		}
	} else {
		fmt.Printf("Warning: Loan still exists in the ledger.\n")
	}
}
```

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)