programs/pitchmarket/src/lib.rs and interface contract §3–§4. Program ID
(pinned in declare_id! and Anchor.toml, deployed on devnet):
Units
Money is integer micro-USDC (1 USDC = 1,000,000). Shares areu64; one share
redeems to 1 USDC if its outcome wins. Prices are integers 1..99 (¢).
Accounts (PDAs)
market_id: [u8;32] is a deterministic hash of (match_id, template_key).
order_hash = sha256(borsh(Order)) is the primary key for an order everywhere —
on-chain, in Postgres, and in the API.
Instructions
The settlement lifecycle —initialize_market, deposit, settle_match,
cancel_order, resolve_market, and redeem — is implemented and exercised end to end.
The combo instructions are typed stubs returning NotImplemented (see below).
initialize_market(market_id, oracle_tier, resolver_authority)
Creates the Market PDA, both outcome mints (mint authority = the market PDA), and the
collateral pool ATA. Called by the backend’s auto market creation, one per template per
fixture.
init_vault() / deposit(amount)
init_vault opens the per-user custody PDA (once per user). deposit moves real USDC
from the user’s wallet ATA into the vault-owned ATA — the only step where the user
signs a live transaction; all trading afterwards is silent, off-chain-signed orders
relayed by the operator. Deposit/redeem ATAs are created lazily via init_if_needed.
settle_match(taker, taker_sig, maker, maker_sig, match_type, fill_price, fill_size)
Settles one match from the off-chain engine. One transaction per fill (single taker,
single maker). In order it:
- Requires the market
Unresolvedand1 ≤ fill_price ≤ 99. - Verifies the caller-supplied outcome mints against the market’s pinned
yes_mint/no_mint(which applies depends on each order’soutcomefield). - Verifies both ed25519 order signatures via instructions-sysvar introspection — see Signed messages for the mandatory transaction layout.
- Applies fill accounting to both
OrderStatusPDAs: initialized on first touch withremaining = order.size; fails closed withOrderClosedif cancelled or fully filled,OverFilliffill_size > remaining. - Executes the money movement for the
match_type:- NORMAL — peer-to-peer swap at
fill_price, - MINT — combines both buyers’ USDC into the pool and mints a complete set,
- MERGE — burns a complete set, releases pooled collateral to both sellers.
- NORMAL — peer-to-peer swap at
cancel_order(order_hash)
Signed directly by the maker. Sets is_filled_or_cancelled; if the order was never
touched by settle_match, OrderStatus is created fresh so a later fill attempt fails
closed with OrderClosed regardless.
resolve_market(outcome) (tier-a only)
Requires oracle_tier == 0 and the signer to equal resolver_authority. Sets the
outcome to No (0) / Yes (1) / Void (2) and stamps resolved_at. Tiers (b)
challenge-window and (d) TxODDS-signed are designed (ADR 0005) but not implemented.
redeem(outcome, amount)
Requires the market resolved and outcome to be the winning side (any side if Void).
Burns amount shares from the caller’s vault-owned outcome ATA and transfers
amount × 1_000_000 micro-USDC from the market pool directly to the caller’s wallet
ATA, 1:1.
combo_accept(quote, taker_sig) · resolve_combo() — not implemented
Typed stubs returning NotImplemented. The designed semantics (ADR 0004 / interface
contract §4): combo_accept verifies the MM’s quote signature, expiry, and
QuoteStatus.!spent, pulls the stake from the taker and payout − stake from the MM
vault into a ComboEscrow, and marks the quote spent. resolve_combo reads the N leg
Market PDAs, computes the AND on-chain, and pays the escrow (VOID leg → refund both).
The backend runs combos off-chain behind an interface seam until these land.
Errors you’ll actually see
A build detail worth knowing: the
SettleMatch account context is Boxed because it
otherwise overflowed the 4 KB BPF stack frame by 64 bytes — a failure that only
surfaces at cargo build-sbf, never at cargo check.