Settlement
Matching decides who trades with whom. Settlement moves the tokens. It
happens on Solana, in the markets program, through a single instruction.
execute_trade
Section titled “execute_trade”Settles one maker leg of a maker↔taker match on one outcome token, as a direct swap. The market vault is not involved.
pub fn execute_trade( ctx: Ctx, side: Side, // the MAKER's side: 0 = BUY, 1 = SELL maker_amount: u64, // the maker order's total size taker_amount: u64, // the taker order's total size fee_rate_bps: u16, making: u64, // this fill: 0 < making <= maker_amount // the maker's off-chain order, for on-chain signature verification salt: u64, signer: Pubkey, order_taker: Pubkey, token_id: Vec<u8>, expiration: u64, nonce: u64, signature_type: u8, signature: [u8; 64],) -> Result<()>What it does, in order:
- Validate
making > 0 && making <= maker_amount, EOA only,signer == maker. taking = calculate_taking_amount(making, maker_amount, taker_amount).fee = calculate_fee(…)on the taker asset.- Require
fee <= taking. - Transfer: maker → taker (
making, via the delegate PDA), taker → maker (taking − fee), taker → fee vault (fee). - Advance the market’s price accumulator.
- Emit
TradeSettled.
Who signs
Section titled “Who signs”| Party | Signs? | Why |
|---|---|---|
| Taker | yes | Their own leg |
| Operator | yes | Only the config operator may settle trades |
| Program | yes (PDA) | Signs for the maker’s delegate |
| Maker | no | Tokens move under a standing Approve allowance |
The maker being absent is the whole point. Post an order, close your laptop; the delegate PDA carries your side.
Delegate PDA seeds = ["delegate", maker]The maker-order verification
Section titled “The maker-order verification”A standing allowance is powerful, so the program will not move a maker’s tokens on the operator’s word alone. The maker’s off-chain order travels as instruction data, and the handler:
- Recomputes the domain-separated SHA-256 order hash.
- Requires a top-level
ed25519verify instruction in the same transaction. Theed25519program cannot be CPI’d, so the handler scans theInstructionssysvar to prove it’s there. - Requires
signature_type == 0andsigner == maker. - Requires the order’s taker to be public or the settling taker.
- Requires a non-zero
expirationto be in the future.
So the allowance can only be spent against an order the maker actually signed, for the amount and price they signed.
Accounts
Section titled “Accounts”Fifteen, in #[derive(Accounts)] order:
| # | Account |
|---|---|
| 1 | Market PDA (must be Open or Closed) |
| 2 | Outcome mint (YES or NO) |
| 3 | Collateral mint |
| 4 | Maker (not a signer) |
| 5 | Taker (signer) |
| 6 | Delegate PDA |
| 7–8 | Maker’s collateral and outcome token accounts |
| 9–10 | Taker’s collateral and outcome token accounts |
| 11 | Fee vault token account |
| 12 | Instructions sysvar |
| 13 | SPL Token program |
| 14 | Config PDA |
| 15 | Config operator (signer) |
The trust model
Section titled “The trust model”The program keeps no per-order state. There is no nonce PDA and no fill-status PDA. It verifies a signature and moves tokens; it does not remember having done so.
Replay protection therefore lives entirely in the Executor:
- De-dupes by full trade key — taker and maker order hashes, fill amounts, fee rate, bucket.
- Persists that key set alongside the pending snapshot, so it survives a restart.
- Bounded, with oldest keys evicted past
MAX_DEDUPE_KEYS.
The executor is the trusted single settlement instance. A caller who
bypasses it and sends execute_trade directly can settle the same signed
order repeatedly — each time up to maker_amount, in total bounded only by
the maker’s standing delegate allowance.
Two implications for anyone trading here:
- The operator signature is what keeps that path closed. It is required on every settlement.
- Size your delegate allowance deliberately. It is the real ceiling on your exposure, not your order size.
Building the transaction
Section titled “Building the transaction”The executor:
- Re-runs order validation on every order in the request.
- Checks the de-dupe key.
- Assembles one
execute_tradeper maker leg, plus the residualmint_tokens/merge_tokensfor same-side matches, the top-leveled25519verify instruction, and ComputeBudget instructions. - Sets the CU budget to
simulateTransaction × 1.2, capped atMAX_CU. - Signs with the operator keypair (payer) and the taker key(s).
- Compiles to v0 + address lookup table when
ADDRESS_LOOKUP_TABLEis set — MINT/MERGE transactions exceed the 1232-byte legacy limit.
Confirmation
Section titled “Confirmation”A worker batches one getSignatureStatuses per tick across all pending
signatures. 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, fee ×2, up to RETRIES_LIMIT |
| Failed | UpdateTrade(FAILED) + OrderSizeUpdate to eject affected orders |
Idle past STUCK_TX_MAX_IDLE_TIME | Re-executed through the same path |
The pending map and de-dupe set are snapshotted to local JSON on every change. On restart, recovered transactions are re-confirmed, never re-sent.
The event
Section titled “The event”#[event]pub struct TradeSettled { pub market_id: u64, pub side: Side, // the maker's side pub making: u64, pub taking: u64, pub fee: u64, pub maker: Pubkey, pub taker: Pubkey, pub signer: Pubkey, // verified on-chain pub outcome_mint: Pubkey, // YES or NO — tells indexers which leg pub order_hash: [u8; 32], // the off-chain de-duping key}order_hash lets an indexer join on-chain settlements to off-chain orders
without guessing, and outcome_mint disambiguates which side of the market
moved.
execute_trade requires the market to be Open or Closed and before
end_time. The Closed allowance lets a match made just before close still
land — it is not a window for new trading.