Skip to main content
Orma exists because one credit event — the first impairment of a previously healthy lending vault — is invisible to every tool that reads vault state by diffing transaction metadata. A vault on XRPL Devnet took 51 XRP of subscriptions, lent 10 XRP through its LoanBroker, and the borrower stopped paying. The manager did the correct and honest thing: they impaired the loan, recognising the loss before anyone redeemed. The transaction succeeded. And every indexer watching that transaction was told nothing at all. Orma’s answer is three steps: measure from ledger state instead of from metadata diffs, publish the result where any counterparty can act on it, and give independent third parties a way to enforce on it at the ledger level.

The problem: an empty change set

When a manager impairs a loan under cash-basis accounting (LEVersion = 1), only one field on the Vault ledger object changes: LossUnrealized moves from 0 to the impaired amount. AssetsTotal stays the same. AssetsAvailable stays the same. Units outstanding, which live on the share token’s MPTokenIssuance, stay the same. Here is the Vault object for Meridian Trade Finance I before and after its impairment, in drops: A naive reading divides assets by units: NAVnaive=AS=51,000,00051,000,000=1.000000\text{NAV}_{\text{naive}} = \frac{A}{S} = \frac{51{,}000{,}000}{51{,}000{,}000} = 1.000000 The correct reading nets out the recognised loss first: NAVheld=ALS=51,000,00010,000,00051,000,000=0.803922\text{NAV}_{\text{held}} = \frac{A - L}{S} = \frac{51{,}000{,}000 - 10{,}000{,}000}{51{,}000{,}000} = 0.803922 That is a gap of 1961 basis points between two readings of the same object at the same instant.
The first impairment of a previously healthy vault emits PreviousFields: {} — an empty change set. rippled omits from PreviousFields any field whose previous value was the type default, and LossUnrealized was 0. Under cash-basis accounting, impairment changes nothing else on the Vault object, so there is no field left to diff. An indexer watching transaction metadata sees a node touched but unchanged, and keeps serving NAV = 1.000000.Every subsequent impairment is visible because LossUnrealized is no longer 0. The single event that matters most is the one that disappears.
The impairment transaction is 075FE6D2E0F29919AF477A2A8F581A680805A006138BB967D8434611E49229C3, result tesSUCCESS. Its metadata for the Vault node reads:
This is not a bug in rippled. It is correct metadata. It is also a silent wrong answer for every downstream consumer that builds vault state by applying deltas. Orma’s reader therefore never diffs PreviousFields. It polls a known list of vault IDs every 4,000 ms and re-reads each object in full.

One asymmetry, two levers

The impairment above was discretionary. Nothing in the protocol required the manager to declare it that day rather than a week later. During the Investment phase, VaultWithdraw returns tecTOO_SOON for the entire phase regardless of how much cash the vault holds, so an investor who suspects a problem cannot leave. That is lever one: when a loss is recognised. Lever two is in what order losses are realised, and it moves real money. When a loan defaults, the cover the broker forfeits is sized against the broker’s total drawn debt at that moment, not against the principal of the loan that defaulted: T(p)=min(Dcmincliq,  p,  C)T(p) = \min\Big(\big\lceil D \cdot c_{\min} \cdot c_{\text{liq}} \big\rceil,\; p,\; C\Big) Because DD is decremented as each default lands, declaring a large exposure first shrinks the base for everything that follows. Total cover consumed across a fixed set of defaults therefore depends entirely on the order they are declared. Kestrel Bridge Financing II had two loans — 30 XRP and 10 XRP — both of which defaulted. The manager declared the 30 XRP loan first: Same two losses, same rates, same day — 0.20 XRP difference in what the manager’s own first-loss capital absorbed. That 0.20 XRP fell on unit holders. By the rearrangement inequality this is general: total first-loss capital consumed is always minimised by declaring the largest exposure first. See Mathematics for the full derivation and three independent proofs. The party who chooses the order is the LoanBroker owner, who is necessarily the vault owner — LoanBrokerSet returns tecNO_PERMISSION otherwise, and XLS-75 delegation is temMALFORMED for the entire lending suite. So the same account decides when a loss is recognised and in what order losses are realised, and both decisions move money from investors to them.

Orma’s three-step answer

Measure

Orma scores five factors independently on a 20-step ordinal ladder from AAA to D: liquidity, first-loss adequacy, concentration, recognition lag, and the redemption cliff. No weights are applied anywhere — a weighted average lets a strong factor pay for a broken one. The composite grade anchors on the redemption cliff and is notched down by rule. A separate conduct score assesses the manager rather than the book: the observed default sequence against its best and worst possible orders, plus penalties for losses written off without prior impairment. The key design choice is realised capital destruction, ρ=max(0,(SA)/S)\rho = \max(0, (S - A)/S), measured against AA rather than ALA - L. A write-off removes the asset from AssetsTotal, releases the provision, and leaves the book reading clean. Without this term, a vault that lost 79% of subscribed capital would grade AAA — identical to one that never lost a penny. With it, Kestrel grades B.

Publish

The reading goes on-chain twice. First as a native XLS-47 Price Oracle: six PriceData entries per vault, one per dimension — NAV, HDL, LIQ, COV, CNC, DDL. An Oracle is a native ledger object, and get_aggregate_price computes a median across independent publishers inside the ledger, so a second publisher who disagrees changes the aggregate without asking. Second, as a pointer inside the share token’s own XLS-89 metadata, under an orma key carrying a nav_url template, so a lender holding nothing but the MPT issuance ID can resolve to a valuation without knowing Orma exists. See Mathematics — Publication as an Oracle Object and NAV Pointer for details.

Enforce

A grade nobody acts on is a press release. Orma issues an XLS-70 credential of type ORMA-IG to a limited partner. The LP accepts it (issuance alone grants nothing — a deposit without the accept is still tecNO_AUTH). A vault owner who wants graded capital names Orma’s issuer in a PermissionedDomain and creates the vault with tfVaultPrivate. After that, the ledger itself refuses a deposit from an uncredentialed account. Orma signs nothing in that step, is never asked, and cannot refuse to be cited. Revocation is asymmetric on purpose: deleting the credential bounces the next deposit and leaves the existing position fully withdrawable. A rater who can trap capital is a worse problem than the one being solved. See Gate for the full enforcement walkthrough.

The four live Devnet facilities

All four facilities are live on Devnet and readable by anyone pointed at wss://s.devnet.rippletest.net:51233 (network id 2). The reader loads all four at startup.
Every monetary quantity Orma serves on the wire is a decimal string, never a JSON number. XRPL NUMBER fields carry up to 19 significant digits and Number() destroys them silently. Orma uses decimal.js at 40 digits of precision throughout.

Where to go next

Concepts

Vault phases, the LoanBroker relationship, cash-basis accounting, and why the broker owner is always the vault owner.

Quickstart

Clone, prove the toolchain, start the reader, and call your first API endpoint.

Mathematics

Every formula Orma computes: NAV two ways, the cover formula, the ordering proof, the five scoring factors, and the oracle encoding.

Gate

Issue XLS-70 credentials, bind them to a permissioned domain, and enforce access at the ledger level.

NAV Pointer

Write a valuation pointer into share token metadata so any lender can price a pledge from the token ID alone.

Use Cases

Four worked cases against three live Devnet facilities, including collateral pricing and gate enforcement.