# Create a Single Asset Vault This tutorial shows you how to create a [single asset vault](/es-es/docs/concepts/tokens/single-asset-vaults) 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](/es-es/docs/concepts/decentralized-storage/credentials) can deposit, managed through [Permissioned Domains](/es-es/docs/concepts/tokens/decentralized-exchange/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. SingleAssetVault ## Goals 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. ## 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 python -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 and transaction handling. - `fs` and `child_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() Python - `json`: Used for loading and formatting JSON data. - `os`, `subprocess`, `sys`: Used for file handling and running setup scripts. - `xrpl`: Used for XRPL client connection and transaction handling. import json import os import subprocess import sys from xrpl.clients import JsonRpcClient from xrpl.models import VaultCreate from xrpl.models.requests import VaultInfo from xrpl.models.transactions.vault_create import VaultCreateFlag, WithdrawalPolicy from xrpl.transaction import submit_and_wait from xrpl.utils import str_to_hex, encode_mptoken_metadata from xrpl.wallet import generate_faucet_wallet # Auto-run setup if needed if not os.path.exists("vault_setup.json"): print("\n=== Vault setup data doesn't exist. Running setup script... ===\n") subprocess.run([sys.executable, "vault_setup.py"], check=True) # Load setup data with open("vault_setup.json", "r") as f: setup_data = json.load(f) # Connect to the network client = JsonRpcClient("https://s.devnet.rippletest.net:51234") 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. JavaScript // 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`. Python # Create and fund vault owner account vault_owner = generate_faucet_wallet(client) # You can replace these values with your own mpt_issuance_id = setup_data["mpt_issuance_id"] domain_id = setup_data["domain_id"] print(f"Vault owner address: {vault_owner.address}") print(f"MPT issuance ID: {mpt_issuance_id}") print(f"Permissioned domain ID: {domain_id}\n") The example uses an existing MPT issuance and permissioned domain data from the `vault_setup.py` script, but you can also provide your own values. If you want to create a public vault, you don't need to provide the `domain_id`. ### 3. Prepare VaultCreate transaction Create the [VaultCreate transaction](/docs/references/protocol/transactions/types/vaultcreate) object: JavaScript // 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](/es-es/docs/references/protocol/ledger-data/ledger-entry-types/vault#data-field-format) 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. Python # Prepare VaultCreate transaction ---------------------- print("\n=== VaultCreate transaction ===") vault_create_tx = VaultCreate( account=vault_owner.address, asset={"mpt_issuance_id": mpt_issuance_id}, flags=VaultCreateFlag.TF_VAULT_PRIVATE, # Omit TF_VAULT_PRIVATE flag for public vaults # To make vault shares non-transferable add the TF_VAULT_SHARE_NON_TRANSFERABLE flag: # flags=VaultCreateFlag.TF_VAULT_PRIVATE | VaultCreateFlag.TF_VAULT_SHARE_NON_TRANSFERABLE, domain_id=domain_id, # Omit for public vaults # Convert Vault data to a string (without excess whitespace), then string to hex. data=str_to_hex(json.dumps( {"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 mptoken_metadata=encode_mptoken_metadata({ "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", }, }), assets_maximum="0", # No cap withdrawal_policy=WithdrawalPolicy.VAULT_STRATEGY_FIRST_COME_FIRST_SERVE, ) print(json.dumps(vault_create_tx.to_xrpl(), indent=2)) The `tfVaultPrivate` flag and `domain_id` 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](/es-es/docs/references/protocol/ledger-data/ledger-entry-types/vault#data-field-format) for better discoverability in the XRPL ecosystem. The `assets_maximum` 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. ### 4. Submit VaultCreate transaction Sign and submit the `VaultCreate` transaction to the XRP Ledger. JavaScript // 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!") Python # Submit, sign, and wait for validation ---------------------- print("\n=== Submitting VaultCreate transaction... ===") submit_response = submit_and_wait(vault_create_tx, client, vault_owner, autofill=True) if submit_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = submit_response.result["meta"]["TransactionResult"] print(f"Error: Unable to create vault: {result_code}", file=sys.stderr) sys.exit(1) print("Vault created successfully!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 5. Get vault information Retrieve the vault's information from the transaction result by checking for the `Vault` object in the transaction metadata. JavaScript // 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}`) } Python # Extract vault information from the transaction result affected_nodes = submit_response.result["meta"].get("AffectedNodes", []) vault_node = next( (node for node in affected_nodes if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Vault"), None ) if vault_node: print(f"\nVault ID: {vault_node['CreatedNode']['LedgerIndex']}") print(f"Vault pseudo-account address: {vault_node['CreatedNode']['NewFields']['Account']}") print(f"Share MPT issuance ID: {vault_node['CreatedNode']['NewFields']['ShareMPTID']}") You can also use the [vault_info method](/docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info) to retrieve the vault's details: JavaScript // 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() Python # Call vault_info method to retrieve the vault's information print("\n=== Getting vault_info... ===") vault_id = vault_node["CreatedNode"]["LedgerIndex"] vault_info_response = client.request( VaultInfo( vault_id=vault_id, ledger_index="validated" ) ) print(json.dumps(vault_info_response.result, indent=2)) This confirms that you have successfully created an empty single asset vault. ## See Also **Concepts**: - [Single Asset Vaults](/es-es/docs/concepts/tokens/single-asset-vaults) - [Credentials](/es-es/docs/concepts/decentralized-storage/credentials) - [Permissioned Domains](/es-es/docs/concepts/tokens/decentralized-exchange/permissioned-domains) **Tutorials**: - [Issue Credentials](/es-es/docs/tutorials/sample-apps/credential-issuing-service-in-javascript) - [Create Permissioned Domain](/es-es/docs/tutorials/compliance-features/create-permissioned-domains-in-javascript) - [Deposit Assets into a Vault](/es-es/docs/tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault) **References**: - [VaultCreate transaction](/docs/references/protocol/transactions/types/vaultcreate)