Skip to content

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.


ServicePortResponsibilityDatabase
Gamma Server3001Event and market catalogue, resolution metadata, comments, searchopenfish_gamma
CLOB Server3002Order book, matching engine, trade execution, WebSocket feeds, rewards, rebates, RFQ, builder attribution, CTF split/merge/redeem, bonds, question auctionsopenfish_clob
Data Server3003Price history, OHLC, positions, leaderboards, activity, holdersopenfish_clob (shared)
Bridge Server3004Cross-chain USDC.e deposits and withdrawalsopenfish_bridge
Admin Server3005Internal admin dashboard API (72 endpoints): users, markets, orders, trades, funds, audit log, waitlistopenfish_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.


The CLOB server is the backbone of the platform — a Rust application built on Axum, organized into the following layers:

HTTP requests arrive through Axum and are dispatched to handler functions grouped by domain:

ModuleEndpoints
auth_routesAPI key creation, derivation, deletion, builder key management
order_routesPOST /order, POST /orders, DELETE /order, DELETE /orders, DELETE /cancel-all
book_routesGET /book, POST /books
price_routesGET /price, GET /midpoint, GET /spread, POST /prices
trade_routesGET /trades, GET /last-trade-price
market_routesGET /market, GET /tick-size, GET /neg-risk
builder_routesPOST /builder/order, POST /builder/cancel
rfq_routesPOST /rfq/request, POST /rfq/quote, POST /rfq/accept, GET /rfq/requests
reward_routesGET /rewards/user, GET /rewards/user/total, GET /rewards/markets/current
rebate_routesGET /rebates
balance_routesGET /balance-allowance
resolution_routesPOST /resolution/dispute
ctf_routesSplit, merge, redeem proxy endpoints
relayer_routesGasless transaction relay

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 startup
let 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.

Openfish implements two authentication layers:

LayerPurposeUsed By
L1 AuthWallet signature verification (EIP-712 style)API key creation and derivation
L2 AuthHMAC signature over request method, path, and bodyAll 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.


The CLOB server runs several periodic workers alongside the HTTP server:

TaskIntervalDescription
Settlement~5 secondsSettles confirmed trades on-chain via the CTF Exchange matchOrders function
Resolution Settlement~30 secondsMonitors 24h cooldown expiry for agent-resolved markets; transitions undisputed RESOLVING markets to RESOLVED
Rebate ComputationDaily (midnight UTC)Aggregates maker volume and computes tiered BPS rebates
RFQ ExpirationPeriodicExpires stale RFQ requests and quotes past their TTL
GTD ExpirationPeriodicCancels Good-Til-Date orders that have passed their expiration timestamp

Trades are matched off-chain by the CLOB engine and settled on-chain by a relayer.

1. Taker submits order --> CLOB matching engine
2. Engine matches fills --> Trades written to DB (status: PENDING)
3. Settlement task picks up PENDING trades
4. Builds matchOrders calldata with signed maker/taker orders
5. Sends transaction to CTF Exchange (or Neg Risk Exchange)
6. Waits for receipt
7. 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.rs
let exchange_addr: Address = if trade.neg_risk {
NEG_RISK_EXCHANGE.parse().unwrap() // 0xC5d563...
} else {
CTF_EXCHANGE.parse().unwrap() // 0x4bFb41...
};

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.


The CLOB server maintains two WebSocket broadcast channels:

ChannelScopeData
Market ChannelPer-marketOrderbook updates, price changes, trade notifications
User ChannelPer-userFill notifications, order status changes

Market makers should subscribe to both channels for real-time visibility.


TableDescription
accountsWallet addresses and ban status
api_keysL2 API credentials (key, secret, passphrase, address, nonce)
builder_api_keysBuilder-specific API credentials with builder_id
clob_marketsMarket configuration (condition_id, token_ids, tick_size, active, fees_enabled)
ordersAll orders (live, matched, cancelled) with builder attribution
tradesExecuted trades with fee data and settlement status
rebatesDaily maker rebate calculations
rfq_requestsOpen RFQ requests with TTL
rfq_quotesQuotes submitted against RFQs with Last Look window

The official Rust SDK (openfish-clob-client) provides typed bindings for every Openfish service:

Feature FlagModulePurpose
clobclobCLOB REST API (orders, trades, markets)
ctfctfConditional Token operations (split, merge, redeem)
gammagammaEvent and market metadata
datadataPrice history, OHLC, analytics
bridgebridgeCross-chain deposits and withdrawals
ws / rtdswsWebSocket subscriptions

Contract addresses for Polygon mainnet and Amoy testnet are embedded in the SDK and accessible through contract_config() and wallet_contract_config().