Order Lifecycle
Every trade on Openfish passes through a well-defined sequence: the order is constructed and signed off-chain, matched by the in-memory engine, and then settled on-chain through smart contracts. This hybrid design captures the low-latency benefits of centralized matching without compromising on the verifiability and custody guarantees of blockchain settlement.
How Orders Work
Section titled “How Orders Work”Every order on Openfish is a limit order — it specifies both a price and a quantity. There is no separate “market order” concept. To achieve immediate execution, you simply set a price aggressive enough to cross the book and fill against resting orders.
Orders are EIP712-signed messages. By signing, you grant the Exchange contract permission to execute the trade on your behalf. At no point does anyone else take custody of your assets.
Order Types
Section titled “Order Types”Four order types govern how the matching engine handles your submission:
| Type | Behavior | Use Case |
|---|---|---|
| GTC | Good Till Cancelled — stays on the book until filled or explicitly cancelled | Standard limit orders; liquidity provision |
| GTD | Good Till Date — automatically expires at a specified Unix timestamp | Time-boxed strategies; event-triggered trading |
| FOK | Fill Or Kill — must fill completely in a single match cycle or is rejected entirely | All-or-nothing requirements; large block trades |
| FAK | Fill And Kill — fills whatever quantity is available immediately, discards the rest | Partial fills acceptable; sweeping available liquidity |
Post-Only Orders
Section titled “Post-Only Orders”A post-only order is guaranteed to add liquidity rather than remove it. If the order would immediately cross the spread and match against a resting order, the engine rejects it outright. This ensures you remain the maker on every fill and never incur taker fees.
// A post-only buy at 0.49 with asks starting at 0.50 will be placed on the book// A post-only buy at 0.50 with asks at 0.50 will be REJECTED (would cross)let order = client .limit_order() .token_id(token_id) .price(dec!(0.49)) .size(dec!(10)) .side(Side::Buy) .post_only(true) .build() .await?;Order Lifecycle Steps
Section titled “Order Lifecycle Steps”1. Create and Sign
Section titled “1. Create and Sign”Your client assembles an order containing:
- token_id — which outcome token you are trading (Yes or No)
- side — BUY or SELL
- price — the limit price (between 0.00 and 1.00)
- size — the number of shares
- order_type — GTC, GTD, FOK, or FAK
- expiration — Unix timestamp for GTD orders (0 for others)
- nonce — for replay protection
Your private key produces an EIP712 signature over this data, authorizing the Exchange contract to carry out the trade.
2. Submit to the CLOB Server
Section titled “2. Submit to the CLOB Server”The signed order is dispatched to POST /order on the CLOB server (port 3002). Before it reaches the engine, the server runs a series of checks:
- Signature validity — the EIP712 signature matches the declared maker address
- L2 authentication — the request carries a valid HMAC signature derived from your API key and secret
- Balance sufficiency — enough USDC.e (for buys) or tokens (for sells) to cover the order
- Allowance — the Exchange contract is approved to spend your assets
- Tick size compliance — the price conforms to the market’s minimum tick size
3. Match or Rest
Section titled “3. Match or Rest”The order enters the in-memory engine, which enforces price-time priority:
- Bids are ranked by price descending (highest first), then by arrival time (FIFO)
- Asks are ranked by price ascending (lowest first), then by arrival time (FIFO)
If marketable (buy price >= lowest ask, or sell price <= highest bid):
The order fills against resting orders. It may consume liquidity across multiple price levels, generating a Fill record for each partial fill — each containing the maker order ID, taker order ID, execution price, and matched size.
If not marketable (GTC/GTD orders only):
The remaining quantity rests on the book until one of the following occurs:
- An incoming order matches against it
- You cancel it through the API
- It expires (GTD orders only — expiration is checked every 10 seconds)
For FOK orders: If the entire quantity cannot be filled, the order is rejected and any partially consumed maker orders are rolled back to their original state.
For FAK orders: The engine fills whatever portion it can, then discards the unfilled remainder.
4. Settlement
Section titled “4. Settlement”Matched trades are queued for on-chain settlement. A background process runs approximately every 5 seconds, picking up matched trades and settling them:
- Load maker and taker order data (EIP712 fields, signatures)
- Construct the settlement transaction
- Submit to the Exchange contract on Polygon
The Exchange contract verifies both signatures and then atomically:
- Transfers outcome tokens from seller to buyer
- Transfers USDC.e from buyer to seller
Settlement is all-or-nothing — the entire trade either succeeds or is rolled back.
5. Confirmation
Section titled “5. Confirmation”The trade reaches finality on Polygon. Trade statuses advance through these stages:
| Status | Terminal | Description |
|---|---|---|
MATCHED | No | Trade matched in the engine, sent to the settlement executor |
MINED | No | Settlement transaction mined into the blockchain |
CONFIRMED | Yes | Trade achieved finality, settlement successful |
RETRYING | No | Transaction failed, being retried |
FAILED | Yes | Trade failed permanently after retries |
Order Statuses
Section titled “Order Statuses”| Status | Description |
|---|---|
LIVE | Order is resting on the book, waiting to be matched |
MATCHED | Order matched immediately (fully filled) |
CANCELED | Order was cancelled by the user, expired, or rejected (FOK/FAK/post-only) |
Maker vs Taker
Section titled “Maker vs Taker”| Role | Description | When |
|---|---|---|
| Maker | Adds liquidity to the book | Your order rests on the book and is later filled by an incoming order |
| Taker | Removes liquidity from the book | Your order fills immediately against resting orders |
The taker benefits from price improvement. A buy order at $0.55 that matches a resting sell at $0.52 executes at $0.52 — the maker’s price.
Cancellation
Section titled “Cancellation”Orders can be withdrawn at any point before they are fully matched:
- Cancel one:
DELETE /orderwith the order ID - Cancel all:
DELETE /cancel-allclears every open order for the authenticated user - Cancel by market:
DELETE /cancel-market-ordersremoves all orders tied to a specific condition ID
For partially filled orders, only the unfilled portion is cancelled. The already-filled portion settles normally.
Heartbeat-Based Cancellation
Section titled “Heartbeat-Based Cancellation”Openfish provides a heartbeat mechanism designed for automated trading systems. After registering a heartbeat via GET /auth/heartbeat, your system must continue sending heartbeats at least every 10 seconds. If the heartbeats stop, all of your open orders are automatically cancelled. This acts as a safety net to prevent stale quotes from lingering on the book when a trading bot disconnects or crashes.
L2 Authentication
Section titled “L2 Authentication”All order management endpoints require L2 HMAC authentication. The process works as follows:
- Derive API credentials: Sign a message with your private key (L1 auth) to obtain an API key, secret, and passphrase
- Sign each request: Every API call includes an HMAC signature computed from the API secret, the request timestamp, method, path, and body
- Server verification: The CLOB server validates the HMAC signature before processing the request
The Rust SDK automates this entirely when you call .authenticate() during client initialization.
Requirements Summary
Section titled “Requirements Summary”| Requirement | Description |
|---|---|
| USDC.e balance | Sufficient collateral for buy orders |
| Token balance | Sufficient outcome tokens for sell orders |
| Exchange allowance | Approve the Exchange contract to spend your assets |
| API credentials | Valid L2 API key for authenticated endpoints |
| Tick size | Price must be a multiple of the market’s minimum tick size |
The maximum order size is determined by your available balance minus the amounts reserved by existing open orders:
max_order_size = balance - sum(open_order_size - filled_amount)Next Steps
Section titled “Next Steps”- Resolution — Learn how markets are resolved and winning tokens redeemed
- Prices & Orderbook — Understand the in-memory order book structure