> ## Documentation Index
> Fetch the complete documentation index at: https://ormaprotocol.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# XRPL Vault Mechanics: Phases, Levers, and Accounting

> Learn how XLS-65 closed-ended vaults work: the three phases, the LoanBroker relationship, cash-basis accounting, and the two manager levers Orma measures.

Orma measures credit risk in XLS-65 vaults and XLS-66 lending on the XRP Ledger. This page explains the on-chain mechanics the rest of the documentation assumes. It is written for a reader who understands credit funds and is new to XRPL. Everything described here was measured on Devnet against `rippled 3.4.0-rc5` at `wss://s.devnet.rippletest.net:51233` (network id 2).

## The vault is a ledger object, not a contract

A vault is a native object on the XRP Ledger. There is no contract code to read or audit. Depositors send `VaultDeposit`, receive units of a share token, and the object carries the fund's balance sheet in a handful of fields:

| Field                               | Meaning                                                                |
| ----------------------------------- | ---------------------------------------------------------------------- |
| `AssetsTotal`                       | Everything the vault counts as an asset: cash plus loans at book value |
| `AssetsAvailable`                   | The portion not currently lent out                                     |
| `LossUnrealized`                    | Loss the manager has recognised but not yet written off                |
| `MPTokenIssuance.OutstandingAmount` | Units outstanding. There is no `SharesTotal` field on the Vault itself |
| `Owner`                             | The account that created the vault and controls it                     |
| `Account`                           | The vault's pseudo-account, which actually holds the funds             |

Two of those deserve immediate attention. `AssetsTotal` does **not** net `LossUnrealized`, and units outstanding live on a separate ledger object — the share token's `MPTokenIssuance`. A tool that reads `AssetsTotal` and divides by units is reading the fund before provisions.

<Warning>
  `rippled` omits any field whose value equals the type default. A vault with no recognised loss has no `LossUnrealized` key at all — not `"0"`. "Absent" means zero everywhere in Orma's arithmetic, and confusing "absent because zero" with "absent because the field doesn't exist" is a real source of wrong numbers.
</Warning>

## Open-ended and closed-ended vaults

An **open-ended** vault has no calendar. Deposits and withdrawals are legal at any time, subject to liquidity. It cannot be used for lending — attaching a `LoanBroker` returns `tecNO_PERMISSION`.

A **closed-ended** vault is created with `VaultKind: 1` plus a `SubscriptionDate` and a `RedemptionDate`. It is a fixed-term fund: capital is raised during Subscription, deployed during Investment, then returned during Redemption.

<Note>
  `VaultKind` is immutable. A vault created open-ended can never become a lending vault. If you plan to lend, you must decide that at `VaultCreate` — you cannot change it afterwards.
</Note>

## The three phases

Phase is derived, not stored. It is a comparison between the validated ledger's `close_time` and the two dates on the vault object. The clock is the validated ledger close time, not the wall clock — Devnet close time can lag wall time by up to ten seconds, and a countdown computed against the wrong clock goes negative on stage.

**Subscription** runs until `SubscriptionDate`. Capital comes in and may leave freely. No lending is possible yet — `LoanSet` returns `tecTOO_SOON`.

**Investment** runs from `SubscriptionDate` to `RedemptionDate`. The book is locked. Loans are originated, impaired, and defaulted here. Investors cannot deposit and cannot withdraw for the entire phase — `VaultDeposit` returns `tecEXPIRED` and `VaultWithdraw` returns `tecTOO_SOON`. This is a phase predicate, not a liquidity check: Orma measured it with 80 XRP of `AssetsAvailable` sitting idle and the exit was still closed.

**Redemption** begins at `RedemptionDate`. Withdrawals reopen, deposits stay closed, and no new loan can be written. Under the default `WithdrawalPolicy: 1`, redemption is first-come first-served, so the order investors queue matters when liquidity is short.

### Phase rejection matrix

The table below is measured, not inferred. Each row was verified against a live Devnet facility with a passing control alongside it (a control that succeeds where the action is legal proves the rejection is about phase, not about a malformed transaction).

| Phase        | Transaction                          | Expected | Measured result      |
| ------------ | ------------------------------------ | -------- | -------------------- |
| OpenEnded    | `VaultDeposit` (control)             | succeed  | `tesSUCCESS`         |
| OpenEnded    | `VaultWithdraw` (control)            | succeed  | `tesSUCCESS`         |
| OpenEnded    | `LoanBrokerSet` on open vault        | reject   | `tecNO_PERMISSION`   |
| Subscription | `VaultDeposit` (control)             | succeed  | `tesSUCCESS`         |
| Subscription | `VaultWithdraw` (control)            | succeed  | `tesSUCCESS`         |
| Subscription | `LoanSet` before subscription closes | reject   | `tecTOO_SOON`        |
| Investment   | `VaultDeposit`                       | reject   | `tecEXPIRED`         |
| Investment   | `VaultWithdraw`                      | reject   | `tecTOO_SOON`        |
| Investment   | `LoanSet` (control)                  | succeed  | `tecNO_PERMISSION`\* |
| Redemption   | `VaultDeposit`                       | reject   | `tecEXPIRED`         |
| Redemption   | `VaultWithdraw` (control)            | succeed  | `tesSUCCESS`         |
| Redemption   | `LoanSet` after redemption opens     | reject   | `tecEXPIRED`         |

