Skip to content
Last updated
Edit

This tutorial shows you how to manage a 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.

Requires the LendingProtocol amendment. Loading...

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:

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

From the code sample folder, use npm to install dependencies:

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 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()

Next, load the loan broker account and loan ID.

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

3. Check loan status

Check the current status of the loan using the ledger_entry method:

// 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()}`)

This shows the total amount owed and the next payment due date. The Ripple Epoch timestamp is converted to a JavaScript Date object for readability.

4. Prepare LoanManage transaction to impair the loan

Create the LoanManage transaction with the tfLoanImpair flag:

// 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))

5. Submit LoanManage impairment transaction

Sign and submit the LoanManage transaction to impair the loan:

// 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!')

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:

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

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.

// 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)
})

8. Prepare LoanManage transaction to default the loan

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

// 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))

9. Submit LoanManage default transaction

Sign and submit the LoanManage transaction to default the loan:

// 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!')

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:

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

The parseTransactionFlags function converts the numeric flags to a readable format, showing that the tfLoanDefault flag is now set.

See Also

Concepts:

Tutorials:

References: