The consolidated design document: vision, actors, mechanisms, genesis, infrastructure decisions and roadmap. It reflects the system as built (V1, live on Sepolia). Deep dives: monetary-design.md (the why), architecture.md (the how), contracts.md, scenarios.md, threat-model.md, frontend.md, indexer.md.
A complete on-chain simulation of the two-tier monetary system, deployed and verified on Sepolia: tokenized central bank money (wCBDC), tokenized commercial bank deposits (DEP-A/DEP-B), and a fiat-backed stablecoin (sEUR) whose reserves are tokenized deposits β with atomic interbank settlement, health monitoring, and a role-based frontend.
What it demonstrates:
- The singleness of money: a euro at Bank A equals a euro at Bank B because every interbank transfer of commercial money settles in central bank money, atomically.
- Structural bank fragility: fractional reserves make runs possible; health states (HEALTHY / STRESSED / ILLIQUID) make stress observable and gradual.
- TradFi β stablecoin contagion: sEUR's reserves are bank deposits; trouble at the bank is trouble for the coin.
- Intermediated vs permissionless money: DEP depends on its bank and relayer; sEUR keeps circulating when both are down.
- The transparency paradox: everything is public on-chain β which builds trust and lets panics coordinate faster.
Guiding principle: the EOA decides, the contract enforces, the token accounts. All rule-bound funds live in contracts, never on operator EOAs.
| Actor | EOA | Contract | The contract's job |
|---|---|---|---|
| Central bank | operator (= deployer) | CentralBank |
M0 issuance, bank allowlist, ratio parameter |
| Bank A / Bank B | one operator each | CommercialBank Γ2 |
Reserves, client registry, freeze, owns its DepositToken |
| StableCo | operator | StableCo |
Reserve vault, 1:1 mint/redeem, on-chain proof of reserves |
| Clients | alice1/2 (Bank A), bob1/2 (Bank B) | none β bare EOAs | Clients carry no rules; they only sign (EIP-712 intents, EIP-3009 authorizations) |
Plus a dedicated relayer EOA (infrastructure, not an actor). No HD mnemonic: the
.env holds an individual private key per server-signing role and addresses only for
clients β the relayer cannot derive or impersonate anyone
(architecture.md Β§7). The StableCo contract itself is registered as
a client of Bank A (a contract can be a client).
| Token | Layer | Issuer | Holders | Decimals |
|---|---|---|---|---|
| wCBDC | M0 | CentralBank | Allowlisted banks only | 6 |
| DEP-A / DEP-B | M1 | Each CommercialBank | Registered clients of that bank | 6 |
| sEUR | private | StableCo | Anyone (permissionless) | 6 |
- Atomic interbank settlement β the core: burn payer's DEP β move wCBDC between bank reserves β mint payee's DEP, in one transaction. Intrabank payments are pure book transfers (no wCBDC). Walk-through with numbers: scenarios.md S1βS2.
- Routing rule (one line): client-initiated monetary flow β EIP-712 intent via the
relayer (gasless); institutional/admin action β direct operator transaction. sEUR P2P
additionally supports a plain
transfer()(holder pays gas) β both paths exist on purpose. - Stablecoin issuance is endogenous: minted only against reserves received, atomically, to the buyer; redeemed symmetrically; no admin mint exists. Cross-bank mint/redeem composes an interbank settlement in the same transaction β the richest flow in the system (scenarios.md S7βS8). The StableCo operator can only pause.
- Hard vs soft constraint: only insufficient wCBDC blocks a payment (revert). A ratio
below threshold emits
ReserveRatioBreachedand settlement continues β monitored, not enforced per transaction. - Health states: π’ HEALTHY (ratio β₯ 10%) Β· π STRESSED (below, still paying) Β· π΄ ILLIQUID (cannot cover the next payment β revert; illiquid β insolvent).
- Freeze/unfreeze: pauses a bank's DepositToken entirely (settlements included, both directions) β realism (suspended banks), the future depeg trigger, and the DEP-vs-sEUR contrast in one switch.
- Events rule: every monetary movement emits a rich event (
Settled,IntrabankTransfer,StableMinted,StableRedeemed,ReserveRatioBreached,BankFrozen,ClientRegistered, β¦). The frontend and all metrics are built purely from events + view calls. No event, no feature.
| # | Use case | Initiator | Path |
|---|---|---|---|
| 1 | Intrabank payment | Client | Intent β relayer β engine (book transfer) |
| 2 | Interbank payment | Client | Intent β relayer β engine (atomic settlement) |
| 3 | Mint sEUR (Bank A client) | Client | Intent β relayer β StableCo (book transfer + issue) |
| 4 | Mint sEUR (Bank B client) | Client | Intent β relayer β StableCo β engine (composed settlement) |
| 5 | Redeem sEUR (both cases) | Client | Mirror of 3/4 |
| 6 | sEUR P2P | Client | EIP-3009 gasless via relayer, or direct transfer() |
| 7 | Public onboarding (Option B) | Visitor | POST /faucet β relayer's FAUCET_ROLE β onboard() (register + capped credit, one-shot) |
| 8 | Admin: clients, freeze, allowlist, threshold | Operators | Direct transactions |
| Item | Value |
|---|---|
| wCBDC total (M0) | 1,000,000 β constant in V1 (invariant 1) |
| Reserves per bank | 500,000 / 500,000 |
| Deposits per bank | 4,000,000 β split 2,400,000 / 1,600,000 across its two clients |
| Initial ratio / threshold | 12.5% / 10% |
| "Loans" balance-sheet line | 3,500,000 per bank β documented convention, not a token |
| StableCo | Starts at zero (the first mint happens live) |
| sETH | Relayer funded + balance alert; operators ~0.05; clients ~0.02 (direct sEUR path only) |
The deploy script produces this state reproducibly and idempotently
(make deploy-local / make deploy-sepolia, then make seed to re-seed).
Tested with Foundry invariant testing (handler-driven fuzzing, zero tolerated reverts):
wCBDC.totalSupply()constant at 1,000,000 (V1).- Interbank M1 transfers move an exactly equal amount of wCBDC, in the same transaction.
sEUR.totalSupply() β€ DEP-A.balanceOf(StableCo)β 100% coverage, always.- Only allowlisted banks hold wCBDC.
- Only registered clients hold a bank's deposits.
- No intent or authorization replay (three separate nonce namespaces + deadlines).
- Payments conserve aggregate value.
Full mapping to code and tests: contracts.md.
| Domain | Choice | Why |
|---|---|---|
| Contracts | Solidity ^0.8.24, Foundry, OpenZeppelin v5 | Professional standard; native invariant testing |
| Chains | Anvil (daily dev) + Sepolia (live demo, Etherscan-verified) | Instant local loop; durable public artifact |
| Relayer | Node 20 + TypeScript + Fastify + viem; POST /intent + POST /faucet |
The EIP-712 signature is the authentication β no sessions, no users table |
| Frontend | React + wagmi/viem, addresses from committed deployments/<chain>.json |
Role-resolved views, direct chain reads |
| Indexer | Ponder + custom REST API | History without rescanning; a derived, rebuildable store |
| Persistence | None. Chain = source of truth; relayer stateless | Everything re-derivable from chain + config |
| Auth | The wallet. SIWE deliberately deferred to V2 (scenario-control endpoints) | V1's single business endpoint is self-authenticating |
| Orchestration | Makefile host targets β three terminals (make relayer, make indexer, make front) |
Docker was dropped (2026-06-29): unvalidated images and broken local tooling added no value to the demo |
| Verification | Inline --verify during make deploy-sepolia |
All 9 instances readable on Etherscan during demos |
Lifecycle / restarts. Everything vital is on-chain (balances, registries, nonces,
history). The repo persists deployments/<chain>.json and .env.example; the gitignored
.env persists keys. At boot the relayer re-reads on-chain nonces and resyncs its own
pending transaction count; its idempotency cache is disposable. Redeploying (for V2) means
a new deployments file + re-seed β non-destructive, and a reproducibility test of the
genesis.
Assumed operational risk. The relayer is a single point of failure β accepted in V1, documented in threat-model.md (with the TARGET2-outage parallel), and exploited as a V2 scenario (censorship/outage: DEP payments stall, sEUR keeps moving).
V1 β the core (done, live on Sepolia). 3 token types, 7 contracts, atomic settlement, EIP-712 + EIP-3009 gasless flows, stateless relayer, public onboarding, 5-view frontend, indexer, 155 Foundry tests + invariant suite, full E2E validated on the live deployment.
V2 β crisis and liquidity. Orchestrated scenarios via off-chain agents: bank run
(agents follow "if my bank's ratio < X%, flee" β cascade), depeg (freeze Bank A β redeems
revert β coverage breaks), relayer censorship/outage (DEP dead, sEUR alive). Interbank
lending market. Refinancing against collateral (BondToken, haircut, rate) and lender
of last resort (larger haircut, penalty rate β Bagehot). SIWE (EIP-4361) to protect the
scenario-control endpoints. Dynamic M0 β invariant 1 refined to ΞM0 == net refinancing.
V3 β markets and policy. An sEUR/DEP AMM so a market price exists β observable depeg. Remunerated reserves (interest-rate policy). Credit creation ("loans make deposits") turning the genesis loans line into real dynamics.
V4 β multi-chain. The natural continuation: the L1 as the central bank / settlement layer, one rollup per commercial bank, cross-chain settlement via HTLCs and intents β the unified-ledger idea stretched across chains.
- Contracts + tests (
contracts/), relayer (relayer/), frontend (frontend/), indexer (indexer/) β all live on Sepolia, addresses indeployments/sepolia.json. - Documentation: this file plus the seven focused docs linked at the top.
- README: pitch, quickstart, verified contracts and reference transactions.