# Withdraw from a Vault This tutorial shows you how to withdraw assets from a [single asset vault](/ja/docs/concepts/tokens/single-asset-vaults). You can withdraw by specifying either how many assets you want to receive or how many shares you want to redeem. The vault burns the necessary shares and transfers the corresponding assets to your account. SingleAssetVault ## Goals By the end of this tutorial, you will be able to: - Withdraw assets from a private/public vault. - Check the vault's state after a successful withdrawal. - Check the depositor account's state after the withdrawal. ## Prerequisites To complete this tutorial, you should: - Have a basic understanding of the XRP Ledger. - Have previously deposited into a vault. This tutorial uses an account that has already deposited into a vault. To deposit your own asset, see [Deposit into a Vault](/ja/docs/tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault). - 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 VaultWithdraw from xrpl.models.requests import VaultInfo, LedgerEntry from xrpl.transaction import submit_and_wait from xrpl.wallet import 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") Provide the depositor account and specify the vault details. JavaScript // You can replace these values with your own const depositor = xrpl.Wallet.fromSeed(setupData.depositor.seed) const vaultID = setupData.vaultID const assetMPTIssuanceId = setupData.mptIssuanceId const shareMPTIssuanceId = setupData.vaultShareMPTIssuanceId This example uses preconfigured accounts and vault data from the `vaultSetup.js` script, but you can replace these values with your own. Python # You can replace these values with your own depositor = Wallet.from_seed(setup_data["depositor"]["seed"]) vault_id = setup_data["vault_id"] asset_mpt_issuance_id = setup_data["mpt_issuance_id"] share_mpt_issuance_id = setup_data["vault_share_mpt_issuance_id"] This example uses preconfigured accounts and vault data from the `vault_setup.py` script, but you can replace these values with your own. ### 3. Check initial vault state Before withdrawing, check the vault's current state to see its total assets and available liquidity. JavaScript // Get initial vault state ---------------------- console.log("\n=== Getting initial vault state... ===") const initialVaultInfo = await client.request({ command: "vault_info", vault_id: vaultID, ledger_index: "validated" }) console.log(`Initial vault state:`) console.log(` Assets Total: ${initialVaultInfo.result.vault.AssetsTotal}`) console.log(` Assets Available: ${initialVaultInfo.result.vault.AssetsAvailable}`) Python # Get initial vault state print("\n=== Getting initial vault state... ===") initial_vault_info = client.request( VaultInfo( vault_id=vault_id, ledger_index="validated" ) ) print("Initial vault state:") print(f" Assets Total: {initial_vault_info.result['vault']['AssetsTotal']}") print(f" Assets Available: {initial_vault_info.result['vault']['AssetsAvailable']}") ### 4. Check share balance Verify that the depositor account has vault shares to redeem. If not, the transaction will fail with a `tecINSUFFICIENT_FUNDS` error. JavaScript // Check depositor's share balance ---------------------- console.log("\n=== Checking depositor's share balance... ===") try { const shareBalanceResult = await client.request({ command: "ledger_entry", mptoken: { mpt_issuance_id: shareMPTIssuanceId, account: depositor.address }, ledger_index: "validated" }) const shareBalance = shareBalanceResult.result.node?.MPTAmount console.log(`Shares held: ${shareBalance}`) } catch (error) { if (error.data?.error === 'entryNotFound') { console.error(`Error: The depositor doesn't hold any vault shares with ID: ${shareMPTIssuanceId}.`) } else { console.error(`Error checking MPT: ${error}`) } await client.disconnect() process.exit(1) } Python # Check depositor's share balance print("\n=== Checking depositor's share balance... ===") try: share_balance_result = client.request( LedgerEntry( mptoken={ "mpt_issuance_id": share_mpt_issuance_id, "account": depositor.address }, ledger_index="validated" ) ) share_balance = share_balance_result.result["node"]["MPTAmount"] print(f"Shares held: {share_balance}") except Exception as error: error_data = getattr(error, 'data', {}) if 'error' in error_data and error_data['error'] == 'entryNotFound': print(f"Error: The depositor doesn't hold any vault shares with ID: {share_mpt_issuance_id}.", file=sys.stderr) else: print(f"Error checking MPT: {error}", file=sys.stderr) sys.exit(1) ### 5. Prepare VaultWithdraw transaction Create a [VaultWithdraw transaction](/docs/references/protocol/transactions/types/vaultwithdraw) to withdraw assets from the vault. JavaScript // Prepare VaultWithdraw transaction ---------------------- console.log(`\n=== Preparing VaultWithdraw transaction ===`) const vaultWithdrawTx = { TransactionType: "VaultWithdraw", Account: depositor.address, VaultID: vaultID, Amount: { mpt_issuance_id: assetMPTIssuanceId, value: withdrawAmount }, // Optional: Add Destination field to send assets to a different account // Destination: "rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY" } // Validate the transaction structure before submitting xrpl.validate(vaultWithdrawTx) console.log(JSON.stringify(vaultWithdrawTx, null, 2)) The transaction defines the account requesting the withdrawal, the vault's unique identifier (`VaultID`), and the amount to withdraw or redeem. You can specify the `Amount` field in two ways: Python # Prepare VaultWithdraw transaction print("\n=== Preparing VaultWithdraw transaction ===") vault_withdraw_tx = VaultWithdraw( account=depositor.address, vault_id=vault_id, amount={ "mpt_issuance_id": asset_mpt_issuance_id, "value": str(withdraw_amount) } # Optional: Add destination field to send assets to a different account # destination="rGg4tHPRGJfewwJkd8immCFx9uSo2GgcoY" ) print(json.dumps(vault_withdraw_tx.to_xrpl(), indent=2)) The transaction defines the account requesting the withdrawal, the vault's unique identifier (`vault_id`), and the amount to withdraw or redeem. You can specify the `amount` field in two ways: - **Asset amount**: When you specify an asset amount, the vault burns the necessary shares to provide that amount. - **Share amount**: When you specify a share amount, the vault converts those shares into the corresponding asset amount. While not required, you can provide a destination account to receive the assets; if omitted, assets go to the account submitting the transaction. Note You can withdraw from a vault regardless of whether it's private or public. If you hold vault shares, you can always redeem them, even if your credentials in a private vault's permissioned domain have expired or been revoked. This prevents you from being locked out of your funds. ### 6. Submit VaultWithdraw transaction Submit the `VaultWithdraw` transaction to the XRP Ledger. JavaScript // Submit VaultWithdraw transaction ---------------------- console.log("\n=== Submitting VaultWithdraw transaction... ===") const withdrawResult = await client.submitAndWait(vaultWithdrawTx, { wallet: depositor, autofill: true, }) if (withdrawResult.result.meta.TransactionResult !== "tesSUCCESS") { const result_code = withdrawResult.result.meta.TransactionResult console.error("Error: Unable to withdraw from vault:", result_code) await client.disconnect() process.exit(1) } console.log("Withdrawal successful!") Python # Submit VaultWithdraw transaction print("\n=== Submitting VaultWithdraw transaction... ===") withdraw_result = submit_and_wait(vault_withdraw_tx, client, depositor, autofill=True) if withdraw_result.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = withdraw_result.result["meta"]["TransactionResult"] print(f"Error: Unable to withdraw from vault: {result_code}", file=sys.stderr) sys.exit(1) print("Withdrawal successful!") When the transaction succeeds: - The vault calculates how many shares need to be burned to provide the requested asset amount. - The vault transfers the assets from its pseudo-account to the depositor account (or the destination account if specified). Note Transfer fees are not charged on `VaultWithdraw` transactions. ### 7. Verify withdrawal After withdrawing, check the vault's state. You can extract this information directly from the transaction metadata. JavaScript // Extract vault state from transaction metadata ---------------------- console.log("\n=== Vault state after withdrawal ===") const affectedNodes = withdrawResult.result.meta.AffectedNodes const vaultNode = affectedNodes.find( (node) => { const modifiedNode = node.ModifiedNode || node.DeletedNode return ( modifiedNode && modifiedNode.LedgerEntryType === "Vault" && modifiedNode.LedgerIndex === vaultID ) } ) if (vaultNode) { if (vaultNode.DeletedNode) { console.log(` Vault empty (all assets withdrawn)`) } else { const vaultFields = vaultNode.ModifiedNode.FinalFields console.log(` Assets Total: ${vaultFields.AssetsTotal}`) console.log(` Assets Available: ${vaultFields.AssetsAvailable}`) } } Python # Extract vault state from transaction metadata print("\n=== Vault state after withdrawal ===") affected_nodes = withdraw_result.result["meta"]["AffectedNodes"] vault_node = None for node in affected_nodes: if "ModifiedNode" in node or "DeletedNode" in node: modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"] if modified_node["LedgerEntryType"] == "Vault" and modified_node["LedgerIndex"] == vault_id: vault_node = node break if vault_node: if "DeletedNode" in vault_node: print(" Vault empty (all assets withdrawn)") else: vault_fields = vault_node["ModifiedNode"]["FinalFields"] print(f" Assets Total: {vault_fields['AssetsTotal']}") print(f" Assets Available: {vault_fields['AssetsAvailable']}") Then, check the depositor's share balance: JavaScript // Get the depositor's share balance ---------------------- console.log("\n=== Depositor's share balance ==") const depositorShareNode = affectedNodes.find((node) => { const modifiedNode = node.ModifiedNode || node.DeletedNode return ( modifiedNode && modifiedNode.LedgerEntryType === "MPToken" && modifiedNode.FinalFields?.Account === depositor.address && modifiedNode.FinalFields?.MPTokenIssuanceID === shareMPTIssuanceId ) }) if (depositorShareNode) { if (depositorShareNode.DeletedNode) { console.log(`No more shares held (redeemed all shares)`) } else { const shareFields = depositorShareNode.ModifiedNode.FinalFields console.log(`Shares held: ${shareFields.MPTAmount}`) } } Python # Get the depositor's share balance print("\n=== Depositor's share balance ===") depositor_share_node = None for node in affected_nodes: if "ModifiedNode" in node or "DeletedNode" in node: modified_node = node["ModifiedNode"] if "ModifiedNode" in node else node["DeletedNode"] if "FinalFields" in modified_node: fields = modified_node["FinalFields"] if (modified_node["LedgerEntryType"] == "MPToken" and fields["Account"] == depositor.address and fields["MPTokenIssuanceID"] == share_mpt_issuance_id): depositor_share_node = node break if depositor_share_node: if "DeletedNode" in depositor_share_node: print("No more shares held (redeemed all shares)") else: share_fields = depositor_share_node["ModifiedNode"]["FinalFields"] print(f"Shares held: {share_fields['MPTAmount']}") Finally, verify the correct asset amount has been received by the depositor account: JavaScript // Get the depositor's asset balance ---------------------- console.log("\n=== Depositor's asset balance ==") const depositorAssetNode = affectedNodes.find((node) => { const assetNode = node.ModifiedNode || node.CreatedNode const fields = assetNode?.FinalFields || assetNode?.NewFields return ( assetNode && assetNode.LedgerEntryType === "MPToken" && fields?.Account === depositor.address && fields?.MPTokenIssuanceID === assetMPTIssuanceId ) }) if (depositorAssetNode) { const assetNode = depositorAssetNode.ModifiedNode || depositorAssetNode.CreatedNode const assetFields = assetNode.FinalFields || assetNode.NewFields console.log(`Balance: ${assetFields.MPTAmount}`) } await client.disconnect() Python # Get the depositor's asset balance print("\n=== Depositor's asset balance ===") depositor_asset_node = None for node in affected_nodes: if "ModifiedNode" in node: asset_node = node["ModifiedNode"] fields = asset_node["FinalFields"] elif "CreatedNode" in node: asset_node = node["CreatedNode"] fields = asset_node["NewFields"] else: continue if (asset_node["LedgerEntryType"] == "MPToken" and fields["Account"] == depositor.address and fields["MPTokenIssuanceID"] == asset_mpt_issuance_id): depositor_asset_node = node break if depositor_asset_node: if "ModifiedNode" in depositor_asset_node: asset_fields = depositor_asset_node["ModifiedNode"]["FinalFields"] else: asset_fields = depositor_asset_node["CreatedNode"]["NewFields"] print(f"Balance: {asset_fields['MPTAmount']}") ## See Also **Concepts**: - [Single Asset Vaults](/ja/docs/concepts/tokens/single-asset-vaults) - [Credentials](/ja/docs/concepts/decentralized-storage/credentials) - [Permissioned Domains](/ja/docs/concepts/tokens/decentralized-exchange/permissioned-domains) **Tutorials**: - [Create a Single Asset Vault](/ja/docs/tutorials/defi/lending/use-single-asset-vaults/create-a-single-asset-vault) - [Deposit into a Vault](/ja/docs/tutorials/defi/lending/use-single-asset-vaults/deposit-into-a-vault) **References**: - [VaultWithdraw transaction](/docs/references/protocol/transactions/types/vaultwithdraw) - [vault_info method](/docs/references/http-websocket-apis/public-api-methods/vault-methods/vault_info)