\* The Investment `LoanSet` returned `tecNO_PERMISSION` in this particular matrix run because the loan's scheduled maturity overran the vault's `RedemptionDate`. The ledger rule is `StartDate + PaymentInterval × PaymentTotal + 60 ≤ RedemptionDate`. Three of Orma's four live facilities have loans written successfully in Investment — this was a window-sizing issue in the matrix script, not a phase rule.

<Warning>
  `tecNO_PERMISSION` on `LoanSet` is overloaded. It means both "this vault is not closed-ended" and "this loan matures after the vault's `RedemptionDate`". A 180-second loan on the same vault returned `tesSUCCESS`; a 1200-second loan returned `tecNO_PERMISSION`. Read the code in `LoanSet.cpp` — the rule involves `RedemptionDate` and `GracePeriod` is not part of it.
</Warning>

<Note>
  Because the exit is closed for the entire Investment phase, a credit signal published during Investment cannot be acted on by selling. That is why the redemption cliff anchors the composite grade rather than sitting as one factor among five, and why Orma publishes the signal where a third party can act on it — not just display it. See [Mathematics — The Five Measured Factors](/mathematics).
</Note>

## The LoanBroker and first-loss capital

Loans are not made directly by the vault. They flow through a **LoanBroker**, a separate ledger object that points at the vault and holds its own fields:

| Field                  | Meaning                                                      |
| ---------------------- | ------------------------------------------------------------ |
| `DebtTotal`            | Principal currently drawn across all loans under this broker |
| `DebtMaximum`          | Ceiling on drawn debt                                        |
| `CoverAvailable`       | First-loss capital posted and not yet consumed               |
| `CoverRateMinimum`     | Cover the broker must hold, as a fraction of `DebtTotal`     |
| `CoverRateLiquidation` | Fraction of that minimum that is liquidated on a default     |

Both rates are expressed in units of $10^{-5}$. A stated `10000` means $0.10$ — ten percent — divided by $10^5$. The cover consumed on a single default is:

$T(p) = \min\Big(\big\lceil D \cdot c_{\min} \cdot c_{\text{liq}} \big\rceil,\; p,\; C\Big)$

where $D$ is the broker's total drawn debt, $p$ is the principal of the defaulting loan, and $C$ is `CoverAvailable`. At $c_{\min} = c_{\text{liq}} = 10000$ the effective coefficient is $0.10 \times 0.10 = 0.01$ — one percent of the book, not ten.

Note what the base is. The formula uses the broker's **total** book $D$, not the principal of the loan that failed. This means the same loan defaulting inside a larger book liquidates more cover, and a loan that represents most of the book liquidates cover keyed to that entire book and leaves little behind it.

Orma reports `strandedCoverFraction` to surface this. On Calder Structured Credit III — 10 XRP of debt, 5 XRP of cover posted — 98 percent of the posted cover cannot be reached by any single default at the current book size.

## The broker owner is the vault owner

This is a protocol fact, not a configuration choice. `LoanBrokerSet` requires the submitting account to be the vault's `Owner`. So the party who posts first-loss capital and the party who manages the fund are necessarily the same account.

Only that owner may impair, unimpair, or default a loan. There is no independent servicer, no trustee, and no rating committee. Credit events are declared by the party whose own capital those declarations consume.

<Warning>
  Delegation does not fix this. XLS-75 permission delegation returns `temMALFORMED` for the entire lending transaction suite, so credit decisions cannot be handed to a separate account even if a manager wanted to.
</Warning>

## Cash-basis accounting and `LossUnrealized`

Under `LEVersion: 1` the vault keeps cash-basis books. The practical consequences:

* **Origination moves `DebtTotal`, not `AssetsTotal`.** Lending out cash is not a gain or a loss, so the vault's asset total does not change when a loan is written.
* **Impairment moves only `LossUnrealized`.** Nothing else on the Vault object changes.
* **Default moves assets.** The loss is realised: the asset is written off, the provision is released, cover is liquidated, and the book reads clean afterwards.

### Impairment vs. default

