Architecture
Openfish is a prediction market platform deployed on Polygon. Under the hood, five backend services coordinate with an in-memory matching engine, a suite of background workers, and on-chain settlement through the CTF Exchange contracts.
Service Overview
Section titled “Service Overview”| Service | Port | Responsibility | Database |
|---|---|---|---|
| Gamma Server | 3001 | Event and market catalogue, resolution metadata, comments, search | openfish_gamma |
| CLOB Server | 3002 | Order book, matching engine, trade execution, WebSocket feeds, rewards, rebates, RFQ, builder attribution, CTF split/merge/redeem, bonds, question auctions | openfish_clob |
| Data Server | 3003 | Price history, OHLC, positions, leaderboards, activity, holders | openfish_clob (shared) |
| Bridge Server | 3004 | Cross-chain USDC.e deposits and withdrawals | openfish_bridge |
| Admin Server | 3005 | Internal admin dashboard API (72 endpoints): users, markets, orders, trades, funds, audit log, waitlist | openfish_admin |
The CLOB Server and Data Server share a single PostgreSQL database because trade records feed directly into analytics computations. Gamma, Bridge, and Admin each operate against their own isolated databases. The Admin Server is internal-only and powers the admin web frontend — it is not part of the public-facing API.
CLOB Server Internals
Section titled “CLOB Server Internals”The CLOB server is the backbone of the platform — a Rust application built on Axum, organized into the following layers:
Request Routing
Section titled “Request Routing”HTTP requests arrive through Axum and are dispatched to handler functions grouped by domain:
| Module | Endpoints |
|---|---|
auth_routes | API key creation, derivation, deletion, builder key management |
order_routes | POST /order, POST /orders, DELETE /order, DELETE /orders, DELETE /cancel-all |
book_routes | GET /book, POST /books |
price_routes | GET /price, GET /midpoint, GET /spread, POST /prices |
trade_routes | GET /trades, GET /last-trade-price |
market_routes | GET /market, GET /tick-size, GET /neg-risk |
builder_routes | POST /builder/order, POST /builder/cancel |
rfq_routes | POST /rfq/request, POST /rfq/quote, POST /rfq/accept, GET /rfq/requests |
reward_routes | GET /rewards/user, GET /rewards/user/total, GET /rewards/markets/current |
rebate_routes | GET /rebates |
balance_routes | GET /balance-allowance |
resolution_routes | POST /resolution/dispute |
ctf_routes | Split, merge, redeem proxy endpoints |
relayer_routes | Gasless transaction relay |
Matching Engine
Section titled “Matching Engine”The matching engine operates entirely in memory within the CLOB server process. At startup, it reconstructs all order books by loading every LIVE order from the database:
// From main.rs -- order book recovery on startuplet live_orders = db::queries::list_live_orders(&pool).await?;for order in live_orders { let remaining = order.original_size - order.size_matched; if remaining > Decimal::ZERO { engine.restore_order(&order.token_id, &order.condition_id, &order.side, entry); }}When a new order arrives, the engine tries to match it against resting liquidity. Fills are persisted to the database immediately. Any unmatched remainder stays on the book.
Authentication
Section titled “Authentication”Openfish implements two authentication layers:
| Layer | Purpose | Used By |
|---|---|---|
| L1 Auth | Wallet signature verification (EIP-712 style) | API key creation and derivation |
| L2 Auth | HMAC signature over request method, path, and body | All authenticated endpoints (orders, cancels, rewards, etc.) |
L2 credentials (API key, secret, passphrase) are derived from an L1 wallet signature and stored on the server side.
Background Tasks
Section titled “Background Tasks”The CLOB server runs several periodic workers alongside the HTTP server:
| Task | Interval | Description |
|---|---|---|
| Settlement | ~5 seconds | Settles confirmed trades on-chain via the CTF Exchange matchOrders function |
| Resolution Settlement | ~30 seconds | Monitors 24h cooldown expiry for agent-resolved markets; transitions undisputed RESOLVING markets to RESOLVED |
| Rebate Computation | Daily (midnight UTC) | Aggregates maker volume and computes tiered BPS rebates |
| RFQ Expiration | Periodic | Expires stale RFQ requests and quotes past their TTL |
| GTD Expiration | Periodic | Cancels Good-Til-Date orders that have passed their expiration timestamp |
Settlement Flow
Section titled “Settlement Flow”Trades are matched off-chain by the CLOB engine and settled on-chain by a relayer.
1. Taker submits order --> CLOB matching engine2. Engine matches fills --> Trades written to DB (status: PENDING)3. Settlement task picks up PENDING trades4. Builds matchOrders calldata with signed maker/taker orders5. Sends transaction to CTF Exchange (or Neg Risk Exchange)6. Waits for receipt7. Updates trade status: CONFIRMED (success) or FAILED (revert)The settlement layer routes trades to the appropriate exchange contract based on whether the market is a standard binary market or a negative risk multi-outcome market:
// From settlement/onchain.rslet exchange_addr: Address = if trade.neg_risk { NEG_RISK_EXCHANGE.parse().unwrap() // 0xC5d563...} else { CTF_EXCHANGE.parse().unwrap() // 0x4bFb41...};matchOrders ABI
Section titled “matchOrders ABI”On-chain settlement invokes the CTF Exchange’s matchOrders function:
function matchOrders( Order takerOrder, Order[] makerOrders, uint256 takerFillAmount, uint256[] makerFillAmounts) external;Each Order struct contains the salt, maker, signer, taker addresses, token ID, amounts, expiration, nonce, feeRateBps, side, signatureType, and signature bytes. The fee rate is embedded in each order (not passed as a separate parameter) and is committed at signing time.
WebSocket Channels
Section titled “WebSocket Channels”The CLOB server maintains two WebSocket broadcast channels:
| Channel | Scope | Data |
|---|---|---|
| Market Channel | Per-market | Orderbook updates, price changes, trade notifications |
| User Channel | Per-user | Fill notifications, order status changes |
Market makers should subscribe to both channels for real-time visibility.
Database Schema (Key Tables)
Section titled “Database Schema (Key Tables)”| Table | Description |
|---|---|
accounts | Wallet addresses and ban status |
api_keys | L2 API credentials (key, secret, passphrase, address, nonce) |
builder_api_keys | Builder-specific API credentials with builder_id |
clob_markets | Market configuration (condition_id, token_ids, tick_size, active, fees_enabled) |
orders | All orders (live, matched, cancelled) with builder attribution |
trades | Executed trades with fee data and settlement status |
rebates | Daily maker rebate calculations |
rfq_requests | Open RFQ requests with TTL |
rfq_quotes | Quotes submitted against RFQs with Last Look window |
Rust SDK (openfish-clob-client)
Section titled “Rust SDK (openfish-clob-client)”The official Rust SDK (openfish-clob-client) provides typed bindings for every Openfish service:
| Feature Flag | Module | Purpose |
|---|---|---|
clob | clob | CLOB REST API (orders, trades, markets) |
ctf | ctf | Conditional Token operations (split, merge, redeem) |
gamma | gamma | Event and market metadata |
data | data | Price history, OHLC, analytics |
bridge | bridge | Cross-chain deposits and withdrawals |
ws / rtds | ws | WebSocket subscriptions |
Contract addresses for Polygon mainnet and Amoy testnet are embedded in the SDK and accessible through contract_config() and wallet_contract_config().
Next Steps
Section titled “Next Steps”- Contract Addresses — All deployed contract addresses.
- Error Codes — API error reference.
- Getting Started — Set up your trading environment.