1 · Architecture overview.
Wift is a composition of five XRPL native primitives. There is no custom smart contract.
┌──────────────────────────────────────────────────────┐ │ XRPL L1 │ │ │ │ SingleAssetVault → LoanBroker │ │ (XLS-65) (XLS-66) │ │ shares as MPT originates loans │ │ │ │ │ │ share MPT │ │ ▼ │ │ Wift Wrapper ←── 3-of-5 multisig │ │ pseudo-account asfDisableMaster │ │ │ │ │ │ PT IOU / YT IOU │ │ ▼ │ │ Native XRPL DEX order book (OfferCreate) │ │ │ │ │ ▼ │ │ Price discovery · Fixed rate lock · Credit curve │ └──────────────────────────────────────────────────────┘Every arrow above is a real XRPL transaction. No off-chain state, no oracle, no sidechain. The “contract” in Wift is the 3-of-5 multisig rule enforced by five independent signers.
2 · XRPL primitives used.
| Spec | Role | Status on target |
|---|---|---|
| XLS-65 SingleAssetVault | underlying yield source | live (rippleci/rippled) |
| XLS-66 LendingProtocol | broker originates loans | live |
| XLS-33 MPT | vault shares | live |
| IOU trustlines | PT and YT tokens | live (P0.3 fallback path) |
| Native DEX (OfferCreate) | PT/XRP and YT/XRP markets | live |
| SignerListSet | 3-of-5 wrapper multisig | live |
| Batch | atomic split (optional) | live on rippleci |
| XLS-101d SmartContract | v2 trustless wrapper | not yet upstream |
The P0.3 preflight established that AMMCreate currently rejects MPT amounts with Amount can not be MPT. Wift therefore issues PT and YT as IOUs, not MPTs, so the native DEX can list them. Vault shares themselves stay MPT because they never touch the DEX — they're locked in the wrapper and burned at redemption.
3 · The wrapper pseudo-account.
The Wift wrapper is a standard AccountRoot created by the team:
- A
SignerListSettransaction installs five independent public keys with quorum 3. - An
AccountSettransaction sets theasfDisableMasterflag. The master key is permanently disabled — the only way to authorise anything from this account is via 3-of-5 multisig. - The wrapper holds locked vault shares (MPT) for every active series and is the issuer of every PT and YT IOU.
- Relayer policy (public, deterministic): sign nothing that would violate the invariant
shares_locked == PT_supply == YT_supply. A relayer that signs a non-compliant op is removed from the signer list.
The wrapper address, signer list and quorum live in shared/config.yaml and are served via the indexer /wrapper endpoint.
4 · Series lifecycle.
A series is a {broker, maturity} pair. Each series has its own PT currency, YT currency, order book offers, and maturity timestamp. Setup sequence:
VaultCreateby the broker owner. Asset is XRP for v1. Returns a vault pseudo-account and aShareMPTID.LoanBrokerSeton the vault, withCoverRateMinimumin 1/10 bps (not plain bps — range 0..100000). Returns a LoanBroker object.- The depositor
VaultDeposits XRP into the vault and receives MPT shares. - The depositor sends those shares to the Wift wrapper account via
Payment. - The wrapper multisig signs a
Paymentback to the depositor with PT IOU + YT IOU (equal amounts). Atomic viaBatchif available, sequential + compensation otherwise. - The depositor (or any other user) calls
OfferCreateon the native DEX to seed the PT/XRP and YT/XRP order books.
All of the above is scripted in the seeder (backend/seeder/) and produces the live entries in shared/series_registry.json.
5 · The split operation.
The user-facing Mint flow:
1. User → Wrapper: Payment(share MPT, amount N) 2. Wrapper (multisig 3/5) → User: Payment(PT IOU, amount N) + Payment(YT IOU, amount N)Steps 2a and 2b are wrapped in a Batch tx when the amendment is enabled, which gives atomic all-or-nothing semantics. When Batch is unavailable the relayer leader falls back to sequential submission with a compensation tx (a Payment(share → user) refund) if step 2b fails.
Post-op, the wrapper re-checks the invariant by reading its locked share balance and the twoIOUissuances' outstanding amounts. Mismatch triggers a loud alert and blocks further signing until reconciled.
6 · Trading on the native DEX order book.
Wift originally targeted the native XRPL AMM, but the Phase 0 preflight established that AMMCreate rejects MPT-typed amounts, and the upstream rippleci build also has rough edges around AMMCreate with fresh IOUs. The Phase 1 implementation switched to the native XRPL DEX order book via OfferCreate:
- The depositor (“Alice” in the seeder) holds a large reserve of PT and YT and places resting sell offers at gradually walked prices. This is the market animator (Track E, see
backend/arb_bot). - Buyers place matching
OfferCreateor use pathfindingPaymentto fill. Every fill is a real XRPL tx with a verifiable hash. - The indexer derives the spot PT price from the best ask at each ledger and inserts an
amm_observationsrow (the table name pre-dates the pivot). - Prices are quoted in drops per token. 1 drop = 1e-6 XRP. A PT at 0.95 drops/PT with par at 1.00 implies a ~7% APR for a 90-day maturity.
7 · Redemption at maturity.
Once maturity_unix ≤ now, the wrapper opens the redemption window. The multisig signs payouts according to which legs the user holds:
| User holds | Per-unit payout | Math |
|---|---|---|
| PT + YT (pair) | 1 share returned per unit | invariant-neutral |
| PT only | XRP | min(1, NAV_T) |
| YT only | XRP | max(0, NAV_T − 1) |
NAV_T is read from the vault via the vault_info RPC after the broker collects all LoanPay transactions due at maturity. The wrapper burns the incoming PT and/or YT IOUs and emits the matching Payment to the user.
8 · NAV and YTM math.
NAV per share (XLS-65)
NAV_per_share = (Vault.AssetsTotal − Vault.LossUnrealized) / MPTokenIssuance(ShareMPTID).OutstandingAmountAssetsTotal is the full accrued value of the vault including scheduled interest. LossUnrealized is moved only by LoanManage (impair / unimpair / default). On deposit (mint), LossUnrealized is not subtracted — the formula above applies to redemption only.
Implied YTM from PT price
YTM = (1 / PT_price)^(365 / days_to_maturity) − 1PT_price is expressed in the underlying unit such that PT redeems to 1 unit at par (e.g. drops/PT with par = 1 drop/PT). The formula annualises the implied discount rate. Used by the dashboard credit curve and by GET /curve.
Cover rate (XLS-66)
required_cover = DebtTotal × CoverRateMinimum / 1_000_000 (CoverRateMinimum unit is 1/10 basis points, range 0..100000)On LoanSet the ledger rejects origination with tecINSUFFICIENT_FUNDS ifCoverAvailable < required_cover. When cover is below the floor, broker feePaid is automatically redirected to the cover pool until the floor is refilled.
9 · Indexer REST API.
The Track C indexer exposes FastAPI at http://localhost:8000 by default (CORS wide-open). Read-only endpoints:
GET /health # liveness probe GET /wrapper # wrapper multisig + signer list GET /series # all active series with joined live state GET /series/{id} # one series, full detail GET /curve # credit curve (one point per series) GET /observations/{id}?side=PT&limit=100 # time-series for the price chart GET /holders/{id}?limit=20 # top-N holders of PT/YTResponse shapes mirror shared/series_registry.json for static fields and the indexer Postgres schema for live state (see backend/indexer/schema.sql). All decimals are returned as floats; all timestamps as ISO-8601.
10 · Security model.
Trust assumption (v1)
Three honest relayers out of five. Each relayer runs a watcher that refuses to sign any operation that would violate the wrapper invariant. The master key of the wrapper account is disabled (asfDisableMaster), so no key can bypass the quorum.
Adversary scope
- A single malicious relayer cannot mint unbacked PT or YT — its partial is useless without 2 others.
- Three colluding relayers can mint unbacked tokens. The economic deterrent is the public signer list and social slashing at the protocol level. This is an explicit trust assumption, not a theorem.
- The wrapper cannot create shares out of thin air — the MPT-backed vault share is the only thing the invariant checks against.
v2 trustless path
When XLS-101d SmartContract lands upstream, the wrapper becomes a ContractCall. The invariant becomes a hard ledger rule, trust assumption drops to zero. Existing PT and YT holders are unaffected — the wrapper account simply becomes contract-controlled instead of multisig-controlled.
11 · Error codes and edge cases.
tecINSUFFICIENT_FUNDSonLoanSet— the broker's cover pool is belowDebtTotal × CoverRateMinimum. Deposit more cover before originating.temINVALIDonLoanBrokerSet— check that fee-rate fields are within documented ranges.ManagementFeeRate,CoverRateLiquidation, andCoverRateMinimumare all in 1/10 bps and are immutable after creation.Amount can not be MPTonAMMCreate— the documented P0.3 failure. Use IOUs for PT/YT.- Invariant drift detected by the indexer — the affected series is flagged
status = 'degraded'and a row is inserted ininvariant_violations. The relayer leader stops signing for that series until a human reconciles. - Late
LoanPayat maturity — extra interest is added toVault.AssetsTotaland flows to YT holders via a higher NAV_T. LoanManage tfLoanImpairmid-series —Vault.LossUnrealizedis incremented and NAV drops immediately. PT and YT prices re-quote on the next indexer poll cycle.
12 · Roadmap.
- Hackathon (April 2026) — v1 wrapper, 3 live series (safe / medium / risky), live credit curve, full Mint / Redeem / Trade / Portfolio flow on local standalone rippled.
- Post-hackathon Q2 — switch to public devnet once XLS-66 lands there, add one real broker integration, open the relayer set to three external operators.
- Q3 — migrate to
ContractCallonce XLS-101d is live upstream. Wrapper becomes trustless. No breaking change for PT/YT holders. - Q4 — YT stream (continuous distributions on every
LoanPay), multi-asset vault support, cross-series portfolio risk dashboard.