Skip to content

Architecture

GoMarket is a hybrid-decentralized system: order matching off-chain, settlement on-chain, non-custodial throughout.

┌─────────────────────────────────────────────────────────────────────┐
│ CLIENTS │
│ Web · Mobile · Bots · Platform API (Django) │
└───────────────┬─────────────────────────────────────────────────────┘
│ HTTP/JSON (SIWS) · gRPC
▼
┌─────────────────────────────────────────────────────────────────────┐
│ OFF-CHAIN SERVICES (Rust) │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Order Manager │ │
│ │ Books (BTreeMap) · Matching · Validator · Event loop (3 thr) │ │
│ │ Transports: axum HTTP /v1 (SIWS) + tonic gRPC │ │
│ └────┬──────────────────┬────────────────────┬─────────────────┘ │
│ │ gRPC │ gRPC │ REST │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Ledger │ │ Executor │ │ Markets API │ │
│ │ PG │◀─────│ build/sign/ │───▶│ catalogue │ │
│ │ Redis │ │ send + conf │ │ PG │ │
│ └──────────┘ └──────┬───────┘ └──────────────┘ │
│ │ OrderSizeUpdate → Order Manager │
└───────────────────────────┼─────────────────────────────────────────┘
│ Solana JSON-RPC
▼
┌─────────────────────────────────────────────────────────────────────┐
│ ON-CHAIN (Solana, Anchor) │
│ │
│ markets high-market-parlay │
│ ├ execute_trade ├ create_parlay │
│ ├ mint / merge ├ settle_leg (permissionless) │
│ ├ redeem / refund ├ claim_parlay │
│ ├ lifecycle + disputes ├ void_parlay (admin) │
│ ├ set_delegate └ vault: seed / withdraw │
│ └ price accumulator ──────── read (no CPI) ─────────▶ │
└─────────────────────────────────────────────────────────────────────┘
ComponentTypeResponsibility
markets programOn-chainSettlement (execute_trade), market lifecycle, mint/merge/redeem/refund, maker delegation, fees, price accumulator
high-market-parlay programOn-chainParlay slips, vault solvency, per-leg settlement, claims
Order ManagerRustIn-memory books, validation, matching; HTTP (SIWS) + gRPC
Markets APIRustThe market catalogue; create-market CLI
LedgerRustPostgreSQL order/trade persistence; Redis notifications fan-out
ExecutorRustBuilds/signs/sends settlement txs; confirmation tracking; retries; reconciliation
Platform APIDjangoIndexes both programs from Helius webhooks; product REST surface
PDASeeds
Config["markets_config"]
Market["market", market_id:u64 LE]
YES mint["yes_mint", market_pda]
NO mint["no_mint", market_pda]
Collateral vault["vault", market_pda]
Fee vault["fee_vault", mint]
Delegate["delegate", maker]

Settles one maker leg as a direct swap. Signers: the taker, the config operator, and the program (for the maker’s delegate PDA). The maker does not sign — its tokens move under a standing Approve allowance.

The handler recomputes the domain-separated order hash and requires a top-level ed25519 verify instruction in the same transaction; the ed25519 program cannot be CPI’d, so it scans the Instructions sysvar to prove presence.

Same-side matches prepend mint_tokens or merge_tokens in the same transaction, keeping the sequence atomic.

→ Settlement

There is no nonce PDA and no fill-status PDA. Nonce, replay and fill bookkeeping are off-chain, in the executor — the trusted single settlement instance. A caller bypassing it can settle the same signed order repeatedly, bounded only by the maker’s delegate allowance.

Separate program, its own USDC vault, its own Parlay accounts. It never touches outcome token mints.

PDASeeds
ParlayConfig["parlay_config"]
Vault["parlay_vault"]
Parlay["parlay", user, user_seq:u64 LE]

Keying the Parlay on (user, user_seq) lets two users create their first slip in the same slot without racing a global counter.

The parlay program deserialises Market and MarketEventLink accounts directly:

1. require!(account.owner == amm_program_id, InvalidMarketOwner);
2. let market = AmmMarket::try_deserialize(&mut data.as_ref())
.map_err(|_| InvalidMarketAccount)?;

No CPI means the parlay is decoupled at runtime — an upgrade to the market program doesn’t require redeploying the parlay. The cost is a compile-time coupling: the parlay links the market crate as a workspace path dependency, so a layout change fails the build rather than silently misreading bytes.

amm_program_id is frozen at initialize and absent from update_config, because a rotatable program id would let an admin point the parlay at a malicious program whose accounts decode to chosen outcomes.