|                             | Impairment                       | Default                             |
| --------------------------- | -------------------------------- | ----------------------------------- |
| Transaction                 | `LoanManage` flag `0x20000`      | `LoanManage` flag `0x10000`         |
| Precondition                | Loan must already be past due    | Past due plus `GracePeriod` elapsed |
| Reversible                  | Yes — unimpair is flag `0x40000` | No                                  |
| `Vault.LossUnrealized`      | Increases by the exposure        | Released (decreases back)           |
| `Vault.AssetsTotal`         | Unchanged                        | Decreases by the amount written off |
| `LoanBroker.CoverAvailable` | Unchanged                        | Decreases by $T(p)$                 |
| `LoanBroker.DebtTotal`      | Unchanged                        | Decreases by the principal          |

<Note>
  Test loan flags by bit, never by equality. A loan can carry more than one flag, and `Flags === 131072` silently stops matching the moment a second flag is set. Use a bitwise check: `(flags & 0x20000) !== 0` for impaired, `(flags & 0x10000) !== 0` for defaulted.
</Note>

An impairment is a disclosure — it says "this exposure is in trouble" without moving a single asset. A default is a write-off. Both are the vault owner's unilateral decision, and neither has a deadline attached.

Realisation is also why a clean-looking book is not evidence of a clean history. Kestrel Bridge Financing II took 50 XRP of subscriptions, defaulted on 40 XRP, and now holds 10.5 XRP with `lossUnrealized` of `"0"` and a NAV divergence of zero basis points. The naive and correct unit values agree at `0.210000` — and only the 79% gap between units outstanding and assets held tells you what happened.

## Lever one: when to recognise a loss

The manager chooses the moment `LossUnrealized` moves. Until they act, `AssetsTotal` carries the troubled loan at book and a naive reader sees a fund at par:

$\text{NAV}_{\text{naive}} = \frac{A}{S}, \qquad \text{NAV}_{\text{held}} = \frac{A - L}{S}$

On Meridian Trade Finance I: $A = 51{,}000{,}000$ drops, $L = 10{,}000{,}000$, $S = 51{,}000{,}000$. Naive reads `1.000000` and held reads `0.803922` — a divergence of 1961 basis points on the same object at the same instant.

The delay is not merely invisible to careless tools. It is invisible to careful ones that watch transaction metadata. On the first impairment of a healthy vault, `LossUnrealized` moves from zero and under cash-basis accounting nothing else changes, so `PreviousFields` is emitted **empty**. An indexer that diffs metadata sees no change at all on the most important credit event the protocol has.

Delay also flatters the manager directly: `CoverRateMinimum` is keyed to `DebtTotal`, so the cover they must hold, and the cover liquidated when they eventually act, both shrink as the book ages.

<Tip>
  This is why Orma is a four-second poller that re-reads full object state rather than a metadata differ. Re-reading cannot miss a field that was never diffed. See `src/poll.mjs`.
</Tip>

## Lever two: in what order to realise losses

Because $T$ is keyed to `DebtTotal` at the instant of each default, and `DebtTotal` is decremented as each default lands, declaring losses in a different order changes the total cover consumed. With $c = c_{\min} c_{\text{liq}}$, $D_0$ the book at the first default, and $k$ defaults declared in order $\sigma$:

$T(\sigma, k) = c \left[ k D_0 - \sum_{i=1}^{k} (k - i)\, p_{\sigma(i)} \right]$

Only the sum depends on $\sigma$, and its coefficient $(k-i)$ decreases in $i$. By the rearrangement inequality the sum is maximised — and $T$ minimised — when principals are declared in decreasing order.

> Total first-loss capital consumed is **minimised by declaring the largest exposure first**, and **maximised by declaring the smallest first**.

Orma places the observed sequence between the two extremes as a sequence score:

$\phi = \frac{T_{\text{observed}} - T_{\min}}{T_{\max} - T_{\min}}, \qquad \phi \in [0, 1]$

$\phi = 0$ is the sequence worst for investors; $\phi = 1$ is the best. Kestrel Bridge Financing II scored $\phi = 0.0000$ — the manager chose the investor-worst order on live Devnet. See [Mathematics](/mathematics) for the full derivation and three independent cross-checks.

## One asymmetry, two levers, then enforcement

The vault owner and the broker owner are the same account — that is the single asymmetry. It gives one party two levers over investor money: **when** a loss is recognised, and **in what order** losses are realised. Both are discretionary, both move money, and neither leaves a signal a metadata-diffing tool would catch.

Orma's response is three steps, each on its own page:

* **Measure** both levers from public ledger state, with no privileged access. See [Mathematics](/mathematics).
* **Publish** the result where it can be acted on: a native XLS-47 Oracle object and a pointer inside the share token's own metadata. See [Mathematics — Publication](/mathematics) and [NAV Pointer](/nav-pointer).
* **Enforce** with XLS-70 credentials and a permissioned domain, so an independent rater decides who may subscribe. See [Gate](/gate).
