Our work
We developed the order-matching, risk and settlement services for ThermoMarket, together with live market data delivery and Signal Orders. Our work connected signed user orders to recoverable execution while keeping private keys with their owners.
A prediction market that stays predictable under pressure
ThermoMarket needed a prediction market where users could trade event outcomes while retaining control of their private keys. We built the trading services around the busiest part of that workflow: orders, cancellations and position updates arriving together when attention concentrates on one event. The system preserves execution order and tracks funds through matching and settlement.
The architecture separates order matching from blockchain settlement. That decision removes block confirmation from the order admission path but creates another problem. A trade can be matched before its asset transfer is final. Funds can move outside the platform during that gap. A matching node can fail after processing an order. We made those intermediate states explicit in the order lifecycle and recovery path.
Off-chain matching with contract-enforced settlement
Putting the entire order book on-chain would make routine placements and cancellations depend on blockchain transactions. Keeping everything inside the backend would leave asset ownership dependent on the operator. The selected model uses a hybrid central limit order book. Users sign orders with EIP-712 typed signatures. The backend validates and matches them outside the blockchain while the exchange contract checks signatures and filled amounts before transferring assets. Routine order placement and cancellation avoid individual blockchain transactions. An on-chain cancellation path remains available for invalidating signed orders at the contract level.
That separation removes block confirmation from admission, but a valid signature alone does not establish available funds. The same wallet may have open orders in several markets. Its owner can also transfer funds through another application. The architecture therefore includes a dedicated Risk Service with one writer per user and an internal reservation ledger. Available funds account for confirmed balances, token allowances, known pending outflows and amounts already reserved for open orders or unsettled trades. The service grants reservations atomically so two orders cannot both consume the same available balance within the platform.
One deliberate restriction makes the settlement model easier to control. Assets bought in an unconfirmed trade cannot fund another order until the purchase is confirmed on-chain. Users have to wait before selling a newly acquired position. In return the platform avoids chains of dependent settlements where one failed purchase invalidates several later trades. Independent settlement lanes can then operate without relying on unconfirmed incoming assets.
Reservations cannot prevent someone from moving funds directly through their wallet. The Settlement Coordinator therefore checks the latest chain state and runs an eth_call simulation before submitting a transaction. It groups compatible fills into bounded batches and uses separate authorised operator wallets with independent nonce sequences. This avoids making every settlement wait behind one operator account. If a batch fails simulation the coordinator isolates the offending fills so that unaffected trades can proceed. A trade that can no longer settle remains visible as voided and triggers balance reconciliation. It does not disappear from the user's history.
Fast processing still needs a durable acceptance boundary
Processing an order in memory is only part of the latency problem. We made durable recording the boundary for order acceptance. Responding before durable storage creates a failure window. Waiting for a separate storage operation on every command adds overhead. The architecture uses a replicated Redpanda command journal with small group commits to balance those requirements.
The matcher assigns each command a sequence number and applies it to the in-memory book. Commands are then journaled in small batches. The journal uses acks=all and a replication factor of three across availability zones. Client responses and derived events are released only after the batch is acknowledged. If the matcher fails before that acknowledgement the client sees a timeout and can retry using the same order hash. Deduplication prevents the retry from creating another order.
This is also the basis for recovery. A replacement matcher loads a snapshot and replays the recorded commands. Matching uses integer price ticks and recorded timestamps so that replay does not depend on floating-point behaviour or the replacement machine's clock. Deterministic event IDs allow downstream services to discard duplicates if recovery republishes an event. Acceptance therefore has a precise meaning. The command has reached the durable journal and its effect can be reconstructed.
Speed only becomes useful when the system can explain what happened to an order. The architectural boundary is the acceptance response. The acceptance response follows durable command recording. Subsequent transitions retain their link to that command through matching, settlement and recovery.
One writer preserves order in a busy market
Adding more servers does not automatically make one order book faster. Several concurrent writers would need to agree on which order arrived first and which resting order gets filled. Network arrival times across API nodes cannot provide that common sequence. The architecture keeps one active writer per market and scales the work around it. Each market is a logical MarketActor that owns its book and sequence counter. It processes one command at a time in deterministic order. Markets are distributed across matcher processes and a particularly busy market can receive a dedicated process.
Signature verification runs in parallel workers before commands reach the matching loop. Order book data stays in memory and client updates pass through the dedicated distribution layer rather than directly from the matcher. Within each binary market a unified book expresses both YES and NO orders in YES price ticks. Mirroring complementary prices into one book preserves a single price-time queue and removes a separate cross-book matching step.
There is a cost to this choice. Moving a busy market between processes requires a short halt while the system takes a snapshot, changes ownership and restores the book on its new node. Recovery also needs protection against an old node continuing to write after a network partition. Epoch fencing handles that case. A new leader writes an EpochStart marker and consumers ignore subsequent records from older epochs. The design accepts a controlled interruption in exchange for a clear source of authoritative trading history.
Market updates need their own capacity
We separated WebSocket delivery from the matching engine so an increase in viewers does not add client connections to the trading loop. A Market Data Processor derives public updates from matcher events, combines redundant changes over short intervals and publishes them to the WebSocket fleet. This keeps order processing and price distribution on separate scaling paths.
Each client receives a snapshot followed by numbered updates. If it detects a gap it requests a fresh snapshot instead of continuing with an incomplete order book. Per-connection outbound queues have configured bounds on buffered updates. The service first combines redundant updates and can eventually disconnect clients that cannot keep up. This creates separate controls for audience growth and trading activity. More viewers can be served by expanding the distribution layer without adding the same capacity to every matcher.
Signal Orders turn architecture into a product feature
Signal Orders let a user place a trade condition on another market. A user might want to buy YES in Market B at no more than 0.41 only after YES in Market A stays above 0.65 for five seconds. The user signs the trading order once and submits the trigger condition through the authenticated API. The order stays dormant without reserving funds. When the condition is met it enters the normal admission path and receives a fresh balance check. Activation can still be rejected if the user no longer has sufficient funds.
The Trigger Engine consumes the platform's sequenced market data and evaluates the condition using journaled timestamps. This makes the decision replayable rather than dependent on the trigger server's local clock. A unique activation record prevents the same signal from firing twice. Stale data cannot activate an order and conditions missed during an outage are not executed retrospectively. The trust boundary remains explicit. The operator evaluates the trigger outside the blockchain while the contract enforces the signed trading order. The feature adds a new strategy workflow without introducing a second settlement mechanism.
Order acceptance and settlement have separate states
We kept acceptance, matching and blockchain confirmation separate in the order lifecycle. An acceptance response follows the durable journal acknowledgement. A match creates work for the Settlement Coordinator. Final confirmation comes from the chain. The client can follow those transitions without treating a quick API response as completed asset delivery.
The same separation controls overload. Bounded queues contain slow consumers, reservations account for open obligations and settlement backpressure limits new exposure. Cancellations and reconciliation remain distinct from admitting more trades.
Keeping the scope narrow enough to operate
The first version excludes leverage, lending against positions and liquidity shared across blockchains. Each would add dependencies to an already demanding balance and settlement model. The same restraint applies during incidents. If blockchain congestion causes unsettled exposure to grow, backpressure limits actions that increase risk. Available cancellations receive separate priority. Operators can restrict trading based on settlement health instead of allowing the matching engine to build an unlimited backlog.
The implementation connects each order to its reservation, journal record, match and settlement state. Parallel validation keeps signature work outside the matching loop, while one writer preserves order within each market. Replicated commands support recovery, and independent settlement lanes let unrelated transfers progress separately. Those boundaries give operators a record to reconcile when a process or external dependency fails.
See our architecture in practice.
DEVLAB · ARCHITECTURE EXAMPLE
Agent
Commerce
A look inside the software architecture behind Agent Commerce.
View architecture