# Create a Loan This tutorial shows you how to create a [Loan](/docs/references/protocol/ledger-data/ledger-entry-types/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. LendingProtocol ## Goals 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. ## 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. ## 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 ``` ### 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. // 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() 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. # IMPORTANT: This example creates a loan using a preconfigured # loan broker, borrower, and private vault. import json import os import subprocess import sys from xrpl.clients import JsonRpcClient from xrpl.models import LoanSet from xrpl.transaction import autofill, sign, sign_loan_set_by_counterparty, submit_and_wait from xrpl.wallet import Wallet # Set up client ---------------------- client = JsonRpcClient("https://s.devnet.rippletest.net:51234") Next, load the loan broker account, borrower account, and loan broker 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 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. Python # 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_broker_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"]) borrower = Wallet.from_seed(setup_data["borrower"]["seed"]) loan_broker_id = setup_data["loan_broker_id"] print(f"\nLoan broker address: {loan_broker.address}") print(f"Borrower address: {borrower.address}") print(f"LoanBrokerID: {loan_broker_id}") This example uses preconfigured accounts and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `borrower`, and `loan_broker_id` with your own values. ### 3. Prepare LoanSet transaction Create the [LoanSet transaction](/docs/references/protocol/transactions/types/loanset) object with the loan terms. JavaScript // 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. Python # Prepare LoanSet transaction ---------------------- # Account and Counterparty accounts can be swapped, but determines signing order. # Account signs first, Counterparty signs second. print("\n=== Preparing LoanSet transaction ===\n") loan_set_tx = autofill(LoanSet( account=loan_broker.address, counterparty=borrower.address, loan_broker_id=loan_broker_id, principal_requested="1000", interest_rate=500, payment_total=12, payment_interval=2592000, grace_period=604800, loan_origination_fee="100", loan_service_fee="10", ), client) print(json.dumps(loan_set_tx.to_xrpl(), indent=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: - `principal_requested`: The amount of an asset requested by the borrower. You don't have to specify the type of asset in this field. - `interest_rate`: The annualized interest rate in 1/10th basis points (500 = 0.5%). - `payment_total`: The number of payments to be made. - `payment_interval`: The number of seconds between payments (2592000 = 30 days). - `grace_period`: The number of seconds after a missed payment before the loan can be defaulted (604800 = 7 days). - `loan_origination_fee`: A one-time fee charged when the loan is created, paid in the borrowed asset. - `loan_service_fee`: A fee charged with every loan payment, paid in the borrowed asset. ### 4. Add loan broker signature The loan broker (the `Account`) signs the transaction first, adding their `TxnSignature` and `SigningPubKey` to the `LoanSet` transaction object. JavaScript // Loan broker signs first console.log(`\n=== Adding loan broker signature ===\n`) const loanBrokerSigned = loanBroker.sign(loanSetTx) const loanBrokerSignedTx = xrpl.decode(loanBrokerSigned.tx_blob) console.log(`TxnSignature: ${loanBrokerSignedTx.TxnSignature}`) console.log(`SigningPubKey: ${loanBrokerSignedTx.SigningPubKey}\n`) console.log(`Signed loanSetTx for borrower to sign over:\n${JSON.stringify(loanBrokerSignedTx, null, 2)}`) Python # Loan broker signs first. print("\n=== Adding loan broker signature ===\n") loan_broker_signed = sign(loan_set_tx, loan_broker) print(f"TxnSignature: {loan_broker_signed.txn_signature}") print(f"SigningPubKey: {loan_broker_signed.signing_pub_key}\n") print(f"Signed loan_set_tx for borrower to sign over:\n{json.dumps(loan_broker_signed.to_xrpl(), indent=2)}") ### 5. Add borrower signature The borrower (the `Counterparty`) signs the transaction second. Their `TxnSignature` and `SigningPubKey` are stored in a `CounterpartySignature` field, which is added to the `LoanSet` transaction object. JavaScript // Borrower signs second console.log(`\n=== Adding borrower signature ===\n`) const fullySigned = xrpl.signLoanSetByCounterparty(borrower, loanBrokerSignedTx) console.log(`Borrower TxnSignature: ${fullySigned.tx.CounterpartySignature.TxnSignature}`) console.log(`Borrower SigningPubKey: ${fullySigned.tx.CounterpartySignature.SigningPubKey}`) // Validate the transaction structure before submitting. xrpl.validate(fullySigned.tx) console.log(`\nFully signed LoanSet transaction:\n${JSON.stringify(fullySigned.tx, null, 2)}`) Python # Borrower signs second. print("\n=== Adding borrower signature ===\n") fully_signed = sign_loan_set_by_counterparty(borrower, loan_broker_signed) print(f"Borrower TxnSignature: {fully_signed.tx.counterparty_signature.txn_signature}") print(f"Borrower SigningPubKey: {fully_signed.tx.counterparty_signature.signing_pub_key}") print(f"\nFully signed LoanSet transaction:\n{json.dumps(fully_signed.tx.to_xrpl(), indent=2)}") ### 6. Submit LoanSet transaction Submit the fully signed `LoanSet` transaction to the XRP Ledger. JavaScript // Submit and wait for validation ---------------------- console.log(`\n=== Submitting signed LoanSet transaction ===\n`) const submitResponse = await client.submitAndWait(fullySigned.tx) 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!') Python # Submit and wait for validation ---------------------- print("\n=== Submitting signed LoanSet transaction ===\n") submit_response = submit_and_wait(fully_signed.tx, client) if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = submit_response.result["meta"]["TransactionResult"] print(f"Error: Unable to create loan: {result_code}") sys.exit(1) print("Loan created successfully!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 7. Get loan information Retrieve the loan's information from the transaction result by checking for the `Loan` entry in the transaction metadata. JavaScript // 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() Python # Extract loan information from the transaction result. print("\n=== Loan Information ===\n") loan_node = next( node for node in submit_response.result["meta"]["AffectedNodes"] if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan" ) print(json.dumps(loan_node["CreatedNode"]["NewFields"], indent=2)) ## See Also **Concepts**: - [Lending Protocol](/docs/concepts/tokens/lending-protocol) **Tutorials**: - [Create a Loan Broker](/es-es/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan-broker) - [Manage a Loan](/es-es/docs/tutorials/defi/lending/use-the-lending-protocol/manage-a-loan) **References**: - [LoanSet transaction](/docs/references/protocol/transactions/types/loanset)