Architecture overview
Three pieces, two languages, one idea. This page connects everything before you read the deep-dives.
The big picture
The frontend never talks to the database. It talks only to the Rust node's HTTP API. The node talks to Postgres. The interesting part: the blockchain itself is stored in Postgres — blocks and transactions are just rows, and the in-memory order book is rebuilt from the orders table at boot.
What happens when the node starts
So the same process is both the API server and the block producer. They share the same AppState. That is the single most important architectural decision to be able to explain.
1#[derive(Clone)]2pub struct AppState {3pub db: PgPool, // every handler can query4pub cfg: Config, // chain params from env5pub node_address: String, // this node's identity6pub orderbooks: Arc<Mutex<HashMap<String, OrderBook>>>, // live order books7pub rate: Arc<RwLock<HashMap<String, (u32, i64)>>>, // rate limiter8pub faucet: Option<(String, Vec<u8>)>, // faucet key for minting9}
Every HTTP handler receives a clone of AppState (cheap — it's mostly Arcs), so the handler can query the DB and read the order books directly.
Inside the Rust node
Think of the node as four layers:
Routes + handlers + middleware. Each route is a small function that extracts JSON, does a thing, returns JSON. Middleware wraps everything: CORS, rate limiting, JWT auth.
The "blockchain" — but as pure functions: Block, compute_hash, validate_block, apply_block, plus consensus (select_proposer) and the mempool.
Balances (per address × asset), staking (validators, positions, epoch rewards) and the order book. Money is always integer minor units — no floats.
One db.rs with every SQL query and row struct. The only place SQL lives.
Module map of the backend
1src/2├── main.rs # boot sequence (startup steps 1-7)3├── bin/seed.rs # demo data seeder4├── config.rs # every env var -> Config5├── state.rs # AppState (shared by all handlers)6├── db.rs # row structs + ALL SQL queries7├── wallet.rs # ed25519 keygen, sign, verify, derive_address8├── exchange.rs # OrderBook, Side, match_order, depth9├── block_producer.rs # genesis bootstrap + 3s produce_block loop10├── chain/11│ ├── blockchain.rs # Block, compute_hash, merkle, validate/apply12│ ├── consensus.rs # select_proposer, epoch math13│ ├── mempool.rs # submit/pending/confirm (stored in Postgres)14│ └── transaction.rs # Transaction, message_to_sign, verify15├── ledger/16│ ├── balances.rs # get/upsert balance, load/persist state view17│ └── staking.rs # stake/unstake/process_epoch, Validator18└── api/19├── routes.rs # build_router() — every route registered here20├── middleware.rs # AppError, JWT, require_auth, CORS, rate_limit21└── handlers/ # one file per resource (auth, tx, chain, …)
The two money flows
There are two different ways value moves. Being able to distinguish them is a strong defense point:
Wallet → wallet, faucet, staking escrow. These are signed transactions, go through the mempool, and are packed into blocks. They live in the transactions and blocks tables. Backend → see transactions
Trades on the order book. Order matching happens in memory, settlement is a direct balances update. No blocks involved — trade records live in orders and trades. Backend → see exchange
Security model in one paragraph
The browser owns the keys and signs every mutating action. The node never sees a secret key. JWTs (issued after proving wallet ownership via a signed challenge) gate the exchange/staking/multisig endpoints. Nonces stop replay attacks. Amounts are integers (no float rounding). The CORS allowlist and rate limiter protect the API. Postgres transactions keep balance updates atomic.
What to read next
- Backend → Backend overview — the Rust node in detail.
- Frontend → Frontend overview — how the browser talks to it.
- Database → Database schema — every table, and which code uses it.