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 SingleAssetVault amendment. Loading...
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 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 setup scripts.
import xrpl from "xrpl"
import { execSync } from "child_process"
import fs from "fs"
// Auto-run setup if needed
if (!fs.existsSync("vaultSetup.json")) {
console.log(`\n=== Vault setup data doesn't exist. Running setup script... ===\n`)
execSync("node vaultSetup.js", { stdio: "inherit" })
}
// Load setup data
const setupData = JSON.parse(fs.readFileSync("vaultSetup.json", "utf8"))
// Connect to the network
const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233")
await client.connect()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()
// You can replace these values with your own
const mptIssuanceId = setupData.mptIssuanceId
const domainId = setupData.domainId
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 data from the vaultSetup.js script, 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
// Convert Vault data to a string (without excess whitespace), then string to hex.
Data: xrpl.convertStringToHex(JSON.stringify(
{ n: "LATAM Fund II", w: "examplefund.com" })
),
// 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 Data field contains hex-encoded metadata about the vault itself, such as its name (n) and website (w). While any data structure is allowed, it's recommended to follow the defined data schema for better discoverability in the XRPL ecosystem.
The AssetsMaximum is set to 0 to indicate no cap on how much of the asset 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: