Outcomes & Tokens
Every market has two SPL token mints, both owned by the markets program and
derived from the market PDA:
YES mint seeds = ["yes_mint", market_pda]NO mint seeds = ["no_mint", market_pda]Holding 1 YES token means: if this market resolves YES, the vault owes me 1 USDC. Holding 1 NO token is the same claim on the other outcome. Outcome tokens use 6 decimals, matching USDC, so raw amounts line up 1:1.
The identity that makes it work
Section titled “The identity that makes it work”1 YES + 1 NO == 1 USDCThis is enforced by the program, not by arbitrage. Two instructions implement it:
| Instruction | Effect |
|---|---|
mint_tokens | Deposit n USDC into the market vault → receive n YES and n NO |
merge_tokens | Return n YES and n NO → withdraw n USDC from the vault |
Neither is a trade. There’s no counterparty and no price — it’s a conversion at par, available to anyone at any time while the market is open.
Three consequences worth internalising:
- The vault is always fully collateralised. Every outcome token in existence was minted against a dollar sitting in the vault.
- Selling a side you don’t hold is possible. Mint a pair, keep the side you want, sell the other.
- The matching engine can create liquidity. Two traders who both want to buy opposite sides are matched by minting a fresh pair from their combined collateral. See Match Types.
Prices are complements
Section titled “Prices are complements”Because the pair is worth exactly $1, the two sides always price as complements:
price(NO) = 1 − price(YES)So there is no meaningful difference between the YES book and the NO book —
a bid for YES at $0.40 is an offer of NO at $0.60. The order manager keeps a
separate book per token ID, and the aggregated view at /v1/book is the one
to read if you want both sides of a market together.
The on-chain price accumulator stores everything in YES terms: a fill on the NO leg is recorded as its YES complement, so a single field describes the whole market. → CLOB Markets as Legs
Redeeming
Section titled “Redeeming”Once a market is Resolved, the winning token pays 1 USDC and the losing
token pays nothing.
markets::redeem winning outcome tokens → USDC, 1:1 from the market vaultmarkets::refund after a cancellation → collateral back, both sidesRedemption is a transaction you send yourself; it isn’t automatic and there
is no deadline enforced in the program. withdraw_residual lets an admin
sweep a market only once it is fully settled.
For a cancelled market, refund returns collateral against either side —
the market never had a winner, so both token types are claims on the same
pot.
What you hold, per product
Section titled “What you hold, per product”| CLOB position | Parlay | |
|---|---|---|
| Represented by | Fungible SPL tokens in your ATA | A Parlay PDA account |
| Transferable | Yes | No |
| Sellable before resolution | Yes, into the book | No |
| Partially exitable | Yes | No — all or nothing |
| Redeemed by | markets::redeem | high-market-parlay::claim_parlay |
A parlay is not a basket of outcome tokens. The parlay program never touches the YES/NO mints at all — it holds USDC and pays USDC. The legs are references to markets, priced once at create time.
Balances and allowances
Section titled “Balances and allowances”Two on-chain reads gate every order you place:
- ATA balance — do you hold what you’re offering? A BUY needs collateral; a SELL needs outcome tokens.
- Delegate allowance — has the maker approved the program’s delegate PDA to move those tokens at settlement?
Delegate PDA seeds = ["delegate", maker]The allowance is a standing SPL Approve. It’s what lets a maker post an
order and go offline — the maker never signs the settlement transaction; the
program signs for the delegate PDA instead. The order manager’s risk backend
checks both over JSON-RPC on insert, and reserves the maker amount so a
single balance can’t back two orders.
A missing allowance is a warning at insert time, not a rejection — but the settlement will fail on-chain. Set it before you trade:
cargo run -p delegate --quiet -- set --keypair ~/.config/solana/id.json --amount 1000000000