From a4d1d0b2b9346f595f8d24f77d0ac0c6ec6b242e Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 15:15:31 +0100
Subject: [PATCH 01/27] reformats failure and state changes to use numerical
indexes
---
XLS-0065-single-asset-vault/README.md | 292 +++++++++++++-------------
1 file changed, 144 insertions(+), 148 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 2d5230a3e..67104303c 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -397,39 +397,37 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
##### 3.1.1.4 Failure Conditions
-- The `Asset` is `XRP`:
- - The `Scale` parameter is provided.
+1. The `Asset` is `XRP`:
+ 1. The `Scale` parameter is provided.
-- The `Asset` is `MPT`:
- - The `Scale` parameter is provided.
- - The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
- - The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
+2. The `Asset` is `MPT`:
+ 1. The `Scale` parameter is provided.
+ 2. The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
+ 3. The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `Scale` parameter is provided, and is less than **0** or greater than **18**.
+3. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `Scale` parameter is provided, and is less than **0** or greater than **18**.
-- The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
+4. The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
-- The `PermissionedDomain` object does not exist with the provided `DomainID`.
-
-- The `Data` field is larger than 256 bytes.
-- The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
+5. The `PermissionedDomain` object does not exist with the provided `DomainID`.
+6. The `Data` field is larger than 256 bytes.
+7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
##### 3.1.1.5 State Changes
-- Create a new `Vault` ledger object.
-- Create a new `MPTokenIssuance` ledger object for the vault shares.
- - If the `DomainID` is provided:
- - `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
- - Create an `MPToken` object for the Vault Owner to hold Vault Shares.
-- Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
+1. Create a new `Vault` ledger object.
+2. Create a new `MPTokenIssuance` ledger object for the vault shares.
+ 1. If the `DomainID` is provided: `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
+ 2. Create an `MPToken` object for the Vault Owner to hold Vault Shares.
+3. Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
-- If `Vault.Asset` is an `IOU`:
- - Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
+4. If `Vault.Asset` is an `IOU`:
+ 1. Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
-- If `Vault.Asset` is an `MPT`:
- - Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
+5. If `Vault.Asset` is an `MPT`:
+ 1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
##### 3.1.1.6 Invariants
@@ -451,21 +449,21 @@ The `VaultSet` updates an existing `Vault` ledger object.
##### 3.1.2.1 Failure Conditions
-- `Vault` object with the specified `VaultID` does not exist on the ledger.
-- The submitting account is not the `Owner` of the vault.
-- The `Data` field is larger than 256 bytes.
-- If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
- - The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
-- The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
-- The `PermissionedDomain` object does not exist with the provided `DomainID`.
-- The transaction is attempting to modify an immutable field.
-- The transaction does not specify any of the modifiable fields.
+1. `Vault` object with the specified `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. The `Data` field is larger than 256 bytes.
+4. If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
+ 1. The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
+5. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
+6. The `PermissionedDomain` object does not exist with the provided `DomainID`.
+7. The transaction is attempting to modify an immutable field.
+8. The transaction does not specify any of the modifiable fields.
##### 3.1.2.2 State Changes
-- Update mutable fields in the `Vault` ledger object.
-- If `DomainID` is provided:
- - Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
+1. Update mutable fields in the `Vault` ledger object.
+2. If `DomainID` is provided:
+ 1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
##### 3.1.2.3 Invariants
@@ -484,18 +482,18 @@ The `VaultDelete` transaction deletes an existing vault object.
##### 3.1.3.1 Failure Conditions
-- `Vault` object with the `VaultID` does not exist on the ledger.
-- The submitting account is not the `Owner` of the vault.
-- `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
-- The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
+4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
##### 3.1.3.2 State Changes
-- Delete the `MPTokenIssuance` object for the vault shares.
-- Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
-- Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
-- Release the Owner Reserve to the `Vault.Owner` account.
-- Delete the `Vault` object.
+1. Delete the `MPTokenIssuance` object for the vault shares.
+2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
+3. Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
+4. Release the Owner Reserve to the `Vault.Owner` account.
+5. Delete the `Vault` object.
##### 3.1.3.3 Invariants
@@ -521,42 +519,42 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
##### 3.2.1.1 Failure conditions
-- `Vault` object with the `VaultID` does not exist on the ledger.
-- The asset type of the vault does not match the asset type the depositor is depositing.
-- The depositor does not have sufficient funds to make a deposit.
-- Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
-- The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The asset type of the vault does not match the asset type the depositor is depositing.
+3. The depositor does not have sufficient funds to make a deposit.
+4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
+5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
-- The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
- - `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
+6. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
+ 4. `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
- - The `RippleState` object `Balance` < `Amount` (insufficient balance).
+7. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
+ 3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
##### 3.2.1.2 State Changes
-If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
+1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
-- Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
-- Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
+2. Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
+3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+4. Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
-- If the `Vault.Asset` is `XRP`:
- - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
- - Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
+5. If the `Vault.Asset` is `XRP`:
+ 1. Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
+ 2. Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
-- If the `Vault.Asset` is an `IOU`:
- - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
- - Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+6. If the `Vault.Asset` is an `IOU`:
+ 1. Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+ 2. Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
-- If the `Vault.Asset` is an `MPT`:
- - Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- - Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
+7. If the `Vault.Asset` is an `MPT`:
+ 1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ 2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
##### 3.2.1.3 Invariants
@@ -589,60 +587,58 @@ In sections below assume the following variables:
##### 3.2.2.1 Failure conditions
-- `Vault` object with the `VaultID` does not exist on the ledger.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
-- The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
+2. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
+3. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
-- The unit of `Amount` is not shares of the vault.
-- The unit of `Amount` is not asset of the vault.
+4. The unit of `Amount` is not shares of the vault.
+5. The unit of `Amount` is not asset of the vault.
-- There is insufficient liquidity in the vault to fill the request:
- - If `Amount` is the vaults share:
- - `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
- - The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
- - `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
+6. There is insufficient liquidity in the vault to fill the request:
+ 1. If `Amount` is the vaults share:
+ 1. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
+ 2. The shares `MPToken(MPTokenIssuanceID, AccountID | Destination).MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
+ 3. `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
- - If `Amount` is the vaults asset:
- - The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
- - `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
+7. If `Amount` is the vaults asset:
+ 1. The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
+ 2. `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
-- The `Destination` account is specified:
- - The account does not have permission to receive the asset.
- - The account does not have a `RippleState` or `MPToken` object for the asset.
+8. The `Destination` account is specified:
+ 1. The account does not have permission to receive the asset.
+ 2. The account does not have a `RippleState` or `MPToken` object for the asset.
##### 3.2.2.2 State Changes
-- If the `Vault.Asset` is XRP:
- - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
- - Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
-
-- If the `Vault.Asset` is an `IOU`:
- - If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
-
- - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
- - Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+1. If the `Vault.Asset` is XRP:
+ 1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
+ 2. Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
-- If the `Vault.Asset` is an `MPT`:
- - If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
+2. If the `Vault.Asset` is an `IOU`:
+ 1. If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
+ 2. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+ 3. Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
- - Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- - Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
+3. If the `Vault.Asset` is an `MPT`:
+ 1. If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
+ 2. Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ 3. Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
-- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- - If `MPToken.MPTAmount == 0`, delete the object.
+4. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ 2. If `MPToken.MPTAmount == 0`, delete the object.
-- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+5. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
+6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
##### 3.2.2.3 Invariants
@@ -665,38 +661,38 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
##### 3.3.1.1 Failure conditions
-- `Vault` object with the `VaultID` does not exist on the ledger.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
-- If `Vault.Asset` is `XRP`.
+2. If `Vault.Asset` is `XRP`.
-- If `Vault.Asset` is an `IOU` and:
- - The `Issuer` account is not the submitter of the transaction.
- - If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
- - If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
+3. If `Vault.Asset` is an `IOU` and:
+ 1. The `Issuer` account is not the submitter of the transaction.
+ 2. If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
+ 3. If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
-- If `Vault.Asset` is an `MPT` and:
- - `MPTokenIssuance.Issuer` is not the submitter of the transaction.
- - `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
- - If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
+4. If `Vault.Asset` is an `MPT` and:
+ 1. `MPTokenIssuance.Issuer` is not the submitter of the transaction.
+ 2. `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
+ 3. If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
-- The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
+5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
##### 3.3.1.2 State Changes
-- If the `Vault.Asset` is an `IOU`:
- - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
+1. If the `Vault.Asset` is an `IOU`:
+ 1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
-- If the `Vault.Asset` is an `MPT`:
- - Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+2. If the `Vault.Asset` is an `MPT`:
+ 1. Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
-- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- - If `MPToken.MPTAmount == 0`, delete the object.
+3. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ 2. If `MPToken.MPTAmount == 0`, delete the object.
-- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+4. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
+5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
##### 3.3.1.3 Invariants
@@ -712,26 +708,26 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
#### 3.4.1.1 Failure Conditions
-- If `Payment.Amount` is a `Vault` share AND:
- - The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
- - The `Vault` `tfVaultShareNonTransferable` flag is set.
+1. If `Payment.Amount` is a `Vault` share AND:
+ 1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
+ 2. The `Vault` `tfVaultShareNonTransferable` flag is set.
- - The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- - `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
- - `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
+ 3. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
+ 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ 5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
- - The `Vault.Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
+ 4. The `Vault.Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
+ 3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
+ 4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
#### 3.4.1.2 State Changes
-- If `MPToken`object for shares does not exist for the destination account, create one.
+1. If `MPToken`object for shares does not exist for the destination account, create one.
[**Return to Index**](#index)
From 5d8a3a5284612ecd7b7f4680e87fee901bf29c34 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 15:28:43 +0100
Subject: [PATCH 02/27] re-indexes transaction sections
---
XLS-0065-single-asset-vault/README.md | 90 ++++++++++++---------------
1 file changed, 39 insertions(+), 51 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 67104303c..c9b1cb998 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -34,15 +34,13 @@ A Single Asset Vault is a new on-chain primitive for aggregating assets from one
- [**2.1.6. Vault Shares**](#216-vault-shares)
- [**2.1.7. Exchange Algorithm**](#217-exchange-algorithm)
- [**3. Transactions**](#3-transactions)
- - [**3.1. VaultCreate, VaultSet & VaultDelete Transactions**](#31-vaultcreate-vaultset-vaultdelete-transactions)
- - [**3.1.1. VaultCreate Transaction**](#311-vaultcreate-transaction)
- - [**3.1.2. VaultSet Transaction**](#312-vaultset-transaction)
- - [**3.1.3. VaultDelete Transaction**](#313-vaultdelete-transaction)
- - [**3.2. VaultDeposit & VaultWithdraw Transactions**](#32-vaultdeposit-and-vaultwithdraw-transactions)
- - [**3.2.1. VaultDeposit Transaction**](#321-vaultdeposit-transaction)
- - [**3.2.2. VaultWithdraw Transaction**](#322-vaultwithdraw-transaction)
- - [**3.3. VaultClawback Transaction**](#33-vaultclawback-transaction)
- - [**3.4. Payment Transaction**](#34-payment-transaction)
+ - [**3.1. VaultCreate Transaction**](#31-vaultcreate-transaction)
+ - [**3.2. VaultSet Transaction**](#32-vaultset-transaction)
+ - [**3.3. VaultDelete Transaction**](#33-vaultdelete-transaction)
+ - [**3.4. VaultDeposit Transaction**](#34-vaultdeposit-transaction)
+ - [**3.5. VaultWithdraw Transaction**](#35-vaultwithdraw-transaction)
+ - [**3.6. VaultClawback Transaction**](#36-vaultclawback-transaction)
+ - [**3.7. Payment Transaction**](#37-payment-transaction)
- [**4. API**](#4-api)
- [Appendix](#appendix)
@@ -356,11 +354,7 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
All transactions introduced by this proposal incorporate the [common transaction fields](https://xrpl.org/docs/references/protocol/transactions/common-fields) that are shared by all transactions. Standard fields are only documented in this proposal if needed because this proposal introduces new possible values for such fields.
-### 3.1 `VaultCreate`, `VaultSet`, `VaultDelete` transactions
-
-The `Vault` object is managed with `VaultCreate`, `VaultSet` and `VaultDelete` transactions.
-
-#### 3.1.1 `VaultCreate` Transaction
+### 3.1 `VaultCreate` Transaction
The `VaultCreate` transaction creates a new `Vault` object.
@@ -376,14 +370,14 @@ The `VaultCreate` transaction creates a new `Vault` object.
| `DomainID` | | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
| `Scale` | | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-##### 3.1.1.1 Flags
+#### 3.1.1 Flags
| Flag Name | Flag Value | Description |
| ----------------------------- | :----------: | :--------------------------------------------------------------------------------------- |
| `tfVaultPrivate` | `0x00010000` | Indicates that the vault is private. It can only be set during Vault creation. |
| `tfVaultShareNonTransferable` | `0x00020000` | Indicates the vault share is non-transferable. It can only be set during Vault creation. |
-###### 3.1.1.2 WithdrawalPolicy
+##### 3.1.2 WithdrawalPolicy
The type indicates the withdrawal strategy supported by the vault. The following values are supported:
@@ -391,11 +385,11 @@ The type indicates the withdrawal strategy supported by the vault. The following
| ---------------------------------- | :------: | :-------------------------------------------------------: |
| `vaultStrategyFirstComeFirstServe` | `0x0001` | Requests are processed on a first-come-first-serve basis. |
-##### 3.1.1.3 Transaction Fees
+#### 3.1.3 Transaction Fees
The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Therefore, the transaction [must destroy](../XLS-0064-pseudo-account/README.md) one incremental owner reserve amount.
-##### 3.1.1.4 Failure Conditions
+#### 3.1.4 Failure Conditions
1. The `Asset` is `XRP`:
1. The `Scale` parameter is provided.
@@ -415,7 +409,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
6. The `Data` field is larger than 256 bytes.
7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
-##### 3.1.1.5 State Changes
+#### 3.1.5 State Changes
1. Create a new `Vault` ledger object.
2. Create a new `MPTokenIssuance` ledger object for the vault shares.
@@ -429,13 +423,13 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
5. If `Vault.Asset` is an `MPT`:
1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
-##### 3.1.1.6 Invariants
+#### 3.1.6 Invariants
**TBD**
[**Return to Index**](#index)
-#### 3.1.2 `VaultSet` Transaction
+### 3.2 `VaultSet` Transaction
The `VaultSet` updates an existing `Vault` ledger object.
@@ -447,7 +441,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
| `AssetsMaximum` | | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
| `DomainID` | | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-##### 3.1.2.1 Failure Conditions
+#### 3.2.1 Failure Conditions
1. `Vault` object with the specified `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
@@ -459,19 +453,19 @@ The `VaultSet` updates an existing `Vault` ledger object.
7. The transaction is attempting to modify an immutable field.
8. The transaction does not specify any of the modifiable fields.
-##### 3.1.2.2 State Changes
+#### 3.2.2 State Changes
1. Update mutable fields in the `Vault` ledger object.
2. If `DomainID` is provided:
1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
-##### 3.1.2.3 Invariants
+#### 3.2.3 Invariants
**TBD**
[**Return to Index**](#index)
-#### 3.1.3 `VaultDelete` Transaction
+### 3.3 `VaultDelete` Transaction
The `VaultDelete` transaction deletes an existing vault object.
@@ -480,14 +474,14 @@ The `VaultDelete` transaction deletes an existing vault object.
| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `60` | Transaction type. |
| `VaultID` | :heavy_check_mark: | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
-##### 3.1.3.1 Failure Conditions
+#### 3.3.1 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
-##### 3.1.3.2 State Changes
+#### 3.3.2 State Changes
1. Delete the `MPTokenIssuance` object for the vault shares.
2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
@@ -495,19 +489,13 @@ The `VaultDelete` transaction deletes an existing vault object.
4. Release the Owner Reserve to the `Vault.Owner` account.
5. Delete the `Vault` object.
-##### 3.1.3.3 Invariants
+#### 3.3.3 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.2 `VaultDeposit` and `VaultWithdraw` transactions
-
-Depositors call the `VaultDeposit` and `VaultWithdraw` transactions to add or remove assets from the Tokenized Vault.
-
-[**Return to Index**](#index)
-
-#### 3.2.1 `VaultDeposit` transaction
+### 3.4 `VaultDeposit` transaction
The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
@@ -517,7 +505,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
| `Amount` | :heavy_check_mark: | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
-##### 3.2.1.1 Failure conditions
+#### 3.4.1 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
@@ -536,7 +524,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
-##### 3.2.1.2 State Changes
+#### 3.4.2 State Changes
1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
@@ -556,13 +544,13 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
-##### 3.2.1.3 Invariants
+#### 3.4.3 Invariants
**TBD**
[**Return to Index**](#index)
-#### 3.2.2 `VaultWithdraw` transaction
+### 3.5 `VaultWithdraw` transaction
The `VaultWithdraw` transaction withdraws assets in exchange for the vault's shares.
@@ -585,7 +573,7 @@ In sections below assume the following variables:
- $\Delta_{asset}$ - the change in the total amount of assets after a deposit, withdrawal, or redemption.
- $\Delta_{share}$ - che change in the total amount of shares after a deposit, withdrawal, or redemption.
-##### 3.2.2.1 Failure conditions
+#### 3.5.1 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -615,7 +603,7 @@ In sections below assume the following variables:
1. The account does not have permission to receive the asset.
2. The account does not have a `RippleState` or `MPToken` object for the asset.
-##### 3.2.2.2 State Changes
+#### 3.5.2 State Changes
1. If the `Vault.Asset` is XRP:
1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
@@ -640,15 +628,15 @@ In sections below assume the following variables:
6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
-##### 3.2.2.3 Invariants
+#### 3.5.3 Invariants
**TBD**
[**Return to Index**](#index)
-## 3.3 VaultClawback Transaction
+### 3.6 VaultClawback Transaction
-#### 3.3.1 `VaultClawback` Transaction
+#### 3.6.1 `VaultClawback` Transaction
The `VaultClawback` transaction performs a Clawback from the Vault, exchanging the shares of an account. Conceptually, the transaction performs `VaultWithdraw` on behalf of the `Holder`, sending the funds to the `Issuer` account of the asset. In case there are insufficient funds for the entire `Amount` the transaction will perform a partial Clawback, up to the `Vault.AssetsAvailable`. The Clawback transaction must respect any future fees or penalties.
@@ -659,7 +647,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| `Holder` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
-##### 3.3.1.1 Failure conditions
+##### 3.6.1.1 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -677,7 +665,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
-##### 3.3.1.2 State Changes
+##### 3.6.1.2 State Changes
1. If the `Vault.Asset` is an `IOU`:
1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
@@ -694,19 +682,19 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
-##### 3.3.1.3 Invariants
+##### 3.6.1.3 Invariants
**TBD**
[**Return to Index**](#index)
-## 3.4 Payment Transaction
+### 3.7 Payment Transaction
-### 3.4.1 `Payment` transaction
+#### 3.7.1 `Payment` transaction
The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
-#### 3.4.1.1 Failure Conditions
+##### 3.7.1.1 Failure Conditions
1. If `Payment.Amount` is a `Vault` share AND:
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
@@ -725,7 +713,7 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
-#### 3.4.1.2 State Changes
+##### 3.7.1.2 State Changes
1. If `MPToken`object for shares does not exist for the destination account, create one.
From 82cd4e450625846776a5594d608da179165aee4f Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 16:07:32 +0100
Subject: [PATCH 03/27] adds fields section to each tx
---
XLS-0065-single-asset-vault/README.md | 62 +++++++++++++++------------
1 file changed, 35 insertions(+), 27 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index c9b1cb998..a1e1929b6 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -358,6 +358,8 @@ All transactions introduced by this proposal incorporate the [common transaction
The `VaultCreate` transaction creates a new `Vault` object.
+#### 3.1.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ------------------ | :----------------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `58` | The transaction type. |
@@ -370,14 +372,14 @@ The `VaultCreate` transaction creates a new `Vault` object.
| `DomainID` | | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
| `Scale` | | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-#### 3.1.1 Flags
+#### 3.1.2 Flags
| Flag Name | Flag Value | Description |
| ----------------------------- | :----------: | :--------------------------------------------------------------------------------------- |
| `tfVaultPrivate` | `0x00010000` | Indicates that the vault is private. It can only be set during Vault creation. |
| `tfVaultShareNonTransferable` | `0x00020000` | Indicates the vault share is non-transferable. It can only be set during Vault creation. |
-##### 3.1.2 WithdrawalPolicy
+##### 3.1.3 WithdrawalPolicy
The type indicates the withdrawal strategy supported by the vault. The following values are supported:
@@ -385,11 +387,11 @@ The type indicates the withdrawal strategy supported by the vault. The following
| ---------------------------------- | :------: | :-------------------------------------------------------: |
| `vaultStrategyFirstComeFirstServe` | `0x0001` | Requests are processed on a first-come-first-serve basis. |
-#### 3.1.3 Transaction Fees
+#### 3.1.4 Transaction Fees
The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Therefore, the transaction [must destroy](../XLS-0064-pseudo-account/README.md) one incremental owner reserve amount.
-#### 3.1.4 Failure Conditions
+#### 3.1.5 Failure Conditions
1. The `Asset` is `XRP`:
1. The `Scale` parameter is provided.
@@ -409,7 +411,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
6. The `Data` field is larger than 256 bytes.
7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
-#### 3.1.5 State Changes
+#### 3.1.6 State Changes
1. Create a new `Vault` ledger object.
2. Create a new `MPTokenIssuance` ledger object for the vault shares.
@@ -423,7 +425,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
5. If `Vault.Asset` is an `MPT`:
1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
-#### 3.1.6 Invariants
+#### 3.1.7 Invariants
**TBD**
@@ -433,6 +435,8 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
The `VaultSet` updates an existing `Vault` ledger object.
+#### 3.2.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `59` | The transaction type. |
@@ -441,7 +445,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
| `AssetsMaximum` | | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
| `DomainID` | | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-#### 3.2.1 Failure Conditions
+#### 3.2.2 Failure Conditions
1. `Vault` object with the specified `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
@@ -453,13 +457,13 @@ The `VaultSet` updates an existing `Vault` ledger object.
7. The transaction is attempting to modify an immutable field.
8. The transaction does not specify any of the modifiable fields.
-#### 3.2.2 State Changes
+#### 3.2.3 State Changes
1. Update mutable fields in the `Vault` ledger object.
2. If `DomainID` is provided:
1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
-#### 3.2.3 Invariants
+#### 3.2.4 Invariants
**TBD**
@@ -469,19 +473,21 @@ The `VaultSet` updates an existing `Vault` ledger object.
The `VaultDelete` transaction deletes an existing vault object.
+#### 3.3.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `60` | Transaction type. |
| `VaultID` | :heavy_check_mark: | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
-#### 3.3.1 Failure Conditions
+#### 3.3.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
-#### 3.3.2 State Changes
+#### 3.3.3 State Changes
1. Delete the `MPTokenIssuance` object for the vault shares.
2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
@@ -489,7 +495,7 @@ The `VaultDelete` transaction deletes an existing vault object.
4. Release the Owner Reserve to the `Vault.Owner` account.
5. Delete the `Vault` object.
-#### 3.3.3 Invariants
+#### 3.3.4 Invariants
**TBD**
@@ -499,13 +505,15 @@ The `VaultDelete` transaction deletes an existing vault object.
The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
+#### 3.4.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `61` | Transaction type. |
| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
| `Amount` | :heavy_check_mark: | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
-#### 3.4.1 Failure conditions
+#### 3.4.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
@@ -524,7 +532,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
-#### 3.4.2 State Changes
+#### 3.4.3 State Changes
1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
@@ -544,7 +552,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
-#### 3.4.3 Invariants
+#### 3.4.4 Invariants
**TBD**
@@ -554,6 +562,8 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
The `VaultWithdraw` transaction withdraws assets in exchange for the vault's shares.
+#### 3.5.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `62` | Transaction type. |
@@ -573,7 +583,7 @@ In sections below assume the following variables:
- $\Delta_{asset}$ - the change in the total amount of assets after a deposit, withdrawal, or redemption.
- $\Delta_{share}$ - che change in the total amount of shares after a deposit, withdrawal, or redemption.
-#### 3.5.1 Failure conditions
+#### 3.5.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -603,7 +613,7 @@ In sections below assume the following variables:
1. The account does not have permission to receive the asset.
2. The account does not have a `RippleState` or `MPToken` object for the asset.
-#### 3.5.2 State Changes
+#### 3.5.3 State Changes
1. If the `Vault.Asset` is XRP:
1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
@@ -628,7 +638,7 @@ In sections below assume the following variables:
6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
-#### 3.5.3 Invariants
+#### 3.5.4 Invariants
**TBD**
@@ -636,10 +646,10 @@ In sections below assume the following variables:
### 3.6 VaultClawback Transaction
-#### 3.6.1 `VaultClawback` Transaction
-
The `VaultClawback` transaction performs a Clawback from the Vault, exchanging the shares of an account. Conceptually, the transaction performs `VaultWithdraw` on behalf of the `Holder`, sending the funds to the `Issuer` account of the asset. In case there are insufficient funds for the entire `Amount` the transaction will perform a partial Clawback, up to the `Vault.AssetsAvailable`. The Clawback transaction must respect any future fees or penalties.
+#### 3.6.1 Fields
+
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `63` | Transaction type. |
@@ -647,7 +657,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| `Holder` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
-##### 3.6.1.1 Failure conditions
+#### 3.6.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -665,7 +675,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
-##### 3.6.1.2 State Changes
+#### 3.6.3 State Changes
1. If the `Vault.Asset` is an `IOU`:
1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
@@ -682,7 +692,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
-##### 3.6.1.3 Invariants
+#### 3.6.4 Invariants
**TBD**
@@ -690,11 +700,9 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
### 3.7 Payment Transaction
-#### 3.7.1 `Payment` transaction
-
The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
-##### 3.7.1.1 Failure Conditions
+#### 3.7.1 Failure Conditions
1. If `Payment.Amount` is a `Vault` share AND:
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
@@ -713,7 +721,7 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
-##### 3.7.1.2 State Changes
+#### 3.7.2 State Changes
1. If `MPToken`object for shares does not exist for the destination account, create one.
From 22edf4259840b36d0c1c06cc369e1bf5fbc24c1b Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 16:10:10 +0100
Subject: [PATCH 04/27] minor formattign changes
---
XLS-0065-single-asset-vault/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index a1e1929b6..3feec9745 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -8,12 +8,12 @@
category: Amendment
requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)
created: 2024-04-12
- updated: 2025-11-17
+ updated: 2026-02-11
# Single Asset Vault
-## _Abstract_
+## Abstract
A Single Asset Vault is a new on-chain primitive for aggregating assets from one or more depositors, and making the assets available for other on-chain protocols. The Single Asset Vault uses [Multi-Purpose-Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens) to represent ownership shares of the Vault. The Vault serves diverse purposes, such as lending markets, aggregators, yield-bearing tokens, asset management, etc. The Single Asset Vault decouples the liquidity provision functionality from the specific protocol logic.
From 2922aded4938a94289123f5f79ae0972d417507f Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 16:22:39 +0100
Subject: [PATCH 05/27] moves header levels left by one
---
XLS-0065-single-asset-vault/README.md | 172 +++++++++++++-------------
1 file changed, 83 insertions(+), 89 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 3feec9745..987d2333e 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -24,24 +24,22 @@ A Single Asset Vault is a new on-chain primitive for aggregating assets from one
- [**1.2. Terminology**](#12-terminology)
- [**1.3. Actors**](#13-actors)
- [**1.4 Connecting to the Vault**](#14-connecting-to-the-vault)
-- [**2. Ledger Entries**](#2-ledger-entries)
- - [**2.1. `Vault` Ledger Entry**](#21-vault-ledger-entry)
- - [**2.1.1. Object Identifier**](#211-object-identifier)
- - [**2.1.2. Fields**](#212-fields)
- - [**2.1.3. Vault _pseudo-account_**](#213-vault-_pseudo-account_)
- - [**2.1.4. Ownership**](#214-ownership)
- - [**2.1.5. Owner Reserve**](#215-owner-reserve)
- - [**2.1.6. Vault Shares**](#216-vault-shares)
- - [**2.1.7. Exchange Algorithm**](#217-exchange-algorithm)
-- [**3. Transactions**](#3-transactions)
- - [**3.1. VaultCreate Transaction**](#31-vaultcreate-transaction)
- - [**3.2. VaultSet Transaction**](#32-vaultset-transaction)
- - [**3.3. VaultDelete Transaction**](#33-vaultdelete-transaction)
- - [**3.4. VaultDeposit Transaction**](#34-vaultdeposit-transaction)
- - [**3.5. VaultWithdraw Transaction**](#35-vaultwithdraw-transaction)
- - [**3.6. VaultClawback Transaction**](#36-vaultclawback-transaction)
- - [**3.7. Payment Transaction**](#37-payment-transaction)
-- [**4. API**](#4-api)
+- [**2. Ledger Entry: `Vault`**](#2-ledger-entry-vault)
+ - [**2.1. Object Identifier**](#21-object-identifier)
+ - [**2.2. Fields**](#22-fields)
+ - [**2.3. Vault _pseudo-account_**](#23-vault-_pseudo-account_)
+ - [**2.4. Ownership**](#24-ownership)
+ - [**2.5. Owner Reserve**](#25-owner-reserve)
+ - [**2.6. Vault Shares**](#26-vault-shares)
+ - [**2.7. Exchange Algorithm**](#27-exchange-algorithm)
+- [**3. Transaction: `VaultCreate`**](#3-transaction-vaultcreate)
+- [**4. Transaction: `VaultSet`**](#4-transaction-vaultset)
+- [**5. Transaction: `VaultDelete`**](#5-transaction-vaultdelete)
+- [**6. Transaction: `VaultDeposit`**](#6-transaction-vaultdeposit)
+- [**7. Transaction: `VaultWithdraw`**](#7-transaction-vaultwithdraw)
+- [**8. Transaction: `VaultClawback`**](#8-transaction-vaultclawback)
+- [**9. Transaction: `Payment`**](#9-transaction-payment)
+- [**10. API**](#10-api)
- [Appendix](#appendix)
## 1. Introduction
@@ -98,13 +96,11 @@ A protocol connecting to a Vault must track its debt. Furthermore, the updates t
[**Return to Index**](#index)
-## 2. Ledger Entries
-
-### 2.1 `Vault` Ledger Entry
+## 2. Ledger Entry: `Vault`
The **`Vault`** ledger entry describes the state of the tokenized vault.
-#### 2.1.1 Object Identifier
+### 2.1 Object Identifier
The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order:
@@ -112,7 +108,7 @@ The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/
- The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
- The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value.
-#### 2.1.2 Fields
+### 2.2 Fields
A vault has the following fields:
@@ -137,7 +133,7 @@ A vault has the following fields:
| `WithdrawalPolicy` | `No` | :heavy_check_mark: | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
| `Scale` | `No` | :heavy_check_mark: | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-##### 2.1.2.1 Flags
+#### 2.2.1 Flags
The `Vault` object supports the following flags:
@@ -145,48 +141,48 @@ The `Vault` object supports the following flags:
| ----------------- | :----------: | :---------: | :------------------------------------------: |
| `lsfVaultPrivate` | `0x00010000` | `No` | If set, indicates that the vault is private. |
-#### 2.1.3 Vault `_pseudo-account_`
+### 2.3 Vault `_pseudo-account_`
An AccountRoot entry holds the XRP, IOU or MPT deposited into the vault. It also acts as the issuer of the vault's shares. The _pseudo-account_ follows the XLS-64 specification for pseudo accounts. The `AccountRoot` object is created when creating the `Vault` object.
-#### 2.1.4 Ownership
+### 2.4 Ownership
The `Vault` objects are stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `VaultSet` transaction. Furthermore, to facilitate `Vault` object lookup from the vault shares, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_.
-#### 2.1.5 Owner Reserve
+### 2.5 Owner Reserve
The `Vault` object costs one reserve fee per object created:
- The `Vault` object itself.
- The `MPTokenIssuance` associated with the shares of the Vault.
-#### 2.1.6 Vault Shares
+### 2.6 Vault Shares
Shares represent the portion of the Vault assets a depositor owns. Vault Owners set the currency code of the share and whether the token is transferable during the vault's creation. These two values are immutable. The share is represented by a [Multi-Purpose Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens). The MPT is issued by the vault's pseudo-account.
-##### 2.1.6.1 `Scale`
+#### 2.6.1 `Scale`
The **`Scale`** field enables the vault to accurately represent fractional asset values using integer-only MPT shares, which prevents the loss of value from decimal truncation. It defines a scaling factor, calculated as $10^{\text{Scale}}$, that converts a decimal asset amount into a corresponding whole number of shares. For example, with a `Scale` of `6`, a deposit of **20.3** assets is multiplied by $10^6$ and credited as **20,300,000** shares.
As a general rule, all calculations involving MPTs are executed with a precision of a single MPT, treating them as indivisible units. If a calculation results in a fractional amount, it will be rounded up, down or to the nearest whole number depending on the context. Crucially, the rounding direction is determined by the protocol and is not controlled by the transaction submitter, which may lead to unexpected results.
-##### 2.1.6.1.1 `IOU`
+##### 2.6.1.1 `IOU`
When a vault holds an **`IOU`**, the `Scale` is configurable by the Vault Owner at the time of the vault's creation. The value can range from **0** to a maximum of **18**, with a default of **6**. This flexibility allows issuers to set a level of precision appropriate for their specific token.
-##### 2.1.6.1.2 `XRP`
+##### 2.6.1.2 `XRP`
When a vault holds **`XRP`**, the `Scale` is fixed at **0**. This aligns with XRP's native structure, where one share represents one drop (the smallest unit of XRP), and one XRP equals 1,000,000 drops. Therefore, a deposit of 10 XRP to an empty Vault will result in the issuance of 10,000,000 shares ($10 \times 10^6$).
-##### 2.1.6.1.3 `MPT`
+##### 2.6.1.3 `MPT`
When a vault holds `MPT`, its `Scale` is fixed at **0**. This creates a 1-to-1 relationship between deposited MPT units and the shares issued (for example, depositing 10 MPTs to an empty Vault issues 10 shares). The value of a single MPT is determined at the issuer's discretion. If an MPT is set to represent a large value, the vault owner and the depositor must be cautious. Since only whole MPT units are used in calculations, any value that is not a multiple of a single MPT's value may be lost due to rounding during a transaction.
-##### 2.1.6.2 `MPTokenIssuance`
+#### 2.6.2 `MPTokenIssuance`
The `MPTokenIssuance` object represents the share on the ledger. It is created and deleted together with the `Vault` object.
-###### 2.1.6.2.1 `MPTokenIssuance` Values
+##### 2.6.2.1 `MPTokenIssuance` Values
Here’s the table with the headings "Field," "Description," and "Value":
@@ -207,11 +203,11 @@ The following flags are set based on whether the shares are transferable and if
| **Public Vault** | `lsfMPTCanEscrow`
`lsfMPTCanTrade`
`lsfMPTCanTransfer` | No Flags |
| **Private Vault** | `lsfMPTCanEscrow`
`lsfMPTCanTrade`
`lsfMPTCanTransfer`
`lsfMPTRequireAuth` | `lsfMPTRequireAuth` |
-##### 2.1.6.3 `MPToken`
+#### 2.6.3 `MPToken`
The `MPToken` object represents the amount of shares held by a depositor. It is created when the account deposits liquidity into the vault and is deleted when a depositor redeems (or transfers) all shares.
-###### 2.1.6.3.1 `MPToken` Values
+##### 2.6.3.1 `MPToken` Values
The `MPToken` values should be set as per the `MPT` [specification](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens#2112-fields).
@@ -220,11 +216,11 @@ The `MPToken` values should be set as per the `MPT` [specification](https://gith
| **Public Vault** | No Flags | `lsfMPTAuthorized` |
| **Private Vault** | `lsfMPTAuthorized` | `lsfMPTAuthorized` |
-#### 2.1.7 Exchange Algorithm
+### 2.7 Exchange Algorithm
Exchange Algorithm refers to the logic that is used to exchange assets into shares and shares into assets. This logic is executed when depositing or redeeming liquidity. A Vault comes with the default exchange algorithm, which is detailed below.
-##### 2.1.7.1 Unrealized Loss
+#### 2.7.1 Unrealized Loss
A well-informed depositor may learn of an incoming loss and redeem their shares early, causing the remaining depositors to bear the full loss. To discourage such behaviour, we introduce a concept of "paper loss," captured by the `Vault` object's `LossUnrealized` attribute. The "paper loss" captures a potential loss the vault may experience and thus temporarily decreases the vault value. Only a protocol connected to the `Vault` may increase or decrease the `LossUnrealized` attribute.
@@ -254,7 +250,7 @@ A depositor could deposit $100k assets at a 0.1 exchange rate and get 1.0m share
To account for this problem, the Vault must use two different exchange rate models: one for depositing assets and one for withdrawing them.
-### 2.1.7.2 Exchange Rate Algorithms
+#### 2.7.2 Exchange Rate Algorithms
This section details the algorithms used to calculate the exchange between assets and shares for deposits, redemptions, and withdrawals.
@@ -267,7 +263,7 @@ This section details the algorithms used to calculate the exchange between asset
- **$\iota$**: The vault's total **unrealized loss**.
- **$\sigma$**: The scaling factor derived from `Scale`, used to convert fractional assets into integer shares.
-### 2.1.7.2.1 Deposit
+##### 2.7.2.1 Deposit
The deposit function calculates the number of shares a user receives for their assets.
@@ -292,7 +288,7 @@ The vault's totals are updated with the final calculated amounts.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} + \Delta_{assets'}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} + \Delta_{shares}$
-### 2.1.7.2.2 Redeem
+##### 2.7.2.2 Redeem
The redeem function calculates the asset payout for a user burning a specific number of shares.
@@ -309,7 +305,7 @@ The vault's totals are reduced after the redemption.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} - \Delta_{assets}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} - \Delta_{shares}$
-### 2.1.7.2.3 Withdraw
+##### 2.7.2.3 Withdraw
The withdraw function handles a request for a specific amount of assets, which involves a two-step process to determine the final payout.
@@ -332,33 +328,31 @@ The vault's totals are reduced by the final calculated amounts.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} - \Delta_{assets\_out}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} - \Delta_{shares}$
-##### 2.1.7.3 Withdrawal Policy
+#### 2.7.3 Withdrawal Policy
Withdrawal policy controls the logic used when removing liquidity from a vault. Each strategy has its own implementation, but it can be used in multiple vaults once implemented. Therefore, different vaults may have different withdrawal policies. The specification introduces the following options:
-###### 2.1.7.3.1 `first-come-first-serve`
+##### 2.7.3.1 `first-come-first-serve`
The First Come, First Serve strategy treats all requests equally, allowing a depositor to redeem any amount of assets provided they have a sufficient number of shares.
-#### 2.1.8 Frozen Assets
+### 2.8 Frozen Assets
The issuer of the Vaults asset may enact a freeze either through a [Global Freeze](https://xrpl.org/docs/concepts/tokens/fungible-tokens/freezes/#global-freeze) for IOUs or [locking MPT](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens#21122-flags). When the vaults asset is frozen, it can only be withdrawn by specifying the `Destination` account as the `Issuer` of the asset. Similarly, a frozen asset _may not_ be deposited into a vault. Furthermore, when the asset of a vault is frozen, the shares corresponding to the asset may not be transferred.
-#### 2.1.9 Transfer Fees
+### 2.9 Transfer Fees
The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/tokens/transfer-fees) to `VaultDeposit` and `VaultWithdraw` transactions. Furthermore, whenever a protocol moves assets from or to a Vault, the `Transfer Fee` must not be charged.
[**Return to Index**](#index)
-## 3. Transactions
+## 3. Transaction: `VaultCreate`
All transactions introduced by this proposal incorporate the [common transaction fields](https://xrpl.org/docs/references/protocol/transactions/common-fields) that are shared by all transactions. Standard fields are only documented in this proposal if needed because this proposal introduces new possible values for such fields.
-### 3.1 `VaultCreate` Transaction
-
The `VaultCreate` transaction creates a new `Vault` object.
-#### 3.1.1 Fields
+### 3.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ------------------ | :----------------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -372,14 +366,14 @@ The `VaultCreate` transaction creates a new `Vault` object.
| `DomainID` | | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
| `Scale` | | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-#### 3.1.2 Flags
+### 3.2 Flags
| Flag Name | Flag Value | Description |
| ----------------------------- | :----------: | :--------------------------------------------------------------------------------------- |
| `tfVaultPrivate` | `0x00010000` | Indicates that the vault is private. It can only be set during Vault creation. |
| `tfVaultShareNonTransferable` | `0x00020000` | Indicates the vault share is non-transferable. It can only be set during Vault creation. |
-##### 3.1.3 WithdrawalPolicy
+#### 3.3 WithdrawalPolicy
The type indicates the withdrawal strategy supported by the vault. The following values are supported:
@@ -387,11 +381,11 @@ The type indicates the withdrawal strategy supported by the vault. The following
| ---------------------------------- | :------: | :-------------------------------------------------------: |
| `vaultStrategyFirstComeFirstServe` | `0x0001` | Requests are processed on a first-come-first-serve basis. |
-#### 3.1.4 Transaction Fees
+### 3.4 Transaction Fees
The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Therefore, the transaction [must destroy](../XLS-0064-pseudo-account/README.md) one incremental owner reserve amount.
-#### 3.1.5 Failure Conditions
+### 3.5 Failure Conditions
1. The `Asset` is `XRP`:
1. The `Scale` parameter is provided.
@@ -411,7 +405,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
6. The `Data` field is larger than 256 bytes.
7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
-#### 3.1.6 State Changes
+### 3.6 State Changes
1. Create a new `Vault` ledger object.
2. Create a new `MPTokenIssuance` ledger object for the vault shares.
@@ -425,17 +419,17 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
5. If `Vault.Asset` is an `MPT`:
1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
-#### 3.1.7 Invariants
+### 3.7 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.2 `VaultSet` Transaction
+## 4. Transaction: `VaultSet`
The `VaultSet` updates an existing `Vault` ledger object.
-#### 3.2.1 Fields
+### 4.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
@@ -445,7 +439,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
| `AssetsMaximum` | | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
| `DomainID` | | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-#### 3.2.2 Failure Conditions
+### 4.2 Failure Conditions
1. `Vault` object with the specified `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
@@ -457,37 +451,37 @@ The `VaultSet` updates an existing `Vault` ledger object.
7. The transaction is attempting to modify an immutable field.
8. The transaction does not specify any of the modifiable fields.
-#### 3.2.3 State Changes
+### 4.3 State Changes
1. Update mutable fields in the `Vault` ledger object.
2. If `DomainID` is provided:
1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
-#### 3.2.4 Invariants
+### 4.4 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.3 `VaultDelete` Transaction
+## 5. Transaction: `VaultDelete`
The `VaultDelete` transaction deletes an existing vault object.
-#### 3.3.1 Fields
+### 5.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `60` | Transaction type. |
| `VaultID` | :heavy_check_mark: | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
-#### 3.3.2 Failure Conditions
+### 5.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
-#### 3.3.3 State Changes
+### 5.3 State Changes
1. Delete the `MPTokenIssuance` object for the vault shares.
2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
@@ -495,17 +489,17 @@ The `VaultDelete` transaction deletes an existing vault object.
4. Release the Owner Reserve to the `Vault.Owner` account.
5. Delete the `Vault` object.
-#### 3.3.4 Invariants
+### 5.4 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.4 `VaultDeposit` transaction
+## 6. Transaction: `VaultDeposit`
The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
-#### 3.4.1 Fields
+### 6.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
@@ -513,7 +507,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
| `Amount` | :heavy_check_mark: | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
-#### 3.4.2 Failure conditions
+### 6.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
@@ -532,7 +526,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
-#### 3.4.3 State Changes
+### 6.3 State Changes
1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
@@ -552,17 +546,17 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
-#### 3.4.4 Invariants
+### 6.4 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.5 `VaultWithdraw` transaction
+## 7. Transaction: `VaultWithdraw`
The `VaultWithdraw` transaction withdraws assets in exchange for the vault's shares.
-#### 3.5.1 Fields
+### 7.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
@@ -583,7 +577,7 @@ In sections below assume the following variables:
- $\Delta_{asset}$ - the change in the total amount of assets after a deposit, withdrawal, or redemption.
- $\Delta_{share}$ - che change in the total amount of shares after a deposit, withdrawal, or redemption.
-#### 3.5.2 Failure conditions
+### 7.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -613,7 +607,7 @@ In sections below assume the following variables:
1. The account does not have permission to receive the asset.
2. The account does not have a `RippleState` or `MPToken` object for the asset.
-#### 3.5.3 State Changes
+### 7.3 State Changes
1. If the `Vault.Asset` is XRP:
1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
@@ -638,17 +632,17 @@ In sections below assume the following variables:
6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
-#### 3.5.4 Invariants
+### 7.4 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.6 VaultClawback Transaction
+## 8. Transaction: `VaultClawback`
The `VaultClawback` transaction performs a Clawback from the Vault, exchanging the shares of an account. Conceptually, the transaction performs `VaultWithdraw` on behalf of the `Holder`, sending the funds to the `Issuer` account of the asset. In case there are insufficient funds for the entire `Amount` the transaction will perform a partial Clawback, up to the `Vault.AssetsAvailable`. The Clawback transaction must respect any future fees or penalties.
-#### 3.6.1 Fields
+### 8.1 Fields
| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
@@ -657,7 +651,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| `Holder` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
-#### 3.6.2 Failure conditions
+### 8.2 Failure conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -675,7 +669,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
-#### 3.6.3 State Changes
+### 8.3 State Changes
1. If the `Vault.Asset` is an `IOU`:
1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
@@ -692,17 +686,17 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
-#### 3.6.4 Invariants
+### 8.4 Invariants
**TBD**
[**Return to Index**](#index)
-### 3.7 Payment Transaction
+## 9. Transaction: `Payment`
The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
-#### 3.7.1 Failure Conditions
+### 9.1 Failure Conditions
1. If `Payment.Amount` is a `Vault` share AND:
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
@@ -721,19 +715,19 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
-#### 3.7.2 State Changes
+### 9.2 State Changes
1. If `MPToken`object for shares does not exist for the destination account, create one.
[**Return to Index**](#index)
-## 4. API
+## 10. API
-### 4.1 RPC `vault_info`
+### 10.1 RPC `vault_info`
This RPC retrieves the Vault ledger entry and the IDs associated with it.
-#### 4.1.1 Request Fields
+#### 10.1.1 Request Fields
We propose adding the following fields to the `ledger_entry` method:
@@ -741,7 +735,7 @@ We propose adding the following fields to the `ledger_entry` method:
| ---------- | :----------------: | :-------: | :----------------------------------------: |
| `vault` | :heavy_check_mark: | `string` | The object ID of the Vault to be returned. |
-#### 4.1.2 Response
+#### 10.1.2 Response
| Field Name | Required? | JSON Type | Description |
| -------------------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -776,7 +770,7 @@ We propose adding the following fields to the `ledger_entry` method:
| `vault.shares.mpt_issuance_id` | `no` | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
| `vault.Scale` | `yes` | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-#### 4.1.2.1 Example
+##### 10.1.2.1 Example
Vault holding an `IOU`:
From da9a789bfce3eb1a941c500b7d87e899a7a32747 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 11 Feb 2026 17:43:31 +0100
Subject: [PATCH 06/27] renames column names and emojis to strings
---
XLS-0065-single-asset-vault/README.md | 128 +++++++++++++-------------
1 file changed, 64 insertions(+), 64 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 987d2333e..156fcc584 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -112,26 +112,26 @@ The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/
A vault has the following fields:
-| Field Name | Modifiable? | Required? | JSON Type | Internal Type | Default Value | Description |
-| ------------------- | :---------: | :----------------: | :----------------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `LedgerEntryType` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `0x0084` | Ledger object type. |
-| `LedgerIndex` | `N/A` | :heavy_check_mark: | `string` | `UINT16` | `N/A` | Ledger object identifier. |
-| `Flags` | `Yes` | :heavy_check_mark: | `string` | `UINT32` | 0 | Ledger object flags. |
-| `PreviousTxnID` | `N/A` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | Identifies the transaction ID that most recently modified this object. |
-| `PreviousTxnLgrSeq` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
-| `Sequence` | `N/A` | :heavy_check_mark: | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
-| `OwnerNode` | `N/A` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
-| `Owner` | `No` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
-| `Account` | `N/A` | :heavy_check_mark: | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
-| `Data` | `Yes` | | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
-| `Asset` | `No` | :heavy_check_mark: | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
-| `AssetsTotal` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The total value of the vault. |
-| `AssetsAvailable` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The asset amount that is available in the vault. |
-| `LossUnrealized` | `N/A` | :heavy_check_mark: | `number` | `NUMBER` | 0 | The potential loss amount that is not yet realized expressed as the vaults asset. |
-| `AssetsMaximum` | `Yes` | | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in the vault. Zero value `0` indicates there is no cap. |
-| `ShareMPTID` | `N/A` | :heavy_check_mark: | `number` | `UINT192` | 0 | The identifier of the share MPTokenIssuance object. |
-| `WithdrawalPolicy` | `No` | :heavy_check_mark: | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
-| `Scale` | `No` | :heavy_check_mark: | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| Field Name | Constant | Required | JSON Type | Internal Type | Default Value | Description |
+| ------------------- | :------: | :------: | :----------------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `LedgerEntryType` | `No` | Yes | `string` | `UINT16` | `0x0084` | Ledger object type. |
+| `LedgerIndex` | `No` | Yes | `string` | `UINT16` | `N/A` | Ledger object identifier. |
+| `Flags` | `Yes` | Yes | `string` | `UINT32` | 0 | Ledger object flags. |
+| `PreviousTxnID` | `No` | Yes | `string` | `HASH256` | `N/A` | Identifies the transaction ID that most recently modified this object. |
+| `PreviousTxnLgrSeq` | `No` | Yes | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
+| `Sequence` | `No` | Yes | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
+| `OwnerNode` | `No` | Yes | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
+| `Owner` | `No` | Yes | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
+| `Account` | `No` | Yes | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
+| `Data` | `Yes` | No | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
+| `Asset` | `No` | Yes | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
+| `AssetsTotal` | `No` | Yes | `number` | `NUMBER` | 0 | The total value of the vault. |
+| `AssetsAvailable` | `No` | Yes | `number` | `NUMBER` | 0 | The asset amount that is available in the vault. |
+| `LossUnrealized` | `No` | Yes | `number` | `NUMBER` | 0 | The potential loss amount that is not yet realized expressed as the vaults asset. |
+| `AssetsMaximum` | `Yes` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in the vault. Zero value `0` indicates there is no cap. |
+| `ShareMPTID` | `No` | Yes | `number` | `UINT192` | 0 | The identifier of the share MPTokenIssuance object. |
+| `WithdrawalPolicy` | `No` | Yes | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
+| `Scale` | `No` | Yes | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
#### 2.2.1 Flags
@@ -354,17 +354,17 @@ The `VaultCreate` transaction creates a new `Vault` object.
### 3.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ------------------ | :----------------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `58` | The transaction type. |
-| `Flags` | :heavy_check_mark: | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
-| `Data` | | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `Asset` | :heavy_check_mark: | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
-| `AssetsMaximum` | | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
-| `MPTokenMetadata` | | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
-| `WithdrawalPolicy` | | `number` | `UINT8` | `strFirstComeFirstServe` | Indicates the withdrawal strategy used by the Vault. |
-| `DomainID` | | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-| `Scale` | | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ------------------ | :-------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `58` | The transaction type. |
+| `Flags` | Yes | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
+| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
+| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
+| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
+| `WithdrawalPolicy` | No | `number` | `UINT8` | `strFirstComeFirstServe` | Indicates the withdrawal strategy used by the Vault. |
+| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
### 3.2 Flags
@@ -431,13 +431,13 @@ The `VaultSet` updates an existing `Vault` ledger object.
### 4.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `59` | The transaction type. |
-| `VaultID` | :heavy_check_mark: | `string` | `Hash256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
-| `Data` | | `string` | `Blob` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `AssetsMaximum` | | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
-| `DomainID` | | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `Uint16` | `59` | The transaction type. |
+| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
+| `Data` | No | `string` | `Blob` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `AssetsMaximum` | No | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
+| `DomainID` | No | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
### 4.2 Failure Conditions
@@ -469,10 +469,10 @@ The `VaultDelete` transaction deletes an existing vault object.
### 5.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
-| `TransactionType` | :heavy_check_mark: | `string` | `Uint16` | `60` | Transaction type. |
-| `VaultID` | :heavy_check_mark: | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
+| `TransactionType` | Yes | `string` | `Uint16` | `60` | Transaction type. |
+| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
### 5.2 Failure Conditions
@@ -501,11 +501,11 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
### 6.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :----------------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
-| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `61` | Transaction type. |
-| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
-| `Amount` | :heavy_check_mark: | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ----------------- | :-------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `61` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
+| `Amount` | Yes | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
### 6.2 Failure conditions
@@ -558,13 +558,13 @@ The `VaultWithdraw` transaction withdraws assets in exchange for the vault's sha
### 7.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
-| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `62` | Transaction type. |
-| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Amount` | :heavy_check_mark: | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
-| `Destination` | | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
-| `DestinationTag` | | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `62` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
+| `Amount` | Yes | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
+| `Destination` | No | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
+| `DestinationTag` | No | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
- If `Amount` is the Vaults asset, calculate the share cost using the [**Withdraw formula**](#21723-withdraw).
- If `Amount` is the Vaults share, calculate the assets amount using the [**Redeem formula**](#21722-redeem).
@@ -644,12 +644,12 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
### 8.1 Fields
-| Field Name | Required? | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `63` | Transaction type. |
-| `VaultID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Holder` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
-| `Amount` | | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `63` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
+| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
+| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
### 8.2 Failure conditions
@@ -731,13 +731,13 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
We propose adding the following fields to the `ledger_entry` method:
-| Field Name | Required? | JSON Type | Description |
-| ---------- | :----------------: | :-------: | :----------------------------------------: |
-| `vault` | :heavy_check_mark: | `string` | The object ID of the Vault to be returned. |
+| Field Name | Required | JSON Type | Description |
+| ---------- | :-------: | :-------: | :----------------------------------------: |
+| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
#### 10.1.2 Response
-| Field Name | Required? | JSON Type | Description |
+| Field Name | Required | JSON Type | Description |
| -------------------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `vault` | `yes` | `object` | Root object representing the vault. |
| `vault.Account` | `yes` | `string` | The pseudo-account ID of the vault. |
From 866caa4c7705bac96c26d0c12f8a57388248be89 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Thu, 12 Feb 2026 10:30:34 +0100
Subject: [PATCH 07/27] formatting
---
XLS-0065-single-asset-vault/README.md | 134 +++++++++++++-------------
1 file changed, 67 insertions(+), 67 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 156fcc584..8496c717f 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -355,16 +355,16 @@ The `VaultCreate` transaction creates a new `Vault` object.
### 3.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ------------------ | :-------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `UINT16` | `58` | The transaction type. |
-| `Flags` | Yes | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
-| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
-| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
-| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
-| `WithdrawalPolicy` | No | `number` | `UINT8` | `strFirstComeFirstServe` | Indicates the withdrawal strategy used by the Vault. |
-| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| ------------------ | :------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `58` | The transaction type. |
+| `Flags` | Yes | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
+| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
+| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
+| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
+| `WithdrawalPolicy` | No | `number` | `UINT8` | `strFirstComeFirstServe` | Indicates the withdrawal strategy used by the Vault. |
+| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
### 3.2 Flags
@@ -432,12 +432,12 @@ The `VaultSet` updates an existing `Vault` ledger object.
### 4.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `Uint16` | `59` | The transaction type. |
-| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
-| `Data` | No | `string` | `Blob` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `AssetsMaximum` | No | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
-| `DomainID` | No | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| ----------------- | :------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `Uint16` | `59` | The transaction type. |
+| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
+| `Data` | No | `string` | `Blob` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `AssetsMaximum` | No | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
+| `DomainID` | No | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
### 4.2 Failure Conditions
@@ -470,9 +470,9 @@ The `VaultDelete` transaction deletes an existing vault object.
### 5.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
-| `TransactionType` | Yes | `string` | `Uint16` | `60` | Transaction type. |
-| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
+| ----------------- | :------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
+| `TransactionType` | Yes | `string` | `Uint16` | `60` | Transaction type. |
+| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
### 5.2 Failure Conditions
@@ -502,10 +502,10 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
### 6.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :-------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `UINT16` | `61` | Transaction type. |
-| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
-| `Amount` | Yes | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
+| ----------------- | :------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `61` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
+| `Amount` | Yes | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
### 6.2 Failure conditions
@@ -559,12 +559,12 @@ The `VaultWithdraw` transaction withdraws assets in exchange for the vault's sha
### 7.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `UINT16` | `62` | Transaction type. |
-| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Amount` | Yes | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
-| `Destination` | No | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
-| `DestinationTag` | No | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
+| ----------------- | :------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `62` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
+| `Amount` | Yes | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
+| `Destination` | No | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
+| `DestinationTag` | No | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
- If `Amount` is the Vaults asset, calculate the share cost using the [**Withdraw formula**](#21723-withdraw).
- If `Amount` is the Vaults share, calculate the assets amount using the [**Redeem formula**](#21722-redeem).
@@ -645,11 +645,11 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
### 8.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ----------------- | :-------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `UINT16` | `63` | Transaction type. |
-| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
-| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
+| ----------------- | :------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `63` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
+| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
+| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
### 8.2 Failure conditions
@@ -732,43 +732,43 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
We propose adding the following fields to the `ledger_entry` method:
| Field Name | Required | JSON Type | Description |
-| ---------- | :-------: | :-------: | :----------------------------------------: |
-| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
+| ---------- | :------: | :-------: | :----------------------------------------: |
+| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
#### 10.1.2 Response
| Field Name | Required | JSON Type | Description |
-| -------------------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `vault` | `yes` | `object` | Root object representing the vault. |
-| `vault.Account` | `yes` | `string` | The pseudo-account ID of the vault. |
-| `vault.Asset` | `yes` | `object` | Object representing the asset held in the vault. |
-| `vault.Asset.currency` | `yes` | `string` | Currency code of the asset stored in the vault. |
-| `vault.Asset.issuer` | `no` | `string` | Issuer address of the asset. |
-| `vault.AssetsAvailable` | `yes` | `string` | Amount of assets currently available for withdrawal. |
-| `vault.AssetsTotal` | `yes` | `string` | Total amount of assets in the vault. |
-| `vault.Flags` | `no` | `number` | Bit-field flags associated with the vault. |
-| `vault.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "Vault". |
-| `vault.LossUnrealized` | `no` | `string` | Unrealized loss associated with the vault. |
-| `vault.Owner` | `yes` | `string` | ID of the Vault Owner account. |
-| `vault.OwnerNode` | `no` | `string` | Identifier for the owner node in the ledger tree. |
-| `vault.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to this vault. |
-| `vault.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying this vault. |
-| `vault.Sequence` | `yes` | `number` | Sequence number of the vault entry. |
-| `vault.ShareMPTID` | `no` | `string` | Multi-purpose token ID associated with this vault. |
-| `vault.WithdrawalPolicy` | `no` | `number` | Policy defining withdrawal conditions. |
-| `vault.index` | `yes` | `string` | Unique index of the vault ledger entry. |
-| `vault.shares` | `yes` | `object` | Object containing details about issued shares. |
-| `vault.shares.Flags` | `no` | `number` | Bit-field flags associated with the shares issuance. |
-| `vault.shares.Issuer` | `yes` | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
-| `vault.shares.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "MPTokenIssuance". |
-| `vault.shares.OutstandingAmount` | `yes` | `string` | Total outstanding shares issued. |
-| `vault.shares.OwnerNode` | `no` | `string` | Identifier for the owner node of the shares. |
-| `vault.shares.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to the shares issuance. |
-| `vault.shares.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
-| `vault.shares.Sequence` | `yes` | `number` | Sequence number of the shares issuance entry. |
-| `vault.shares.index` | `yes` | `string` | Unique index of the shares ledger entry. |
-| `vault.shares.mpt_issuance_id` | `no` | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
-| `vault.Scale` | `yes` | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| -------------------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `vault` | `yes` | `object` | Root object representing the vault. |
+| `vault.Account` | `yes` | `string` | The pseudo-account ID of the vault. |
+| `vault.Asset` | `yes` | `object` | Object representing the asset held in the vault. |
+| `vault.Asset.currency` | `yes` | `string` | Currency code of the asset stored in the vault. |
+| `vault.Asset.issuer` | `no` | `string` | Issuer address of the asset. |
+| `vault.AssetsAvailable` | `yes` | `string` | Amount of assets currently available for withdrawal. |
+| `vault.AssetsTotal` | `yes` | `string` | Total amount of assets in the vault. |
+| `vault.Flags` | `no` | `number` | Bit-field flags associated with the vault. |
+| `vault.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "Vault". |
+| `vault.LossUnrealized` | `no` | `string` | Unrealized loss associated with the vault. |
+| `vault.Owner` | `yes` | `string` | ID of the Vault Owner account. |
+| `vault.OwnerNode` | `no` | `string` | Identifier for the owner node in the ledger tree. |
+| `vault.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to this vault. |
+| `vault.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying this vault. |
+| `vault.Sequence` | `yes` | `number` | Sequence number of the vault entry. |
+| `vault.ShareMPTID` | `no` | `string` | Multi-purpose token ID associated with this vault. |
+| `vault.WithdrawalPolicy` | `no` | `number` | Policy defining withdrawal conditions. |
+| `vault.index` | `yes` | `string` | Unique index of the vault ledger entry. |
+| `vault.shares` | `yes` | `object` | Object containing details about issued shares. |
+| `vault.shares.Flags` | `no` | `number` | Bit-field flags associated with the shares issuance. |
+| `vault.shares.Issuer` | `yes` | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
+| `vault.shares.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "MPTokenIssuance". |
+| `vault.shares.OutstandingAmount` | `yes` | `string` | Total outstanding shares issued. |
+| `vault.shares.OwnerNode` | `no` | `string` | Identifier for the owner node of the shares. |
+| `vault.shares.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to the shares issuance. |
+| `vault.shares.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
+| `vault.shares.Sequence` | `yes` | `number` | Sequence number of the shares issuance entry. |
+| `vault.shares.index` | `yes` | `string` | Unique index of the shares ledger entry. |
+| `vault.shares.mpt_issuance_id` | `no` | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
+| `vault.Scale` | `yes` | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
##### 10.1.2.1 Example
From 6b30e8527400a904beb027dec853fce51bc9497c Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 24 Feb 2026 10:27:52 +0100
Subject: [PATCH 08/27] addressees review comments
---
XLS-0065-single-asset-vault/README.md | 104 +++++++++++++-------------
1 file changed, 52 insertions(+), 52 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 8496c717f..d28bdb548 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -114,24 +114,24 @@ A vault has the following fields:
| Field Name | Constant | Required | JSON Type | Internal Type | Default Value | Description |
| ------------------- | :------: | :------: | :----------------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `LedgerEntryType` | `No` | Yes | `string` | `UINT16` | `0x0084` | Ledger object type. |
-| `LedgerIndex` | `No` | Yes | `string` | `UINT16` | `N/A` | Ledger object identifier. |
-| `Flags` | `Yes` | Yes | `string` | `UINT32` | 0 | Ledger object flags. |
-| `PreviousTxnID` | `No` | Yes | `string` | `HASH256` | `N/A` | Identifies the transaction ID that most recently modified this object. |
-| `PreviousTxnLgrSeq` | `No` | Yes | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
-| `Sequence` | `No` | Yes | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
-| `OwnerNode` | `No` | Yes | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
-| `Owner` | `No` | Yes | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
-| `Account` | `No` | Yes | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
-| `Data` | `Yes` | No | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
-| `Asset` | `No` | Yes | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
-| `AssetsTotal` | `No` | Yes | `number` | `NUMBER` | 0 | The total value of the vault. |
-| `AssetsAvailable` | `No` | Yes | `number` | `NUMBER` | 0 | The asset amount that is available in the vault. |
-| `LossUnrealized` | `No` | Yes | `number` | `NUMBER` | 0 | The potential loss amount that is not yet realized expressed as the vaults asset. |
-| `AssetsMaximum` | `Yes` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in the vault. Zero value `0` indicates there is no cap. |
-| `ShareMPTID` | `No` | Yes | `number` | `UINT192` | 0 | The identifier of the share MPTokenIssuance object. |
-| `WithdrawalPolicy` | `No` | Yes | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
-| `Scale` | `No` | Yes | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| `LedgerEntryType` | No | Yes | `string` | `UINT16` | `0x0084` | Ledger object type. |
+| `LedgerIndex` | No | Yes | `string` | `UINT16` | `N/A` | Ledger object identifier. |
+| `Flags` | Yes | Yes | `string` | `UINT32` | 0 | Ledger object flags. |
+| `PreviousTxnID` | No | Yes | `string` | `HASH256` | `N/A` | Identifies the transaction ID that most recently modified this object. |
+| `PreviousTxnLgrSeq` | No | Yes | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
+| `Sequence` | No | Yes | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
+| `OwnerNode` | No | Yes | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
+| `Owner` | No | Yes | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
+| `Account` | No | Yes | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
+| `Data` | Yes | No | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
+| `Asset` | No | Yes | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
+| `AssetsTotal` | No | Yes | `number` | `NUMBER` | 0 | The total value of the vault. |
+| `AssetsAvailable` | No | Yes | `number` | `NUMBER` | 0 | The asset amount that is available in the vault. |
+| `LossUnrealized` | No | Yes | `number` | `NUMBER` | 0 | The potential loss amount that is not yet realized expressed as the vaults asset. |
+| `AssetsMaximum` | Yes | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in the vault. Zero value `0` indicates there is no cap. |
+| `ShareMPTID` | No | Yes | `number` | `UINT192` | 0 | The identifier of the share MPTokenIssuance object. |
+| `WithdrawalPolicy` | No | Yes | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
+| `Scale` | No | Yes | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
#### 2.2.1 Flags
@@ -139,7 +139,7 @@ The `Vault` object supports the following flags:
| Flag Name | Flag Value | Modifiable? | Description |
| ----------------- | :----------: | :---------: | :------------------------------------------: |
-| `lsfVaultPrivate` | `0x00010000` | `No` | If set, indicates that the vault is private. |
+| `lsfVaultPrivate` | `0x00010000` | No | If set, indicates that the vault is private. |
### 2.3 Vault `_pseudo-account_`
@@ -507,7 +507,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
| `Amount` | Yes | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
-### 6.2 Failure conditions
+### 6.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
@@ -577,7 +577,7 @@ In sections below assume the following variables:
- $\Delta_{asset}$ - the change in the total amount of assets after a deposit, withdrawal, or redemption.
- $\Delta_{share}$ - che change in the total amount of shares after a deposit, withdrawal, or redemption.
-### 7.2 Failure conditions
+### 7.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -651,7 +651,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
-### 8.2 Failure conditions
+### 8.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -739,36 +739,36 @@ We propose adding the following fields to the `ledger_entry` method:
| Field Name | Required | JSON Type | Description |
| -------------------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `vault` | `yes` | `object` | Root object representing the vault. |
-| `vault.Account` | `yes` | `string` | The pseudo-account ID of the vault. |
-| `vault.Asset` | `yes` | `object` | Object representing the asset held in the vault. |
-| `vault.Asset.currency` | `yes` | `string` | Currency code of the asset stored in the vault. |
-| `vault.Asset.issuer` | `no` | `string` | Issuer address of the asset. |
-| `vault.AssetsAvailable` | `yes` | `string` | Amount of assets currently available for withdrawal. |
-| `vault.AssetsTotal` | `yes` | `string` | Total amount of assets in the vault. |
-| `vault.Flags` | `no` | `number` | Bit-field flags associated with the vault. |
-| `vault.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "Vault". |
-| `vault.LossUnrealized` | `no` | `string` | Unrealized loss associated with the vault. |
-| `vault.Owner` | `yes` | `string` | ID of the Vault Owner account. |
-| `vault.OwnerNode` | `no` | `string` | Identifier for the owner node in the ledger tree. |
-| `vault.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to this vault. |
-| `vault.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying this vault. |
-| `vault.Sequence` | `yes` | `number` | Sequence number of the vault entry. |
-| `vault.ShareMPTID` | `no` | `string` | Multi-purpose token ID associated with this vault. |
-| `vault.WithdrawalPolicy` | `no` | `number` | Policy defining withdrawal conditions. |
-| `vault.index` | `yes` | `string` | Unique index of the vault ledger entry. |
-| `vault.shares` | `yes` | `object` | Object containing details about issued shares. |
-| `vault.shares.Flags` | `no` | `number` | Bit-field flags associated with the shares issuance. |
-| `vault.shares.Issuer` | `yes` | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
-| `vault.shares.LedgerEntryType` | `yes` | `string` | Ledger entry type, always "MPTokenIssuance". |
-| `vault.shares.OutstandingAmount` | `yes` | `string` | Total outstanding shares issued. |
-| `vault.shares.OwnerNode` | `no` | `string` | Identifier for the owner node of the shares. |
-| `vault.shares.PreviousTxnID` | `yes` | `string` | Transaction ID of the last modification to the shares issuance. |
-| `vault.shares.PreviousTxnLgrSeq` | `yes` | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
-| `vault.shares.Sequence` | `yes` | `number` | Sequence number of the shares issuance entry. |
-| `vault.shares.index` | `yes` | `string` | Unique index of the shares ledger entry. |
-| `vault.shares.mpt_issuance_id` | `no` | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
-| `vault.Scale` | `yes` | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| `vault` | yes | `object` | Root object representing the vault. |
+| `vault.Account` | yes | `string` | The pseudo-account ID of the vault. |
+| `vault.Asset` | yes | `object` | Object representing the asset held in the vault. |
+| `vault.Asset.currency` | yes | `string` | Currency code of the asset stored in the vault. |
+| `vault.Asset.issuer` | no | `string` | Issuer address of the asset. |
+| `vault.AssetsAvailable` | yes | `string` | Amount of assets currently available for withdrawal. |
+| `vault.AssetsTotal` | yes | `string` | Total amount of assets in the vault. |
+| `vault.Flags` | no | `number` | Bit-field flags associated with the vault. |
+| `vault.LedgerEntryType` | yes | `string` | Ledger entry type, always "Vault". |
+| `vault.LossUnrealized` | no | `string` | Unrealized loss associated with the vault. |
+| `vault.Owner` | yes | `string` | ID of the Vault Owner account. |
+| `vault.OwnerNode` | no | `string` | Identifier for the owner node in the ledger tree. |
+| `vault.PreviousTxnID` | yes | `string` | Transaction ID of the last modification to this vault. |
+| `vault.PreviousTxnLgrSeq` | yes | `number` | Ledger sequence number of the last transaction modifying this vault. |
+| `vault.Sequence` | yes | `number` | Sequence number of the vault entry. |
+| `vault.ShareMPTID` | no | `string` | Multi-purpose token ID associated with this vault. |
+| `vault.WithdrawalPolicy` | no | `number` | Policy defining withdrawal conditions. |
+| `vault.index` | yes | `string` | Unique index of the vault ledger entry. |
+| `vault.shares` | yes | `object` | Object containing details about issued shares. |
+| `vault.shares.Flags` | no | `number` | Bit-field flags associated with the shares issuance. |
+| `vault.shares.Issuer` | yes | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
+| `vault.shares.LedgerEntryType` | yes | `string` | Ledger entry type, always "MPTokenIssuance". |
+| `vault.shares.OutstandingAmount` | yes | `string` | Total outstanding shares issued. |
+| `vault.shares.OwnerNode` | no | `string` | Identifier for the owner node of the shares. |
+| `vault.shares.PreviousTxnID` | yes | `string` | Transaction ID of the last modification to the shares issuance. |
+| `vault.shares.PreviousTxnLgrSeq` | yes | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
+| `vault.shares.Sequence` | yes | `number` | Sequence number of the shares issuance entry. |
+| `vault.shares.index` | yes | `string` | Unique index of the shares ledger entry. |
+| `vault.shares.mpt_issuance_id` | no | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
+| `vault.Scale` | yes | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
##### 10.1.2.1 Example
From d8381d618e8c3b214c29af885b6020d4218b796a Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:03:42 +0100
Subject: [PATCH 09/27] adds example JON
---
XLS-0065-single-asset-vault/README.md | 126 ++++++++++++++++++++++++++
1 file changed, 126 insertions(+)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index d28bdb548..bc8e0b19a 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -344,6 +344,34 @@ The issuer of the Vaults asset may enact a freeze either through a [Global Freez
The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/tokens/transfer-fees) to `VaultDeposit` and `VaultWithdraw` transactions. Furthermore, whenever a protocol moves assets from or to a Vault, the `Transfer Fee` must not be charged.
+### 2.10 Example JSON
+
+```json
+{
+ "LedgerEntryType": "Vault",
+ "LedgerIndex": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Flags": 0,
+ "PreviousTxnID": "",
+ "PreviousTxnLgrSeq": 0,
+ "Sequence": 3990518,
+ "OwnerNode": "0",
+ "Owner": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
+ "Account": "rDe7soaAox8bk2Srfi7n7Y1wzbmcn8RksQ",
+ "Data": "555344204C656E64696E67205661756C74",
+ "Asset": {
+ "currency": "USD",
+ "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb"
+ },
+ "AssetsTotal": "0",
+ "AssetsAvailable": "0",
+ "LossUnrealized": "0",
+ "AssetsMaximum": "100000",
+ "ShareMPTID": "000000018AB77A8ADC472FBB7991AA311AAEB5D2FA7A793B",
+ "WithdrawalPolicy": 1,
+ "Scale": 6
+}
+```
+
[**Return to Index**](#index)
## 3. Transaction: `VaultCreate`
@@ -423,6 +451,24 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
**TBD**
+### 3.8 Example JSON
+
+```json
+{
+ "TransactionType": "VaultCreate",
+ "Flags": 0,
+ "Data": "555344204C656E64696E67205661756C74",
+ "Asset": {
+ "currency": "USD",
+ "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb"
+ },
+ "AssetsMaximum": "100000",
+ "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
+ "Fee": "200000",
+ "Sequence": 3990518
+}
+```
+
[**Return to Index**](#index)
## 4. Transaction: `VaultSet`
@@ -461,6 +507,20 @@ The `VaultSet` updates an existing `Vault` ledger object.
**TBD**
+### 4.5 Example JSON
+
+```json
+{
+ "TransactionType": "VaultSet",
+ "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Data": "5570646174656420566F756C74204D65746164617461",
+ "AssetsMaximum": "200000",
+ "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
+ "Fee": "10",
+ "Sequence": 3990519
+}
+```
+
[**Return to Index**](#index)
## 5. Transaction: `VaultDelete`
@@ -493,6 +553,18 @@ The `VaultDelete` transaction deletes an existing vault object.
**TBD**
+### 5.5 Example JSON
+
+```json
+{
+ "TransactionType": "VaultDelete",
+ "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
+ "Fee": "10",
+ "Sequence": 3990520
+}
+```
+
[**Return to Index**](#index)
## 6. Transaction: `VaultDeposit`
@@ -550,6 +622,23 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
**TBD**
+### 6.5 Example JSON
+
+```json
+{
+ "TransactionType": "VaultDeposit",
+ "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Amount": {
+ "currency": "USD",
+ "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
+ "value": "5000"
+ },
+ "Account": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
+ "Fee": "1",
+ "Sequence": 3990518
+}
+```
+
[**Return to Index**](#index)
## 7. Transaction: `VaultWithdraw`
@@ -636,6 +725,23 @@ In sections below assume the following variables:
**TBD**
+### 7.5 Example JSON
+
+```json
+{
+ "TransactionType": "VaultWithdraw",
+ "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Amount": {
+ "currency": "USD",
+ "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
+ "value": "4083.333642504084"
+ },
+ "Account": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
+ "Fee": "1",
+ "Sequence": 3990519
+}
+```
+
[**Return to Index**](#index)
## 8. Transaction: `VaultClawback`
@@ -690,6 +796,20 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
**TBD**
+### 8.5 Example JSON
+
+```json
+{
+ "TransactionType": "VaultClawback",
+ "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
+ "Holder": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
+ "Amount": "4083333642504084",
+ "Account": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
+ "Fee": "1",
+ "Sequence": 3990520
+}
+```
+
[**Return to Index**](#index)
## 9. Transaction: `Payment`
@@ -719,6 +839,12 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
1. If `MPToken`object for shares does not exist for the destination account, create one.
+### 9.3 Example JSON
+
+```json
+{}
+```
+
[**Return to Index**](#index)
## 10. API
From d47b3a854a0665febf2347b4b08e7c5bbae875c2 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:20:23 +0100
Subject: [PATCH 10/27] Update vault spec: add example JSONs, invariants,
restructure API section
- Add Example JSON sections for Vault ledger entry and all transactions
(VaultCreate, VaultSet, VaultDelete, VaultDeposit, VaultWithdraw,
VaultClawback, Payment) with real transaction data
- Add invariants for the Vault ledger entry (universal checks) and all
transaction types derived from the ValidVault invariant checker
- Restructure section 10 from "API" to "RPC: vault_info" matching the
amendment template format with Request Fields, Response Fields,
Failure Conditions, Example Request, and Example Response subsections
- Update response fields table with missing fields (Data, Asset.mpt_issuance_id,
shares.DomainID, shares.MPTokenMetadata) and correct Always Present values
- Update response examples to use proper JSON format with response envelope
- Add section 9.1 Fields for Payment transaction
- Remove Index section and all Return to Index links
Co-Authored-By: Claude Opus 4.6 (1M context)
---
XLS-0065-single-asset-vault/README.md | 455 ++++++++++++++------------
1 file changed, 248 insertions(+), 207 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index bc8e0b19a..19fc7568a 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -17,31 +17,6 @@
A Single Asset Vault is a new on-chain primitive for aggregating assets from one or more depositors, and making the assets available for other on-chain protocols. The Single Asset Vault uses [Multi-Purpose-Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens) to represent ownership shares of the Vault. The Vault serves diverse purposes, such as lending markets, aggregators, yield-bearing tokens, asset management, etc. The Single Asset Vault decouples the liquidity provision functionality from the specific protocol logic.
-## Index
-
-- [**1. Introduction**](#1-introduction)
- - [**1.1. Feature Overview**](#11-overview)
- - [**1.2. Terminology**](#12-terminology)
- - [**1.3. Actors**](#13-actors)
- - [**1.4 Connecting to the Vault**](#14-connecting-to-the-vault)
-- [**2. Ledger Entry: `Vault`**](#2-ledger-entry-vault)
- - [**2.1. Object Identifier**](#21-object-identifier)
- - [**2.2. Fields**](#22-fields)
- - [**2.3. Vault _pseudo-account_**](#23-vault-_pseudo-account_)
- - [**2.4. Ownership**](#24-ownership)
- - [**2.5. Owner Reserve**](#25-owner-reserve)
- - [**2.6. Vault Shares**](#26-vault-shares)
- - [**2.7. Exchange Algorithm**](#27-exchange-algorithm)
-- [**3. Transaction: `VaultCreate`**](#3-transaction-vaultcreate)
-- [**4. Transaction: `VaultSet`**](#4-transaction-vaultset)
-- [**5. Transaction: `VaultDelete`**](#5-transaction-vaultdelete)
-- [**6. Transaction: `VaultDeposit`**](#6-transaction-vaultdeposit)
-- [**7. Transaction: `VaultWithdraw`**](#7-transaction-vaultwithdraw)
-- [**8. Transaction: `VaultClawback`**](#8-transaction-vaultclawback)
-- [**9. Transaction: `Payment`**](#9-transaction-payment)
-- [**10. API**](#10-api)
-- [Appendix](#appendix)
-
## 1. Introduction
### 1.1 Overview
@@ -94,7 +69,6 @@ Shares represent the ownership of a portion of the vault's assets. On-chain shar
A protocol connecting to a Vault must track its debt. Furthermore, the updates to the Vault state when funds are removed or added back must be handled in the transactors of the protocol. For an example, please refer to the [Lending Protocol](https://github.com/XRPLF/XRPL-Standards/discussions/190) specification.
-[**Return to Index**](#index)
## 2. Ledger Entry: `Vault`
@@ -344,7 +318,25 @@ The issuer of the Vaults asset may enact a freeze either through a [Global Freez
The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/tokens/transfer-fees) to `VaultDeposit` and `VaultWithdraw` transactions. Furthermore, whenever a protocol moves assets from or to a Vault, the `Transfer Fee` must not be charged.
-### 2.10 Example JSON
+### 2.10 Invariants
+
+- A transaction must not modify more than one `Vault` object.
+- `.Asset == '.Asset` (the asset is immutable).
+- `.Account == '.Account` (the _pseudo-account_ is immutable).
+- `.ShareMPTID == '.ShareMPTID` (the share MPT ID is immutable).
+- An updated `Vault` must always have an associated `MPTokenIssuance` (shares) object.
+- `IF MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0 THEN '.AssetsTotal == 0 AND '.AssetsAvailable == 0`.
+- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount <= MPTokenIssuance(Vault.ShareMPTID).MaximumAmount`.
+- `'.AssetsAvailable >= 0`.
+- `'.AssetsAvailable <= '.AssetsTotal`.
+- `'.LossUnrealized <= '.AssetsTotal - '.AssetsAvailable`.
+- `'.AssetsTotal >= 0`.
+- `'.AssetsMaximum >= 0`.
+- Only `VaultCreate` may create a new `Vault` object.
+- Only `VaultDelete` may delete a `Vault` object.
+- `.LossUnrealized == '.LossUnrealized` for all vault transactions (only protocol transactions such as `LoanManage` and `LoanPay` may change `LossUnrealized`).
+
+### 2.11 Example JSON
```json
{
@@ -372,7 +364,6 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
}
```
-[**Return to Index**](#index)
## 3. Transaction: `VaultCreate`
@@ -449,7 +440,11 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
### 3.7 Invariants
-**TBD**
+- The transaction must not modify an existing `Vault` object (i.e. no before-state).
+- `'.AssetsAvailable == 0 AND '.AssetsTotal == 0 AND '.LossUnrealized == 0 AND MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (created vault must be empty).
+- `'.Account == MPTokenIssuance(Vault.ShareMPTID).Issuer` (shares issuer must equal the vault's _pseudo-account_).
+- The shares issuer `AccountRoot` must exist and must be a _pseudo-account_.
+- The shares issuer _pseudo-account_ must reference the created vault via its `VaultID` field.
### 3.8 Example JSON
@@ -469,7 +464,6 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
}
```
-[**Return to Index**](#index)
## 4. Transaction: `VaultSet`
@@ -505,7 +499,11 @@ The `VaultSet` updates an existing `Vault` ledger object.
### 4.4 Invariants
-**TBD**
+- The _pseudo-account_ asset balance must not change.
+- `.AssetsTotal == '.AssetsTotal` (assets total must not change).
+- `.AssetsAvailable == '.AssetsAvailable` (assets available must not change).
+- `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
+- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must not change.
### 4.5 Example JSON
@@ -521,7 +519,6 @@ The `VaultSet` updates an existing `Vault` ledger object.
}
```
-[**Return to Index**](#index)
## 5. Transaction: `VaultDelete`
@@ -551,7 +548,11 @@ The `VaultDelete` transaction deletes an existing vault object.
### 5.4 Invariants
-**TBD**
+- The `Vault` object must be deleted (i.e. no after-state).
+- The `MPTokenIssuance` object for the vault shares must also be deleted.
+- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (no shares outstanding at time of deletion).
+- `.AssetsTotal == 0` (no assets outstanding at time of deletion).
+- `.AssetsAvailable == 0` (no assets available at time of deletion).
### 5.5 Example JSON
@@ -565,7 +566,6 @@ The `VaultDelete` transaction deletes an existing vault object.
}
```
-[**Return to Index**](#index)
## 6. Transaction: `VaultDeposit`
@@ -620,7 +620,16 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
### 6.4 Invariants
-**TBD**
+Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the depositor's share balance.
+
+- The _pseudo-account_ asset balance must increase: $\Delta_{asset} > 0$.
+- $\Delta_{asset}$ must not exceed `Amount`.
+- The depositor's asset balance must decrease by $\Delta_{asset}$ (unless the depositor is the asset issuer).
+- `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
+- $\Delta_{share} > 0$ (depositor shares must increase).
+- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
+- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
### 6.5 Example JSON
@@ -639,7 +648,6 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
}
```
-[**Return to Index**](#index)
## 7. Transaction: `VaultWithdraw`
@@ -723,7 +731,14 @@ In sections below assume the following variables:
### 7.4 Invariants
-**TBD**
+Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the withdrawer's share balance.
+
+- The _pseudo-account_ asset balance must decrease: $\Delta_{asset} < 0$.
+- Exactly one destination account balance must increase by $|\Delta_{asset}|$ (unless the destination is the asset issuer).
+- $\Delta_{share} < 0$ (withdrawer shares must decrease).
+- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
+- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
### 7.5 Example JSON
@@ -742,7 +757,6 @@ In sections below assume the following variables:
}
```
-[**Return to Index**](#index)
## 8. Transaction: `VaultClawback`
@@ -794,7 +808,14 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
### 8.4 Invariants
-**TBD**
+Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the holder's share balance.
+
+- The transaction submitter must be the asset issuer, or the vault owner of an empty vault with outstanding shares.
+- If the vault holds assets: $\Delta_{asset} < 0$ (vault balance must decrease).
+- If the vault holds assets: `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+- If the vault holds assets: `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
+- $\Delta_{share} < 0$ (holder shares must decrease).
+- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
### 8.5 Example JSON
@@ -810,13 +831,16 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
}
```
-[**Return to Index**](#index)
## 9. Transaction: `Payment`
The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
-### 9.1 Failure Conditions
+### 9.1 Fields
+
+The Single Asset Vault does not introduce or modify any `Payment` transaction fields. Refer to the existing [Payment transaction fields](https://xrpl.org/docs/references/protocol/transactions/types/payment).
+
+### 9.2 Failure Conditions
1. If `Payment.Amount` is a `Vault` share AND:
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
@@ -835,205 +859,222 @@ The Single Asset Vault does not introduce new `Payment` transaction fields. Howe
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
-### 9.2 State Changes
+### 9.3 State Changes
1. If `MPToken`object for shares does not exist for the destination account, create one.
-### 9.3 Example JSON
+### 9.4 Example JSON
```json
{}
```
-[**Return to Index**](#index)
-
-## 10. API
-### 10.1 RPC `vault_info`
+## 10. RPC: `vault_info`
This RPC retrieves the Vault ledger entry and the IDs associated with it.
-#### 10.1.1 Request Fields
-
-We propose adding the following fields to the `ledger_entry` method:
-
-| Field Name | Required | JSON Type | Description |
-| ---------- | :------: | :-------: | :----------------------------------------: |
-| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
-
-#### 10.1.2 Response
-
-| Field Name | Required | JSON Type | Description |
-| -------------------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `vault` | yes | `object` | Root object representing the vault. |
-| `vault.Account` | yes | `string` | The pseudo-account ID of the vault. |
-| `vault.Asset` | yes | `object` | Object representing the asset held in the vault. |
-| `vault.Asset.currency` | yes | `string` | Currency code of the asset stored in the vault. |
-| `vault.Asset.issuer` | no | `string` | Issuer address of the asset. |
-| `vault.AssetsAvailable` | yes | `string` | Amount of assets currently available for withdrawal. |
-| `vault.AssetsTotal` | yes | `string` | Total amount of assets in the vault. |
-| `vault.Flags` | no | `number` | Bit-field flags associated with the vault. |
-| `vault.LedgerEntryType` | yes | `string` | Ledger entry type, always "Vault". |
-| `vault.LossUnrealized` | no | `string` | Unrealized loss associated with the vault. |
-| `vault.Owner` | yes | `string` | ID of the Vault Owner account. |
-| `vault.OwnerNode` | no | `string` | Identifier for the owner node in the ledger tree. |
-| `vault.PreviousTxnID` | yes | `string` | Transaction ID of the last modification to this vault. |
-| `vault.PreviousTxnLgrSeq` | yes | `number` | Ledger sequence number of the last transaction modifying this vault. |
-| `vault.Sequence` | yes | `number` | Sequence number of the vault entry. |
-| `vault.ShareMPTID` | no | `string` | Multi-purpose token ID associated with this vault. |
-| `vault.WithdrawalPolicy` | no | `number` | Policy defining withdrawal conditions. |
-| `vault.index` | yes | `string` | Unique index of the vault ledger entry. |
-| `vault.shares` | yes | `object` | Object containing details about issued shares. |
-| `vault.shares.Flags` | no | `number` | Bit-field flags associated with the shares issuance. |
-| `vault.shares.Issuer` | yes | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
-| `vault.shares.LedgerEntryType` | yes | `string` | Ledger entry type, always "MPTokenIssuance". |
-| `vault.shares.OutstandingAmount` | yes | `string` | Total outstanding shares issued. |
-| `vault.shares.OwnerNode` | no | `string` | Identifier for the owner node of the shares. |
-| `vault.shares.PreviousTxnID` | yes | `string` | Transaction ID of the last modification to the shares issuance. |
-| `vault.shares.PreviousTxnLgrSeq` | yes | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
-| `vault.shares.Sequence` | yes | `number` | Sequence number of the shares issuance entry. |
-| `vault.shares.index` | yes | `string` | Unique index of the shares ledger entry. |
-| `vault.shares.mpt_issuance_id` | no | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
-| `vault.Scale` | yes | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-
-##### 10.1.2.1 Example
+### 10.1 Request Fields
+
+| Field Name | Required? | JSON Type | Description |
+| ---------- | :-------: | :-------: | :----------------------------------------: |
+| `command` | Yes | `string` | Must be `"vault_info"`. |
+| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
+
+### 10.2 Response Fields
+
+| Field Name | Always Present? | JSON Type | Description |
+| -------------------------------- | :-------------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `vault` | Yes | `object` | Root object representing the vault. |
+| `vault.Account` | Yes | `string` | The pseudo-account ID of the vault. |
+| `vault.Asset` | Yes | `object` | Object representing the asset held in the vault. |
+| `vault.Asset.currency` | Conditional | `string` | Currency code of the asset. Present when the asset is `XRP` or an `IOU`. |
+| `vault.Asset.issuer` | Conditional | `string` | Issuer address of the asset. Present when the asset is an `IOU`. |
+| `vault.Asset.mpt_issuance_id` | Conditional | `string` | The `MPTokenIssuance` ID of the asset. Present when the asset is an `MPT`. |
+| `vault.AssetsAvailable` | Yes | `string` | Amount of assets currently available for withdrawal. |
+| `vault.AssetsTotal` | Yes | `string` | Total amount of assets in the vault. |
+| `vault.Data` | No | `string` | Arbitrary metadata about the Vault, in hex format. |
+| `vault.Flags` | Yes | `number` | Bit-field flags associated with the vault. |
+| `vault.LedgerEntryType` | Yes | `string` | Ledger entry type, always `"Vault"`. |
+| `vault.LossUnrealized` | Yes | `string` | Unrealized loss associated with the vault. |
+| `vault.Owner` | Yes | `string` | ID of the Vault Owner account. |
+| `vault.OwnerNode` | Yes | `string` | Identifier for the owner node in the ledger tree. |
+| `vault.PreviousTxnID` | Yes | `string` | Transaction ID of the last modification to this vault. |
+| `vault.PreviousTxnLgrSeq` | Yes | `number` | Ledger sequence number of the last transaction modifying this vault. |
+| `vault.Scale` | Yes | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| `vault.Sequence` | Yes | `number` | Sequence number of the vault entry. |
+| `vault.ShareMPTID` | Yes | `string` | Multi-purpose token ID associated with the vault's shares. |
+| `vault.WithdrawalPolicy` | Yes | `number` | Policy defining withdrawal conditions. |
+| `vault.index` | Yes | `string` | Unique index of the vault ledger entry. |
+| `vault.shares` | Yes | `object` | Object containing details about issued shares. |
+| `vault.shares.DomainID` | No | `string` | The `PermissionedDomain` object ID associated with the shares, if set. |
+| `vault.shares.Flags` | Yes | `number` | Bit-field flags associated with the shares issuance. |
+| `vault.shares.Issuer` | Yes | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
+| `vault.shares.LedgerEntryType` | Yes | `string` | Ledger entry type, always `"MPTokenIssuance"`. |
+| `vault.shares.MPTokenMetadata` | No | `string` | Arbitrary metadata about the share MPT, in hex format. |
+| `vault.shares.OutstandingAmount` | Yes | `string` | Total outstanding shares issued. |
+| `vault.shares.OwnerNode` | Yes | `string` | Identifier for the owner node of the shares. |
+| `vault.shares.PreviousTxnID` | Yes | `string` | Transaction ID of the last modification to the shares issuance. |
+| `vault.shares.PreviousTxnLgrSeq` | Yes | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
+| `vault.shares.Sequence` | Yes | `number` | Sequence number of the shares issuance entry. |
+| `vault.shares.index` | Yes | `string` | Unique index of the shares ledger entry. |
+| `vault.shares.mpt_issuance_id` | Yes | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
+
+### 10.3 Failure Conditions
+
+1. The `vault` field is not provided or is not a valid object ID (`invalidParams`).
+2. The `Vault` object with the specified ID does not exist on the ledger (`entryNotFound`).
+
+### 10.4 Example Request
+
+```json
+{
+ "command": "vault_info",
+ "vault": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7"
+}
+```
+
+### 10.5 Example Response
Vault holding an `IOU`:
-```type-script
- "result" :
- {
- "ledger_current_index" : 7,
- "status" : "success",
- "validated" : false,
- "vault" :
- {
- "Account" : "rKwvc1mgHLyHKY3yRUqVwffWtsxYb3QLWf",
- "Asset" :
- {
- "currency" : "IOU",
- "issuer" : "r9cZ5oHbdL4Z9Maj6TdnfAos35nVzYuNds"
- },
- "AssetsAvailable" : "100",
- "AssetsTotal" : "100",
- "Flags" : 0,
- "LedgerEntryType" : "Vault",
- "LossUnrealized" : "0",
- "Owner" : "rwhaYGnJMexktjhxAKzRwoCcQ2g6hvBDWu",
- "OwnerNode" : "0",
- "PreviousTxnID" : "1484794AE38DBB7C6F4E0B7536CC560B418135BEDB0F8904349F7F8A3B496826",
- "PreviousTxnLgrSeq" : 6,
- "Sequence" : 5,
- "ShareMPTID" : "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E",
- "WithdrawalPolicy" : 1,
- "index" : "2DE64CA41250EF3CB7D2B127D6CEC31F747492CAE2BD1628CA02EA1FFE7475B3",
- "shares" :
- {
- "DomainID" : "3B61A239626565A3FBEFC32863AFBF1AD3325BD1669C2C9BC92954197842B564",
- "Flags" : 0,
- "Issuer" : "rKwvc1mgHLyHKY3yRUqVwffWtsxYb3QLWf",
- "LedgerEntryType" : "MPTokenIssuance",
- "OutstandingAmount" : "100",
- "OwnerNode" : "0",
- "PreviousTxnID" : "1484794AE38DBB7C6F4E0B7536CC560B418135BEDB0F8904349F7F8A3B496826",
- "PreviousTxnLgrSeq" : 6,
- "Sequence" : 1,
- "index" : "F84AE266C348540D7134F1A683392C3B97C3EEFDE9FEF6F2055B3B92550FB44A",
- "mpt_issuance_id" : "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E"
- },
- "Scale": 6,
- }
- }
+```json
+{
+ "result": {
+ "ledger_current_index": 7,
+ "validated": false,
+ "vault": {
+ "Account": "rKwvc1mgHLyHKY3yRUqVwffWtsxYb3QLWf",
+ "Asset": {
+ "currency": "USD",
+ "issuer": "r9cZ5oHbdL4Z9Maj6TdnfAos35nVzYuNds"
+ },
+ "AssetsAvailable": "100",
+ "AssetsTotal": "100",
+ "Flags": 0,
+ "LedgerEntryType": "Vault",
+ "LossUnrealized": "0",
+ "Owner": "rwhaYGnJMexktjhxAKzRwoCcQ2g6hvBDWu",
+ "OwnerNode": "0",
+ "PreviousTxnID": "1484794AE38DBB7C6F4E0B7536CC560B418135BEDB0F8904349F7F8A3B496826",
+ "PreviousTxnLgrSeq": 6,
+ "Sequence": 5,
+ "ShareMPTID": "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E",
+ "WithdrawalPolicy": 1,
+ "index": "2DE64CA41250EF3CB7D2B127D6CEC31F747492CAE2BD1628CA02EA1FFE7475B3",
+ "shares": {
+ "DomainID": "3B61A239626565A3FBEFC32863AFBF1AD3325BD1669C2C9BC92954197842B564",
+ "Flags": 0,
+ "Issuer": "rKwvc1mgHLyHKY3yRUqVwffWtsxYb3QLWf",
+ "LedgerEntryType": "MPTokenIssuance",
+ "OutstandingAmount": "100",
+ "OwnerNode": "0",
+ "PreviousTxnID": "1484794AE38DBB7C6F4E0B7536CC560B418135BEDB0F8904349F7F8A3B496826",
+ "PreviousTxnLgrSeq": 6,
+ "Sequence": 1,
+ "index": "F84AE266C348540D7134F1A683392C3B97C3EEFDE9FEF6F2055B3B92550FB44A",
+ "mpt_issuance_id": "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E"
+ }
+ }
+ },
+ "status": "success",
+ "type": "response"
+}
```
Vault holding an `MPT`:
-```
+```json
{
- "LedgerEntryType": "Vault",
- "LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
- "Flags": "0",
- "PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
- "PreviousTxnLgrSeq": 12345678,
- "Sequence": 1,
- "OwnerNode": 2,
- "Owner": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
- "Account": "rPseudoAcc1234567890abcdef1234567890abcdef",
- "Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865207661756C742E",
- "Asset": {
- "currency": "USD",
- "issuer": "rIssuer1234567890abcdef1234567890abcdef",
- "value": "1000"
+ "result": {
+ "ledger_current_index": 3990828,
+ "validated": false,
+ "vault": {
+ "Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
+ "Asset": {
+ "mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
+ },
+ "Data": "50726976617465207661756C7420666F72207475746F7269616C73",
+ "Flags": 65536,
+ "LedgerEntryType": "Vault",
+ "Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
+ "OwnerNode": "0",
+ "PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
+ "PreviousTxnLgrSeq": 3113735,
+ "Sequence": 3113728,
+ "ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
+ "WithdrawalPolicy": 1,
+ "index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
+ "shares": {
+ "DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
+ "Flags": 60,
+ "Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
+ "LedgerEntryType": "MPTokenIssuance",
+ "MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
+ "PreviousTxnLgrSeq": 3113735,
+ "Sequence": 1,
+ "index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
+ "mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
+ }
+ }
},
- "AssetsTotal": 1000000,
- "AssetsAvailable": 800000,
- "LossUnrealized": 200000,
- "AssetsMaximum": 0,
- "Share": {
- "mpt_issuance_id": "0000012FFD9EE5DA93AC614B4DB94D7E0FCE415CA51BED47",
- "value": "1",
- },
- "ShareTotal": 5000,
- "WithdrawalPolicy": "0x0001",
- "Scale": 0
+ "status": "success",
+ "type": "response"
}
```
Vault holding `XRP`:
-```type-script
+```json
{
- "result" :
- {
- "ledger_hash" : "6FFF56DF92D54D01EE3D5487787F4430D66F89C6BC74B00C276262A0207B2FAD",
- "ledger_index" : 6,
- "status" : "success",
- "validated" : true,
- "vault" :
- {
- "Account" : "rBVxExjRR6oDMWCeQYgJP7q4JBLGeLBPyv",
- "Asset" :
- {
- "currency" : "XRP"
- },
- "AssetsAvailable" : "0",
- "AssetsTotal" : "0",
- "Flags" : 0,
- "LedgerEntryType" : "Vault",
- "LossUnrealized" : "0",
- "Owner" : "rwhaYGnJMexktjhxAKzRwoCcQ2g6hvBDWu",
- "OwnerNode" : "0",
- "PreviousTxnID" : "25C3C8BF2C9EE60DFCDA02F3919D0C4D6BF2D0A4AC9354EFDA438F2ECDDA65E4",
- "PreviousTxnLgrSeq" : 5,
- "Sequence" : 4,
- "ShareMPTID" : "00000001732B0822A31109C996BCDD7E64E05D446E7998EE",
- "WithdrawalPolicy" : 1,
- "index" : "C043BB1B350FFC5FED21E40535609D3D95BC0E3CE252E2F69F85BE0157020A52",
- "shares" :
- {
- "DomainID" : "3B61A239626565A3FBEFC32863AFBF1AD3325BD1669C2C9BC92954197842B564",
- "Flags" : 56,
- "Issuer" : "rBVxExjRR6oDMWCeQYgJP7q4JBLGeLBPyv",
- "LedgerEntryType" : "MPTokenIssuance",
- "OutstandingAmount" : "0",
- "OwnerNode" : "0",
- "PreviousTxnID" : "25C3C8BF2C9EE60DFCDA02F3919D0C4D6BF2D0A4AC9354EFDA438F2ECDDA65E4",
- "PreviousTxnLgrSeq" : 5,
- "Sequence" : 1,
- "index" : "4B25BDE141E248E5D585FEB6100E137D3C2475CEE62B28446391558F0BEA23B5",
- "mpt_issuance_id" : "00000001732B0822A31109C996BCDD7E64E05D446E7998EE"
- },
- "Scale": 0
- }
- }
+ "result": {
+ "ledger_hash": "6FFF56DF92D54D01EE3D5487787F4430D66F89C6BC74B00C276262A0207B2FAD",
+ "ledger_index": 6,
+ "validated": true,
+ "vault": {
+ "Account": "rBVxExjRR6oDMWCeQYgJP7q4JBLGeLBPyv",
+ "Asset": {
+ "currency": "XRP"
+ },
+ "AssetsAvailable": "0",
+ "AssetsTotal": "0",
+ "Flags": 0,
+ "LedgerEntryType": "Vault",
+ "LossUnrealized": "0",
+ "Owner": "rwhaYGnJMexktjhxAKzRwoCcQ2g6hvBDWu",
+ "OwnerNode": "0",
+ "PreviousTxnID": "25C3C8BF2C9EE60DFCDA02F3919D0C4D6BF2D0A4AC9354EFDA438F2ECDDA65E4",
+ "PreviousTxnLgrSeq": 5,
+ "Sequence": 4,
+ "ShareMPTID": "00000001732B0822A31109C996BCDD7E64E05D446E7998EE",
+ "WithdrawalPolicy": 1,
+ "index": "C043BB1B350FFC5FED21E40535609D3D95BC0E3CE252E2F69F85BE0157020A52",
+ "shares": {
+ "DomainID": "3B61A239626565A3FBEFC32863AFBF1AD3325BD1669C2C9BC92954197842B564",
+ "Flags": 56,
+ "Issuer": "rBVxExjRR6oDMWCeQYgJP7q4JBLGeLBPyv",
+ "LedgerEntryType": "MPTokenIssuance",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "PreviousTxnID": "25C3C8BF2C9EE60DFCDA02F3919D0C4D6BF2D0A4AC9354EFDA438F2ECDDA65E4",
+ "PreviousTxnLgrSeq": 5,
+ "Sequence": 1,
+ "index": "4B25BDE141E248E5D585FEB6100E137D3C2475CEE62B28446391558F0BEA23B5",
+ "mpt_issuance_id": "00000001732B0822A31109C996BCDD7E64E05D446E7998EE"
+ }
+ }
+ },
+ "status": "success",
+ "type": "response"
}
```
-[**Return to Index**](#index)
# Appendix
-[**Return to Index**](#index)
## A-1 F.A.Q.
From 9ac0f3793e24dd68ea47b7d83ea57d07205a31a8 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 24 Feb 2026 11:42:43 +0100
Subject: [PATCH 11/27] Restructure vault spec to match XLS template
- Reorganize top-level sections: Abstract (1), Introduction (2),
Specification (3), Rationale (4), Security Considerations (5),
Appendix
- Move all ledger entry, transaction, and RPC sections under
"3. Specification" as subsections (3.1-3.9)
- Remove "1.1 Overview" heading, merge content into Introduction body
- Renumber Introduction subsections: Terminology (2.1), Actors (2.2),
Connecting to the Vault (2.3)
- Demote all specification headings by one level with new numbering
- Add Rationale section explaining decoupled vault design
- Rename FAQ section to "Appendix A: FAQ" with A.x numbering
- Fix heading levels for Key Variables and Vault State Update
Co-Authored-By: Claude Opus 4.6 (1M context)
---
XLS-0065-single-asset-vault/README.md | 225 +++++++++++++-------------
1 file changed, 114 insertions(+), 111 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 19fc7568a..d3f97fe9e 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -13,13 +13,11 @@
# Single Asset Vault
-## Abstract
+## 1. Abstract
A Single Asset Vault is a new on-chain primitive for aggregating assets from one or more depositors, and making the assets available for other on-chain protocols. The Single Asset Vault uses [Multi-Purpose-Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens) to represent ownership shares of the Vault. The Vault serves diverse purposes, such as lending markets, aggregators, yield-bearing tokens, asset management, etc. The Single Asset Vault decouples the liquidity provision functionality from the specific protocol logic.
-## 1. Introduction
-
-### 1.1 Overview
+## 2. Introduction
The **Single Asset Vault** is an on-chain object that aggregates assets from one or more depositors and represents ownership through shares. Other protocols, such as the [Lending Protocol](https://github.com/XRPLF/XRPL-Standards/discussions/190), can access these assets via the vault, whether or not they generate yield. Currently, other protocols must be created by the same Account that created the Vault. However, this may change in the future.
@@ -42,39 +40,40 @@ Additionally, an issuer can perform a **Clawback** operation:
- **`VaultClawback`**: Allows the issuer of an IOU or MPT to claw back funds from the vault, as outlined in the [Clawback documentation](https://xrpl.org/docs/use-cases/tokenization/stablecoin-issuer#clawback).
-#### 1.1.1 Vault Ownership and Management
+#### 2.0.1 Vault Ownership and Management
A Single Asset Vault is owned and managed by an account called the **Vault Owner**. The account is reponsible for creating, updating and deleting the Vault object.
-#### 1.1.2 Access Control
+#### 2.0.2 Access Control
-A Single Asset Vault can be either public or private. Any depositor can deposit and redeem liquidity from a public vault, provided they own sufficient shares. In contrast, access to private shares is controlled via [Permissioned Domains](../XLS-0080-permissioned-domains/README.md), which use on-chain [Credentials](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0070-credentials) to manage access to the vault. Only depositors with the necessary credentials can deposit assets to a private vault. To prevent Vault Owner from locking away depositor funds, any shareholder can withdraw funds. Furthermore, the Vault Owner has an implicit permission to deposit and withdraw assets to and from the Vault. I.e. they do not have to have credentials in the Permissioned Domain.
+A Single Asset Vault can be either public or private. Any depositor can deposit and redeem liquidity from a public vault, provided they own sufficient shares. In contrast, access to private shares is controlled via [Permissioned Domains](../XLS-0080-permissioned-domains/README.md), which use on-chain [Credentials](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens) to manage access to the vault. Only depositors with the necessary credentials can deposit assets to a private vault. To prevent Vault Owner from locking away depositor funds, any shareholder can withdraw funds. Furthermore, the Vault Owner has an implicit permission to deposit and withdraw assets to and from the Vault. I.e. they do not have to have credentials in the Permissioned Domain.
-#### 1.1.3 Yield Bearing Shares
+#### 2.0.3 Yield Bearing Shares
Shares represent the ownership of a portion of the vault's assets. On-chain shares are represented by a [Multi-Purpose Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens). When creating the vault, the Vault Owner can configure the shares to be non-transferable. Non-transferable shares cannot be transferred to any other account -- they can only be redeemed. If the vault is private, shares can be transferred and used in other DeFi protocols as long as the receiving account is authorized to hold the shares. The vault's shares may be yield-bearing, depending on the protocol connected to the vault, meaning that a holder may be able to withdraw more (or less) liquidity than they initially deposited.
-### 1.2 Terminology
+### 2.1 Terminology
- **Vault**: A ledger entry for aggregating liquidity and providing this liquidity to one or more accessors.
- **Asset**: The currency of a vault. It is either XRP, a [Fungible Token](https://xrpl.org/docs/concepts/tokens/fungible-tokens/) or a [Multi-Purpose Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens).
- **Share**: Shares represent the depositors' portion of the vault's assets. Shares are a [Multi-Purpose Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens) created by the _pseudo-account_ of the vault.
-### 1.3 Actors
+### 2.2 Actors
- **Vault Owner**: An account responsible for creating and deleting The Vault.
- **Depositor**: An entity that deposits and withdraws assets to and from the vault.
-### 1.4 Connecting to the Vault
+### 2.3 Connecting to the Vault
A protocol connecting to a Vault must track its debt. Furthermore, the updates to the Vault state when funds are removed or added back must be handled in the transactors of the protocol. For an example, please refer to the [Lending Protocol](https://github.com/XRPLF/XRPL-Standards/discussions/190) specification.
+## 3. Specification
-## 2. Ledger Entry: `Vault`
+### 3.1 Ledger Entry: `Vault`
The **`Vault`** ledger entry describes the state of the tokenized vault.
-### 2.1 Object Identifier
+#### 3.1.1 Object Identifier
The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order:
@@ -82,7 +81,7 @@ The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/
- The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
- The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value.
-### 2.2 Fields
+#### 3.1.2 Fields
A vault has the following fields:
@@ -107,7 +106,7 @@ A vault has the following fields:
| `WithdrawalPolicy` | No | Yes | `string` | `UINT8` | `N/A` | Indicates the withdrawal strategy used by the Vault. |
| `Scale` | No | Yes | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-#### 2.2.1 Flags
+##### 3.1.2.1 Flags
The `Vault` object supports the following flags:
@@ -115,50 +114,50 @@ The `Vault` object supports the following flags:
| ----------------- | :----------: | :---------: | :------------------------------------------: |
| `lsfVaultPrivate` | `0x00010000` | No | If set, indicates that the vault is private. |
-### 2.3 Vault `_pseudo-account_`
+#### 3.1.3 Vault `_pseudo-account_`
An AccountRoot entry holds the XRP, IOU or MPT deposited into the vault. It also acts as the issuer of the vault's shares. The _pseudo-account_ follows the XLS-64 specification for pseudo accounts. The `AccountRoot` object is created when creating the `Vault` object.
-### 2.4 Ownership
+#### 3.1.4 Ownership
The `Vault` objects are stored in the ledger and tracked in an [Owner Directory](https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/directorynode) owned by the account submitting the `VaultSet` transaction. Furthermore, to facilitate `Vault` object lookup from the vault shares, the object is also tracked in the `OwnerDirectory` of the _`pseudo-account`_.
-### 2.5 Owner Reserve
+#### 3.1.5 Owner Reserve
The `Vault` object costs one reserve fee per object created:
- The `Vault` object itself.
- The `MPTokenIssuance` associated with the shares of the Vault.
-### 2.6 Vault Shares
+#### 3.1.6 Vault Shares
Shares represent the portion of the Vault assets a depositor owns. Vault Owners set the currency code of the share and whether the token is transferable during the vault's creation. These two values are immutable. The share is represented by a [Multi-Purpose Token](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens). The MPT is issued by the vault's pseudo-account.
-#### 2.6.1 `Scale`
+##### 3.1.6.1 `Scale`
The **`Scale`** field enables the vault to accurately represent fractional asset values using integer-only MPT shares, which prevents the loss of value from decimal truncation. It defines a scaling factor, calculated as $10^{\text{Scale}}$, that converts a decimal asset amount into a corresponding whole number of shares. For example, with a `Scale` of `6`, a deposit of **20.3** assets is multiplied by $10^6$ and credited as **20,300,000** shares.
As a general rule, all calculations involving MPTs are executed with a precision of a single MPT, treating them as indivisible units. If a calculation results in a fractional amount, it will be rounded up, down or to the nearest whole number depending on the context. Crucially, the rounding direction is determined by the protocol and is not controlled by the transaction submitter, which may lead to unexpected results.
-##### 2.6.1.1 `IOU`
+###### 3.1.6.1.1 `IOU`
When a vault holds an **`IOU`**, the `Scale` is configurable by the Vault Owner at the time of the vault's creation. The value can range from **0** to a maximum of **18**, with a default of **6**. This flexibility allows issuers to set a level of precision appropriate for their specific token.
-##### 2.6.1.2 `XRP`
+###### 3.1.6.1.2 `XRP`
When a vault holds **`XRP`**, the `Scale` is fixed at **0**. This aligns with XRP's native structure, where one share represents one drop (the smallest unit of XRP), and one XRP equals 1,000,000 drops. Therefore, a deposit of 10 XRP to an empty Vault will result in the issuance of 10,000,000 shares ($10 \times 10^6$).
-##### 2.6.1.3 `MPT`
+###### 3.1.6.1.3 `MPT`
When a vault holds `MPT`, its `Scale` is fixed at **0**. This creates a 1-to-1 relationship between deposited MPT units and the shares issued (for example, depositing 10 MPTs to an empty Vault issues 10 shares). The value of a single MPT is determined at the issuer's discretion. If an MPT is set to represent a large value, the vault owner and the depositor must be cautious. Since only whole MPT units are used in calculations, any value that is not a multiple of a single MPT's value may be lost due to rounding during a transaction.
-#### 2.6.2 `MPTokenIssuance`
+##### 3.1.6.2 `MPTokenIssuance`
The `MPTokenIssuance` object represents the share on the ledger. It is created and deleted together with the `Vault` object.
-##### 2.6.2.1 `MPTokenIssuance` Values
+###### 3.1.6.2.1 `MPTokenIssuance` Values
-Here’s the table with the headings "Field," "Description," and "Value":
+Here's the table with the headings "Field," "Description," and "Value":
| **Field** | **Description** | **Value** |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
@@ -177,11 +176,11 @@ The following flags are set based on whether the shares are transferable and if
| **Public Vault** | `lsfMPTCanEscrow`
`lsfMPTCanTrade`
`lsfMPTCanTransfer` | No Flags |
| **Private Vault** | `lsfMPTCanEscrow`
`lsfMPTCanTrade`
`lsfMPTCanTransfer`
`lsfMPTRequireAuth` | `lsfMPTRequireAuth` |
-#### 2.6.3 `MPToken`
+##### 3.1.6.3 `MPToken`
The `MPToken` object represents the amount of shares held by a depositor. It is created when the account deposits liquidity into the vault and is deleted when a depositor redeems (or transfers) all shares.
-##### 2.6.3.1 `MPToken` Values
+###### 3.1.6.3.1 `MPToken` Values
The `MPToken` values should be set as per the `MPT` [specification](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens#2112-fields).
@@ -190,11 +189,11 @@ The `MPToken` values should be set as per the `MPT` [specification](https://gith
| **Public Vault** | No Flags | `lsfMPTAuthorized` |
| **Private Vault** | `lsfMPTAuthorized` | `lsfMPTAuthorized` |
-### 2.7 Exchange Algorithm
+#### 3.1.7 Exchange Algorithm
Exchange Algorithm refers to the logic that is used to exchange assets into shares and shares into assets. This logic is executed when depositing or redeeming liquidity. A Vault comes with the default exchange algorithm, which is detailed below.
-#### 2.7.1 Unrealized Loss
+##### 3.1.7.1 Unrealized Loss
A well-informed depositor may learn of an incoming loss and redeem their shares early, causing the remaining depositors to bear the full loss. To discourage such behaviour, we introduce a concept of "paper loss," captured by the `Vault` object's `LossUnrealized` attribute. The "paper loss" captures a potential loss the vault may experience and thus temporarily decreases the vault value. Only a protocol connected to the `Vault` may increase or decrease the `LossUnrealized` attribute.
@@ -224,11 +223,11 @@ A depositor could deposit $100k assets at a 0.1 exchange rate and get 1.0m share
To account for this problem, the Vault must use two different exchange rate models: one for depositing assets and one for withdrawing them.
-#### 2.7.2 Exchange Rate Algorithms
+##### 3.1.7.2 Exchange Rate Algorithms
This section details the algorithms used to calculate the exchange between assets and shares for deposits, redemptions, and withdrawals.
-#### Key Variables
+##### Key Variables
- **$\Gamma_{assets}$**: The total balance of assets held within the vault.
- **$\Gamma_{shares}$**: The total number of shares currently issued by the vault.
@@ -237,7 +236,7 @@ This section details the algorithms used to calculate the exchange between asset
- **$\iota$**: The vault's total **unrealized loss**.
- **$\sigma$**: The scaling factor derived from `Scale`, used to convert fractional assets into integer shares.
-##### 2.7.2.1 Deposit
+###### 3.1.7.2.1 Deposit
The deposit function calculates the number of shares a user receives for their assets.
@@ -255,14 +254,14 @@ Because the share amount is rounded down, the actual assets taken from the depos
$$\Delta_{assets'} = \frac{\Delta_{shares} \times \Gamma_{assets}}{\Gamma_{shares}}$$
-#### Vault State Update
+##### Vault State Update
The vault's totals are updated with the final calculated amounts.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} + \Delta_{assets'}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} + \Delta_{shares}$
-##### 2.7.2.2 Redeem
+###### 3.1.7.2.2 Redeem
The redeem function calculates the asset payout for a user burning a specific number of shares.
@@ -279,7 +278,7 @@ The vault's totals are reduced after the redemption.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} - \Delta_{assets}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} - \Delta_{shares}$
-##### 2.7.2.3 Withdraw
+###### 3.1.7.2.3 Withdraw
The withdraw function handles a request for a specific amount of assets, which involves a two-step process to determine the final payout.
@@ -302,23 +301,23 @@ The vault's totals are reduced by the final calculated amounts.
- **New Total Assets**: $\Gamma_{assets} \leftarrow \Gamma_{assets} - \Delta_{assets\_out}$
- **New Total Shares**: $\Gamma_{shares} \leftarrow \Gamma_{shares} - \Delta_{shares}$
-#### 2.7.3 Withdrawal Policy
+##### 3.1.7.3 Withdrawal Policy
Withdrawal policy controls the logic used when removing liquidity from a vault. Each strategy has its own implementation, but it can be used in multiple vaults once implemented. Therefore, different vaults may have different withdrawal policies. The specification introduces the following options:
-##### 2.7.3.1 `first-come-first-serve`
+###### 3.1.7.3.1 `first-come-first-serve`
The First Come, First Serve strategy treats all requests equally, allowing a depositor to redeem any amount of assets provided they have a sufficient number of shares.
-### 2.8 Frozen Assets
+#### 3.1.8 Frozen Assets
The issuer of the Vaults asset may enact a freeze either through a [Global Freeze](https://xrpl.org/docs/concepts/tokens/fungible-tokens/freezes/#global-freeze) for IOUs or [locking MPT](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033-multi-purpose-tokens#21122-flags). When the vaults asset is frozen, it can only be withdrawn by specifying the `Destination` account as the `Issuer` of the asset. Similarly, a frozen asset _may not_ be deposited into a vault. Furthermore, when the asset of a vault is frozen, the shares corresponding to the asset may not be transferred.
-### 2.9 Transfer Fees
+#### 3.1.9 Transfer Fees
The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/tokens/transfer-fees) to `VaultDeposit` and `VaultWithdraw` transactions. Furthermore, whenever a protocol moves assets from or to a Vault, the `Transfer Fee` must not be charged.
-### 2.10 Invariants
+#### 3.1.10 Invariants
- A transaction must not modify more than one `Vault` object.
- `.Asset == '.Asset` (the asset is immutable).
@@ -336,7 +335,7 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
- Only `VaultDelete` may delete a `Vault` object.
- `.LossUnrealized == '.LossUnrealized` for all vault transactions (only protocol transactions such as `LoanManage` and `LoanPay` may change `LossUnrealized`).
-### 2.11 Example JSON
+#### 3.1.11 Example JSON
```json
{
@@ -364,14 +363,13 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
}
```
-
-## 3. Transaction: `VaultCreate`
+### 3.2 Transaction: `VaultCreate`
All transactions introduced by this proposal incorporate the [common transaction fields](https://xrpl.org/docs/references/protocol/transactions/common-fields) that are shared by all transactions. Standard fields are only documented in this proposal if needed because this proposal introduces new possible values for such fields.
The `VaultCreate` transaction creates a new `Vault` object.
-### 3.1 Fields
+#### 3.2.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ------------------ | :------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -385,14 +383,14 @@ The `VaultCreate` transaction creates a new `Vault` object.
| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
-### 3.2 Flags
+#### 3.2.2 Flags
| Flag Name | Flag Value | Description |
| ----------------------------- | :----------: | :--------------------------------------------------------------------------------------- |
| `tfVaultPrivate` | `0x00010000` | Indicates that the vault is private. It can only be set during Vault creation. |
| `tfVaultShareNonTransferable` | `0x00020000` | Indicates the vault share is non-transferable. It can only be set during Vault creation. |
-#### 3.3 WithdrawalPolicy
+##### 3.2.3 WithdrawalPolicy
The type indicates the withdrawal strategy supported by the vault. The following values are supported:
@@ -400,11 +398,11 @@ The type indicates the withdrawal strategy supported by the vault. The following
| ---------------------------------- | :------: | :-------------------------------------------------------: |
| `vaultStrategyFirstComeFirstServe` | `0x0001` | Requests are processed on a first-come-first-serve basis. |
-### 3.4 Transaction Fees
+#### 3.2.4 Transaction Fees
The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Therefore, the transaction [must destroy](../XLS-0064-pseudo-account/README.md) one incremental owner reserve amount.
-### 3.5 Failure Conditions
+#### 3.2.5 Failure Conditions
1. The `Asset` is `XRP`:
1. The `Scale` parameter is provided.
@@ -424,7 +422,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
6. The `Data` field is larger than 256 bytes.
7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
-### 3.6 State Changes
+#### 3.2.6 State Changes
1. Create a new `Vault` ledger object.
2. Create a new `MPTokenIssuance` ledger object for the vault shares.
@@ -438,7 +436,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
5. If `Vault.Asset` is an `MPT`:
1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
-### 3.7 Invariants
+#### 3.2.7 Invariants
- The transaction must not modify an existing `Vault` object (i.e. no before-state).
- `'.AssetsAvailable == 0 AND '.AssetsTotal == 0 AND '.LossUnrealized == 0 AND MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (created vault must be empty).
@@ -446,7 +444,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
- The shares issuer `AccountRoot` must exist and must be a _pseudo-account_.
- The shares issuer _pseudo-account_ must reference the created vault via its `VaultID` field.
-### 3.8 Example JSON
+#### 3.2.8 Example JSON
```json
{
@@ -464,12 +462,11 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
}
```
-
-## 4. Transaction: `VaultSet`
+### 3.3 Transaction: `VaultSet`
The `VaultSet` updates an existing `Vault` ledger object.
-### 4.1 Fields
+#### 3.3.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
@@ -479,7 +476,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
| `AssetsMaximum` | No | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
| `DomainID` | No | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-### 4.2 Failure Conditions
+#### 3.3.2 Failure Conditions
1. `Vault` object with the specified `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
@@ -491,13 +488,13 @@ The `VaultSet` updates an existing `Vault` ledger object.
7. The transaction is attempting to modify an immutable field.
8. The transaction does not specify any of the modifiable fields.
-### 4.3 State Changes
+#### 3.3.3 State Changes
1. Update mutable fields in the `Vault` ledger object.
2. If `DomainID` is provided:
1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
-### 4.4 Invariants
+#### 3.3.4 Invariants
- The _pseudo-account_ asset balance must not change.
- `.AssetsTotal == '.AssetsTotal` (assets total must not change).
@@ -505,7 +502,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
- `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must not change.
-### 4.5 Example JSON
+#### 3.3.5 Example JSON
```json
{
@@ -519,26 +516,25 @@ The `VaultSet` updates an existing `Vault` ledger object.
}
```
-
-## 5. Transaction: `VaultDelete`
+### 3.4 Transaction: `VaultDelete`
The `VaultDelete` transaction deletes an existing vault object.
-### 5.1 Fields
+#### 3.4.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
| `TransactionType` | Yes | `string` | `Uint16` | `60` | Transaction type. |
| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
-### 5.2 Failure Conditions
+#### 3.4.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
-### 5.3 State Changes
+#### 3.4.3 State Changes
1. Delete the `MPTokenIssuance` object for the vault shares.
2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
@@ -546,7 +542,7 @@ The `VaultDelete` transaction deletes an existing vault object.
4. Release the Owner Reserve to the `Vault.Owner` account.
5. Delete the `Vault` object.
-### 5.4 Invariants
+#### 3.4.4 Invariants
- The `Vault` object must be deleted (i.e. no after-state).
- The `MPTokenIssuance` object for the vault shares must also be deleted.
@@ -554,7 +550,7 @@ The `VaultDelete` transaction deletes an existing vault object.
- `.AssetsTotal == 0` (no assets outstanding at time of deletion).
- `.AssetsAvailable == 0` (no assets available at time of deletion).
-### 5.5 Example JSON
+#### 3.4.5 Example JSON
```json
{
@@ -566,12 +562,11 @@ The `VaultDelete` transaction deletes an existing vault object.
}
```
-
-## 6. Transaction: `VaultDeposit`
+### 3.5 Transaction: `VaultDeposit`
The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
-### 6.1 Fields
+#### 3.5.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :------------------: | :-----------: | :-----------: | :----------------------------------------------------- |
@@ -579,7 +574,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to which the assets are deposited. |
| `Amount` | Yes | `string` or `object` | `STAmount` | `N/A` | Asset amount to deposit. |
-### 6.2 Failure Conditions
+#### 3.5.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
@@ -598,7 +593,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
-### 6.3 State Changes
+#### 3.5.3 State Changes
1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
@@ -618,7 +613,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
-### 6.4 Invariants
+#### 3.5.4 Invariants
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the depositor's share balance.
@@ -631,7 +626,7 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-### 6.5 Example JSON
+#### 3.5.5 Example JSON
```json
{
@@ -648,12 +643,11 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
}
```
-
-## 7. Transaction: `VaultWithdraw`
+### 3.6 Transaction: `VaultWithdraw`
The `VaultWithdraw` transaction withdraws assets in exchange for the vault's shares.
-### 7.1 Fields
+#### 3.6.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------- |
@@ -674,7 +668,7 @@ In sections below assume the following variables:
- $\Delta_{asset}$ - the change in the total amount of assets after a deposit, withdrawal, or redemption.
- $\Delta_{share}$ - che change in the total amount of shares after a deposit, withdrawal, or redemption.
-### 7.2 Failure Conditions
+#### 3.6.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -704,7 +698,7 @@ In sections below assume the following variables:
1. The account does not have permission to receive the asset.
2. The account does not have a `RippleState` or `MPToken` object for the asset.
-### 7.3 State Changes
+#### 3.6.3 State Changes
1. If the `Vault.Asset` is XRP:
1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
@@ -729,7 +723,7 @@ In sections below assume the following variables:
6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
-### 7.4 Invariants
+#### 3.6.4 Invariants
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the withdrawer's share balance.
@@ -740,7 +734,7 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-### 7.5 Example JSON
+#### 3.6.5 Example JSON
```json
{
@@ -757,12 +751,11 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
}
```
-
-## 8. Transaction: `VaultClawback`
+### 3.7 Transaction: `VaultClawback`
The `VaultClawback` transaction performs a Clawback from the Vault, exchanging the shares of an account. Conceptually, the transaction performs `VaultWithdraw` on behalf of the `Holder`, sending the funds to the `Issuer` account of the asset. In case there are insufficient funds for the entire `Amount` the transaction will perform a partial Clawback, up to the `Vault.AssetsAvailable`. The Clawback transaction must respect any future fees or penalties.
-### 8.1 Fields
+#### 3.7.1 Fields
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
@@ -771,7 +764,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
-### 8.2 Failure Conditions
+#### 3.7.2 Failure Conditions
1. `Vault` object with the `VaultID` does not exist on the ledger.
@@ -789,7 +782,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
-### 8.3 State Changes
+#### 3.7.3 State Changes
1. If the `Vault.Asset` is an `IOU`:
1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
@@ -806,7 +799,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
-### 8.4 Invariants
+#### 3.7.4 Invariants
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the holder's share balance.
@@ -817,7 +810,7 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
- $\Delta_{share} < 0$ (holder shares must decrease).
- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-### 8.5 Example JSON
+#### 3.7.5 Example JSON
```json
{
@@ -831,16 +824,15 @@ Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and
}
```
-
-## 9. Transaction: `Payment`
+### 3.8 Transaction: `Payment`
The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
-### 9.1 Fields
+#### 3.8.1 Fields
The Single Asset Vault does not introduce or modify any `Payment` transaction fields. Refer to the existing [Payment transaction fields](https://xrpl.org/docs/references/protocol/transactions/types/payment).
-### 9.2 Failure Conditions
+#### 3.8.2 Failure Conditions
1. If `Payment.Amount` is a `Vault` share AND:
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
@@ -859,38 +851,37 @@ The Single Asset Vault does not introduce or modify any `Payment` transaction fi
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
-### 9.3 State Changes
+#### 3.8.3 State Changes
1. If `MPToken`object for shares does not exist for the destination account, create one.
-### 9.4 Example JSON
+#### 3.8.4 Example JSON
```json
{}
```
-
-## 10. RPC: `vault_info`
+### 3.9 RPC: `vault_info`
This RPC retrieves the Vault ledger entry and the IDs associated with it.
-### 10.1 Request Fields
+#### 3.9.1 Request Fields
| Field Name | Required? | JSON Type | Description |
| ---------- | :-------: | :-------: | :----------------------------------------: |
-| `command` | Yes | `string` | Must be `"vault_info"`. |
+| `command` | Yes | `string` | Must be `"vault_info"`. |
| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
-### 10.2 Response Fields
+#### 3.9.2 Response Fields
| Field Name | Always Present? | JSON Type | Description |
| -------------------------------- | :-------------: | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `vault` | Yes | `object` | Root object representing the vault. |
| `vault.Account` | Yes | `string` | The pseudo-account ID of the vault. |
| `vault.Asset` | Yes | `object` | Object representing the asset held in the vault. |
-| `vault.Asset.currency` | Conditional | `string` | Currency code of the asset. Present when the asset is `XRP` or an `IOU`. |
-| `vault.Asset.issuer` | Conditional | `string` | Issuer address of the asset. Present when the asset is an `IOU`. |
-| `vault.Asset.mpt_issuance_id` | Conditional | `string` | The `MPTokenIssuance` ID of the asset. Present when the asset is an `MPT`. |
+| `vault.Asset.currency` | Conditional | `string` | Currency code of the asset. Present when the asset is `XRP` or an `IOU`. |
+| `vault.Asset.issuer` | Conditional | `string` | Issuer address of the asset. Present when the asset is an `IOU`. |
+| `vault.Asset.mpt_issuance_id` | Conditional | `string` | The `MPTokenIssuance` ID of the asset. Present when the asset is an `MPT`. |
| `vault.AssetsAvailable` | Yes | `string` | Amount of assets currently available for withdrawal. |
| `vault.AssetsTotal` | Yes | `string` | Total amount of assets in the vault. |
| `vault.Data` | No | `string` | Arbitrary metadata about the Vault, in hex format. |
@@ -920,12 +911,12 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
| `vault.shares.index` | Yes | `string` | Unique index of the shares ledger entry. |
| `vault.shares.mpt_issuance_id` | Yes | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
-### 10.3 Failure Conditions
+#### 3.9.3 Failure Conditions
1. The `vault` field is not provided or is not a valid object ID (`invalidParams`).
2. The `Vault` object with the specified ID does not exist on the ledger (`entryNotFound`).
-### 10.4 Example Request
+#### 3.9.4 Example Request
```json
{
@@ -934,7 +925,7 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
}
```
-### 10.5 Example Response
+#### 3.9.5 Example Response
Vault holding an `IOU`:
@@ -1072,32 +1063,44 @@ Vault holding `XRP`:
}
```
+## 4. Rationale
-# Appendix
+The Single Asset Vault is intentionally decoupled from the protocols that rely on it for liquidity. Rather than embedding liquidity provisioning logic into each protocol the vault provides a reusable on-chain building block that a protocol can connect to. This separation means that protocol developers do not need to implement liquidity provisioning mechanics they simply draw from and return assets to the vault, while the vault handles share accounting, access control, and asset custody independently.
+
+This design was chosen over a tightly-coupled alternative where each protocol manages its own depositor pool, because:
+
+- **Reusability**: A single vault implementation serves multiple protocols, reducing code duplication and the surface area for bugs.
+- **Composability**: Vault shares are first-class MPT assets that can be transferred, escrowed, or used in other DeFi protocols.
+- **Separation of concerns**: Protocols connecting to the vault only need to track their debt and update vault state through well-defined interfaces, rather than managing individual depositor balances.
+## 5. Security Considerations
+
+The security properties of the Single Asset Vault are enforced through the invariant checks described in sections 3.1.10 and 3.2.7 through 3.7.4. These invariants guarantee conservation of assets across deposits, withdrawals, and clawbacks, immutability of critical vault parameters, and correctness of share issuance and redemption.
+
+# Appendix
-## A-1 F.A.Q.
+## Appendix A: FAQ
-### A-1.1 Why does the specification allow both `Withdraw` and `Redeem` and not just one of them?
+### A.1 Why does the specification allow both `Withdraw` and `Redeem` and not just one of them?
We chose this design in order to reduce the amount of off-chain math required to be implemented by XRPL users and/or developers
-### A-1.2 Why can any account that holds Vaults shares submit a `VaultWithdraw` transaction?
+### A.2 Why can any account that holds Vaults shares submit a `VaultWithdraw` transaction?
The `VaultWithdraw` transaction does not respect the permissioned domain rules. In other words, any account that holds the shares of the Vault can withdraw them.
The decision was made to avoid a situation where a depositor deposits assets to a private vault to then have their access revoked by invalidating their credentials, and thus loosing access to their funds.
-### A-1.3 How can a depositor transfer shares to another account?
+### A.3 How can a depositor transfer shares to another account?
Vault shares are a first-class assets, meaning that they can be transfered and used in other on-ledger protocols that support MPTokens. However, the payee (or the receiver) of the shares must have permissions to hold the shares and the assset that the shares represent. For example, if the shares are for a private Vault containing `USDC` the destination account must be in the permissioned domain of the Vault, and have permissions to hold `USDC`.
In addion, any compliance mechanisms applied to `USDC` will also apply to the share. For example, if the Issuer of `USDC` freezes the Trustline of the payee, then the payee will not be able to receive any shares representing `USDC`.
-### A-1.4 What is the difference between the `VaultOwner` and the `pseudo-account`?
+### A.4 What is the difference between the `VaultOwner` and the `pseudo-account`?
XRP Ledger is an account based blockchain. That means that assets (XRP, IOU and MPT) must be held by an account. The Vault Object (or any other object, such as the AMM) cannot hold assets directly. Therefore, a pseudo-account is created that holds the assets on behalf of that object. The pseudo-account is a stand-alone account, that cannot receive funds, it cannot send transactions, it is there to only hold assets. So for example, when a depositor deposits assets into a vault, in reality this transaction moves the assets from the depositor account to the pseudo-account. Furthermore, the Vault `pseudo-account` is the Issuer of the Vault shares.
-### A-1.5 Do `VaultDeposit` or `VaultWithdraw` transactions charge transfer fees?
+### A.5 Do `VaultDeposit` or `VaultWithdraw` transactions charge transfer fees?
No, neither of the transactions charge transfer fees when depositing or withdrawing assets to and from the Vault.
From 29d603ea11ffb3adb7abb5b124c5a653aad35511 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 25 Feb 2026 14:15:08 +0100
Subject: [PATCH 12/27] addresses review feedback
---
XLS-0065-single-asset-vault/README.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index bd54fce4e..47dcdd0a3 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -36,19 +36,19 @@ The specification includes the following transactions:
- **`VaultDeposit`**: Deposits a specified number of assets into the Vault in exchange for shares.
- **`VaultWithdraw`**: Withdraws a specified number of assets from the Vault in exchange for shares.
-Additionally, an issuer can perform a **Clawback** operation:
+Additionally, an issuer can perform a **Clawback** operation:
- **`VaultClawback`**: Allows the issuer of an IOU or MPT to claw back funds from the vault, as outlined in the [Clawback documentation](https://xrpl.org/docs/use-cases/tokenization/stablecoin-issuer#clawback).
-#### 2.0.1 Vault Ownership and Management
+**Vault Ownership and Management**
A Single Asset Vault is owned and managed by an account called the **Vault Owner**. The account is reponsible for creating, updating and deleting the Vault object.
-#### 2.0.2 Access Control
+**Access Control**
A Single Asset Vault can be either public or private. Any depositor can deposit and redeem liquidity from a public vault, provided they own sufficient shares. In contrast, access to private shares is controlled via [Permissioned Domains](../XLS-0080-permissioned-domains/README.md), which use on-chain [Credentials](../XLS-0070-credentials/README.md) to manage access to the vault. Only depositors with the necessary credentials can deposit assets to a private vault. To prevent Vault Owner from locking away depositor funds, any shareholder can withdraw funds. Furthermore, the Vault Owner has an implicit permission to deposit and withdraw assets to and from the Vault. I.e. they do not have to have credentials in the Permissioned Domain.
-#### 2.0.3 Yield Bearing Shares
+**Yield Bearing Shares**
Shares represent the ownership of a portion of the vault's assets. On-chain shares are represented by a [Multi-Purpose Token](../XLS-0033-multi-purpose-tokens/README.md). When creating the vault, the Vault Owner can configure the shares to be non-transferable. Non-transferable shares cannot be transferred to any other account -- they can only be redeemed. If the vault is private, shares can be transferred and used in other DeFi protocols as long as the receiving account is authorized to hold the shares. The vault's shares may be yield-bearing, depending on the protocol connected to the vault, meaning that a holder may be able to withdraw more (or less) liquidity than they initially deposited.
@@ -114,7 +114,7 @@ The `Vault` object supports the following flags:
| ----------------- | :----------: | :---------: | :------------------------------------------: |
| `lsfVaultPrivate` | `0x00010000` | No | If set, indicates that the vault is private. |
-#### 3.1.3 Vault `_pseudo-account_`
+#### 3.1.3 Pseudo-Account
An AccountRoot entry holds the XRP, IOU or MPT deposited into the vault. It also acts as the issuer of the vault's shares. The _pseudo-account_ follows the XLS-64 specification for pseudo accounts. The `AccountRoot` object is created when creating the `Vault` object.
From 2ec8b80c9fadbe6b87ed5852eac1a2bc8a338085 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 25 Feb 2026 14:18:19 +0100
Subject: [PATCH 13/27] formatting
---
XLS-0065-single-asset-vault/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 47dcdd0a3..e569c1f29 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -36,7 +36,7 @@ The specification includes the following transactions:
- **`VaultDeposit`**: Deposits a specified number of assets into the Vault in exchange for shares.
- **`VaultWithdraw`**: Withdraws a specified number of assets from the Vault in exchange for shares.
-Additionally, an issuer can perform a **Clawback** operation:
+Additionally, an issuer can perform a **Clawback** operation:
- **`VaultClawback`**: Allows the issuer of an IOU or MPT to claw back funds from the vault, as outlined in the [Clawback documentation](https://xrpl.org/docs/use-cases/tokenization/stablecoin-issuer#clawback).
From 72b7ab3f7be3b2afdabdb8cc3d557b325cd9ac87 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 25 Feb 2026 16:57:01 +0100
Subject: [PATCH 14/27] adds error codes to failure conditions
---
XLS-0065-single-asset-vault/README.md | 211 ++++++++++++++------------
1 file changed, 117 insertions(+), 94 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index e569c1f29..2f36d8330 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -78,7 +78,7 @@ The **`Vault`** ledger entry describes the state of the tokenized vault.
The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order:
- The `Vault` space key `0x0056` (capital V)
-- The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
+- The [`ACCOUNTID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
- The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value.
#### 3.1.2 Fields
@@ -94,7 +94,7 @@ A vault has the following fields:
| `PreviousTxnLgrSeq` | No | Yes | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `Sequence` | No | Yes | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
| `OwnerNode` | No | Yes | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
-| `Owner` | No | Yes | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
+| `Owner` | No | Yes | `string` | `ACCOUNTID` | `N/A` | The account address of the Vault Owner. |
| `Account` | No | Yes | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
| `Data` | Yes | No | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
| `Asset` | No | Yes | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
@@ -161,7 +161,7 @@ Here's the table with the headings "Field," "Description," and "Value":
| **Field** | **Description** | **Value** |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
-| `Issuer` | The AccountID of the Vault's _pseudo-account_. | _pseudo-account_ ID |
+| `Issuer` | The ACCOUNTID of the Vault's _pseudo-account_. | _pseudo-account_ ID |
| `MaximumAmount` | No limit to the number of shares that can be issued. | `0xFFFFFFFFFFFFFFFF` |
| `TransferFee` | The fee paid to transfer the shares. | 0 |
| `MPTokenMetadata` | Arbitrary metadata about the share MPT, in hex format. | - |
@@ -404,23 +404,27 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
#### 3.2.5 Failure Conditions
-1. The `Asset` is `XRP`:
- 1. The `Scale` parameter is provided.
+##### 3.2.5.1 Data Verification
-2. The `Asset` is `MPT`:
- 1. The `Scale` parameter is provided.
- 2. The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
- 3. The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
+1. `Data` field exceeds 256 bytes. (`temMALFORMED`)
+2. `WithdrawalPolicy` is provided and is not `first-come-first-serve`. (`temMALFORMED`)
+3. `DomainID` is provided but is zero. (`temMALFORMED`)
+4. `DomainID` is provided but `tfVaultPrivate` flag is not set. (`temMALFORMED`)
+5. `AssetsMaximum` is negative. (`temMALFORMED`)
+6. `MPTokenMetadata` is provided but is empty or exceeds the maximum length. (`temMALFORMED`)
+7. `Scale` is provided and the `Asset` is `XRP` or `MPT` (only valid for `IOU`). (`temMALFORMED`)
+8. `Scale` is provided and exceeds 18. (`temMALFORMED`)
-3. The `Asset` is an `IOU`:
- 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- 2. The `Scale` parameter is provided, and is less than **0** or greater than **18**.
+##### 3.2.5.2 Protocol-Level Failures
-4. The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
-
-5. The `PermissionedDomain` object does not exist with the provided `DomainID`.
-6. The `Data` field is larger than 256 bytes.
-7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
+1. The `Asset` is an `IOU` and the issuer account does not exist. (`terNO_ACCOUNT`)
+2. The `Asset` is an `IOU` and the issuer has not enabled `lsfDefaultRipple`. (`terNO_RIPPLE`)
+3. The `Asset` is an `MPT` and the `MPTokenIssuance` object does not exist. (`tecOBJECT_NOT_FOUND`)
+4. The `Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance`. (`tecNO_AUTH`)
+5. The `Asset`'s issuer is a pseudo-account (e.g. vault shares or AMM LP tokens). (`tecWRONG_ASSET`)
+6. The `Asset` is frozen or locked for the transaction submitter. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
+7. The `PermissionedDomain` object does not exist for the provided `DomainID`. (`tecOBJECT_NOT_FOUND`)
+8. The submitting account has insufficient balance to meet the owner reserve. (`tecINSUFFICIENT_RESERVE`)
#### 3.2.6 State Changes
@@ -470,23 +474,28 @@ The `VaultSet` updates an existing `Vault` ledger object.
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :-------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `Uint16` | `59` | The transaction type. |
-| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
-| `Data` | No | `string` | `Blob` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `AssetsMaximum` | No | `number` | `Number` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
-| `DomainID` | No | `string` | `Hash256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| `TransactionType` | Yes | `string` | `UINT16` | `59` | The transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the Vault to be modified. Must be included when updating the Vault. |
+| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `AssetsMaximum` | No | `number` | `NUMBER` | | The maximum asset amount that can be held in a vault. The value cannot be lower than the current `AssetsTotal` unless the value is `0`. |
+| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
#### 3.3.2 Failure Conditions
-1. `Vault` object with the specified `VaultID` does not exist on the ledger.
-2. The submitting account is not the `Owner` of the vault.
-3. The `Data` field is larger than 256 bytes.
-4. If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
- 1. The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
-5. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
-6. The `PermissionedDomain` object does not exist with the provided `DomainID`.
-7. The transaction is attempting to modify an immutable field.
-8. The transaction does not specify any of the modifiable fields.
+##### 3.3.2.1 Data Verification
+
+1. `VaultID` is zero. (`temMALFORMED`)
+2. `Data` is present but empty or exceeds 256 bytes. (`temMALFORMED`)
+3. `AssetsMaximum` is negative. (`temMALFORMED`)
+4. None of `DomainID`, `AssetsMaximum`, or `Data` are present (nothing to update). (`temMALFORMED`)
+
+##### 3.3.2.2 Protocol-Level Failures
+
+1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
+2. The submitting account is not the `Owner` of the vault. (`tecNO_PERMISSION`)
+3. `DomainID` is provided and the vault does not have `lsfVaultPrivate` set. (`tecNO_PERMISSION`)
+4. `DomainID` is provided, is non-zero, and the `PermissionedDomain` object does not exist. (`tecOBJECT_NOT_FOUND`)
+5. `AssetsMaximum` is non-zero and less than the current `Vault.AssetsTotal`. (`tecLIMIT_EXCEEDED`)
#### 3.3.3 State Changes
@@ -524,15 +533,23 @@ The `VaultDelete` transaction deletes an existing vault object.
| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :--------------------------------: |
-| `TransactionType` | Yes | `string` | `Uint16` | `60` | Transaction type. |
-| `VaultID` | Yes | `string` | `Hash256` | `N/A` | The ID of the vault to be deleted. |
+| `TransactionType` | Yes | `string` | `UINT16` | `60` | Transaction type. |
+| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault to be deleted. |
#### 3.4.2 Failure Conditions
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The submitting account is not the `Owner` of the vault.
-3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
-4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
+##### 3.4.2.1 Data Verification
+
+1. `VaultID` is zero. (`temMALFORMED`)
+
+##### 3.4.2.2 Protocol-Level Failures
+
+1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
+2. The submitting account is not the `Owner` of the vault. (`tecNO_PERMISSION`)
+3. `Vault.AssetsAvailable` is greater than zero. (`tecHAS_OBLIGATIONS`)
+4. `Vault.AssetsTotal` is greater than zero. (`tecHAS_OBLIGATIONS`)
+5. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` is greater than zero. (`tecHAS_OBLIGATIONS`)
+6. The vault pseudo-account's `OwnerCount` is non-zero. (`tecHAS_OBLIGATIONS`)
#### 3.4.3 State Changes
@@ -576,22 +593,26 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
#### 3.5.2 Failure Conditions
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The asset type of the vault does not match the asset type the depositor is depositing.
-3. The depositor does not have sufficient funds to make a deposit.
-4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
-5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+##### 3.5.2.1 Data Verification
+
+1. `VaultID` is zero. (`temMALFORMED`)
+2. `Amount` is zero or negative. (`temBAD_AMOUNT`)
-6. The `Vault.Asset` is `MPT`:
- 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
- 4. `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
+##### 3.5.2.2 Protocol-Level Failures
-7. The `Asset` is an `IOU`:
- 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
- 3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
+1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
+2. The `Amount` asset does not match `Vault.Asset`. (`tecWRONG_ASSET`)
+3. The `Vault.Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance` (asset is non-transferable). (`tecNO_AUTH`)
+4. The `Vault.Asset` is an `IOU` and rippling is disabled on both the depositor's and vault's trust lines with the issuer. (`terNO_RIPPLE`)
+5. The `Vault.Asset` is frozen for the depositor. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
+6. The vault shares (`MPTokenIssuance`) are locked for the depositor. (`tecLOCKED`)
+7. The vault is private, the depositor is not the vault owner, and the vault has no `DomainID` set. (`tecNO_AUTH`)
+8. The vault is private, the depositor is not the vault owner, and the depositor does not have valid credentials in the vault's `PermissionedDomain`. (`tecNO_AUTH` / `tecOBJECT_NOT_FOUND`)
+9. The `Vault.Asset` is an `MPT` and the depositor does not have an authorized `MPToken` for the asset. (`tecNO_AUTH`)
+10. The depositor has insufficient balance to cover `Amount`. (`tecINSUFFICIENT_FUNDS`)
+11. Depositing `Amount` would cause `Vault.AssetsTotal` to exceed `Vault.AssetsMaximum`. (`tecLIMIT_EXCEEDED`)
+12. The deposit amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
+13. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
#### 3.5.3 State Changes
@@ -654,7 +675,7 @@ The `VaultWithdraw` transaction withdraws assets in exchange for the vault's sha
| `TransactionType` | Yes | `string` | `UINT16` | `62` | Transaction type. |
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
| `Amount` | Yes | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
-| `Destination` | No | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
+| `Destination` | No | `string` | `ACCOUNTID` | Empty | An account to receive the assets. It must be able to receive the asset. |
| `DestinationTag` | No | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
- If `Amount` is the Vaults asset, calculate the share cost using the [**Withdraw formula**](#21723-withdraw).
@@ -670,33 +691,29 @@ In sections below assume the following variables:
#### 3.6.2 Failure Conditions
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-
-2. The `Vault.Asset` is `MPT`:
- 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
-
-3. The `Asset` is an `IOU`:
- 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
-
-4. The unit of `Amount` is not shares of the vault.
-5. The unit of `Amount` is not asset of the vault.
-
-6. There is insufficient liquidity in the vault to fill the request:
- 1. If `Amount` is the vaults share:
- 1. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
- 2. The shares `MPToken(MPTokenIssuanceID, AccountID | Destination).MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
- 3. `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
-
-7. If `Amount` is the vaults asset:
- 1. The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
- 2. `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
-
-8. The `Destination` account is specified:
- 1. The account does not have permission to receive the asset.
- 2. The account does not have a `RippleState` or `MPToken` object for the asset.
+##### 3.6.2.1 Data Verification
+
+1. `VaultID` is zero. (`temMALFORMED`)
+2. `Amount` is zero or negative. (`temBAD_AMOUNT`)
+3. `Destination` is present but is zero. (`temMALFORMED`)
+
+##### 3.6.2.2 Protocol-Level Failures
+
+1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
+2. The `Amount` asset is neither `Vault.Asset` nor the vault's share `MPTokenIssuance`. (`tecWRONG_ASSET`)
+3. The `Vault.Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance` (asset is non-transferable). (`tecNO_AUTH`)
+4. The `Vault.Asset` is an `IOU` and rippling is disabled on both the vault's and destination's trust lines with the issuer. (`terNO_RIPPLE`)
+5. The destination account does not exist. (`tecNO_DST`)
+6. The destination account requires a destination tag and none was provided. (`tecDST_TAG_NEEDED`)
+7. The destination account has `lsfDepositAuth` set and the submitting account is not preauthorized. (`tecNO_PERMISSION`)
+8. The `Vault.Asset` is an `IOU`, the destination is a third party, and the withdrawal amount would exceed the destination's trust line limit. (`tecNO_LINE`)
+9. The destination's `IOU` trust line does not exist or is not authorized. (`tecNO_LINE` / `tecNO_AUTH`)
+10. The `Vault.Asset` is frozen for the destination account. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
+11. The vault shares are frozen for the submitting account. (`tecLOCKED`)
+12. The submitting account holds fewer shares than required to cover the withdrawal. (`tecINSUFFICIENT_FUNDS`)
+13. `Vault.AssetsAvailable` is less than the assets to be withdrawn. (`tecINSUFFICIENT_FUNDS`)
+14. The withdrawal amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
+15. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
#### 3.6.3 State Changes
@@ -761,26 +778,32 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | Yes | `string` | `UINT16` | `63` | Transaction type. |
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
+| `Holder` | Yes | `string` | `ACCOUNTID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
#### 3.7.2 Failure Conditions
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-
-2. If `Vault.Asset` is `XRP`.
+##### 3.7.2.1 Data Verification
-3. If `Vault.Asset` is an `IOU` and:
- 1. The `Issuer` account is not the submitter of the transaction.
- 2. If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
- 3. If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
+1. `VaultID` is zero. (`temMALFORMED`)
+2. `Amount` is negative. (`temBAD_AMOUNT`)
+3. `Amount` is provided and the asset is `XRP`. (`temMALFORMED`)
-4. If `Vault.Asset` is an `MPT` and:
- 1. `MPTokenIssuance.Issuer` is not the submitter of the transaction.
- 2. `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
- 3. If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
+##### 3.7.2.2 Protocol-Level Failures
-5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
+1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
+2. `Amount` is not provided and `Vault.Asset` is not `XRP`, and the asset issuer is the vault owner — the caller must specify an explicit `Amount` to resolve the ambiguity. (`tecWRONG_ASSET`)
+3. The resolved clawback asset is the vault share (`MPTokenIssuance`) and the submitting account is not the vault owner. (`tecNO_PERMISSION`)
+4. The resolved clawback asset is the vault share and `Vault.AssetsTotal` or `Vault.AssetsAvailable` is non-zero (vault owner can only burn shares when vault has no assets). (`tecNO_PERMISSION`)
+5. The resolved clawback asset is the vault share, `Amount` is non-zero, and it does not equal the holder's full share balance (vault owner must burn all shares at once). (`tecLIMIT_EXCEEDED`)
+6. The resolved clawback asset is `Vault.Asset` and `Vault.Asset` is `XRP`. (`tecNO_PERMISSION`)
+7. The resolved clawback asset is `Vault.Asset` and the submitting account is not the asset issuer. (`tecNO_PERMISSION`)
+8. The submitting account and `Holder` are the same account (issuer cannot clawback from itself). (`tecNO_PERMISSION`)
+9. The `Vault.Asset` is an `MPT` and the `MPTokenIssuance` object does not exist. (`tecOBJECT_NOT_FOUND`)
+10. The `Vault.Asset` is an `MPT` and `lsfMPTCanClawback` is not set on the `MPTokenIssuance`. (`tecNO_PERMISSION`)
+11. The `Vault.Asset` is an `IOU` and the issuer does not have `lsfAllowTrustLineClawback` set, or has `lsfNoFreeze` set. (`tecNO_PERMISSION`)
+12. The resolved clawback amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
+13. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
#### 3.7.3 State Changes
@@ -841,8 +864,8 @@ The Single Asset Vault does not introduce or modify any `Payment` transaction fi
3. The `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ 3. `MPToken(MPTokenIssuanceID, ACCOUNTID).lsfMPTLocked` flag is set (the asset is locked for the payer).
+ 4. `MPToken(MPTokenIssuanceID, PseudoACCOUNTID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
4. The `Vault.Asset` is an `IOU`:
From e3ce4a2848afcb478b89845ff1f7fac035d15e3c Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:55:28 +0200
Subject: [PATCH 15/27] address review comments
---
XLS-0065-single-asset-vault/README.md | 118 +++++++++++++-------------
1 file changed, 60 insertions(+), 58 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 2f36d8330..1578703bc 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -8,7 +8,7 @@
category: Amendment
requires: [XLS-33](../XLS-0033-multi-purpose-tokens/README.md)
created: 2024-04-12
- updated: 2026-02-11
+ updated: 2026-04-27
# Single Asset Vault
@@ -23,7 +23,9 @@ The **Single Asset Vault** is an on-chain object that aggregates assets from one
The specification introduces a new `Vault` ledger entry, which contains key details such as available assets, shares, total value, and other relevant information.
-The specification includes the following transactions:
+### 2.1. Transactions
+
+2. The specification includes the following transactions:
**Vault Management**:
@@ -40,30 +42,30 @@ Additionally, an issuer can perform a **Clawback** operation:
- **`VaultClawback`**: Allows the issuer of an IOU or MPT to claw back funds from the vault, as outlined in the [Clawback documentation](https://xrpl.org/docs/use-cases/tokenization/stablecoin-issuer#clawback).
-**Vault Ownership and Management**
+### 2.2. Vault Ownership and Management
A Single Asset Vault is owned and managed by an account called the **Vault Owner**. The account is reponsible for creating, updating and deleting the Vault object.
-**Access Control**
+### 2.3. Access Control
A Single Asset Vault can be either public or private. Any depositor can deposit and redeem liquidity from a public vault, provided they own sufficient shares. In contrast, access to private shares is controlled via [Permissioned Domains](../XLS-0080-permissioned-domains/README.md), which use on-chain [Credentials](../XLS-0070-credentials/README.md) to manage access to the vault. Only depositors with the necessary credentials can deposit assets to a private vault. To prevent Vault Owner from locking away depositor funds, any shareholder can withdraw funds. Furthermore, the Vault Owner has an implicit permission to deposit and withdraw assets to and from the Vault. I.e. they do not have to have credentials in the Permissioned Domain.
-**Yield Bearing Shares**
+### 2.4. Yield Bearing Shares
Shares represent the ownership of a portion of the vault's assets. On-chain shares are represented by a [Multi-Purpose Token](../XLS-0033-multi-purpose-tokens/README.md). When creating the vault, the Vault Owner can configure the shares to be non-transferable. Non-transferable shares cannot be transferred to any other account -- they can only be redeemed. If the vault is private, shares can be transferred and used in other DeFi protocols as long as the receiving account is authorized to hold the shares. The vault's shares may be yield-bearing, depending on the protocol connected to the vault, meaning that a holder may be able to withdraw more (or less) liquidity than they initially deposited.
-### 2.1 Terminology
+### 2.5. Terminology
- **Vault**: A ledger entry for aggregating liquidity and providing this liquidity to one or more accessors.
- **Asset**: The currency of a vault. It is either XRP, a [Fungible Token](https://xrpl.org/docs/concepts/tokens/fungible-tokens/) or a [Multi-Purpose Token](../XLS-0033-multi-purpose-tokens/README.md).
- **Share**: Shares represent the depositors' portion of the vault's assets. Shares are a [Multi-Purpose Token](../XLS-0033-multi-purpose-tokens/README.md) created by the _pseudo-account_ of the vault.
-### 2.2 Actors
+### 2.6. Actors
- **Vault Owner**: An account responsible for creating and deleting The Vault.
- **Depositor**: An entity that deposits and withdraws assets to and from the vault.
-### 2.3 Connecting to the Vault
+### 2.7. Connecting to the Vault
A protocol connecting to a Vault must track its debt. Furthermore, the updates to the Vault state when funds are removed or added back must be handled in the transactors of the protocol. For an example, please refer to the [Lending Protocol](../XLS-0066-lending-protocol/README.md) specification.
@@ -319,21 +321,21 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
#### 3.1.10 Invariants
-- A transaction must not modify more than one `Vault` object.
-- `.Asset == '.Asset` (the asset is immutable).
-- `.Account == '.Account` (the _pseudo-account_ is immutable).
-- `.ShareMPTID == '.ShareMPTID` (the share MPT ID is immutable).
-- An updated `Vault` must always have an associated `MPTokenIssuance` (shares) object.
-- `IF MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0 THEN '.AssetsTotal == 0 AND '.AssetsAvailable == 0`.
-- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount <= MPTokenIssuance(Vault.ShareMPTID).MaximumAmount`.
-- `'.AssetsAvailable >= 0`.
-- `'.AssetsAvailable <= '.AssetsTotal`.
-- `'.LossUnrealized <= '.AssetsTotal - '.AssetsAvailable`.
-- `'.AssetsTotal >= 0`.
-- `'.AssetsMaximum >= 0`.
-- Only `VaultCreate` may create a new `Vault` object.
-- Only `VaultDelete` may delete a `Vault` object.
-- `.LossUnrealized == '.LossUnrealized` for all vault transactions (only protocol transactions such as `LoanManage` and `LoanPay` may change `LossUnrealized`).
+1. A transaction must not modify more than one `Vault` object.
+2. `.Asset == '.Asset` (the asset is immutable).
+3. `.Account == '.Account` (the _pseudo-account_ is immutable).
+4. `.ShareMPTID == '.ShareMPTID` (the share MPT ID is immutable).
+5. An updated `Vault` must always have an associated `MPTokenIssuance` (shares) object.
+6. `IF MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0 THEN '.AssetsTotal == 0 AND '.AssetsAvailable == 0`.
+7. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount <= MPTokenIssuance(Vault.ShareMPTID).MaximumAmount`.
+8. `'.AssetsAvailable >= 0`.
+9. `'.AssetsAvailable <= '.AssetsTotal`.
+10. `'.LossUnrealized <= '.AssetsTotal - '.AssetsAvailable`.
+11. `'.AssetsTotal >= 0`.
+12. `'.AssetsMaximum >= 0`.
+13. Only `VaultCreate` may create a new `Vault` object.
+14. Only `VaultDelete` may delete a `Vault` object.
+15. `.LossUnrealized == '.LossUnrealized` for all vault transactions (only protocol transactions such as `LoanManage` and `LoanPay` may change `LossUnrealized`).
#### 3.1.11 Example JSON
@@ -442,11 +444,11 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
#### 3.2.7 Invariants
-- The transaction must not modify an existing `Vault` object (i.e. no before-state).
-- `'.AssetsAvailable == 0 AND '.AssetsTotal == 0 AND '.LossUnrealized == 0 AND MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (created vault must be empty).
-- `'.Account == MPTokenIssuance(Vault.ShareMPTID).Issuer` (shares issuer must equal the vault's _pseudo-account_).
-- The shares issuer `AccountRoot` must exist and must be a _pseudo-account_.
-- The shares issuer _pseudo-account_ must reference the created vault via its `VaultID` field.
+1. The transaction must not modify an existing `Vault` object (i.e. no before-state).
+2. `'.AssetsAvailable == 0 AND '.AssetsTotal == 0 AND '.LossUnrealized == 0 AND MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (created vault must be empty).
+3. `'.Account == MPTokenIssuance(Vault.ShareMPTID).Issuer` (shares issuer must equal the vault's _pseudo-account_).
+4. The shares issuer `AccountRoot` must exist and must be a _pseudo-account_.
+5. The shares issuer _pseudo-account_ must reference the created vault via its `VaultID` field.
#### 3.2.8 Example JSON
@@ -505,11 +507,11 @@ The `VaultSet` updates an existing `Vault` ledger object.
#### 3.3.4 Invariants
-- The _pseudo-account_ asset balance must not change.
-- `.AssetsTotal == '.AssetsTotal` (assets total must not change).
-- `.AssetsAvailable == '.AssetsAvailable` (assets available must not change).
-- `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
-- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must not change.
+1. The _pseudo-account_ asset balance must not change.
+2. `.AssetsTotal == '.AssetsTotal` (assets total must not change).
+3. `.AssetsAvailable == '.AssetsAvailable` (assets available must not change).
+4. `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
+5. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must not change.
#### 3.3.5 Example JSON
@@ -561,11 +563,11 @@ The `VaultDelete` transaction deletes an existing vault object.
#### 3.4.4 Invariants
-- The `Vault` object must be deleted (i.e. no after-state).
-- The `MPTokenIssuance` object for the vault shares must also be deleted.
-- `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (no shares outstanding at time of deletion).
-- `.AssetsTotal == 0` (no assets outstanding at time of deletion).
-- `.AssetsAvailable == 0` (no assets available at time of deletion).
+1. The `Vault` object must be deleted (i.e. no after-state).
+2. The `MPTokenIssuance` object for the vault shares must also be deleted.
+3. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (no shares outstanding at time of deletion).
+4. `.AssetsTotal == 0` (no assets outstanding at time of deletion).
+5. `.AssetsAvailable == 0` (no assets available at time of deletion).
#### 3.4.5 Example JSON
@@ -638,14 +640,14 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the depositor's share balance.
-- The _pseudo-account_ asset balance must increase: $\Delta_{asset} > 0$.
-- $\Delta_{asset}$ must not exceed `Amount`.
-- The depositor's asset balance must decrease by $\Delta_{asset}$ (unless the depositor is the asset issuer).
-- `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
-- $\Delta_{share} > 0$ (depositor shares must increase).
-- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
+1. The _pseudo-account_ asset balance must increase: $\Delta_{asset} > 0$.
+2. $\Delta_{asset}$ must not exceed `Amount`.
+3. The depositor's asset balance must decrease by $\Delta_{asset}$ (unless the depositor is the asset issuer).
+4. `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
+5. $\Delta_{share} > 0$ (depositor shares must increase).
+6. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
+7. `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+8. `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
#### 3.5.5 Example JSON
@@ -744,12 +746,12 @@ In sections below assume the following variables:
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the withdrawer's share balance.
-- The _pseudo-account_ asset balance must decrease: $\Delta_{asset} < 0$.
-- Exactly one destination account balance must increase by $|\Delta_{asset}|$ (unless the destination is the asset issuer).
-- $\Delta_{share} < 0$ (withdrawer shares must decrease).
-- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-- `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-- `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
+1. The _pseudo-account_ asset balance must decrease: $\Delta_{asset} < 0$.
+2. Exactly one destination account balance must increase by $|\Delta_{asset}|$ (unless the destination is the asset issuer).
+3. $\Delta_{share} < 0$ (withdrawer shares must decrease).
+4. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
+5. `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+6. `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
#### 3.6.5 Example JSON
@@ -826,12 +828,12 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the holder's share balance.
-- The transaction submitter must be the asset issuer, or the vault owner of an empty vault with outstanding shares.
-- If the vault holds assets: $\Delta_{asset} < 0$ (vault balance must decrease).
-- If the vault holds assets: `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-- If the vault holds assets: `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-- $\Delta_{share} < 0$ (holder shares must decrease).
-- The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
+1. The transaction submitter must be the asset issuer, or the vault owner of an empty vault with outstanding shares.
+2. If the vault holds assets: $\Delta_{asset} < 0$ (vault balance must decrease).
+3. If the vault holds assets: `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
+4. If the vault holds assets: `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
+5. $\Delta_{share} < 0$ (holder shares must decrease).
+6. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
#### 3.7.5 Example JSON
From 746d4f11094ff0845ede523257e8f16c0ea5784c Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Mon, 27 Apr 2026 11:57:41 +0200
Subject: [PATCH 16/27] clean up
---
XLS-0065-single-asset-vault/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 1578703bc..7b1ce27ed 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -25,7 +25,7 @@ The specification introduces a new `Vault` ledger entry, which contains key deta
### 2.1. Transactions
-2. The specification includes the following transactions:
+The specification includes the following transactions:
**Vault Management**:
From f2dbe60859b527df144c1cc1e872b5a85f935031 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 6 May 2026 13:23:50 +0200
Subject: [PATCH 17/27] address PR review
---
XLS-0065-single-asset-vault/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 7b1ce27ed..c4f8c822b 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -163,7 +163,7 @@ Here's the table with the headings "Field," "Description," and "Value":
| **Field** | **Description** | **Value** |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
-| `Issuer` | The ACCOUNTID of the Vault's _pseudo-account_. | _pseudo-account_ ID |
+| `Issuer` | The AccountID of the Vault's _pseudo-account_. | _pseudo-account_ ID |
| `MaximumAmount` | No limit to the number of shares that can be issued. | `0xFFFFFFFFFFFFFFFF` |
| `TransferFee` | The fee paid to transfer the shares. | 0 |
| `MPTokenMetadata` | Arbitrary metadata about the share MPT, in hex format. | - |
From 79af1cde5a05cb7b253108ef5b1db32423a2d619 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 26 May 2026 17:34:18 +0200
Subject: [PATCH 18/27] revert functional changes, keep template compliance
Remove functional additions (invariants, example JSONs, error codes)
added in this branch and retain only structural changes that bring
the spec into conformance with AMENDMENT_TEMPLATE.md and XLS_TEMPLATE.md.
Co-Authored-By: Claude Sonnet 4.6
---
XLS-0065-single-asset-vault/README.md | 468 +++++++-------------------
1 file changed, 124 insertions(+), 344 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index c4f8c822b..a02f3fe51 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -321,49 +321,7 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
#### 3.1.10 Invariants
-1. A transaction must not modify more than one `Vault` object.
-2. `.Asset == '.Asset` (the asset is immutable).
-3. `.Account == '.Account` (the _pseudo-account_ is immutable).
-4. `.ShareMPTID == '.ShareMPTID` (the share MPT ID is immutable).
-5. An updated `Vault` must always have an associated `MPTokenIssuance` (shares) object.
-6. `IF MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0 THEN '.AssetsTotal == 0 AND '.AssetsAvailable == 0`.
-7. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount <= MPTokenIssuance(Vault.ShareMPTID).MaximumAmount`.
-8. `'.AssetsAvailable >= 0`.
-9. `'.AssetsAvailable <= '.AssetsTotal`.
-10. `'.LossUnrealized <= '.AssetsTotal - '.AssetsAvailable`.
-11. `'.AssetsTotal >= 0`.
-12. `'.AssetsMaximum >= 0`.
-13. Only `VaultCreate` may create a new `Vault` object.
-14. Only `VaultDelete` may delete a `Vault` object.
-15. `.LossUnrealized == '.LossUnrealized` for all vault transactions (only protocol transactions such as `LoanManage` and `LoanPay` may change `LossUnrealized`).
-
-#### 3.1.11 Example JSON
-
-```json
-{
- "LedgerEntryType": "Vault",
- "LedgerIndex": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Flags": 0,
- "PreviousTxnID": "",
- "PreviousTxnLgrSeq": 0,
- "Sequence": 3990518,
- "OwnerNode": "0",
- "Owner": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
- "Account": "rDe7soaAox8bk2Srfi7n7Y1wzbmcn8RksQ",
- "Data": "555344204C656E64696E67205661756C74",
- "Asset": {
- "currency": "USD",
- "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb"
- },
- "AssetsTotal": "0",
- "AssetsAvailable": "0",
- "LossUnrealized": "0",
- "AssetsMaximum": "100000",
- "ShareMPTID": "000000018AB77A8ADC472FBB7991AA311AAEB5D2FA7A793B",
- "WithdrawalPolicy": 1,
- "Scale": 6
-}
-```
+**TBD**
### 3.2 Transaction: `VaultCreate`
@@ -408,25 +366,19 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
##### 3.2.5.1 Data Verification
-1. `Data` field exceeds 256 bytes. (`temMALFORMED`)
-2. `WithdrawalPolicy` is provided and is not `first-come-first-serve`. (`temMALFORMED`)
-3. `DomainID` is provided but is zero. (`temMALFORMED`)
-4. `DomainID` is provided but `tfVaultPrivate` flag is not set. (`temMALFORMED`)
-5. `AssetsMaximum` is negative. (`temMALFORMED`)
-6. `MPTokenMetadata` is provided but is empty or exceeds the maximum length. (`temMALFORMED`)
-7. `Scale` is provided and the `Asset` is `XRP` or `MPT` (only valid for `IOU`). (`temMALFORMED`)
-8. `Scale` is provided and exceeds 18. (`temMALFORMED`)
+1. The `Asset` is `XRP` and the `Scale` parameter is provided.
+2. The `Asset` is `MPT` and the `Scale` parameter is provided.
+3. The `Asset` is an `IOU` and the `Scale` parameter is provided and is less than **0** or greater than **18**.
+4. The `tfVaultPrivate` flag is not set and `DomainID` is provided.
+5. The `Data` field is larger than 256 bytes.
##### 3.2.5.2 Protocol-Level Failures
-1. The `Asset` is an `IOU` and the issuer account does not exist. (`terNO_ACCOUNT`)
-2. The `Asset` is an `IOU` and the issuer has not enabled `lsfDefaultRipple`. (`terNO_RIPPLE`)
-3. The `Asset` is an `MPT` and the `MPTokenIssuance` object does not exist. (`tecOBJECT_NOT_FOUND`)
-4. The `Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance`. (`tecNO_AUTH`)
-5. The `Asset`'s issuer is a pseudo-account (e.g. vault shares or AMM LP tokens). (`tecWRONG_ASSET`)
-6. The `Asset` is frozen or locked for the transaction submitter. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
-7. The `PermissionedDomain` object does not exist for the provided `DomainID`. (`tecOBJECT_NOT_FOUND`)
-8. The submitting account has insufficient balance to meet the owner reserve. (`tecINSUFFICIENT_RESERVE`)
+1. The `Asset` is `MPT` and `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object (the asset is not transferable).
+2. The `Asset` is `MPT` and the `lsfMPTLocked` flag is set in the `MPTokenIssuance` object (the asset is locked).
+3. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+4. The `PermissionedDomain` object does not exist with the provided `DomainID`.
+5. The account submitting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
#### 3.2.6 State Changes
@@ -444,29 +396,7 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
#### 3.2.7 Invariants
-1. The transaction must not modify an existing `Vault` object (i.e. no before-state).
-2. `'.AssetsAvailable == 0 AND '.AssetsTotal == 0 AND '.LossUnrealized == 0 AND MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (created vault must be empty).
-3. `'.Account == MPTokenIssuance(Vault.ShareMPTID).Issuer` (shares issuer must equal the vault's _pseudo-account_).
-4. The shares issuer `AccountRoot` must exist and must be a _pseudo-account_.
-5. The shares issuer _pseudo-account_ must reference the created vault via its `VaultID` field.
-
-#### 3.2.8 Example JSON
-
-```json
-{
- "TransactionType": "VaultCreate",
- "Flags": 0,
- "Data": "555344204C656E64696E67205661756C74",
- "Asset": {
- "currency": "USD",
- "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb"
- },
- "AssetsMaximum": "100000",
- "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
- "Fee": "200000",
- "Sequence": 3990518
-}
-```
+**TBD**
### 3.3 Transaction: `VaultSet`
@@ -486,18 +416,17 @@ The `VaultSet` updates an existing `Vault` ledger object.
##### 3.3.2.1 Data Verification
-1. `VaultID` is zero. (`temMALFORMED`)
-2. `Data` is present but empty or exceeds 256 bytes. (`temMALFORMED`)
-3. `AssetsMaximum` is negative. (`temMALFORMED`)
-4. None of `DomainID`, `AssetsMaximum`, or `Data` are present (nothing to update). (`temMALFORMED`)
+1. The `Data` field is larger than 256 bytes.
+2. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
+3. The transaction is attempting to modify an immutable field.
+4. The transaction does not specify any of the modifiable fields.
##### 3.3.2.2 Protocol-Level Failures
-1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
-2. The submitting account is not the `Owner` of the vault. (`tecNO_PERMISSION`)
-3. `DomainID` is provided and the vault does not have `lsfVaultPrivate` set. (`tecNO_PERMISSION`)
-4. `DomainID` is provided, is non-zero, and the `PermissionedDomain` object does not exist. (`tecOBJECT_NOT_FOUND`)
-5. `AssetsMaximum` is non-zero and less than the current `Vault.AssetsTotal`. (`tecLIMIT_EXCEEDED`)
+1. `Vault` object with the specified `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND the `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
+4. The `PermissionedDomain` object does not exist with the provided `DomainID`.
#### 3.3.3 State Changes
@@ -507,25 +436,7 @@ The `VaultSet` updates an existing `Vault` ledger object.
#### 3.3.4 Invariants
-1. The _pseudo-account_ asset balance must not change.
-2. `.AssetsTotal == '.AssetsTotal` (assets total must not change).
-3. `.AssetsAvailable == '.AssetsAvailable` (assets available must not change).
-4. `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
-5. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must not change.
-
-#### 3.3.5 Example JSON
-
-```json
-{
- "TransactionType": "VaultSet",
- "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Data": "5570646174656420566F756C74204D65746164617461",
- "AssetsMaximum": "200000",
- "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
- "Fee": "10",
- "Sequence": 3990519
-}
-```
+**TBD**
### 3.4 Transaction: `VaultDelete`
@@ -542,16 +453,14 @@ The `VaultDelete` transaction deletes an existing vault object.
##### 3.4.2.1 Data Verification
-1. `VaultID` is zero. (`temMALFORMED`)
+_None._
##### 3.4.2.2 Protocol-Level Failures
-1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
-2. The submitting account is not the `Owner` of the vault. (`tecNO_PERMISSION`)
-3. `Vault.AssetsAvailable` is greater than zero. (`tecHAS_OBLIGATIONS`)
-4. `Vault.AssetsTotal` is greater than zero. (`tecHAS_OBLIGATIONS`)
-5. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` is greater than zero. (`tecHAS_OBLIGATIONS`)
-6. The vault pseudo-account's `OwnerCount` is non-zero. (`tecHAS_OBLIGATIONS`)
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
+4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
#### 3.4.3 State Changes
@@ -563,23 +472,7 @@ The `VaultDelete` transaction deletes an existing vault object.
#### 3.4.4 Invariants
-1. The `Vault` object must be deleted (i.e. no after-state).
-2. The `MPTokenIssuance` object for the vault shares must also be deleted.
-3. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount == 0` (no shares outstanding at time of deletion).
-4. `.AssetsTotal == 0` (no assets outstanding at time of deletion).
-5. `.AssetsAvailable == 0` (no assets available at time of deletion).
-
-#### 3.4.5 Example JSON
-
-```json
-{
- "TransactionType": "VaultDelete",
- "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Account": "rfMpGAdXe6pjgnVisNe5FbCSxQ7YkfQG2D",
- "Fee": "10",
- "Sequence": 3990520
-}
-```
+**TBD**
### 3.5 Transaction: `VaultDeposit`
@@ -597,24 +490,22 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
##### 3.5.2.1 Data Verification
-1. `VaultID` is zero. (`temMALFORMED`)
-2. `Amount` is zero or negative. (`temBAD_AMOUNT`)
+_None._
##### 3.5.2.2 Protocol-Level Failures
-1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
-2. The `Amount` asset does not match `Vault.Asset`. (`tecWRONG_ASSET`)
-3. The `Vault.Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance` (asset is non-transferable). (`tecNO_AUTH`)
-4. The `Vault.Asset` is an `IOU` and rippling is disabled on both the depositor's and vault's trust lines with the issuer. (`terNO_RIPPLE`)
-5. The `Vault.Asset` is frozen for the depositor. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
-6. The vault shares (`MPTokenIssuance`) are locked for the depositor. (`tecLOCKED`)
-7. The vault is private, the depositor is not the vault owner, and the vault has no `DomainID` set. (`tecNO_AUTH`)
-8. The vault is private, the depositor is not the vault owner, and the depositor does not have valid credentials in the vault's `PermissionedDomain`. (`tecNO_AUTH` / `tecOBJECT_NOT_FOUND`)
-9. The `Vault.Asset` is an `MPT` and the depositor does not have an authorized `MPToken` for the asset. (`tecNO_AUTH`)
-10. The depositor has insufficient balance to cover `Amount`. (`tecINSUFFICIENT_FUNDS`)
-11. Depositing `Amount` would cause `Vault.AssetsTotal` to exceed `Vault.AssetsMaximum`. (`tecLIMIT_EXCEEDED`)
-12. The deposit amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
-13. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The asset type of the vault does not match the asset type the depositor is depositing.
+3. The depositor does not have sufficient funds to make a deposit.
+4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
+5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+6. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+7. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+8. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
+9. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
+10. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+11. The `Asset` is an `IOU` and the `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
+12. The `Asset` is an `IOU` and the `RippleState` object `Balance` < `Amount` (insufficient balance).
#### 3.5.3 State Changes
@@ -638,33 +529,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
#### 3.5.4 Invariants
-Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the depositor's share balance.
-
-1. The _pseudo-account_ asset balance must increase: $\Delta_{asset} > 0$.
-2. $\Delta_{asset}$ must not exceed `Amount`.
-3. The depositor's asset balance must decrease by $\Delta_{asset}$ (unless the depositor is the asset issuer).
-4. `IF '.AssetsMaximum > 0 THEN '.AssetsTotal <= '.AssetsMaximum`.
-5. $\Delta_{share} > 0$ (depositor shares must increase).
-6. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-7. `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-8. `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-
-#### 3.5.5 Example JSON
-
-```json
-{
- "TransactionType": "VaultDeposit",
- "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Amount": {
- "currency": "USD",
- "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
- "value": "5000"
- },
- "Account": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
- "Fee": "1",
- "Sequence": 3990518
-}
-```
+**TBD**
### 3.6 Transaction: `VaultWithdraw`
@@ -695,27 +560,25 @@ In sections below assume the following variables:
##### 3.6.2.1 Data Verification
-1. `VaultID` is zero. (`temMALFORMED`)
-2. `Amount` is zero or negative. (`temBAD_AMOUNT`)
-3. `Destination` is present but is zero. (`temMALFORMED`)
+_None._
##### 3.6.2.2 Protocol-Level Failures
-1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
-2. The `Amount` asset is neither `Vault.Asset` nor the vault's share `MPTokenIssuance`. (`tecWRONG_ASSET`)
-3. The `Vault.Asset` is an `MPT` and `lsfMPTCanTransfer` is not set on the `MPTokenIssuance` (asset is non-transferable). (`tecNO_AUTH`)
-4. The `Vault.Asset` is an `IOU` and rippling is disabled on both the vault's and destination's trust lines with the issuer. (`terNO_RIPPLE`)
-5. The destination account does not exist. (`tecNO_DST`)
-6. The destination account requires a destination tag and none was provided. (`tecDST_TAG_NEEDED`)
-7. The destination account has `lsfDepositAuth` set and the submitting account is not preauthorized. (`tecNO_PERMISSION`)
-8. The `Vault.Asset` is an `IOU`, the destination is a third party, and the withdrawal amount would exceed the destination's trust line limit. (`tecNO_LINE`)
-9. The destination's `IOU` trust line does not exist or is not authorized. (`tecNO_LINE` / `tecNO_AUTH`)
-10. The `Vault.Asset` is frozen for the destination account. (`tecFROZEN` for `IOU` / `tecLOCKED` for `MPT`)
-11. The vault shares are frozen for the submitting account. (`tecLOCKED`)
-12. The submitting account holds fewer shares than required to cover the withdrawal. (`tecINSUFFICIENT_FUNDS`)
-13. `Vault.AssetsAvailable` is less than the assets to be withdrawn. (`tecINSUFFICIENT_FUNDS`)
-14. The withdrawal amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
-15. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+3. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+4. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
+5. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+6. The `Asset` is an `IOU` and the `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
+7. The unit of `Amount` is not shares of the vault.
+8. The unit of `Amount` is not asset of the vault.
+9. `Amount` is the vault's share and `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
+10. `Amount` is the vault's share and the shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
+11. `Amount` is the vault's share and `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
+12. `Amount` is the vault's asset and the shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
+13. `Amount` is the vault's asset and `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
+14. The `Destination` account is specified and does not have permission to receive the asset.
+15. The `Destination` account is specified and does not have a `RippleState` or `MPToken` object for the asset.
#### 3.6.3 State Changes
@@ -744,31 +607,7 @@ In sections below assume the following variables:
#### 3.6.4 Invariants
-Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the withdrawer's share balance.
-
-1. The _pseudo-account_ asset balance must decrease: $\Delta_{asset} < 0$.
-2. Exactly one destination account balance must increase by $|\Delta_{asset}|$ (unless the destination is the asset issuer).
-3. $\Delta_{share} < 0$ (withdrawer shares must decrease).
-4. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-5. `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-6. `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-
-#### 3.6.5 Example JSON
-
-```json
-{
- "TransactionType": "VaultWithdraw",
- "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Amount": {
- "currency": "USD",
- "issuer": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
- "value": "4083.333642504084"
- },
- "Account": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
- "Fee": "1",
- "Sequence": 3990519
-}
-```
+**TBD**
### 3.7 Transaction: `VaultClawback`
@@ -787,25 +626,19 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
##### 3.7.2.1 Data Verification
-1. `VaultID` is zero. (`temMALFORMED`)
-2. `Amount` is negative. (`temBAD_AMOUNT`)
-3. `Amount` is provided and the asset is `XRP`. (`temMALFORMED`)
+_None._
##### 3.7.2.2 Protocol-Level Failures
-1. `Vault` object with the provided `VaultID` does not exist. (`tecNO_ENTRY`)
-2. `Amount` is not provided and `Vault.Asset` is not `XRP`, and the asset issuer is the vault owner — the caller must specify an explicit `Amount` to resolve the ambiguity. (`tecWRONG_ASSET`)
-3. The resolved clawback asset is the vault share (`MPTokenIssuance`) and the submitting account is not the vault owner. (`tecNO_PERMISSION`)
-4. The resolved clawback asset is the vault share and `Vault.AssetsTotal` or `Vault.AssetsAvailable` is non-zero (vault owner can only burn shares when vault has no assets). (`tecNO_PERMISSION`)
-5. The resolved clawback asset is the vault share, `Amount` is non-zero, and it does not equal the holder's full share balance (vault owner must burn all shares at once). (`tecLIMIT_EXCEEDED`)
-6. The resolved clawback asset is `Vault.Asset` and `Vault.Asset` is `XRP`. (`tecNO_PERMISSION`)
-7. The resolved clawback asset is `Vault.Asset` and the submitting account is not the asset issuer. (`tecNO_PERMISSION`)
-8. The submitting account and `Holder` are the same account (issuer cannot clawback from itself). (`tecNO_PERMISSION`)
-9. The `Vault.Asset` is an `MPT` and the `MPTokenIssuance` object does not exist. (`tecOBJECT_NOT_FOUND`)
-10. The `Vault.Asset` is an `MPT` and `lsfMPTCanClawback` is not set on the `MPTokenIssuance`. (`tecNO_PERMISSION`)
-11. The `Vault.Asset` is an `IOU` and the issuer does not have `lsfAllowTrustLineClawback` set, or has `lsfNoFreeze` set. (`tecNO_PERMISSION`)
-12. The resolved clawback amount rounds down to zero shares due to precision loss. (`tecPRECISION_LOSS`)
-13. An arithmetic overflow occurs during share calculation (e.g. large `Scale`). (`tecPATH_DRY`)
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. `Vault.Asset` is `XRP`.
+3. `Vault.Asset` is an `IOU` and the `Issuer` account is not the submitter of the transaction.
+4. `Vault.Asset` is an `IOU` and the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
+5. `Vault.Asset` is an `IOU` and the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
+6. `Vault.Asset` is an `MPT` and `MPTokenIssuance.Issuer` is not the submitter of the transaction.
+7. `Vault.Asset` is an `MPT` and `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
+8. `Vault.Asset` is an `MPT` and the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
+9. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
#### 3.7.3 State Changes
@@ -826,28 +659,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
#### 3.7.4 Invariants
-Let $\Delta_{asset}$ denote the change in the _pseudo-account_ asset balance and $\Delta_{share}$ denote the change in the holder's share balance.
-
-1. The transaction submitter must be the asset issuer, or the vault owner of an empty vault with outstanding shares.
-2. If the vault holds assets: $\Delta_{asset} < 0$ (vault balance must decrease).
-3. If the vault holds assets: `.AssetsTotal +` $\Delta_{asset}$ `== '.AssetsTotal`.
-4. If the vault holds assets: `.AssetsAvailable +` $\Delta_{asset}$ `== '.AssetsAvailable`.
-5. $\Delta_{share} < 0$ (holder shares must decrease).
-6. The change in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal $\Delta_{share}$.
-
-#### 3.7.5 Example JSON
-
-```json
-{
- "TransactionType": "VaultClawback",
- "VaultID": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7",
- "Holder": "raZazWJ29vzR4EdcqKi9fh3TARP6Y11jQx",
- "Amount": "4083333642504084",
- "Account": "rGTx5c5zRFtUXj3zsAaTEEhnAkYaH1bFAb",
- "Fee": "1",
- "Sequence": 3990520
-}
-```
+**TBD**
### 3.8 Transaction: `Payment`
@@ -866,8 +678,8 @@ The Single Asset Vault does not introduce or modify any `Payment` transaction fi
3. The `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, ACCOUNTID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- 4. `MPToken(MPTokenIssuanceID, PseudoACCOUNTID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
+ 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
4. The `Vault.Asset` is an `IOU`:
@@ -880,21 +692,16 @@ The Single Asset Vault does not introduce or modify any `Payment` transaction fi
1. If `MPToken`object for shares does not exist for the destination account, create one.
-#### 3.8.4 Example JSON
-
-```json
-{}
-```
-
### 3.9 RPC: `vault_info`
This RPC retrieves the Vault ledger entry and the IDs associated with it.
#### 3.9.1 Request Fields
+We propose adding the following fields to the `ledger_entry` method:
+
| Field Name | Required? | JSON Type | Description |
| ---------- | :-------: | :-------: | :----------------------------------------: |
-| `command` | Yes | `string` | Must be `"vault_info"`. |
| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
#### 3.9.2 Response Fields
@@ -904,51 +711,41 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
| `vault` | Yes | `object` | Root object representing the vault. |
| `vault.Account` | Yes | `string` | The pseudo-account ID of the vault. |
| `vault.Asset` | Yes | `object` | Object representing the asset held in the vault. |
-| `vault.Asset.currency` | Conditional | `string` | Currency code of the asset. Present when the asset is `XRP` or an `IOU`. |
-| `vault.Asset.issuer` | Conditional | `string` | Issuer address of the asset. Present when the asset is an `IOU`. |
-| `vault.Asset.mpt_issuance_id` | Conditional | `string` | The `MPTokenIssuance` ID of the asset. Present when the asset is an `MPT`. |
+| `vault.Asset.currency` | Yes | `string` | Currency code of the asset stored in the vault. |
+| `vault.Asset.issuer` | No | `string` | Issuer address of the asset. |
| `vault.AssetsAvailable` | Yes | `string` | Amount of assets currently available for withdrawal. |
| `vault.AssetsTotal` | Yes | `string` | Total amount of assets in the vault. |
-| `vault.Data` | No | `string` | Arbitrary metadata about the Vault, in hex format. |
-| `vault.Flags` | Yes | `number` | Bit-field flags associated with the vault. |
-| `vault.LedgerEntryType` | Yes | `string` | Ledger entry type, always `"Vault"`. |
-| `vault.LossUnrealized` | Yes | `string` | Unrealized loss associated with the vault. |
+| `vault.Flags` | No | `number` | Bit-field flags associated with the vault. |
+| `vault.LedgerEntryType` | Yes | `string` | Ledger entry type, always "Vault". |
+| `vault.LossUnrealized` | No | `string` | Unrealized loss associated with the vault. |
| `vault.Owner` | Yes | `string` | ID of the Vault Owner account. |
-| `vault.OwnerNode` | Yes | `string` | Identifier for the owner node in the ledger tree. |
+| `vault.OwnerNode` | No | `string` | Identifier for the owner node in the ledger tree. |
| `vault.PreviousTxnID` | Yes | `string` | Transaction ID of the last modification to this vault. |
| `vault.PreviousTxnLgrSeq` | Yes | `number` | Ledger sequence number of the last transaction modifying this vault. |
-| `vault.Scale` | Yes | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
| `vault.Sequence` | Yes | `number` | Sequence number of the vault entry. |
-| `vault.ShareMPTID` | Yes | `string` | Multi-purpose token ID associated with the vault's shares. |
-| `vault.WithdrawalPolicy` | Yes | `number` | Policy defining withdrawal conditions. |
+| `vault.ShareMPTID` | No | `string` | Multi-purpose token ID associated with this vault. |
+| `vault.WithdrawalPolicy` | No | `number` | Policy defining withdrawal conditions. |
| `vault.index` | Yes | `string` | Unique index of the vault ledger entry. |
| `vault.shares` | Yes | `object` | Object containing details about issued shares. |
-| `vault.shares.DomainID` | No | `string` | The `PermissionedDomain` object ID associated with the shares, if set. |
-| `vault.shares.Flags` | Yes | `number` | Bit-field flags associated with the shares issuance. |
+| `vault.shares.Flags` | No | `number` | Bit-field flags associated with the shares issuance. |
| `vault.shares.Issuer` | Yes | `string` | The ID of the Issuer of the Share. It will always be the pseudo-account ID. |
-| `vault.shares.LedgerEntryType` | Yes | `string` | Ledger entry type, always `"MPTokenIssuance"`. |
-| `vault.shares.MPTokenMetadata` | No | `string` | Arbitrary metadata about the share MPT, in hex format. |
+| `vault.shares.LedgerEntryType` | Yes | `string` | Ledger entry type, always "MPTokenIssuance". |
| `vault.shares.OutstandingAmount` | Yes | `string` | Total outstanding shares issued. |
-| `vault.shares.OwnerNode` | Yes | `string` | Identifier for the owner node of the shares. |
+| `vault.shares.OwnerNode` | No | `string` | Identifier for the owner node of the shares. |
| `vault.shares.PreviousTxnID` | Yes | `string` | Transaction ID of the last modification to the shares issuance. |
| `vault.shares.PreviousTxnLgrSeq` | Yes | `number` | Ledger sequence number of the last transaction modifying the shares issuance. |
| `vault.shares.Sequence` | Yes | `number` | Sequence number of the shares issuance entry. |
| `vault.shares.index` | Yes | `string` | Unique index of the shares ledger entry. |
-| `vault.shares.mpt_issuance_id` | Yes | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
+| `vault.shares.mpt_issuance_id` | No | `string` | The ID of the `MPTokenIssuance` object. It will always be equal to `vault.ShareMPTID`. |
+| `vault.Scale` | Yes | `number` | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
#### 3.9.3 Failure Conditions
-1. The `vault` field is not provided or is not a valid object ID (`invalidParams`).
-2. The `Vault` object with the specified ID does not exist on the ledger (`entryNotFound`).
+**TBD**
#### 3.9.4 Example Request
-```json
-{
- "command": "vault_info",
- "vault": "48B33DBFA762ECA23CF37CF1A4F93D6D3EBBA710F62F357CF9ADF1146A0E92B7"
-}
-```
+**TBD**
#### 3.9.5 Example Response
@@ -958,11 +755,12 @@ Vault holding an `IOU`:
{
"result": {
"ledger_current_index": 7,
+ "status": "success",
"validated": false,
"vault": {
"Account": "rKwvc1mgHLyHKY3yRUqVwffWtsxYb3QLWf",
"Asset": {
- "currency": "USD",
+ "currency": "IOU",
"issuer": "r9cZ5oHbdL4Z9Maj6TdnfAos35nVzYuNds"
},
"AssetsAvailable": "100",
@@ -990,11 +788,10 @@ Vault holding an `IOU`:
"Sequence": 1,
"index": "F84AE266C348540D7134F1A683392C3B97C3EEFDE9FEF6F2055B3B92550FB44A",
"mpt_issuance_id": "00000001C752C42A1EBD6BF2403134F7CFD2F1D835AFD26E"
- }
+ },
+ "Scale": 6
}
- },
- "status": "success",
- "type": "response"
+ }
}
```
@@ -1002,43 +799,32 @@ Vault holding an `MPT`:
```json
{
- "result": {
- "ledger_current_index": 3990828,
- "validated": false,
- "vault": {
- "Account": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
- "Asset": {
- "mpt_issuance_id": "002F830036E4E56185F871D70CFFC7BDD554F897606BB6D3"
- },
- "Data": "50726976617465207661756C7420666F72207475746F7269616C73",
- "Flags": 65536,
- "LedgerEntryType": "Vault",
- "Owner": "rJdYtgaiEgzL7xD2QdPKg5xoHkWc7CZjvm",
- "OwnerNode": "0",
- "PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
- "PreviousTxnLgrSeq": 3113735,
- "Sequence": 3113728,
- "ShareMPTID": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493",
- "WithdrawalPolicy": 1,
- "index": "9E48171960CD9F62C3A7B6559315A510AE544C3F51E02947B5D4DAC8AA66C3BA",
- "shares": {
- "DomainID": "17060E04AD63975CDE5E4B0C6ACB95ABFA2BA1D569473559448B6E556F261D4A",
- "Flags": 60,
- "Issuer": "rQhUcbJoDfvgXr1EkMwarLP5QT3XinEBDg",
- "LedgerEntryType": "MPTokenIssuance",
- "MPTokenMetadata": "7B226163223A2264656669222C226169223A7B226578616D706C655F696E666F223A2274657374227D2C2264223A2250726F706F7274696F6E616C206F776E65727368697020736861726573206F6620746865207661756C74222C2269223A226578616D706C652E636F6D2F7661756C742D7368617265732D69636F6E2E706E67222C22696E223A225661756C74204F776E6572222C226E223A225661756C7420536861726573222C2274223A22534841524531222C227573223A5B7B2263223A2277656273697465222C2274223A2241737365742057656273697465222C2275223A226578616D706C652E636F6D2F6173736574227D2C7B2263223A22646F6373222C2274223A22446F6373222C2275223A226578616D706C652E636F6D2F646F6373227D5D7D",
- "OutstandingAmount": "0",
- "OwnerNode": "0",
- "PreviousTxnID": "F73B073028D7EF14C5DD907591E579EBFEDBA891F4AE0B951439C240C42AE0D4",
- "PreviousTxnLgrSeq": 3113735,
- "Sequence": 1,
- "index": "F231A0382544EC0ABE810A9D292F3BD455A21CD13CC1DFF75EAFE957A1C8CAB4",
- "mpt_issuance_id": "00000001FCE5D5E313303F3D0C700789108CC6BE7D711493"
- }
- }
+ "LedgerEntryType": "Vault",
+ "LedgerIndex": "E123F4567890ABCDE123F4567890ABCDEF1234567890ABCDEF1234567890ABCD",
+ "Flags": "0",
+ "PreviousTxnID": "9A8765B4321CDE987654321CDE987654321CDE987654321CDE987654321CDE98",
+ "PreviousTxnLgrSeq": 12345678,
+ "Sequence": 1,
+ "OwnerNode": 2,
+ "Owner": "rEXAMPLE9AbCdEfGhIjKlMnOpQrStUvWxYz",
+ "Account": "rPseudoAcc1234567890abcdef1234567890abcdef",
+ "Data": "5468697320697320617262697472617279206D657461646174612061626F757420746865207661756C742E",
+ "Asset": {
+ "currency": "USD",
+ "issuer": "rIssuer1234567890abcdef1234567890abcdef",
+ "value": "1000"
},
- "status": "success",
- "type": "response"
+ "AssetsTotal": 1000000,
+ "AssetsAvailable": 800000,
+ "LossUnrealized": 200000,
+ "AssetsMaximum": 0,
+ "Share": {
+ "mpt_issuance_id": "0000012FFD9EE5DA93AC614B4DB94D7E0FCE415CA51BED47",
+ "value": "1"
+ },
+ "ShareTotal": 5000,
+ "WithdrawalPolicy": "0x0001",
+ "Scale": 0
}
```
@@ -1049,6 +835,7 @@ Vault holding `XRP`:
"result": {
"ledger_hash": "6FFF56DF92D54D01EE3D5487787F4430D66F89C6BC74B00C276262A0207B2FAD",
"ledger_index": 6,
+ "status": "success",
"validated": true,
"vault": {
"Account": "rBVxExjRR6oDMWCeQYgJP7q4JBLGeLBPyv",
@@ -1080,27 +867,20 @@ Vault holding `XRP`:
"Sequence": 1,
"index": "4B25BDE141E248E5D585FEB6100E137D3C2475CEE62B28446391558F0BEA23B5",
"mpt_issuance_id": "00000001732B0822A31109C996BCDD7E64E05D446E7998EE"
- }
+ },
+ "Scale": 0
}
- },
- "status": "success",
- "type": "response"
+ }
}
```
## 4. Rationale
-The Single Asset Vault is intentionally decoupled from the protocols that rely on it for liquidity. Rather than embedding liquidity provisioning logic into each protocol the vault provides a reusable on-chain building block that a protocol can connect to. This separation means that protocol developers do not need to implement liquidity provisioning mechanics they simply draw from and return assets to the vault, while the vault handles share accounting, access control, and asset custody independently.
-
-This design was chosen over a tightly-coupled alternative where each protocol manages its own depositor pool, because:
-
-- **Reusability**: A single vault implementation serves multiple protocols, reducing code duplication and the surface area for bugs.
-- **Composability**: Vault shares are first-class MPT assets that can be transferred, escrowed, or used in other DeFi protocols.
-- **Separation of concerns**: Protocols connecting to the vault only need to track their debt and update vault state through well-defined interfaces, rather than managing individual depositor balances.
+_TBD_
## 5. Security Considerations
-The security properties of the Single Asset Vault are enforced through the invariant checks described in sections 3.1.10 and 3.2.7 through 3.7.4. These invariants guarantee conservation of assets across deposits, withdrawals, and clawbacks, immutability of critical vault parameters, and correctness of share issuance and redemption.
+_TBD_
# Appendix
From 280979bad56696e2d906474dde4fca413cad269d Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 26 May 2026 18:47:54 +0200
Subject: [PATCH 19/27] revert numbered lists to master bullet-point format
Convert failure conditions and state changes from numbered lists back
to master's original nested bullet-point format. Keep the Data
Verification / Protocol-Level Failures subsection headers as template
compliance, but use master's original content and structure inside them.
Co-Authored-By: Claude Sonnet 4.6
---
XLS-0065-single-asset-vault/README.md | 302 +++++++++++++++-----------
1 file changed, 172 insertions(+), 130 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index a02f3fe51..a73be7d52 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -366,33 +366,43 @@ The transaction creates an `AccountRoot` object for the `_pseudo-account_`. Ther
##### 3.2.5.1 Data Verification
-1. The `Asset` is `XRP` and the `Scale` parameter is provided.
-2. The `Asset` is `MPT` and the `Scale` parameter is provided.
-3. The `Asset` is an `IOU` and the `Scale` parameter is provided and is less than **0** or greater than **18**.
-4. The `tfVaultPrivate` flag is not set and `DomainID` is provided.
-5. The `Data` field is larger than 256 bytes.
+_TBD_
##### 3.2.5.2 Protocol-Level Failures
-1. The `Asset` is `MPT` and `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object (the asset is not transferable).
-2. The `Asset` is `MPT` and the `lsfMPTLocked` flag is set in the `MPTokenIssuance` object (the asset is locked).
-3. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
-4. The `PermissionedDomain` object does not exist with the provided `DomainID`.
-5. The account submitting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
+- The `Asset` is `XRP`:
+ - The `Scale` parameter is provided.
+
+- The `Asset` is `MPT`:
+ - The `Scale` parameter is provided.
+ - The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
+ - The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
+
+- The `Asset` is an `IOU`:
+ - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ - The `Scale` parameter is provided, and is less than **0** or greater than **18**.
+
+- The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
+
+- The `PermissionedDomain` object does not exist with the provided `DomainID`.
+
+- The `Data` field is larger than 256 bytes.
+- The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
#### 3.2.6 State Changes
-1. Create a new `Vault` ledger object.
-2. Create a new `MPTokenIssuance` ledger object for the vault shares.
- 1. If the `DomainID` is provided: `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
- 2. Create an `MPToken` object for the Vault Owner to hold Vault Shares.
-3. Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
+- Create a new `Vault` ledger object.
+- Create a new `MPTokenIssuance` ledger object for the vault shares.
+ - If the `DomainID` is provided:
+ - `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
+ - Create an `MPToken` object for the Vault Owner to hold Vault Shares.
+- Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
-4. If `Vault.Asset` is an `IOU`:
- 1. Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
+- If `Vault.Asset` is an `IOU`:
+ - Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
-5. If `Vault.Asset` is an `MPT`:
- 1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
+- If `Vault.Asset` is an `MPT`:
+ - Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
#### 3.2.7 Invariants
@@ -416,23 +426,25 @@ The `VaultSet` updates an existing `Vault` ledger object.
##### 3.3.2.1 Data Verification
-1. The `Data` field is larger than 256 bytes.
-2. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
-3. The transaction is attempting to modify an immutable field.
-4. The transaction does not specify any of the modifiable fields.
+_TBD_
##### 3.3.2.2 Protocol-Level Failures
-1. `Vault` object with the specified `VaultID` does not exist on the ledger.
-2. The submitting account is not the `Owner` of the vault.
-3. `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND the `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
-4. The `PermissionedDomain` object does not exist with the provided `DomainID`.
+- `Vault` object with the specified `VaultID` does not exist on the ledger.
+- The submitting account is not the `Owner` of the vault.
+- The `Data` field is larger than 256 bytes.
+- If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
+ - The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
+- The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
+- The `PermissionedDomain` object does not exist with the provided `DomainID`.
+- The transaction is attempting to modify an immutable field.
+- The transaction does not specify any of the modifiable fields.
#### 3.3.3 State Changes
-1. Update mutable fields in the `Vault` ledger object.
-2. If `DomainID` is provided:
- 1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
+- Update mutable fields in the `Vault` ledger object.
+- If `DomainID` is provided:
+ - Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
#### 3.3.4 Invariants
@@ -457,18 +469,18 @@ _None._
##### 3.4.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The submitting account is not the `Owner` of the vault.
-3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
-4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
+- `Vault` object with the `VaultID` does not exist on the ledger.
+- The submitting account is not the `Owner` of the vault.
+- `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
+- The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
#### 3.4.3 State Changes
-1. Delete the `MPTokenIssuance` object for the vault shares.
-2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
-3. Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
-4. Release the Owner Reserve to the `Vault.Owner` account.
-5. Delete the `Vault` object.
+- Delete the `MPTokenIssuance` object for the vault shares.
+- Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
+- Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
+- Release the Owner Reserve to the `Vault.Owner` account.
+- Delete the `Vault` object.
#### 3.4.4 Invariants
@@ -494,38 +506,42 @@ _None._
##### 3.5.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The asset type of the vault does not match the asset type the depositor is depositing.
-3. The depositor does not have sufficient funds to make a deposit.
-4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
-5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
-6. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
-7. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
-8. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
-9. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
-10. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
-11. The `Asset` is an `IOU` and the `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
-12. The `Asset` is an `IOU` and the `RippleState` object `Balance` < `Amount` (insufficient balance).
+- `Vault` object with the `VaultID` does not exist on the ledger.
+- The asset type of the vault does not match the asset type the depositor is depositing.
+- The depositor does not have sufficient funds to make a deposit.
+- Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
+- The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+
+- The `Vault.Asset` is `MPT`:
+ - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
+ - `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
+
+- The `Asset` is an `IOU`:
+ - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
+ - The `RippleState` object `Balance` < `Amount` (insufficient balance).
#### 3.5.3 State Changes
-1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
+If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
-2. Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
-3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-4. Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
+- Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
+- Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+- Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
-5. If the `Vault.Asset` is `XRP`:
- 1. Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
- 2. Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
+- If the `Vault.Asset` is `XRP`:
+ - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
+ - Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
-6. If the `Vault.Asset` is an `IOU`:
- 1. Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
- 2. Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+- If the `Vault.Asset` is an `IOU`:
+ - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+ - Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
-7. If the `Vault.Asset` is an `MPT`:
- 1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- 2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
+- If the `Vault.Asset` is an `MPT`:
+ - Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ - Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
#### 3.5.4 Invariants
@@ -564,46 +580,60 @@ _None._
##### 3.6.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
-3. The `Vault.Asset` is `MPT` and `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
-4. The `Vault.Asset` is `MPT` and `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
-5. The `Asset` is an `IOU` and the `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
-6. The `Asset` is an `IOU` and the `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
-7. The unit of `Amount` is not shares of the vault.
-8. The unit of `Amount` is not asset of the vault.
-9. `Amount` is the vault's share and `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
-10. `Amount` is the vault's share and the shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
-11. `Amount` is the vault's share and `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
-12. `Amount` is the vault's asset and the shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
-13. `Amount` is the vault's asset and `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
-14. The `Destination` account is specified and does not have permission to receive the asset.
-15. The `Destination` account is specified and does not have a `RippleState` or `MPToken` object for the asset.
+- `Vault` object with the `VaultID` does not exist on the ledger.
+
+- The `Vault.Asset` is `MPT`:
+ - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ - `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
+
+- The `Asset` is an `IOU`:
+ - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
+
+- The unit of `Amount` is not shares of the vault.
+- The unit of `Amount` is not asset of the vault.
+
+- There is insufficient liquidity in the vault to fill the request:
+ - If `Amount` is the vaults share:
+ - `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
+ - The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
+ - `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
+
+ - If `Amount` is the vaults asset:
+ - The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
+ - `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
+
+- The `Destination` account is specified:
+ - The account does not have permission to receive the asset.
+ - The account does not have a `RippleState` or `MPToken` object for the asset.
#### 3.6.3 State Changes
-1. If the `Vault.Asset` is XRP:
- 1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
- 2. Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
+- If the `Vault.Asset` is XRP:
+ - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
+ - Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
+
+- If the `Vault.Asset` is an `IOU`:
+ - If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
-2. If the `Vault.Asset` is an `IOU`:
- 1. If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
- 2. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
- 3. Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+ - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+ - Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
-3. If the `Vault.Asset` is an `MPT`:
- 1. If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
- 2. Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- 3. Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
+- If the `Vault.Asset` is an `MPT`:
+ - If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
-4. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- 2. If `MPToken.MPTAmount == 0`, delete the object.
+ - Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ - Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
-5. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ - If `MPToken.MPTAmount == 0`, delete the object.
-6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
+- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+
+- Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
#### 3.6.4 Invariants
@@ -630,32 +660,38 @@ _None._
##### 3.7.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. `Vault.Asset` is `XRP`.
-3. `Vault.Asset` is an `IOU` and the `Issuer` account is not the submitter of the transaction.
-4. `Vault.Asset` is an `IOU` and the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
-5. `Vault.Asset` is an `IOU` and the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
-6. `Vault.Asset` is an `MPT` and `MPTokenIssuance.Issuer` is not the submitter of the transaction.
-7. `Vault.Asset` is an `MPT` and `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
-8. `Vault.Asset` is an `MPT` and the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
-9. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
+- `Vault` object with the `VaultID` does not exist on the ledger.
+
+- If `Vault.Asset` is `XRP`.
+
+- If `Vault.Asset` is an `IOU` and:
+ - The `Issuer` account is not the submitter of the transaction.
+ - If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
+ - If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
+
+- If `Vault.Asset` is an `MPT` and:
+ - `MPTokenIssuance.Issuer` is not the submitter of the transaction.
+ - `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
+ - If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
+
+- The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
#### 3.7.3 State Changes
-1. If the `Vault.Asset` is an `IOU`:
- 1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
+- If the `Vault.Asset` is an `IOU`:
+ - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
-2. If the `Vault.Asset` is an `MPT`:
- 1. Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+- If the `Vault.Asset` is an `MPT`:
+ - Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
-3. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- 2. If `MPToken.MPTAmount == 0`, delete the object.
+- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ - If `MPToken.MPTAmount == 0`, delete the object.
-4. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
+- Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
#### 3.7.4 Invariants
@@ -671,26 +707,32 @@ The Single Asset Vault does not introduce or modify any `Payment` transaction fi
#### 3.8.2 Failure Conditions
-1. If `Payment.Amount` is a `Vault` share AND:
- 1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
- 2. The `Vault` `tfVaultShareNonTransferable` flag is set.
+##### 3.8.2.1 Data Verification
+
+_None._
+
+##### 3.8.2.2 Protocol-Level Failures
+
+- If `Payment.Amount` is a `Vault` share AND:
+ - The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
+ - The `Vault` `tfVaultShareNonTransferable` flag is set.
- 3. The `Vault.Asset` is `MPT`:
- 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
- 5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
+ - The `Vault.Asset` is `MPT`:
+ - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
+ - `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ - `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
- 4. The `Vault.Asset` is an `IOU`:
- 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
- 3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
- 4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
+ - The `Vault.Asset` is an `IOU`:
+ - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
+ - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
+ - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
#### 3.8.3 State Changes
-1. If `MPToken`object for shares does not exist for the destination account, create one.
+- If `MPToken`object for shares does not exist for the destination account, create one.
### 3.9 RPC: `vault_info`
From 73805118b7d82169107ddc50b2d27b91a8d4f45e Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 26 May 2026 18:56:28 +0200
Subject: [PATCH 20/27] number failure conditions and state changes lists
Convert bullet points in Failure Conditions and State Changes sections
to numbered lists with nested sub-numbering, per template requirements.
Co-Authored-By: Claude Sonnet 4.6
---
XLS-0065-single-asset-vault/README.md | 293 +++++++++++++-------------
1 file changed, 145 insertions(+), 148 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index a73be7d52..403dd3dd4 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -370,39 +370,39 @@ _TBD_
##### 3.2.5.2 Protocol-Level Failures
-- The `Asset` is `XRP`:
- - The `Scale` parameter is provided.
+1. The `Asset` is `XRP`:
+ 1. The `Scale` parameter is provided.
-- The `Asset` is `MPT`:
- - The `Scale` parameter is provided.
- - The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
- - The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
+2. The `Asset` is `MPT`:
+ 1. The `Scale` parameter is provided.
+ 2. The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
+ 3. The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `Scale` parameter is provided, and is less than **0** or greater than **18**.
+3. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `Scale` parameter is provided, and is less than **0** or greater than **18**.
-- The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
+4. The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
-- The `PermissionedDomain` object does not exist with the provided `DomainID`.
+5. The `PermissionedDomain` object does not exist with the provided `DomainID`.
-- The `Data` field is larger than 256 bytes.
-- The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
+6. The `Data` field is larger than 256 bytes.
+7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
#### 3.2.6 State Changes
-- Create a new `Vault` ledger object.
-- Create a new `MPTokenIssuance` ledger object for the vault shares.
- - If the `DomainID` is provided:
- - `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
- - Create an `MPToken` object for the Vault Owner to hold Vault Shares.
-- Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
+1. Create a new `Vault` ledger object.
+2. Create a new `MPTokenIssuance` ledger object for the vault shares.
+ 1. If the `DomainID` is provided:
+ 1. `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
+ 2. Create an `MPToken` object for the Vault Owner to hold Vault Shares.
+3. Create a new `AccountRoot`[_pseudo-account_](../XLS-0064-pseudo-account/README.md) object setting the `PseudoOwner` to `VaultID`.
-- If `Vault.Asset` is an `IOU`:
- - Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
+4. If `Vault.Asset` is an `IOU`:
+ 1. Create a `RippleState` object between the _pseudo-account_ `AccountRoot` and `Issuer` `AccountRoot`.
-- If `Vault.Asset` is an `MPT`:
- - Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
+5. If `Vault.Asset` is an `MPT`:
+ 1. Create `MPToken` object for the _pseudo-account_ for the `Asset.MPTokenIssuance`.
#### 3.2.7 Invariants
@@ -430,21 +430,21 @@ _TBD_
##### 3.3.2.2 Protocol-Level Failures
-- `Vault` object with the specified `VaultID` does not exist on the ledger.
-- The submitting account is not the `Owner` of the vault.
-- The `Data` field is larger than 256 bytes.
-- If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
- - The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
-- The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
-- The `PermissionedDomain` object does not exist with the provided `DomainID`.
-- The transaction is attempting to modify an immutable field.
-- The transaction does not specify any of the modifiable fields.
+1. `Vault` object with the specified `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. The `Data` field is larger than 256 bytes.
+4. If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
+ 1. The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
+5. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
+6. The `PermissionedDomain` object does not exist with the provided `DomainID`.
+7. The transaction is attempting to modify an immutable field.
+8. The transaction does not specify any of the modifiable fields.
#### 3.3.3 State Changes
-- Update mutable fields in the `Vault` ledger object.
-- If `DomainID` is provided:
- - Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
+1. Update mutable fields in the `Vault` ledger object.
+2. If `DomainID` is provided:
+ 1. Set `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain).
#### 3.3.4 Invariants
@@ -469,18 +469,18 @@ _None._
##### 3.4.2.2 Protocol-Level Failures
-- `Vault` object with the `VaultID` does not exist on the ledger.
-- The submitting account is not the `Owner` of the vault.
-- `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
-- The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The submitting account is not the `Owner` of the vault.
+3. `AssetsTotal`, `AssetsAvailable`, or `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` are greater than zero.
+4. The `OwnerDirectory` of the Vault _pseudo-account_ contains pointers to objects other than the `Vault`, the `MPTokenIssuance` for its shares, or an `MPToken` or trust line for its asset.
#### 3.4.3 State Changes
-- Delete the `MPTokenIssuance` object for the vault shares.
-- Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
-- Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
-- Release the Owner Reserve to the `Vault.Owner` account.
-- Delete the `Vault` object.
+1. Delete the `MPTokenIssuance` object for the vault shares.
+2. Delete the `MPToken` or `RippleState` object corresponding to the vault's holding of the asset, if one exists.
+3. Delete the `AccountRoot` object of the _pseudo-account_, and its `DirectoryNode` objects.
+4. Release the Owner Reserve to the `Vault.Owner` account.
+5. Delete the `Vault` object.
#### 3.4.4 Invariants
@@ -506,42 +506,41 @@ _None._
##### 3.5.2.2 Protocol-Level Failures
-- `Vault` object with the `VaultID` does not exist on the ledger.
-- The asset type of the vault does not match the asset type the depositor is depositing.
-- The depositor does not have sufficient funds to make a deposit.
-- Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
-- The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
+2. The asset type of the vault does not match the asset type the depositor is depositing.
+3. The depositor does not have sufficient funds to make a deposit.
+4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
+5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
-- The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
- - `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
+6. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
+ 4. `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
- - The `RippleState` object `Balance` < `Amount` (insufficient balance).
+7. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
+ 3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
#### 3.5.3 State Changes
-If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
+1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
+2. Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
+3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+4. Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
-- Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
-- Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
+5. If the `Vault.Asset` is `XRP`:
+ 1. Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
+ 2. Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
-- If the `Vault.Asset` is `XRP`:
- - Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
- - Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
+6. If the `Vault.Asset` is an `IOU`:
+ 1. Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+ 2. Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
-- If the `Vault.Asset` is an `IOU`:
- - Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
- - Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
-
-- If the `Vault.Asset` is an `MPT`:
- - Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- - Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
+7. If the `Vault.Asset` is an `MPT`:
+ 1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ 2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
#### 3.5.4 Invariants
@@ -580,60 +579,58 @@ _None._
##### 3.6.2.2 Protocol-Level Failures
-- `Vault` object with the `VaultID` does not exist on the ledger.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
-- The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
+2. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
-- The `Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
+3. The `Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
-- The unit of `Amount` is not shares of the vault.
-- The unit of `Amount` is not asset of the vault.
+4. The unit of `Amount` is not shares of the vault.
+5. The unit of `Amount` is not asset of the vault.
-- There is insufficient liquidity in the vault to fill the request:
- - If `Amount` is the vaults share:
- - `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
- - The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
- - `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
+6. There is insufficient liquidity in the vault to fill the request:
+ 1. If `Amount` is the vaults share:
+ 1. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
+ 2. The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
+ 3. `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
- - If `Amount` is the vaults asset:
- - The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
- - `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
+ 2. If `Amount` is the vaults asset:
+ 1. The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
+ 2. `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
-- The `Destination` account is specified:
- - The account does not have permission to receive the asset.
- - The account does not have a `RippleState` or `MPToken` object for the asset.
+7. The `Destination` account is specified:
+ 1. The account does not have permission to receive the asset.
+ 2. The account does not have a `RippleState` or `MPToken` object for the asset.
#### 3.6.3 State Changes
-- If the `Vault.Asset` is XRP:
- - Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
- - Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
-
-- If the `Vault.Asset` is an `IOU`:
- - If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
-
- - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
- - Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+1. If the `Vault.Asset` is XRP:
+ 1. Decrease the `Balance` field of _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
+ 2. Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
-- If the `Vault.Asset` is an `MPT`:
- - If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
+2. If the `Vault.Asset` is an `IOU`:
+ 1. If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
+ 2. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+ 3. Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
- - Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- - Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
+3. If the `Vault.Asset` is an `MPT`:
+ 1. If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
+ 2. Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+ 3. Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
-- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- - If `MPToken.MPTAmount == 0`, delete the object.
+4. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ 2. If `MPToken.MPTAmount == 0`, delete the object.
-- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+5. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
+6. Decrease the `AssetsTotal` and `AssetsAvailable` by $\Delta_{asset}$
#### 3.6.4 Invariants
@@ -660,38 +657,38 @@ _None._
##### 3.7.2.2 Protocol-Level Failures
-- `Vault` object with the `VaultID` does not exist on the ledger.
+1. `Vault` object with the `VaultID` does not exist on the ledger.
-- If `Vault.Asset` is `XRP`.
+2. If `Vault.Asset` is `XRP`.
-- If `Vault.Asset` is an `IOU` and:
- - The `Issuer` account is not the submitter of the transaction.
- - If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
- - If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
+3. If `Vault.Asset` is an `IOU` and:
+ 1. The `Issuer` account is not the submitter of the transaction.
+ 2. If the `AccountRoot(Issuer)` object does not have `lsfAllowTrustLineClawback` flag set (the asset does not support clawback).
+ 3. If the `AccountRoot(Issuer)` has the `lsfNoFreeze` flag set (the asset cannot be frozen).
-- If `Vault.Asset` is an `MPT` and:
- - `MPTokenIssuance.Issuer` is not the submitter of the transaction.
- - `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
- - If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
+4. If `Vault.Asset` is an `MPT` and:
+ 1. `MPTokenIssuance.Issuer` is not the submitter of the transaction.
+ 2. `MPTokenIssuance.lsfMPTCanClawback` flag is not set (the asset does not support clawback).
+ 3. If the `MPTokenIssuance.lsfMPTCanLock` flag is NOT set (the asset cannot be locked).
-- The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
+5. The `MPToken` object for the `Vault.ShareMPTID` of the `Holder` `AccountRoot` does not exist OR `MPToken.MPTAmount == 0`.
#### 3.7.3 State Changes
-- If the `Vault.Asset` is an `IOU`:
- - Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
+1. If the `Vault.Asset` is an `IOU`:
+ 1. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`.
-- If the `Vault.Asset` is an `MPT`:
- - Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
+2. If the `Vault.Asset` is an `MPT`:
+ 1. Decrease the `MPToken.MPTAmount` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
-- Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
- - Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
- - If `MPToken.MPTAmount == 0`, delete the object.
+3. Update the `MPToken` object for the `Vault.ShareMPTID` of the depositor `AccountRoot`:
+ 1. Decrease the `MPToken.MPTAmount` by $\Delta_{share}$.
+ 2. If `MPToken.MPTAmount == 0`, delete the object.
-- Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
- - Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
+4. Update the `MPTokenIssuance` object for the `Vault.ShareMPTID`:
+ 1. Decrease the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-- Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
+5. Decrease the `AssetsTotal` and `AssetsAvailable` by `min(Vault.AssetsAvailable`, $\Delta_{asset}$`)`
#### 3.7.4 Invariants
@@ -713,26 +710,26 @@ _None._
##### 3.8.2.2 Protocol-Level Failures
-- If `Payment.Amount` is a `Vault` share AND:
- - The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
- - The `Vault` `tfVaultShareNonTransferable` flag is set.
+1. If `Payment.Amount` is a `Vault` share AND:
+ 1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
+ 2. The `Vault` `tfVaultShareNonTransferable` flag is set.
- - The `Vault.Asset` is `MPT`:
- - `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- - `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- - `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- - `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
- - `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
+ 3. The `Vault.Asset` is `MPT`:
+ 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
+ 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
+ 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
+ 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ 5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
- - The `Vault.Asset` is an `IOU`:
- - The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
- - The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
+ 4. The `Vault.Asset` is an `IOU`:
+ 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
+ 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
+ 3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
+ 4. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `pseudo-account`.
#### 3.8.3 State Changes
-- If `MPToken`object for shares does not exist for the destination account, create one.
+1. If `MPToken`object for shares does not exist for the destination account, create one.
### 3.9 RPC: `vault_info`
From 81bef87616119d5f44b6b160c05115b471e1c0f3 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 26 May 2026 19:36:04 +0200
Subject: [PATCH 21/27] add section number to Vault State Update heading
Co-Authored-By: Claude Sonnet 4.6
---
XLS-0065-single-asset-vault/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 403dd3dd4..b93bfad65 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -256,7 +256,7 @@ Because the share amount is rounded down, the actual assets taken from the depos
$$\Delta_{assets'} = \frac{\Delta_{shares} \times \Gamma_{assets}}{\Gamma_{shares}}$$
-##### Vault State Update
+###### 3.1.7.2.1.1 Vault State Update
The vault's totals are updated with the final calculated amounts.
From b88058c8adc9f0df9f8cf25cb1761cddf69024e2 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Tue, 26 May 2026 19:37:16 +0200
Subject: [PATCH 22/27] address review comments
---
XLS-0065-single-asset-vault/README.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index b93bfad65..dc267ba22 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -80,7 +80,7 @@ The **`Vault`** ledger entry describes the state of the tokenized vault.
The key of the `Vault` object is the result of [`SHA512-Half`](https://xrpl.org/docs/references/protocol/data-types/basic-data-types/#hashes) of the following values concatenated in order:
- The `Vault` space key `0x0056` (capital V)
-- The [`ACCOUNTID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
+- The [`AccountID`](https://xrpl.org/docs/references/protocol/binary-format/#accountid-fields) of the account submitting the `VaultSet`transaction, i.e.`VaultOwner`.
- The transaction `Sequence` number. If the transaction used a [Ticket](https://xrpl.org/docs/concepts/accounts/tickets/), use the `TicketSequence` value.
#### 3.1.2 Fields
@@ -96,8 +96,8 @@ A vault has the following fields:
| `PreviousTxnLgrSeq` | No | Yes | `number` | `UINT32` | `N/A` | The sequence of the ledger that contains the transaction that most recently modified this object. |
| `Sequence` | No | Yes | `number` | `UINT32` | `N/A` | The transaction sequence number that created the vault. |
| `OwnerNode` | No | Yes | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the owner's directory. |
-| `Owner` | No | Yes | `string` | `ACCOUNTID` | `N/A` | The account address of the Vault Owner. |
-| `Account` | No | Yes | `string` | `ACCOUNTID` | `N/A` | The address of the Vaults _pseudo-account_. |
+| `Owner` | No | Yes | `string` | `AccountID` | `N/A` | The account address of the Vault Owner. |
+| `Account` | No | Yes | `string` | `AccountID` | `N/A` | The address of the Vaults _pseudo-account_. |
| `Data` | Yes | No | `string` | `BLOB` | None | Arbitrary metadata about the Vault. Limited to 256 bytes. |
| `Asset` | No | Yes | `string or object` | `ISSUE` | `N/A` | The asset of the vault. The vault supports `XRP`, `IOU` and `MPT`. |
| `AssetsTotal` | No | Yes | `number` | `NUMBER` | 0 | The total value of the vault. |
@@ -557,7 +557,7 @@ The `VaultWithdraw` transaction withdraws assets in exchange for the vault's sha
| `TransactionType` | Yes | `string` | `UINT16` | `62` | Transaction type. |
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
| `Amount` | Yes | `number` | `STAmount` | 0 | The exact amount of Vault asset to withdraw. |
-| `Destination` | No | `string` | `ACCOUNTID` | Empty | An account to receive the assets. It must be able to receive the asset. |
+| `Destination` | No | `string` | `AccountID` | Empty | An account to receive the assets. It must be able to receive the asset. |
| `DestinationTag` | No | `number` | `UINT32` | Empty | Arbitrary tag identifying the reason for the withdrawal to the destination. |
- If `Amount` is the Vaults asset, calculate the share cost using the [**Withdraw formula**](#21723-withdraw).
@@ -646,7 +646,7 @@ The `VaultClawback` transaction performs a Clawback from the Vault, exchanging t
| ----------------- | :------: | :-------: | :-----------: | :-----------: | :------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | Yes | `string` | `UINT16` | `63` | Transaction type. |
| `VaultID` | Yes | `string` | `HASH256` | `N/A` | The ID of the vault from which assets are withdrawn. |
-| `Holder` | Yes | `string` | `ACCOUNTID` | `N/A` | The account ID from which to clawback the assets. |
+| `Holder` | Yes | `string` | `AccountID` | `N/A` | The account ID from which to clawback the assets. |
| `Amount` | No | `number` | `NUMBER` | 0 | The asset amount to clawback. When Amount is `0` clawback all funds, up to the total shares the `Holder` owns. |
#### 3.7.2 Failure Conditions
@@ -718,7 +718,7 @@ _None._
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
- 4. `MPToken(MPTokenIssuanceID, PseudoAccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
+ 4. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
4. The `Vault.Asset` is an `IOU`:
From 488d024e4341d9c4db03a0ec822634f0191bb214 Mon Sep 17 00:00:00 2001
From: Vito Tumas <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 27 May 2026 10:46:00 +0200
Subject: [PATCH 23/27] Apply suggestions from code review
Co-authored-by: Mayukha Vadari
---
XLS-0065-single-asset-vault/README.md | 34 +++++++++++++--------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index dc267ba22..13cad0845 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -339,7 +339,7 @@ The `VaultCreate` transaction creates a new `Vault` object.
| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
-| `WithdrawalPolicy` | No | `number` | `UINT8` | `strFirstComeFirstServe` | Indicates the withdrawal strategy used by the Vault. |
+| `WithdrawalPolicy` | No | `number` | `UINT8` | `"FirstComeFirstServe"` | Indicates the withdrawal strategy used by the Vault. |
| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
@@ -375,24 +375,24 @@ _TBD_
2. The `Asset` is `MPT`:
1. The `Scale` parameter is provided.
- 2. The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object. (the asset is not transferable).
- 3. The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object. (the asset is locked).
+ 2. The `lsfMPTCanTransfer` is not set in the `MPTokenIssuance` object (the asset is not transferable).
+ 3. The `lsfMPTLocked` flag is set in the `MPTokenIssuance` object (the asset is locked).
3. The `Asset` is an `IOU`:
1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
2. The `Scale` parameter is provided, and is less than **0** or greater than **18**.
-4. The `tfVaultPrivate` flag is not set and the `DomainID` is provided. (The VaultOwner is attempting to create a public Vault with a PermissionedDomain)
+4. The `tfVaultPrivate` flag is not set and the `DomainID` is provided (the Vault Owner is attempting to create a public Vault with a PermissionedDomain)
5. The `PermissionedDomain` object does not exist with the provided `DomainID`.
6. The `Data` field is larger than 256 bytes.
-7. The account submiting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
+7. The account submitting the transaction has insufficient `AccountRoot.Balance` for the Owner Reserve.
#### 3.2.6 State Changes
1. Create a new `Vault` ledger object.
-2. Create a new `MPTokenIssuance` ledger object for the vault shares.
+2. Create a new `MPTokenIssuance` ledger object for the vault shares, and assign its MPTID to `Vault.ShareMPTID`.
1. If the `DomainID` is provided:
1. `MPTokenIssuance(Vault.ShareMPTID).DomainID = DomainID` (Set the Permissioned Domain ID).
2. Create an `MPToken` object for the Vault Owner to hold Vault Shares.
@@ -433,8 +433,8 @@ _TBD_
1. `Vault` object with the specified `VaultID` does not exist on the ledger.
2. The submitting account is not the `Owner` of the vault.
3. The `Data` field is larger than 256 bytes.
-4. If `Vault.AssetsMaximum` > `0` AND `AssetsMaximum` > 0 AND:
- 1. The `AssetsMaximum` < `Vault.AssetsTotal` (new `AssetsMaximum` cannot be lower than the current `AssetsTotal`).
+4. If `Vault.AssetsMaximum` > `0` AND `tx.AssetsMaximum` > 0 AND:
+ 1. The `tx.AssetsMaximum` < `Vault.AssetsTotal` (new `tx.AssetsMaximum` cannot be lower than the current `AssetsTotal`).
5. The `sfVaultPrivate` flag is not set and the `DomainID` is provided (Vault Owner is attempting to set a PermissionedDomain to a public Vault).
6. The `PermissionedDomain` object does not exist with the provided `DomainID`.
7. The transaction is attempting to modify an immutable field.
@@ -509,8 +509,8 @@ _None._
1. `Vault` object with the `VaultID` does not exist on the ledger.
2. The asset type of the vault does not match the asset type the depositor is depositing.
3. The depositor does not have sufficient funds to make a deposit.
-4. Adding the `Amount` to the `AssetsTotal` of the vault would exceed the `AssetsMaximum`.
-5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets does not have credentials in the permissioned domain of the share.
+4. Adding the `Amount` to `Vault.AssetsTotal` would exceed `Vault.AssetsMaximum`.
+5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets is not a member of the `MPTokenIssuance(Vault.ShareMPTID).DomainID` permissioned domain.
6. The `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
@@ -579,7 +579,7 @@ _None._
##### 3.6.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
+1. The `Vault` object corresponding to the `VaultID` field does not exist on the ledger.
2. The `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
@@ -594,12 +594,12 @@ _None._
5. The unit of `Amount` is not asset of the vault.
6. There is insufficient liquidity in the vault to fill the request:
- 1. If `Amount` is the vaults share:
+ 1. If `Amount` is the vault's share:
1. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
2. The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
3. `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
- 2. If `Amount` is the vaults asset:
+ 2. If `Amount` is the vault's asset:
1. The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
2. `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
@@ -614,12 +614,12 @@ _None._
2. Increase the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
2. If the `Vault.Asset` is an `IOU`:
- 1. If the Depositor account does not have a `RippleState` object for the Vaults Asset, create the `RippleState` object.
+ 1. If the Depositor account does not have a `RippleState` object for the Vault's Asset, create the `RippleState` object.
2. Decrease the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
3. Increase the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
3. If the `Vault.Asset` is an `MPT`:
- 1. If the Depositor account does not have a `MPToken` object for the Vaults Asset, create the `MPToken` object.
+ 1. If the Depositor account does not have a `MPToken` object for the Vault's Asset, create the `MPToken` object.
2. Decrease the `MPToken.MPTAmount` by $\Delta_{asset}$ of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
3. Increase the `MPToken.MPTAmount` by $\Delta_{asset}$ of the depositor `MPToken` object for the `Vault.Asset`.
@@ -696,7 +696,7 @@ _None._
### 3.8 Transaction: `Payment`
-The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transfering Vault shares.
+The Single Asset Vault does not introduce new `Payment` transaction fields. However, it adds additional failure conditions and state changes when transferring Vault shares.
#### 3.8.1 Fields
@@ -711,7 +711,7 @@ _None._
##### 3.8.2.2 Protocol-Level Failures
1. If `Payment.Amount` is a `Vault` share AND:
- 1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account does not have credentials in the permissioned domain of the Vaults Share.
+ 1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account is not a member of the permissioned domain specified at `MPTokenIssuance(Vault.ShareMPTID).DomainID`.
2. The `Vault` `tfVaultShareNonTransferable` flag is set.
3. The `Vault.Asset` is `MPT`:
From 94c45ce1141f3da5c4c291fccfbb604981874a1c Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 27 May 2026 10:55:25 +0200
Subject: [PATCH 24/27] address review comments
---
XLS-0065-single-asset-vault/README.md | 49 ++++++++++++---------------
1 file changed, 21 insertions(+), 28 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 13cad0845..7a44262f0 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -159,8 +159,6 @@ The `MPTokenIssuance` object represents the share on the ledger. It is created a
###### 3.1.6.2.1 `MPTokenIssuance` Values
-Here's the table with the headings "Field," "Description," and "Value":
-
| **Field** | **Description** | **Value** |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| `Issuer` | The AccountID of the Vault's _pseudo-account_. | _pseudo-account_ ID |
@@ -229,7 +227,7 @@ To account for this problem, the Vault must use two different exchange rate mode
This section details the algorithms used to calculate the exchange between assets and shares for deposits, redemptions, and withdrawals.
-##### Key Variables
+The following are the **key variables** used in the calculations:
- **$\Gamma_{assets}$**: The total balance of assets held within the vault.
- **$\Gamma_{shares}$**: The total number of shares currently issued by the vault.
@@ -325,23 +323,21 @@ The Vault does not apply the [Transfer Fee](https://xrpl.org/docs/concepts/token
### 3.2 Transaction: `VaultCreate`
-All transactions introduced by this proposal incorporate the [common transaction fields](https://xrpl.org/docs/references/protocol/transactions/common-fields) that are shared by all transactions. Standard fields are only documented in this proposal if needed because this proposal introduces new possible values for such fields.
-
The `VaultCreate` transaction creates a new `Vault` object.
#### 3.2.1 Fields
-| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
-| ------------------ | :------: | :----------------: | :-----------: | :----------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `TransactionType` | Yes | `string` | `UINT16` | `58` | The transaction type. |
-| `Flags` | Yes | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
-| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
-| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
-| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
-| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
+| Field Name | Required | JSON Type | Internal Type | Default Value | Description |
+| ------------------ | :------: | :----------------: | :-----------: | :---------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TransactionType` | Yes | `string` | `UINT16` | `58` | The transaction type. |
+| `Flags` | Yes | `number` | `UINT32` | 0 | Specifies the flags for the Vault. |
+| `Data` | No | `string` | `BLOB` | | Arbitrary Vault metadata, limited to 256 bytes. |
+| `Asset` | Yes | `string or object` | `ISSUE` | `N/A` | The asset (`XRP`, `IOU` or `MPT`) of the Vault. |
+| `AssetsMaximum` | No | `number` | `NUMBER` | 0 | The maximum asset amount that can be held in a vault. |
+| `MPTokenMetadata` | No | `string` | `BLOB` | | Arbitrary metadata about the share `MPT`, in hex format, limited to 1024 bytes. |
| `WithdrawalPolicy` | No | `number` | `UINT8` | `"FirstComeFirstServe"` | Indicates the withdrawal strategy used by the Vault. |
-| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
-| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
+| `DomainID` | No | `string` | `HASH256` | | The `PermissionedDomain` object ID associated with the shares of this Vault. |
+| `Scale` | No | `number` | `UINT8` | 6 | The `Scale` specifies the power of 10 ($10^{\text{scale}}$) to multiply an asset's value by when converting it into an integer-based number of shares. |
#### 3.2.2 Flags
@@ -512,13 +508,13 @@ _None._
4. Adding the `Amount` to `Vault.AssetsTotal` would exceed `Vault.AssetsMaximum`.
5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets is not a member of the `MPTokenIssuance(Vault.ShareMPTID).DomainID` permissioned domain.
-6. The `Vault.Asset` is `MPT`:
+6. If the `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
4. `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
-7. The `Asset` is an `IOU`:
+7. If the `Vault.Asset` is an `IOU`:
1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
@@ -581,20 +577,19 @@ _None._
1. The `Vault` object corresponding to the `VaultID` field does not exist on the ledger.
-2. The `Vault.Asset` is `MPT`:
+2. If the `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
3. `MPToken(MPTokenIssuanceID, AccountID | Destination).lsfMPTLocked` flag is set (the asset is locked for the depositor or the destination).
-3. The `Asset` is an `IOU`:
+3. If the `Asset` is an `IOU`:
1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the `AccountRoot` of the `AccountID` or the `Destination`.
-4. The unit of `Amount` is not shares of the vault.
-5. The unit of `Amount` is not asset of the vault.
+4. The unit of `Amount` is not shares or the asset of the vault.
-6. There is insufficient liquidity in the vault to fill the request:
- 1. If `Amount` is the vault's share:
+5. There is insufficient liquidity in the vault to fill the request:
+ 1. If `Amount` is the vaults share:
1. `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` < `Amount` (attempt to withdraw more shares than there are in total).
2. The shares `MPToken.MPTAmount` of the `Account` is less than `Amount` (attempt to withdraw more shares than owned).
3. `Vault.AssetsAvailable` < $\Delta_{asset}$ (the vault has insufficient assets).
@@ -603,7 +598,7 @@ _None._
1. The shares `MPToken.MPTAmount` of the `Account` is less than $\Delta_{share}$ (attempt to withdraw more shares than owned).
2. `Vault.AssetsAvailable` < `Amount` (the vault has insufficient assets).
-7. The `Destination` account is specified:
+6. The `Destination` account is specified:
1. The account does not have permission to receive the asset.
2. The account does not have a `RippleState` or `MPToken` object for the asset.
@@ -714,14 +709,14 @@ _None._
1. The `Vault` `lsfVaultPrivate` flag is set and the `Payment.Destination` account is not a member of the permissioned domain specified at `MPTokenIssuance(Vault.ShareMPTID).DomainID`.
2. The `Vault` `tfVaultShareNonTransferable` flag is set.
- 3. The `Vault.Asset` is `MPT`:
+ 3. If the `Vault.Asset` is `MPT`:
1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the payer).
4. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the `pseudo-account`).
5. `MPToken(MPTokenIssuanceID, Destination).lsfMPTLocked` flag is set (the asset is locked for the destination account).
- 4. The `Vault.Asset` is an `IOU`:
+ 4. If the `Vault.Asset` is an `IOU`:
1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the payer account.
3. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the destination account.
@@ -737,8 +732,6 @@ This RPC retrieves the Vault ledger entry and the IDs associated with it.
#### 3.9.1 Request Fields
-We propose adding the following fields to the `ledger_entry` method:
-
| Field Name | Required? | JSON Type | Description |
| ---------- | :-------: | :-------: | :----------------------------------------: |
| `vault` | Yes | `string` | The object ID of the Vault to be returned. |
From af4a778a953974e1a5add5c4ab33f803cda2053d Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 27 May 2026 11:33:20 +0200
Subject: [PATCH 25/27] spec: Update VaultDeposit failure conditions and state
changes
---
XLS-0065-single-asset-vault/README.md | 65 ++++++++++++++-------------
1 file changed, 35 insertions(+), 30 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 2c381ab6a..41b6852a8 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -498,45 +498,50 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
##### 3.5.2.1 Data Verification
-_None._
+1. The `VaultID` field is zero. (`temMALFORMED`)
+2. The `Amount` field is zero or negative. (`temBAD_AMOUNT`)
##### 3.5.2.2 Protocol-Level Failures
-1. `Vault` object with the `VaultID` does not exist on the ledger.
-2. The asset type of the vault does not match the asset type the depositor is depositing.
-3. The depositor does not have sufficient funds to make a deposit.
-4. Adding the `Amount` to `Vault.AssetsTotal` would exceed `Vault.AssetsMaximum`.
-5. The `Vault` `lsfVaultPrivate` flag is set and the `Account` depositing the assets is not a member of the `MPTokenIssuance(Vault.ShareMPTID).DomainID` permissioned domain.
-
-6. If the `Vault.Asset` is `MPT`:
- 1. `MPTokenIssuance.lsfMPTCanTransfer` is not set (the asset is not transferable).
- 2. `MPTokenIssuance.lsfMPTLocked` flag is set (the asset is globally locked).
- 3. `MPToken(MPTokenIssuanceID, AccountID).lsfMPTLocked` flag is set (the asset is locked for the depositor).
- 4. `MPToken(MPTokenIssuanceID, AccountID).MPTAmount` < `Amount` (insufficient balance).
-
-7. If the `Vault.Asset` is an `IOU`:
- 1. The `lsfGlobalFreeze` flag is set on the issuing account (the asset is frozen).
- 2. The `lsfHighFreeze` or `lsfLowFreeze` flag is set on the `RippleState` object between the Asset `Issuer` and the depositor.
- 3. The `RippleState` object `Balance` < `Amount` (insufficient balance).
+1. The `Vault` object with the `VaultID` does not exist on the ledger. (`tecNO_ENTRY`)
+2. The `Amount` asset does not match `Vault.Asset`. (`tecWRONG_ASSET`)
+3. The vault has `lsfVaultPrivate` set and the depositor is not the vault owner:
+ 1. No `PermissionedDomain` is configured on `MPTokenIssuance(Vault.ShareMPTID)`. (`tecNO_AUTH`)
+ 2. The depositor is not a valid member of the permissioned domain. (`tecNO_AUTH`)
+4. If `Vault.Asset` is an `MPT`:
+ 1. The `lsfMPTCanTransfer` flag is not set in the `MPTokenIssuance` object (the asset is not transferable). (`tecNO_AUTH`)
+ 2. The asset is globally or individually locked for the depositor. (`tecLOCKED`)
+5. If `Vault.Asset` is an `IOU`:
+ 1. The asset is globally frozen, or the depositor's trust line is frozen. (`tecFROZEN`)
+6. The vault shares are locked for the depositor. (`tecLOCKED`)
+7. The depositor does not have a required authorized holding for the vault asset (e.g., missing `MPToken` for a restricted `MPT`). (`tecNO_AUTH`)
+8. The depositor has insufficient balance to cover the deposit. (`tecINSUFFICIENT_FUNDS`)
+9. The `Amount` rounds to zero at the vault's precision scale. (`tecPRECISION_LOSS`)
+10. The `Amount` rounds to zero at the depositor's trust line scale (IOU only). (`tecPRECISION_LOSS`)
+11. The computed number of shares for the deposit is zero. (`tecPRECISION_LOSS`)
+12. Arithmetic overflow during share calculation. (`tecPATH_DRY`)
+13. Adding the deposited amount to `Vault.AssetsTotal` would exceed `Vault.AssetsMaximum`. (`tecLIMIT_EXCEEDED`)
#### 3.5.3 State Changes
-1. If no `MPToken` object exists for the depositor, create one. For object details, see [2.1.6.2 `MPToken`](#2162-mptoken).
-2. Increase the `MPTAmount` field of the share `MPToken` object of the `Account` by $\Delta_{share}$.
-3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` object by $\Delta_{share}$.
-4. Increase the `AssetsTotal` and `AssetsAvailable` of the `Vault` by `Amount`.
+1. If no share `MPToken` object exists for the depositor, create one. For private vaults, the `MPToken` is created only after domain authorization is verified.
+2. Increase the `MPTAmount` field of the depositor\'s share `MPToken` by $\Delta_{share}$.
+3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` by $\Delta_{share}$.
+4. Increase `Vault.AssetsTotal` and `Vault.AssetsAvailable` by $\Delta_{asset}$.
+
+5. If `Vault.Asset` is `XRP`:
+ 1. Increase the `Balance` field of the _pseudo-account_ `AccountRoot` by $\Delta_{asset}$.
+ 2. Decrease the `Balance` field of the depositor `AccountRoot` by $\Delta_{asset}$.
-5. If the `Vault.Asset` is `XRP`:
- 1. Increase the `Balance` field of _pseudo-account_ `AccountRoot` by `Amount`.
- 2. Decrease the `Balance` field of the depositor `AccountRoot` by `Amount`.
+6. If `Vault.Asset` is an `IOU`:
+ 1. Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
+ 2. Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by $\Delta_{asset}$.
-6. If the `Vault.Asset` is an `IOU`:
- 1. Increase the `RippleState` balance between the _pseudo-account_ `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
- 2. Decrease the `RippleState` balance between the depositor `AccountRoot` and the `Issuer` `AccountRoot` by `Amount`.
+7. If `Vault.Asset` is an `MPT`:
+ 1. Increase the `MPToken.MPTAmount` of the _pseudo-account_ `MPToken` for `Vault.Asset` by $\Delta_{asset}$.
+ 2. Decrease the `MPToken.MPTAmount` of the depositor `MPToken` for `Vault.Asset` by $\Delta_{asset}$.
-7. If the `Vault.Asset` is an `MPT`:
- 1. Increase the `MPToken.MPTAmount` by `Amount` of the _pseudo-account_ `MPToken` object for the `Vault.Asset`.
- 2. Decrease the `MPToken.MPTAmount` by `Amount` of the depositor `MPToken` object for the `Vault.Asset`.
+> **Note:** $\Delta_{asset}$ is the actual asset amount transferred, which may be slightly less than the requested `Amount` due to scale rounding for IOU assets.
#### 3.5.4 Invariants
From 72f5c622e5be1b4115e9c6bedfee05483d3c5055 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 27 May 2026 11:33:54 +0200
Subject: [PATCH 26/27] spec: Fix escaped apostrophes in markdown
---
XLS-0065-single-asset-vault/README.md | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index 41b6852a8..d0311e378 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -505,14 +505,18 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
1. The `Vault` object with the `VaultID` does not exist on the ledger. (`tecNO_ENTRY`)
2. The `Amount` asset does not match `Vault.Asset`. (`tecWRONG_ASSET`)
-3. The vault has `lsfVaultPrivate` set and the depositor is not the vault owner:
+
+3. If the vault has `lsfVaultPrivate` set and the depositor is not the vault owner:
1. No `PermissionedDomain` is configured on `MPTokenIssuance(Vault.ShareMPTID)`. (`tecNO_AUTH`)
2. The depositor is not a valid member of the permissioned domain. (`tecNO_AUTH`)
+
4. If `Vault.Asset` is an `MPT`:
1. The `lsfMPTCanTransfer` flag is not set in the `MPTokenIssuance` object (the asset is not transferable). (`tecNO_AUTH`)
2. The asset is globally or individually locked for the depositor. (`tecLOCKED`)
+
5. If `Vault.Asset` is an `IOU`:
1. The asset is globally frozen, or the depositor's trust line is frozen. (`tecFROZEN`)
+
6. The vault shares are locked for the depositor. (`tecLOCKED`)
7. The depositor does not have a required authorized holding for the vault asset (e.g., missing `MPToken` for a restricted `MPT`). (`tecNO_AUTH`)
8. The depositor has insufficient balance to cover the deposit. (`tecINSUFFICIENT_FUNDS`)
@@ -525,7 +529,7 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
#### 3.5.3 State Changes
1. If no share `MPToken` object exists for the depositor, create one. For private vaults, the `MPToken` is created only after domain authorization is verified.
-2. Increase the `MPTAmount` field of the depositor\'s share `MPToken` by $\Delta_{share}$.
+2. Increase the `MPTAmount` field of the depositor's share `MPToken` by $\Delta_{share}$.
3. Increase the `OutstandingAmount` field of the share `MPTokenIssuance` by $\Delta_{share}$.
4. Increase `Vault.AssetsTotal` and `Vault.AssetsAvailable` by $\Delta_{asset}$.
From 9eccc43e212bb0a4cec38c6b50dfb722c15b3f94 Mon Sep 17 00:00:00 2001
From: Vito <5780819+Tapanito@users.noreply.github.com>
Date: Wed, 27 May 2026 12:10:05 +0200
Subject: [PATCH 27/27] docs: Add VaultDeposit invariants section
---
XLS-0065-single-asset-vault/README.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/XLS-0065-single-asset-vault/README.md b/XLS-0065-single-asset-vault/README.md
index d0311e378..d2bb6043f 100644
--- a/XLS-0065-single-asset-vault/README.md
+++ b/XLS-0065-single-asset-vault/README.md
@@ -549,7 +549,12 @@ The `VaultDeposit` transaction adds Liqudity in exchange for vault shares.
#### 3.5.4 Invariants
-**TBD**
+1. The vault pseudo-account's asset balance must increase by a positive amount not exceeding the transaction `Amount`.
+2. Unless the depositor is the asset issuer, the depositor's asset balance must decrease by the same amount as the vault increases.
+3. The depositor's share `MPToken.MPTAmount` must increase by a positive amount.
+4. The increase in `MPTokenIssuance(Vault.ShareMPTID).OutstandingAmount` must equal the increase in the depositor's share balance.
+5. `Vault.AssetsTotal` and `Vault.AssetsAvailable` must each increase by exactly the vault's asset balance increase.
+6. If `Vault.AssetsMaximum > 0`: `Vault.AssetsTotal <= Vault.AssetsMaximum`.
### 3.6 Transaction: `VaultWithdraw`