How cache. fits together, end to end. Read this first if you're new to the codebase — it should give you the whole topology in ~15 minutes. Deep dives live in the per-area docs linked throughout.
cache. is a DIEM yield vault. Depositors hand it DIEM; it stakes that DIEM on Venice, resells the resulting daily inference allowance on inference marketplaces, and compounds the USDC proceeds back into more DIEM. A depositor's share token (vDIEM) claims a growing slice of the underlying over time.
| Layer | Lives in | Tech | Runs on |
|---|---|---|---|
| Vault contract | contracts/ |
Solidity, Foundry, OpenZeppelin | Base mainnet |
| Off-chain scripts | scripts/ |
TypeScript, viem | Railway (one long-running Node process) |
| Web UI | web/ |
Next.js (App Router), wagmi, RainbowKit, viem | Vercel (static/edge) |
| Ownership | — | Safe multisig | Base mainnet |
flowchart TB
subgraph Base["Base mainnet"]
Vault["DiemVault.sol\n(ERC-4626 + async redeem + ERC-1271)"]
Diem["Venice DIEM\n(stake / cooldown / unstake)"]
Aero["Aerodrome SlipStream\n(USDC/DIEM pool)"]
Safe["Safe multisig\n(vault owner)"]
end
subgraph Railway["Railway (Node process)"]
Cron["cron.ts scheduler"]
Harvest["harvest.ts"]
Pricer["pricer.ts"]
Register["register-seller.ts"]
end
subgraph Vercel["Vercel"]
UI["Next.js UI\n(/, /contracts, /dashboard, /manifesto)"]
end
Surplus["Surplus Intelligence\n(inference marketplace)"]
Venice["Venice API\n(inference)"]
User["Depositor"]
User -->|deposit DIEM / redeem| UI
UI -->|reads + wallet txns| Vault
Vault <-->|stake / unstake| Diem
Vault <-->|swap USDC→DIEM| Aero
Safe -->|admin: setKeeper / setAuthorizedSigner / setDepositCap| Vault
Cron --> Harvest --> Vault
Cron --> Pricer --> Surplus
Cron --> Register --> Surplus
Register -.->|payout_address = Vault| Surplus
Surplus -->|inference requests| Venice
Surplus -->|settle USDC| Vault
A single contract, DiemVault, extending OpenZeppelin ERC4626, Ownable,
ReentrancyGuard, and implementing IERC1271.
ERC-4626 core. Deposits are standard and synchronous: deposit DIEM, the
vault immediately stakes it on Venice's DIEM contract, and mints vDIEM shares.
The share token uses a _decimalsOffset() of 6 (DIEM is 18dp ⇒ vDIEM is 24dp)
as OZ's defence against the first-depositor donation/inflation attack. The
human-readable share rate at a fresh vault is exactly 1.0 — the 10^6 offset
cancels on both sides of every conversion. (See web/lib/format.ts for the
full decimal explainer and the foot-gun it guards against.)
Async redemption. Withdrawals are not synchronous, because Venice's DIEM contract permits only one cooldown per holder. So redemptions are batched:
sequenceDiagram
participant U as User
participant V as DiemVault
participant D as Venice DIEM
U->>V: requestRedeem(shares)
Note over V: shares burned, assets reserved in the open batch
Note over V,U: ≥ minBatchOpenSecs later (default 1 day)
U->>V: flush() (permissionless)
V->>D: initiateUnstake(batchTotal)
Note over D: Venice cooldown begins (~24h)
Note over V,U: after cooldown elapses
U->>V: unstakeBatch(batchId) (permissionless)
V->>D: unstake() → DIEM returns to vault
U->>V: claimRedeem(batchId)
V->>U: DIEM transferred
Worst-case request→claim is ~48h (24h batch window + 24h cooldown); average
~36h. flush(), unstakeBatch(), and claimRedeem() are permissionless —
anyone can poke the lifecycle forward — but claimRedeem only pays out to the
batch's own depositor (msg.sender-keyed accounting).
DIEM accounting. Gross DIEM under custody is split across three buckets:
totalStaked (staked on Venice), cooldownAmount (post-flush, pre-unstake),
and drainedClaimable (returned, earmarked for claims). totalAssets() derives
from these; donations that land in balanceOf are inert.
Harvest. harvest(amountIn) swaps accumulated USDC (from inference sales,
settled directly to the vault) into DIEM via the Aerodrome SlipStream router,
re-stakes it, and so raises the share rate. Slippage is bounded on-chain
(maxHarvestSlippageBps, default 1%, hard-ceiled at 5%) using the SlipStream
quoter. harvest() is keeper-only for now: the spot quoter can't defend
against pre-quote pool manipulation (a flashloan sandwich), so it stays
permissioned until TWAP protection lands (see Tier 2 / the trustless-keeper
work). The owner Safe is always also permitted as a fallback.
ERC-1271. The vault implements isValidSignature(hash, sig) and returns the
magic value iff sig came from authorizedSigner. This is how the vault
authenticates to Venice's headless API-key issuance flow — the operator signs a
challenge as the vault without the vault ever exposing custody.
See contracts/SECURITY.md for the full trust model and
the emergency-response operating model (there is no pause(), by design).
TypeScript + viem. One long-running Node process on Railway; cron.ts is the
entry point and schedules the rest. All three jobs are also runnable one-shot
locally (npm run harvest / register-seller / pricer).
flowchart LR
Cron["cron.ts\n(scheduler)"]
Cron -->|*/30 min| H["harvest.ts"]
Cron -->|hourly :07| P["pricer.ts"]
Cron -->|Sun 08:00| R["register-seller.ts"]
H -->|vault.harvest()| Vault[(DiemVault)]
P -->|PATCH prices| Surplus[(Surplus API)]
R -->|SIWE + list offers| Surplus
harvest.ts— quotes Aerodrome SlipStream off-chain, applies a slippage floor, and callsvault.harvest()when the vault's USDC balance clearsHARVEST_MIN_USDC. Signs as the keeper EOA.register-seller.ts— SIWE-authenticates to Surplus as the seller and reconciles one active offer per Venice model, withpayout_addressset to the vault. Idempotent. Does not touch price (the pricer owns price).pricer.ts— the dynamic-pricing loop. Reads Surplus's public markets feed, computes a target price that undercuts the cheapest healthy competitor (floored at a fraction of the reference price), and PATCHes per-model prices. Pricer is the sole authority on price so it can't fight the reconciler.lib/—env.ts(env validation),viem.ts(Base clients),surplus.ts(Surplus API client).
The operator EOA configured here (OPERATOR_PRIVATE_KEY) is the vault's
keeper and authorizedSigner. It holds no vault custody — its powers are
bounded to harvest() (1% slippage grief ceiling) and signing Venice
challenges. See scripts/README.md for the full env list, schedules, and tuning
knobs.
Next.js App Router, statically rendered, client-side on-chain reads only. No server, no database, no secrets — every number is read from the chain in the user's browser via wagmi/viem; wallet connectivity is RainbowKit + WalletConnect.
Routes:
/— the vault console: stats, deposit/redeem actions, withdrawal queue, harvest history./contracts— every official Base address with Basescan/Safe deep-links./dashboard— read-only on-chain activity: harvests, deposits, redemption-lifecycle events, "days since last harvest", and a reconstructed TVL-over-time chart (useVaultActivityinlib/hooks.ts)./manifesto— the thesis.
Key modules: lib/wagmi.ts (chain config + canonical addresses), lib/abi.ts
(hand-maintained minimal ABIs), lib/hooks.ts (all on-chain reads),
lib/format.ts (token-decimal formatting + the vDIEM explainer). Baseline
security headers are set in next.config.ts; see web/README.md.
flowchart TB
subgraph BaseChain["Base mainnet"]
V[(DiemVault)]
S[(Safe multisig)]
end
Vercel["Vercel\n(web/ — static + edge)"] -->|RPC reads / wallet txns| V
Railway["Railway\n(scripts/ — 1 Node process)"] -->|keeper txns| V
Railway -->|HTTPS| SurplusAPI[(Surplus API)]
Operator["Operator EOA\n(keeper + authorizedSigner)"] -.->|key in Railway env| Railway
HW["Hardware wallet"] -.->|signs| S
S -->|owns / admin| V
- Vault + Safe: Base mainnet. The Safe owns the vault; admin calls
(
setKeeper,setAuthorizedSigner,setDepositCap,setMinBatchOpenSecs,setMaxHarvestSlippageBps) are Safe-only. - UI: Vercel, static. Public env (
NEXT_PUBLIC_*) only. - Scripts: Railway, a single always-on Node process. Holds the operator key
(
OPERATOR_PRIVATE_KEY) encrypted at rest. If Railway is compromised, the Safe rotateskeeper/authorizedSignerand the operator is redeployed fresh — no depositor funds are ever at risk from the operator key.
See LAUNCH_RUNBOOK.md for the full deploy procedure.
The money path, one full cycle:
flowchart LR
A["User deposits DIEM"] --> B["Vault stakes on Venice"]
B --> C["Daily inference allowance accrues"]
C --> D["register-seller lists Venice models on Surplus\n(payout = vault)"]
D --> E["Buyers consume inference via our API key"]
E --> F["Surplus settles USDC → vault"]
F --> G["harvest.ts: swap USDC → DIEM on Aerodrome, re-stake"]
G --> H["Share rate (vDIEM:DIEM) rises"]
H --> A
- Deposit → stake. User deposits DIEM; the vault stakes it on Venice and mints vDIEM.
- Sell.
register-seller.tslists the vault's Venice models on Surplus with the vault as payout address;pricer.tskeeps them competitively priced. - Settle. Buyers consume inference (authenticated by the vault via ERC-1271); Surplus settles USDC directly to the vault.
- Harvest → reinvest.
harvest.tsswaps USDC → DIEM on Aerodrome and re-stakes, raising the share rate. - Redeem. When a user wants out, the async
requestRedeem → flush → unstakeBatch → claimRedeemlifecycle returns their DIEM.
Who can do what:
| Actor | Holds | Can | Cannot |
|---|---|---|---|
| Safe (owner) | multisig keys (hardware-wallet signers) | setKeeper, setAuthorizedSigner, setDepositCap, setMinBatchOpenSecs, setMaxHarvestSlippageBps |
withdraw/seize depositor funds (no such function) |
| Keeper (operator EOA) | OPERATOR_PRIVATE_KEY on Railway |
call harvest() (≤ slippage ceiling) |
move custody; unbounded loss |
| Authorized signer (same EOA) | same key | sign Venice ERC-1271 challenges as the vault | spend vault assets |
| Depositors | their own wallet | deposit, requestRedeem, claimRedeem (their own batch); permissionlessly flush/unstakeBatch |
touch anyone else's position |
| Anyone | — | flush(), unstakeBatch() (poke the lifecycle forward) |
claim funds that aren't theirs |
Soft-pause levers (no hard pause() by design): the Safe can
setDepositCap(0) to halt new deposits and setKeeper(0x0) to halt harvest.
Redemptions remain permissionlessly pokeable so users can always exit. Full
detail in contracts/SECURITY.md.
- Strategy / thesis —
STRATEGY.md,MANIFESTO.md - Roadmap / backlog —
ROADMAP.md,AGENT_BACKLOG.md - Security model —
contracts/SECURITY.md - Operations —
scripts/README.md,LAUNCH_RUNBOOK.md