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



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

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.



```go
// IMPORTANT: This example creates a loan using a preconfigured
// loan broker, borrower, and private vault.

package main

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

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

// ptr is a helper that returns a pointer to the given value,
// used for setting optional transaction fields in Go.
func ptr[T any](v T) *T { return &v }

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, borrower account, and loan broker 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 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

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

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 LoanBrokerID
	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)
	}
	borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
	if err != nil {
		panic(err)
	}
	loanBrokerID := setup["loanBrokerID"].(string)

	fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
	fmt.Printf("Borrower address: %s\n", borrowerWallet.ClassicAddress)
	fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
```

This example uses preconfigured accounts and loan broker data from the `lending-setup` script, but you can replace `loanBrokerWallet`, `borrowerWallet`, and `loanBrokerID` with your own values.

### 3. Prepare LoanSet transaction

Create the [LoanSet transaction](/docs/references/protocol/transactions/types/loanset) object with the loan terms.

JavaScript

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

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


Go

```go
	// Prepare LoanSet transaction ----------------------
	// Account and Counterparty accounts can be swapped, but determines signing order.
	// Account signs first, Counterparty signs second.
	fmt.Printf("\n=== Preparing LoanSet transaction ===\n\n")

	counterparty := borrowerWallet.ClassicAddress
	loanSetTx := transaction.LoanSet{
		BaseTx: transaction.BaseTx{
			Account: loanBrokerWallet.ClassicAddress,
		},
		LoanBrokerID:       loanBrokerID,
		PrincipalRequested: "1000",
		Counterparty:       &counterparty,
		InterestRate:       ptr(types.InterestRate(500)),
		PaymentTotal:       ptr(types.PaymentTotal(12)),
		PaymentInterval:    ptr(types.PaymentInterval(2592000)),
		GracePeriod:        ptr(types.GracePeriod(604800)),
		LoanOriginationFee: ptr(types.XRPLNumber("100")),
		LoanServiceFee:     ptr(types.XRPLNumber("10")),
	}

	// Flatten() converts the struct to a map and adds the TransactionType field.
	// The result is cast to FlatTransaction, which is required by Autofill and signing methods.
	flatLoanSetTx := transaction.FlatTransaction(loanSetTx.Flatten())

	// Autofill the transaction
	if err := client.Autofill(&flatLoanSetTx); err != nil {
		panic(err)
	}

	loanSetTxJSON, _ := json.MarshalIndent(flatLoanSetTx, "", "  ")
	fmt.Printf("%s\n", string(loanSetTxJSON))
```

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.


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

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

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

Go

```go
	// Loan broker signs first
	fmt.Printf("\n=== Adding loan broker signature ===\n\n")
	_, _, err = loanBrokerWallet.Sign(flatLoanSetTx)
	if err != nil {
		panic(err)
	}

	fmt.Printf("TxnSignature: %s\n", flatLoanSetTx["TxnSignature"])
	fmt.Printf("SigningPubKey: %s\n\n", flatLoanSetTx["SigningPubKey"])

	loanBrokerSignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", "  ")
	fmt.Printf("Signed loanSetTx for borrower to sign over:\n%s\n", string(loanBrokerSignedJSON))
```

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

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

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

Go

```go
	// Borrower signs second
	fmt.Printf("\n=== Adding borrower signature ===\n\n")
	fullySignedBlob, _, err := wallet.SignLoanSetByCounterparty(borrowerWallet, &flatLoanSetTx, nil)
	if err != nil {
		panic(err)
	}

	borrowerSignatures := flatLoanSetTx["CounterpartySignature"].(map[string]any)
	fmt.Printf("Borrower TxnSignature: %s\n", borrowerSignatures["TxnSignature"])
	fmt.Printf("Borrower SigningPubKey: %s\n", borrowerSignatures["SigningPubKey"])

	fullySignedJSON, _ := json.MarshalIndent(flatLoanSetTx, "", "  ")
	fmt.Printf("\nFully signed LoanSet transaction:\n%s\n", string(fullySignedJSON))
```

### 6. Submit LoanSet transaction

Submit the fully signed `LoanSet` transaction to the XRP Ledger.

JavaScript

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

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

Go

```go
	// Submit and wait for validation ----------------------
	fmt.Printf("\n=== Submitting signed LoanSet transaction ===\n\n")

	loanSetResponse, err := client.SubmitTxBlobAndWait(fullySignedBlob, false)
	if err != nil {
		panic(err)
	}

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

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

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

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

Go

```go
	// Extract loan information from the transaction result
	fmt.Printf("\n=== Loan Information ===\n\n")
	for _, node := range loanSetResponse.Meta.AffectedNodes {
		if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "Loan" {
			loanJSON, _ := json.MarshalIndent(node.CreatedNode.NewFields, "", "  ")
			fmt.Printf("%s\n", string(loanJSON))
			break
		}
	}
}
```

## See Also

**Concepts**:

- [Lending Protocol](/docs/concepts/tokens/lending-protocol)


**Tutorials**:

- [Create a Loan Broker](/docs/tutorials/defi/lending/use-the-lending-protocol/create-a-loan-broker)
- [Manage a Loan](/docs/tutorials/defi/lending/use-the-lending-protocol/manage-a-loan)


**References**:

- [LoanSet transaction](/docs/references/protocol/transactions/types/loanset)