Architecture
This page explains how the pieces fit together — what talks to what, why the system is split the way it is, and where the trust boundaries fall.
System topology
flowchart LR UI["Frontend<br/><i>Next.js :3001</i><br/>/portfolio /passport /agent /trade"] --> API["zkde backend<br/><i>FastAPI :8003</i>"] API --> POL["Policy + trust"] API --> MKT["Opportunities + adapters"] API --> EXEC["Execution builders"] API --> SEQ["Proof sequencer bridge"] SEQ --> OBS["obsqra backend<br/><i>:8002</i>"] OBS --> MAD["Madara L3<br/><i>:9944</i>"] OBS --> L2["Starknet L2"] EXEC --> L2 MAD --> RCPT["Receipts + status"] L2 --> RCPT RCPT --> UI
The frontend is a Next.js app that presents several product surfaces — /portfolio and /passport are live on Starknet mainnet, while /agent (Capital OS) and /trade are on Sepolia. It talks exclusively to the FastAPI backend on port 8003 — the frontend never calls proof services or chains directly.
The backend is the orchestration layer. It handles four categories of work:
- Policy + trust — reputation queries, risk passports, compliance checks, session key management
- Opportunities + adapters — market surface data, pool metadata, adapter routing for Ekubo/lending/staking
- Execution builders — calldata construction, simulation, wallet signing preparation
- Proof sequencer bridge — forwards proof artifacts to the obsqra backend for aggregation and settlement
The obsqra backend (:8002) is a separate service that handles proof aggregation and settlement. It bridges facts to either the Madara L3 appchain (:9944) for dedicated proof storage, or directly to Starknet L2 for contract settlement. Both paths produce receipts that flow back to the UI.
Why two backends?
The split between the zkde backend (8003) and the obsqra backend (8002) isn't arbitrary. The zkde backend is product-oriented — it knows about users, sessions, policies, and UI state. The obsqra backend is infrastructure-oriented — it knows about proof aggregation, settlement paths, and chain state. Keeping them separate means the proving infrastructure can evolve independently of the product surface.
Component responsibilities
| Component | Port | What it owns |
|---|---|---|
| Frontend (Next.js) | 3001 | UI surfaces, wallet connection, signing UX, state rendering |
| zkde backend (FastAPI) | 8003 | Orchestration, policy enforcement, API composition, receipt lifecycle, WebSocket events |
| obsqra backend | 8002 | Proof aggregation, sequencing, settlement bridge, chain indexing |
| Madara L3 | 9944 | Dedicated appchain for proof fact registration — separate block space for proof throughput |
| Starknet L2 | — | Primary settlement layer — verifier contracts, registries, adapters, privacy pools |
| nginx | 443 | Reverse proxy, TLS termination, static docs serving, cache headers |
Verification modes
Not every action goes through the same level of proof scrutiny. The system uses three modes, applied per-flow:
| Mode | What happens | When you'd see it |
|---|---|---|
| Gate-critical | Proof required before execution proceeds. No proof → no execution. | Deploy to Ekubo, automated rebalance, autonomous mode |
| Advisory | Risk signal returned, but execution isn't blocked. The UI shows warnings. | Pool safety check, pre-trade risk scan |
| Wallet-first | User signs and executes, then the system reconciles. Trust the wallet. | Manual swap, manual LP adjustment |
This isn't a compromise — it's a design choice. Gate-critical mode for everything would make the app unusably slow for operations where the user is already signing with their own wallet. Wallet-first mode for everything would defeat the purpose of proof-gating. The system uses the right mode for each flow.
Trust domains
Reputation, credit, and governance are designed as separate trust domains. A high reputation tier doesn't automatically grant lending capacity. A lending history doesn't automatically confer governance voting power. Signals in one domain should not silently grant rights in another.
This boundary separation is partially implemented today and will be fully formalized in V3. The intent is to prevent the kind of composability risk where a single exploit in one domain cascades across the system.
Health checks
| Endpoint | Service | What it tells you |
|---|---|---|
GET /health | zkde backend | Basic liveness — if this fails, the API is down |
GET /api/v1/zkdefi/proofs/sequencer-status | zkde backend | Whether the proof sequencer bridge is connected |
GET /api/v1/aggregation/stats | obsqra backend | Aggregation stats — proof throughput, queue depth |
GET /api/v1/aggregation/madara/health | obsqra backend | Madara L3 connectivity |
Runtime lifecycle
Here's what happens when a user interacts with the system, end to end:
- Load trust context — the frontend fetches reputation, passport, and policy state from
/profile - Load opportunities — adapter metadata and market surface data from the backend
- Evaluate gates — the UI checks which actions are available given the user's trust context
- Build and simulate — the backend constructs calldata and runs simulation
- Sign and submit — the user signs with their wallet, tx goes to chain
- Reconcile — receipts and outcomes flow back into UI state
- Settlement — for proof flows, settlement updates arrive through the sequencer path and get written to registries
Steps 1–3 happen on every page load. Steps 4–7 happen per action.
Source references
For deeper architecture documentation inside the repo:
docs/ARCHITECTURE.md— internal architecture detailsdocs/AGENT_FLOW.md— how agent actions move through the pipelinedocs/MADARA_L3_APPCHAIN_ARCHITECTURE.md— Madara L3 design and configurationdocs/CAPITAL_OS_PORTABLE_REPUTATION_V3_SPEC.md— V3 trust domain specification