Architecture
ZAAR is a record-keeping network: one deterministic state machine holds every balance, order and credential, and every change to it is a command in one append-only journal. Everything else (history, market data, the API) is derived from that journal.
Principles (from TigerBeetle, adapted)
- The journal is the truth. State =
replay(journal). History tables, candles and caches are projections that can be dropped and rebuilt at any time. - Deterministic core.
zaar-coredoes no I/O, reads no clock, uses no randomness, no floats and no iteration over unordered maps. The sequencer stamps each command with its sequence number and time, and those are journaled, so replay is bit-exact. - Integers only. Amounts are
u128minimal units; prices and quantities are integers whose product is exact by construction (data-model.md). Nothing rounds in matching; fees round once, towards the network, by a documented rule. - Double entry. Every balance change is a transfer between two accounts. Per asset, the sum of all balances is zero. Tests assert this after every command.
- One writer, batched. A single sequencer thread owns
the state. It takes commands in batches, journals the batch with one
fdatasync, and only then releases the responses (group commit). - Authorization is state. Orgs, users, keys and scopes live in the journal, and the core decides every permission. The gateway only verifies signatures. Replay re-checks every decision.
- Assert everything. Invariants are checked in debug builds after every command and in the simulator after every step. A failed invariant stops the process; it never limps on.
Runtime (v1)
Internet ──443──▶ Caddy (TLS, per-host routing)
│
┌─────────────────┴─────────────────┐
▼ ▼
zaard (prod, :8082) zaard (staging, :8081) one e2-micro VM, us-east1-b
┌───────────────────────────────────────────────┐
│ gateway (tokio, axum) │ REST /v1, WebSocket /v1/ws
│ verify Ed25519 signature, rate limit, decode │
│ │ bounded channel │
│ ▼ │
│ sequencer thread ── owns ──▶ zaar-core State │ apply(cmd) → events
│ │ batch → journal append → fdatasync │
│ ▼ │
│ events ──▶ WS hub (market + user streams) │
│ └─▶ history projector ──▶ SQLite │ orders, trades, transfers, klines
└───────────────────────────────────────────────┘
│ journal segments + snapshots (every 30 s and on roll)
▼
gs://zaar-network-backups/<env>/
- zaard is one binary per environment. Staging and
prod are separate processes, users, ports and data directories on the
same VM. Splitting them onto separate machines (or projects) later
changes only
deploy/. - Reads of live state (book depth, balances, open orders) go through the sequencer channel as queries that are not journaled. Reads of history go to SQLite. Both are fast at v1 scale; read replicas fed by the event stream are the scaling path.
- Restart = load latest snapshot, replay the journal tail, rebuild projections if behind.
Crates (lower never depends on higher)
zaar-core types, commands, events, state machine: ledger, matching, fees, authz. No I/O.
└─ zaar-journal segment files, snapshots, recovery, backup hook
└─ zaar-history SQLite projector + history queries
└─ zaar-api HTTP/WS gateway: auth headers, DTOs, error codes, OpenAPI
(the `server` feature reads zaar-history's SQLite; D-19)
└─ zaard the daemon: config, wiring, sequencer thread, backups
zaar-client Rust client: signing, typed requests (used by CLI and integration tests)
└─ zaar-cli the `zaar` binary
Scaling path (not built yet, but nothing above blocks it)
| Step | Change | Unchanged |
|---|---|---|
| More load | Bigger VM, local NVMe, pin the sequencer to a core | everything else |
| Read scale | Read replicas tail the journal; gateways scale out statelessly | core, journal format |
| HA | Replicate the journal (VSR/Raft) to 3 or 6 replicas before acking | core, API |
| HFT | Binary protocol (SBE-style) and colocated gateways; shard the core by market, with the ledger as its own shard and cross-shard locks via pending transfers | data model, API semantics |
Why not TigerBeetle itself (yet)
TigerBeetle wants 6 GiB+ RAM per replica and ECC/NVMe in production, and it has no order book, so we would run two sources of truth. v1 implements the same ledger model (accounts, pending and posted debits/credits, linked transfers, per-asset ledgers) inside our core. Because the model matches, the ledger can move to a TigerBeetle cluster later as the "ledger shard". See decisions.md D-4.