Lending Protocol and Single Asset Vaults are disabled on Devnet. You have two options for testing these features:
- Run
rippledin stand-alone mode and enableLendingProtocolandSingleAssetVaultin the rippled.cfg file. - Connect to the Lending Protocol-specific Devnet at
https://lend.devnet.rippletest.net:51234/.
This tutorial shows you how to create a single asset vault on the XRP Ledger. Vaults can only hold a single type of asset, such as XRP, a trust line token, or a Multi-Purpose Token (MPT).
You can create either a:
- Public vault: Anyone can deposit assets.
- Private vault: Only users with valid Credentials can deposit, managed through Permissioned Domains.
The tutorial demonstrates how a financial institution could use a private vault to pool lender assets for uncollateralized lending while maintaining regulatory compliance through credential-based access control.
(Requires the Single Asset Vault amendment )
By the end of this tutorial, you will be able to:
- Create a private vault.
- Configure vault parameters such as the asset type, maximum deposit amount, and withdrawal policy.
- Configure whether depositors can transfer their vault shares to other accounts.
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 client library and instantiate a client to connect to the XRPL.
import xrpl from "xrpl"
// Connect to the network ----------------------
// This is a lending protocol-specific devnet. This network may be taken
// offline once the lending protocol is live on mainnet.
const client = new xrpl.Client("wss://lend.devnet.rippletest.net:51233")
await client.connect()
// Use the Lending Devnet faucet
const faucetHost = "lend-faucet.devnet.rippletest.net"
const faucetPath = "/accounts"Next, fund a vault owner account, define the MPT issuance ID for the vault's asset, and provide a permissioned domain ID to control who can deposit into the vault.
// Create and fund vault owner account
const { wallet: vaultOwner } = await client.fundWallet(null, { faucetHost, faucetPath })
// A pre-existing Vault asset, created for this tutorial. You can specify your own Vault asset.
const mptIssuanceId = "0003E3B486D3DACD8BB468AB33793B9626BD894A92AB3AB4"
// A pre-existing Permissioned Domain ID, created for this tutorial. You can specify your own Domain ID.
// NOTE: You don't need this if you want to create a public vault.
const domainId = "3BB81D0D164456A2D74720F63FD923F16DE08FB3223D3ED103D09F525A8D69D1"
console.log(`Vault owner address: ${vaultOwner.address}`)
console.log(`MPT issuance ID: ${mptIssuanceId}`)
console.log(`Permissioned domain ID: ${domainId}\n`)The example uses an existing MPT issuance and permissioned domain, but you can also provide your own values. If you want to create a public vault, you don't need to provide the domainId.
Create the VaultCreate transaction object:
// Prepare VaultCreate transaction ----------------------
console.log(`\n=== VaultCreate transaction ===`)
const vaultCreateTx = {
TransactionType: "VaultCreate",
Account: vaultOwner.address,
Asset: { mpt_issuance_id: mptIssuanceId },
Flags: xrpl.VaultCreateFlags.tfVaultPrivate, // Omit tfVaultPrivate flag for public vaults
// To make vault shares non-transferable add the tfVaultShareNonTransferable flag:
// Flags: xrpl.VaultCreateFlags.tfVaultPrivate | xrpl.VaultCreateFlags.tfVaultShareNonTransferable
DomainID: domainId, // Omit for public vaults
Data: xrpl.convertStringToHex("Private vault"),
// Encode JSON metadata as hex string per XLS-89 MPT Metadata Schema.
// See: https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
MPTokenMetadata: xrpl.encodeMPTokenMetadata({
ticker: "SHARE1",
name: "Vault shares",
desc: "Proportional ownership shares of the vault.",
icon: "example.com/asset-icon.png",
asset_class: "defi",
issuer_name: "Asset Issuer Name",
uris: [
{
uri: "example.com/asset",
category: "website",
title: "Asset Website",
},
{
uri: "example.com/docs",
category: "docs",
title: "Docs",
},
],
additional_info: {
example_info: "test",
},
}),
AssetsMaximum: "0", // No cap
WithdrawalPolicy: xrpl.VaultWithdrawalPolicy.vaultStrategyFirstComeFirstServe,
};
// Validate the transaction structure before submitting
xrpl.validate(vaultCreateTx)
console.log(JSON.stringify(vaultCreateTx, null, 2))The tfVaultPrivate flag and DomainID field restrict deposits to accounts with valid credentials in the specified permissioned domain. These can be omitted if you want to create a public vault instead.
The AssetsMaximum is set to 0 to indicate no cap on how many assets the vault can hold, but you can adjust as needed.
Vault shares are transferable by default, meaning depositors can transfer their shares to other accounts. If you don't want the vault's shares to be transferable, enable the tfVaultShareNonTransferable flag.
Sign and submit the VaultCreate transaction to the XRP Ledger.
// Submit, sign, and wait for validation ----------------------
console.log("\n=== Submitting VaultCreate transaction... ===")
const submit_response = await client.submitAndWait(vaultCreateTx, {
wallet: vaultOwner,
autofill: true,
})
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = submit_response.result.meta.TransactionResult;
console.error("Error: Unable to create vault:", result_code)
await client.disconnect()
process.exit(1)
}
console.log("Vault created successfully!")Verify that the transaction succeeded by checking for a tesSUCCESS result code.
Retrieve the vault's information from the transaction result by checking for the Vault object in the transaction metadata.
// Extract vault information from the transaction result
const affectedNodes = submit_response.result.meta.AffectedNodes || []
const vaultNode = affectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === "Vault"
)
if (vaultNode) {
console.log(`\nVault ID: ${vaultNode.CreatedNode.LedgerIndex}`)
console.log(`Vault pseudo-account address: ${vaultNode.CreatedNode.NewFields.Account}`)
console.log(`Share MPT issuance ID: ${vaultNode.CreatedNode.NewFields.ShareMPTID}`)
}You can also use the vault_info method to retrieve the vault's details:
// Call vault_info method to retrieve the vault's information
console.log("\n=== Getting vault_info... ===")
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vault_info_response = await client.request({
command: "vault_info",
vault_id: vaultID,
ledger_index: "validated"
})
console.log(JSON.stringify(vault_info_response, null, 2))
await client.disconnect()This confirms that you have successfully created an empty single asset vault.
Concepts:
Tutorials:
References: