# Create a Single Asset Vault

This tutorial shows you how to create a [single asset vault](/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](/docs/concepts/decentralized-storage/credentials) can deposit, managed through [Permissioned Domains](/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.



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



```python
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

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

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

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

```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](/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

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

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

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

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

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

```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](/docs/concepts/tokens/single-asset-vaults)
- [Credentials](/docs/concepts/decentralized-storage/credentials)
- [Permissioned Domains](/docs/concepts/tokens/decentralized-exchange/permissioned-domains)


**Tutorials**:

- [Issue Credentials](/docs/tutorials/sample-apps/credential-issuing-service-in-javascript)
- [Create Permissioned Domain](/docs/tutorials/compliance-features/create-permissioned-domains-in-javascript)
- [Deposit Assets into a Vault](/docs/tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault)


**References**:

- [VaultCreate transaction](/docs/references/protocol/transactions/types/vaultcreate)