This tutorial shows you how to create a Loan on the XRP Ledger. A loan requires signatures from both the loan broker and the borrower to be created.
This tutorial demonstrates how a loan broker and a borrower can cosign the terms of a loan and create that loan on the XRPL.
Requires the LendingProtocol amendment. Loading...
By the end of this tutorial, you will be able to:
- Create a LoanSet transaction with loan terms.
- Sign and add the loan broker's signature to the transaction.
- Sign and add the borrower's signature to the transaction.
- Submit the cosigned transaction to create a loan.
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. See Get Started Using JavaScript for setup steps.
You can find the complete source code for this tutorial's examples in the code samples section of this website's repository.
From the code sample folder, use npm to install dependencies:
npm install xrplTo 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.fsandchild_process: Used to run tutorial set up scripts.
// IMPORTANT: This example creates a loan using a preconfigured
// loan broker, borrower, and private vault.
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, borrower account, and loan broker 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 LoanBrokerID.
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 borrower = xrpl.Wallet.fromSeed(setupData.borrower.seed)
const loanBrokerID = setupData.loanBrokerID
console.log(`\nLoan broker address: ${loanBroker.address}`)
console.log(`Borrower address: ${borrower.address}`)
console.log(`LoanBrokerID: ${loanBrokerID}`)This example uses preconfigured accounts and loan broker data from the lendingSetup.js script, but you can replace loanBroker, borrower, and loanBrokerID with your own values.
Create the LoanSet transaction object with the loan terms:
// Prepare LoanSet transaction ----------------------
// Account and Counterparty accounts can be swapped, but determines signing order.
// Account signs first, Counterparty signs second.
console.log(`\n=== Preparing LoanSet transaction ===\n`)
// Suppress unnecessary console warning from autofilling LoanSet.
console.warn = () => {}
const loanSetTx = await client.autofill({
TransactionType: 'LoanSet',
Account: loanBroker.address,
Counterparty: borrower.address,
LoanBrokerID: loanBrokerID,
PrincipalRequested: 1000,
InterestRate: 500,
PaymentTotal: 12,
PaymentInterval: 2592000,
GracePeriod: 604800,
LoanOriginationFee: 100,
LoanServiceFee: 10
})
console.log(JSON.stringify(loanSetTx, null, 2))The Account field is the loan broker, and the Counterparty field is the borrower. These fields can be swapped, but determine the signing order: the Account signs first, and the Counterparty signs second.
The loan terms include:
PrincipalRequested: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field.InterestRate: The annualized interest rate in 1/10th basis points (500 = 0.5%).PaymentTotal: The number of payments to be made.PaymentInterval: The number of seconds between payments (2592000 = 30 days).GracePeriod: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days).LoanOriginationFee: A one-time fee charged when the loan is created, paid in the borrowed asset.LoanServiceFee: A fee charged with every loan payment, paid in the borrowed asset.
The loan broker (the Account) signs the transaction first, using the sign method:
// Loan broker signs first
console.log(`\n=== Adding loan broker signature ===\n`)
const loanBrokerSignature = await client.request({
command: 'sign',
tx_json: loanSetTx,
secret: loanBroker.seed
})
const loanBrokerSignatureResult = loanBrokerSignature.result.tx_json
console.log(`TxnSignature: ${loanBrokerSignatureResult.TxnSignature}`)
console.log(`SigningPubKey: ${loanBrokerSignatureResult.SigningPubKey}\n`)
console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignatureResult, null, 2)}`)The loan broker adds their TxnSignature and SigningPubKey to the LoanSet transaction object.
The borrower (the Counterparty) signs the transaction second, using the sign method:
// Borrower signs second
console.log(`\n=== Adding borrower signature ===\n`)
const borrowerSignature = await client.request({
command: 'sign',
tx_json: loanBrokerSignatureResult,
secret: borrower.seed,
signature_target: 'CounterpartySignature'
})
const borrowerSignatureResult = borrowerSignature.result.tx_json
console.log(`Borrower TxnSignature: ${borrowerSignatureResult.CounterpartySignature.TxnSignature}`)
console.log(`Borrower SigningPubKey: ${borrowerSignatureResult.CounterpartySignature.SigningPubKey}`)
// Validate the transaction structure before submitting.
xrpl.validate(borrowerSignatureResult)
console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(borrowerSignatureResult, null, 2)}`)The borrower must specify signature_target: 'CounterpartySignature'. This adds the borrower's signatures to a CounterpartySignature object, which includes the borrower's TxnSignature and SigningPubKey.
Sign and submit the fully signed LoanSet transaction to the XRP Ledger.
// Submit and wait for validation ----------------------
console.log(`\n=== Submitting signed LoanSet transaction ===\n`)
// Submit the transaction
const submitResult = await client.submit(borrowerSignatureResult)
const txHash = submitResult.result.tx_json.hash
// Helper function to check tx hash is validated
async function validateTx (hash, maxRetries = 20) {
for (let i = 0; i < maxRetries; i++) {
await new Promise(resolve => setTimeout(resolve, 1000))
try {
const tx = await client.request({ command: 'tx', transaction: hash })
if (tx.result.validated) {
return tx
}
} catch (error) {
// Transaction not validated yet, check again
}
}
console.error(`Error: Transaction ${hash} not validated after ${maxRetries} attempts.`)
await client.disconnect()
process.exit(1)
}
// Validate the transaction
const submitResponse = await validateTx(txHash)
if (submitResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
const resultCode = submitResponse.result.meta.TransactionResult
console.error('Error: Unable to create loan:', resultCode)
await client.disconnect()
process.exit(1)
}
console.log('Loan created successfully!')Verify that the transaction succeeded by checking for a tesSUCCESS result code.
Retrieve the loan's information from the transaction result by checking for the Loan entry in the transaction metadata.
// Extract loan information from the transaction result.
console.log(`\n=== Loan Information ===\n`)
const loanNode = submitResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
)
console.log(JSON.stringify(loanNode.CreatedNode.NewFields, null, 2))
await client.disconnect()Concepts:
Tutorials:
References: