Lending (Epic 12) — the off-chain money market, backend plus the mobile app.
What this is
A Compound-v1-style money market run off-chain: one position per (user, market), per-second index accrual, no terms, no approval step, over-collateralised borrowing with a health factor. Supplies are collateral. Money and rates cross every boundary as decimal strings; bignumber.js throughout, never a JS number.
Backend
Customer API — GET /lending/{markets,positions,operations}, three quote endpoints, and four money-moving mutations (supply, close supply, borrow, repay). Admin API — 25 endpoints under /admin/lending covering markets, risk parameters, operations, alerts, audits, reconciliation, liquidations and house capital.
Every mutation is guarded by a passcode read from the raw body before validation strips it, and carries a mandatory Idempotency-Key. The id is kept — never reset — for network failures, 5xx and every passcode refusal, so a retry can never produce a second transfer.
GET /lending/positions opens exactly one lock-and-accrue pass and reads everything else from it. A prior incident in this area produced 55P03 lock-timeout cascades from re-locking on that path; the statement budget is pinned by a test that counts queries and asserts no FOR UPDATE and no UPDATE after the first.
Mobile app
Thirteen screens moved from mock data to the live API. The old apparatus — fixed-term loans, an approval modal, origination fees, Pending/Rejected states — is deleted, because it described a product this backend does not implement.
Verified
- BE: 668 lending tests;
yarn build(the fiat-ledger AST gate) green; tsc unchanged at its 57-error baseline. - FE: 74 suites / 601 tests; type-check clean.
- Codex reviewed spec, plan and code. All three gates closed.
- Manual E2E on the iOS simulator against a local stack: hub, supply (idempotency key confirmed in the database), borrow quote, review screen, and all three receipt states seen live. A borrow that failed at the provider left no phantom debt — the lifecycle exclusion proving itself in production shape.
Not verified, and the reason
Borrow completing, repay, and close supply have never been run end to end. All three go through the same outbound Fireblocks transfer, and the sandbox vault has no on-chain balance to make it. Nothing in the codebase provisions or funds that vault; it is faucetted by hand. The market book had the cash — this is an environment gap, not a code defect.
Staging's central vault (account 3) needs the same treatment before these paths can be exercised there.
Also worth knowing
- This merge runs 20 migrations against the staging database automatically.
- The admin console UI still renders
sample-data; the backend it would call is complete. A static HTML mockup of all seven screens is prepared separately, annotating each region with the endpoint that feeds it and naming five places the admin API does not yet reach.