→ Vault & Solvency

  • Books — one BTreeMap per market asset; O(log n) insert/delete, ordered iteration for price-time priority.
  • Matching — the taker walks the book applying fees, tick rounding, expiry checks, a price-distortion guard and a self-trade filter.
  • Validation — one-time checks on insert plus on-chain reads over JSON-RPC (ATA balance, delegate allowance).
  • Event loop — three std threads: new-order ingestion, catalogue pulls, game-start ejections. Queries run inline.
  • Transports — axum HTTP under /v1/ with SIWS, tonic gRPC.

External dependencies sit behind traits — LedgerService, ExecutionService, RiskService, MarketsService, UniqueOrderService, pending services — each with an in-memory implementation, so the crate is self-contained and testable. The binary boots with network backends by default.

One gRPC surface (clob.LedgerService) over PostgreSQL via sqlx. Every state change is encoded as one JSON document and appended to the Redis notifications stream by a single producer. Consumers filter on the message type. Publish errors are fire-and-forget — the stream is a fan-out convenience, not the source of truth.

Validates, de-dupes by full trade key, builds the transaction (one execute_trade per maker leg, the residual mint/merge, the ed25519 verify ix, ComputeBudget), signs with the operator and taker keys, and sends. CU budget is simulateTransaction × 1.2, capped; v0 + address lookup table when configured, because MINT/MERGE transactions exceed the 1232-byte legacy limit.

A confirmation worker batches getSignatureStatuses per tick. The pending map and de-dupe set are snapshotted to local JSON on every change; restored transactions are re-confirmed, never re-sent.

Owns the markets table, serves it over REST, hosts create-market. The single source of truth for the order manager’s books and the executor’s (market_id, outcome_mint) resolution — the program has no on-chain token registry.

Client ─POST /v1/orders──▶ Order Manager ──validate──▶ match
│ │
├──CreateOrder──▶ Ledger │
└──Execute──▶ Executor ──┘
│
build/sign/send
▼
Solana tx
│
finalized│
▼
Ledger UpdateTrade(CONFIRMED)
│
▼
Redis notifications stream

On failure: UpdateTrade(FAILED) plus OrderSizeUpdate back to the order manager, which ejects the affected resting orders.

User ──create_parlay tx──▶ high-market-parlay
│ reads Market + MarketEventLink per leg
│ locks probabilities, reserves exposure
▼
Helius webhook
│
▼
Platform API indexer ──▶ PostgreSQL ──▶ REST

Out-of-order delivery is handled by raising DeferredParlayEvent on a LegSettled whose parent row hasn’t landed; the webhook answers 503 and Helius retries.

DecisionRationale
Off-chain matchingLatency and cost; on-chain matching at this granularity isn’t viable
On-chain settlementAtomicity and non-custody; a match is money or it isn’t
Delegate allowanceMakers can be offline; no co-signing
No on-chain order stateSmaller program surface, cheaper transactions — at the cost of a trusted executor
Trait-abstracted backendsEvery service testable in memory
Catalogue off-chainNo on-chain registry to maintain or pay for
Parlay reads, no CPIRuntime decoupling; compile-time layout safety
Frozen amm_program_idCloses the malicious-AMM substitution vector
TWAP for parlay legsA spot price is one slot of manipulation away
Permissionless settle_legOutcome-determined, so there’s nothing to gain by racing
BoundaryControl
Client → Order ManagerSIWS; sub is the authoritative owner
Executor → Order ManagerShared X-Internal-Token, constant-time, fails closed
Anyone → execute_tradeConfig operator must sign
Maker tokensDelegate allowance — the real exposure ceiling
Order authenticityOn-chain ed25519 verify against the recomputed hash
ReplayExecutor trade-key de-dupe, persisted across restarts
Parlay account readsOwner check + discriminator + compile-time layout pin
Admin operationsSquads multisig
PieceRuntime
Order ManagerRust binary — CLOB_HTTP_ADDR, CLOB_GRPC_ADDR
LedgerRust binary + PostgreSQL + Redis
ExecutorRust binary + operator keypair + local snapshot files
Markets APIRust binary + PostgreSQL
Platform APIDjango + PostgreSQL + Celery
ProgramsSolana, Anchor 0.32

The order manager boots with network backends by default. A self-contained demo:

Terminal window
CLOB_LEDGER_BACKEND=in_memory CLOB_EXECUTION_BACKEND=in_memory \
CLOB_RISK_BACKEND=in_memory CLOB_MARKETS_BACKEND=in_memory \
cargo run -p order-manager