Real-Time Data
Every state change in the CLOB is published once, as a JSON document, to a single Redis stream. On-chain activity is published separately as Anchor events.
The notifications stream
Section titled “The notifications stream”The Ledger writes every order and trade state change to the Redis
notifications stream with XADD. One producer, one stream; consumers
subscribe and filter by the message type.
type | Fires on |
|---|---|
order | Order created, updated, filled, cancelled |
order_live_activity | Order-book activity for live feeds |
price_change | A price level moved |
trade | A trade was posted or its status changed |
last_trade_price | The market’s last traded price changed |
trade_notifications | User-facing trade notifications |
redis-cli XREAD COUNT 10 BLOCK 5000 STREAMS notifications $Design notes that matter when you build on it:
- One stream, not one per topic. Filter client-side on
type. Adding a consumer never requires a producer change. - Publishing is fire-and-forget. A Redis failure does not fail the underlying write. The stream is a fan-out convenience, not the source of truth — the PostgreSQL ledger is.
- Use a consumer group if you need at-least-once delivery across
restarts. A bare
XREAD $starts from now and silently drops anything that happened while you were away.
Reconciling after a gap
Section titled “Reconciling after a gap”The stream is not a replayable log of record. After a disconnect, re-sync from the REST surfaces rather than trying to backfill:
curl -s "http://127.0.0.1:8080/v1/open-orders" -H "Authorization: Bearer $CLOB_TOKEN"curl -s "http://127.0.0.1:8080/v1/trades?after=$CURSOR" -H "Authorization: Bearer $CLOB_TOKEN"Then resume the stream.
Book snapshots and the hash
Section titled “Book snapshots and the hash”The OrderBookSummary carries a hash — SHA-256 over the canonical JSON of
the market, asset, bids and asks, recomputed on every change.
{ "market": "0xdemo-condition", "asset_id": "100", "bids": [{ "price": "0.48", "size": "1200" }], "asks": [{ "price": "0.52", "size": "900" }], "hash": "9f2c7a…"}Two uses: compare hashes instead of diffing levels to detect change, and confirm a cached snapshot matches what a stream event describes.
On-chain events
Section titled “On-chain events”Settlement emits an Anchor event, written to the transaction log as a
base64 Program data: line prefixed with the 8-byte event discriminator.
#[event]pub struct TradeSettled { pub market_id: u64, pub side: Side, pub making: u64, pub taking: u64, pub fee: u64, pub maker: Pubkey, pub taker: Pubkey, pub signer: Pubkey, pub outcome_mint: Pubkey, // which leg — YES or NO pub order_hash: [u8; 32], // joins to the off-chain order}order_hash is the join key between on-chain settlements and off-chain
orders. outcome_mint tells you which side of the market moved.
The parlay program emits its own set — ParlayCreated, LegSettled,
ParlayClaimed, ParlayVoided, plus config and vault events. →
Parlays API
The indexing pipeline
Section titled “The indexing pipeline”Solana ──▶ Helius webhook ──▶ Platform API (Django) ──▶ PostgreSQL ──▶ RESTThe Platform API decodes both programs’ events and mirrors them into
relational tables. Out-of-order delivery is handled explicitly: a
LegSettled arriving before its parent ParlayCreated row raises
DeferredParlayEvent, the webhook answers 503, and Helius retries.
That pattern is worth copying if you index these programs yourself — Solana webhook delivery is not ordered, and a parlay’s create and settle events can easily arrive backwards.
Choosing a feed
Section titled “Choosing a feed”| You want | Use |
|---|---|
| Your fills, low latency | notifications stream, type: trade |
| Book changes | notifications stream, type: price_change + snapshot hash |
| Authoritative settlement state | GET /v1/trades — CONFIRMED means finalized |
| Raw on-chain truth | Solana logs / TradeSettled events |
| Product data (questions, charts) | Platform API REST |
| Parlay state | Platform API /api/parlays/ |
Latency expectations
Section titled “Latency expectations”| Stage | Typical |
|---|---|
| Insert → match decision | In-process, sub-millisecond |
| Match → transaction sent | One gRPC hop plus simulation |
Sent → finalized | Solana’s finality, a few hundred ms to a couple of seconds |
Finalized → CONFIRMED in the ledger | One confirmation-worker tick |
| Ledger write → stream | Immediate, fire-and-forget |
The book updates the moment a match is decided. Money is final at
CONFIRMED. A trading system should treat those as two distinct events —
quote off the first, account off the second.