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



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

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.
- `os` and `os/exec`: Used to run tutorial set up scripts.
- `time`: Used for grace period countdown and date formatting.



```go
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
// After the 60 seconds pass, this example defaults the loan.

package main

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"time"

	"github.com/Peersyst/xrpl-go/xrpl/flag"
	"github.com/Peersyst/xrpl-go/xrpl/queries/common"
	"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
	xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
	"github.com/Peersyst/xrpl-go/xrpl/transaction"
	"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 loan broker account and loan 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 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

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

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
	loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
	if err != nil {
		panic(err)
	}
	loanID := setup["loanID1"].(string)

	fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
	fmt.Printf("LoanID: %s\n", loanID)
```

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

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

Go

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

	fmt.Printf("Total Amount Owed: %s TSTUSD.\n", loanStatus.Node["TotalValueOutstanding"])

	// Convert Ripple Epoch timestamp to local date and time
	nextPaymentDueDate := int64(loanStatus.Node["NextPaymentDueDate"].(float64))
	paymentDue := time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
	fmt.Printf("Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
```

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

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

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

Go

```go
	// Prepare LoanManage transaction to impair the loan ----------------------
	fmt.Printf("\n=== Preparing LoanManage transaction to impair loan ===\n\n")
	loanManageImpair := transaction.LoanManage{
		BaseTx: transaction.BaseTx{
			Account: loanBrokerWallet.ClassicAddress,
		},
		LoanID: loanID,
	}
	loanManageImpair.SetLoanImpairFlag()

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

### 5. Submit LoanManage impairment transaction

Sign and submit the `LoanManage` transaction to impair the loan.

JavaScript

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

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

Go

```go
	// Sign, submit, and wait for impairment validation ----------------------
	fmt.Printf("\n=== Submitting LoanManage impairment transaction ===\n\n")
	impairResponse, err := client.SubmitTxAndWait(flatLoanManageImpair, &wstypes.SubmitOptions{
		Autofill: true,
		Wallet:   &loanBrokerWallet,
	})
	if err != nil {
		panic(err)
	}

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

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

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

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

Go

```go
	// Extract loan impairment info from transaction results ----------------------
	var loanNode map[string]any
	for _, node := range impairResponse.Meta.AffectedNodes {
		if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
			loanNode = node.ModifiedNode.FinalFields
			break
		}
	}

	// Check grace period and next payment due date
	gracePeriod := int64(loanNode["GracePeriod"].(float64))
	nextPaymentDueDate = int64(loanNode["NextPaymentDueDate"].(float64))
	defaultTime := nextPaymentDueDate + gracePeriod
	paymentDue = time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))

	fmt.Printf("New Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
	fmt.Printf("Grace Period: %d seconds\n", gracePeriod)

	// Convert current time to Ripple Epoch timestamp
	currentTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix())
	// Add a small buffer (5 seconds) to account for ledger close time
	secondsUntilDefault := defaultTime - currentTime + 5
```

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

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

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

Go

```go
	// Countdown until loan can be defaulted ----------------------
	fmt.Printf("\n=== Countdown until loan can be defaulted ===\n\n")
	for secondsUntilDefault >= 0 {
		fmt.Printf("\r%d seconds...", secondsUntilDefault)
		time.Sleep(time.Second)
		secondsUntilDefault--
	}
	fmt.Print("\rGrace period expired. Loan can now be defaulted.\n")
```

### 8. Prepare LoanManage transaction to default the loan

After the grace period expires, create a `LoanManage` transaction with the `tfLoanDefault` flag.

JavaScript

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

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

Go

```go
	// Prepare LoanManage transaction to default the loan ----------------------
	fmt.Printf("\n=== Preparing LoanManage transaction to default loan ===\n\n")
	loanManageDefault := transaction.LoanManage{
		BaseTx: transaction.BaseTx{
			Account: loanBrokerWallet.ClassicAddress,
		},
		LoanID: loanID,
	}
	loanManageDefault.SetLoanDefaultFlag()

	flatLoanManageDefault := loanManageDefault.Flatten()
	loanManageDefaultJSON, _ := json.MarshalIndent(flatLoanManageDefault, "", "  ")
	fmt.Printf("%s\n", string(loanManageDefaultJSON))
```

### 9. Submit LoanManage default transaction

Sign and submit the `LoanManage` transaction to default the loan.

JavaScript

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

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

Go

```go
	// Sign, submit, and wait for default validation ----------------------
	fmt.Printf("\n=== Submitting LoanManage default transaction ===\n\n")
	defaultResponse, err := client.SubmitTxAndWait(flatLoanManageDefault, &wstypes.SubmitOptions{
		Autofill: true,
		Wallet:   &loanBrokerWallet,
	})
	if err != nil {
		panic(err)
	}

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

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

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

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

Go

```go
	// Verify loan default status from transaction results ----------------------
	fmt.Printf("\n=== Checking final loan status ===\n\n")
	for _, node := range defaultResponse.Meta.AffectedNodes {
		if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
			loanNode = node.ModifiedNode.FinalFields
			break
		}
	}
	loanFlags := uint32(loanNode["Flags"].(float64))

	// Check which loan flags are set
	activeFlags := []string{}
	if flag.Contains(loanFlags, transaction.TfLoanDefault) {
		activeFlags = append(activeFlags, "tfLoanDefault")
	}
	if flag.Contains(loanFlags, transaction.TfLoanImpair) {
		activeFlags = append(activeFlags, "tfLoanImpair")
	}
	fmt.Printf("Final loan flags: %v\n", activeFlags)
}
```

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](/es-es/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)