Order Lifecycle
An order on GoMarket crosses two trust boundaries: the off-chain matching engine, and the on-chain settlement program. This page traces the whole path.
The path
Section titled “The path” sign insert match execute confirm │ │ │ │ │ ▼ ▼ ▼ ▼ ▼┌──────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐│wallet│ ──▶ │ Order │ ──▶ │ Matching │ ─▶ │ Executor │ ─▶ │ Solana ││ │ │ Manager │ │ engine │ │ builds & │ │ finalized │└──────┘ └────┬────┘ └─────┬────┘ │ signs │ └──────┬─────┘ │ │ └─────┬────┘ │ ▼ ▼ │ ▼ validation Ledger: order Ledger: Ledger: (12 checks) + trade rows MINED CONFIRMED1. Signing
Section titled “1. Signing”You compute the domain-separated SHA-256 order hash and sign it with your
wallet’s ed25519 key. That hash is the order’s identity everywhere — it’s the
orderID the API returns, the de-duplication key, and the digest the
on-chain program recomputes at settlement.
sha256( b"CTF_EXCHANGE_ORDER_V1" ‖ salt ‖ maker ‖ signer ‖ taker ‖ token_id_len ‖ token_id ‖ maker_amount ‖ taker_amount ‖ expiration ‖ nonce ‖ fee_rate_bps ‖ side ‖ signature_type )The signature is not part of the hash.
2. Insert
Section titled “2. Insert”POST /v1/orders runs a fixed pipeline. Any failure returns
success: false with a reason, and nothing is persisted.
| # | Step | Rejects when |
|---|---|---|
| 1 | Resolve token ID | — |
| 2 | Locate the book | No book exists for that token |
| 3 | Build ProcessedOrder | Price/size math fails |
| 4 | Validate on insert | Bad signature, expired, bad fee rate, off-tick |
| 5 | Duplicate check | The order hash is already in the books |
| 6 | Market readiness | Market isn’t ready to trade |
| 7 | Rounding config | Unknown tick size |
| 8 | Amount / tick-size | Amounts don’t land on the grid |
| 9 | Unique-order registry | Hash seen before |
| 10 | Balance / allowance | Insufficient ATA balance (allowance: warning) |
| 11 | Fee rate | Marketable order priced below the taker fee |
| 12 | GTD registration | — (registers expiry on the pending service) |
| 13 | Match / insert / schedule | — |
Step 10 also reserves the maker amount, so one balance cannot back two orders simultaneously.
3. Resting or matching
Section titled “3. Resting or matching”Step 13 forks three ways, and the response’s status tells you which:
status | What happened |
|---|---|
live | Nothing crossed; the order is resting in the book |
matched | It crossed; trades were built and sent to the executor |
delayed | Parked on the pending service inside a game-start window |
unmatched | Couldn’t fill — typically a FOK that found no liquidity |
Note that success: true with status: "live" is a completely normal
outcome. Don’t treat a successful insert as a fill.
Order types
Section titled “Order types”| Type | Behaviour |
|---|---|
GTC | Rests until filled or cancelled. expiration must be "0". |
GTD | Rests until expiration (unix seconds), then is ejected. |
FOK | Fills completely on arrival or is killed. Never rests. |
4. Matching
Section titled “4. Matching”The taker walks the book against resting maker orders in price-time order. For each maker leg the engine computes the fill amount, the fee, and the match type — COMPLEMENTARY, MINT or MERGE. Self-trades are filtered and the price-distortion guard caps how far the walk may push the price.
5. Execution
Section titled “5. Execution”Matched legs go to the Executor over gRPC. The executor:
- Re-runs the order validation on every order in the request.
- De-dupes by full trade key — taker and maker order hashes, fill amounts,
fee rate, bucket. A repeat is rejected as
DuplicateTrade. - Builds the transaction: one
execute_tradeinstruction per maker (15 accounts each), the residualmint_tokens/merge_tokensfor same-side matches, a top-leveled25519verify instruction, and ComputeBudget instructions. - Signs with the operator keypair and the taker key, and sends.
The compute budget is simulateTransaction × 1.2, capped. MINT/MERGE
transactions overflow the 1232-byte legacy limit, so the message compiles to
v0 with an address lookup table when one is configured.
That de-duplication is load-bearing: the on-chain program carries no
per-order fill state. There is no nonce PDA and no fill-status PDA. The
executor is the single trusted settlement instance, and a caller who bypasses
it and sends execute_trade directly can settle the same signed order
repeatedly — bounded only by the maker’s standing delegate allowance.
6. Confirmation
Section titled “6. Confirmation”A background worker batches getSignatureStatuses over all pending
signatures each tick. Solana has no reorgs, so finalized is the settlement
point.
| Outcome | Action |
|---|---|
| Finalized | Ledger UpdateTrade(CONFIRMED) |
| Out of gas (CU ≥ 95% of limit) | Retry with CU ×2 and fee ×2, up to the retry limit |
| Failed | Ledger UpdateTrade(FAILED) + OrderSizeUpdate to eject affected resting orders |
| Stuck past the idle timeout | Re-executed through the same path |
The pending map and the de-dupe key set are snapshotted to disk on every change, so a restart re-confirms in-flight transactions rather than re-sending them.
7. Failure reconciliation
Section titled “7. Failure reconciliation”When settlement fails on-chain, the resting orders involved are now backed by
state that didn’t move. The executor calls the order manager’s internal
POST /v1/orders/size-update (authorized by X-Internal-Token, not SIWS) to
invalidate them. This is the only route where an arbitrary owner’s orders can
be touched, and it fails closed when no token is configured.
Order states in the ledger
Section titled “Order states in the ledger”| State | Meaning |
|---|---|
LIVE | Resting on the book |
MATCHED | Matched, settlement in flight |
MINED | Transaction landed, not yet finalized |
CONFIRMED | Finalized on-chain |
FAILED | Settlement failed; orders reconciled |
CANCELED | Cancelled by the owner or ejected |
Every transition is appended to the ledger’s Redis notifications stream.
→ Real-Time Data
Cancellation and ejection
Section titled “Cancellation and ejection”You can cancel any resting order you own at any time. Orders are also ejected without your involvement when:
- A GTD order reaches its
expiration. - A market’s game start fires — every order created before it is ejected.
- Settlement failed and reconciliation invalidates the order.
Cancellation is off-chain only. It removes the order from the book; it does not touch your delegate allowance, which stays standing until you revoke it.