Everything here runs on XRPL Devnet (
wss://s.devnet.rippletest.net:51233, network id 2). XLS-65 vaults and XLS-66 lending are not on Mainnet, so the reader, scorer, and gate cannot be pointed at production assets today. Devnet is not a convenience — it is the only network where XLS-65 and XLS-66 exist.Prerequisites
Two runtime dependencies, both pinned exactly:
xrpl at 5.2.0 and decimal.js at 10.6.0.
Why the xrpl pin is exact
xrpl@5.2.0 fixes two blocking issues for the lending stack:
VaultCreatewithVaultKind,SubscriptionDate, andRedemptionDatenow encodes natively. Before 5.2.0,validate()rejected those fields, making the very first transaction of the lending workflow unreachable through the model layer.signLoanSetByCounterpartyemits the correctCPT\0(0x43505400) prefix, eliminating the manual prefix swap that previous code needed.
- The
LoanSetfee must be doubled by hand.autofillwarns about the second signature but does not pay for it. Every baker doestx.Fee = String(Number(tx.Fee) * 2). OracleSetis signed raw.validate()capsScaleat 0–10 whilerippledaccepts 0–20, and demandsScalewheneverAssetPriceis present whilerippledforbids an explicitScale: 0. Together these make some legal oracles unreachable through the model layer.
xrpl-py is still on 5.1.0 and did not receive either fix. Keep Python out of the signing path.Steps
1
Verify the toolchain
Run this first, every session, before trusting anything else. It drives the entire lending chain against live Devnet with no workarounds: A successful run looks like this (elapsed times vary with Devnet close times):The middle line is
VaultCreate → VaultDeposit → LoanBrokerSet → LoanBrokerCoverDeposit → LoanSet with a native counterparty signature.autofill’s own warning about the second signature — it prints the warning and then does not double the fee, which is why every baker doubles it manually. The loan ID changes every run because the script builds a fresh vault each time.If this fails, stop. Nothing downstream will work and the failure is almost always the toolchain rather than your code.2
Start the reader
Run from the repository root. On startup, the entrypoint reads every You should see:
demo/*.json, keeps any file carrying a 64-hex vaultId, and serves all four facilities automatically.publishing: false is correct — oracle publication is off until you supply a seed (see the optional step below). Logs go to stderr as JSON lines by default; set LOG_PRETTY=1 for human-readable output.The four facilities the reader discovers:You can override the auto-discovered facilities with explicit arguments or an environment variable:
3
Call the API
With the reader running, verify the full chain is live with four requests.Check the reader is up and has completed its first poll:Notice that Kestrel — the most damaged facility — sorts first because Orma sorts by Amounts are in drops (1 XRP = 1,000,000 drops). A lender valuing this pledge naively is over by 0.196078 XRP on every XRP of stated value.Check the gate on Thorne:
ok stays false until the first poll completes — this is intentional, not a bug.Read all four grades, sorted worst first:gradeNumeric ascending, not by NAV divergence. Kestrel shows zero divergence because its manager never impaired the loans before defaulting them; the losses were realised directly, releasing LossUnrealized back to zero. The score’s memory term (realised capital destruction, ) is what catches it.Value a pledge from the share token ID alone:domainOwner is the vault owner, not Orma. They named our issuer unilaterally — we signed nothing and cannot refuse to be cited. See Gate.4
Start the frontend (optional)
The frontend is a separate package. Run it from the Vite serves on
app/ subdirectory.http://localhost:5173 and polls the reader at http://localhost:8787 by default. Set VITE_API_BASE if your reader is on a different port. Three other optional variables are available: VITE_XRPL_NETWORK, VITE_XAMAN_API_KEY, and VITE_DOCS_URL.Everything under
VITE_ is inlined into the bundle at build time and shipped to every visitor. Never put secrets there. If you serve the frontend over HTTPS but point VITE_API_BASE at an http:// address, the browser will block the request as mixed content. The app detects this and caps its polling interval at 30 seconds rather than retrying at full rate against an address that will never answer.5
Enable oracle publishing (optional)
Reading correctly is the product. Publishing is distribution, and a missing seed must never stop the reader from starting — so it is off by default.Create The entrypoint parses
.env in the repository root:.env itself with no external dependency. An existing shell variable always wins over the file. With a seed set, the reader publishes six PriceData entries per vault into a native XLS-47 Oracle object — one entry per dimension: NAV, HDL, LIQ, COV, CNC, DDL. It publishes on material change (held NAV or grade moved) plus a 60-second heartbeat.You can also set PUBLISH=faucet to fund a throwaway publisher automatically. Every restart will publish under a new account though, so the oracle objects from previous sessions are orphaned. Use a real seed if you want a continuous series.Environment variables
All variables are optional.What this does not do
Orma runs on Devnet, againstrippled 3.4.0-rc5, because XLS-65 and XLS-66 are not on Mainnet. When those amendments activate on Mainnet, the reader needs only a different XRPL_WS and nothing else — but that is a claim about the code, not a current deployment.
Grades are advisory. Every valuation response includes assessment.advisory: true. The only place an Orma opinion has force is the permissioned domain, and there the force comes from the ledger refusing a deposit — not from Orma.