Market Lifecycle
Every Fora prediction market moves through a deterministic state machine. Each state is an integer on-chain (market_state field of PredictionMarketStateFixed); each transition is a public instruction anyone can crank.
The states
| Value | State | Trading | Notes |
|---|---|---|---|
| 0 | INITIALIZED |
No | Market created. Awaiting authority activation. |
| 1 | ACTIVE |
Yes | Trading is open. Orders, swaps, cross-market matching all live. |
| 2 | PAUSED |
No | Authority-paused. Existing orders frozen; no new trades. Returns to ACTIVE. |
| 3 | DETERMINED |
No | Outcome decided. Winner is recorded in winning_outcome. |
| 4 | CLOSED |
No | Trading is permanently closed. Order books frozen. |
| 5 | SETTLED |
No | Winners can redeem winning tokens for USDC. Losers' tokens are worthless. |
| 6 | CLEANED |
No | Manifest seats and intermediate state torn down. Collateral fully drained. |
| 7 | ARCHIVED |
No | Terminal state. Account closed and rent reclaimed. |
The transitions
INITIALIZED ──→ ACTIVE ⇄ PAUSED ──→ DETERMINED ──→ CLOSED ──→ SETTLED ──→ CLEANED ──→ ARCHIVED
│
└──────────── (authority cancel, no trading) ────────────→ CLEANED ──→ ARCHIVED
Each arrow corresponds to an on-chain instruction:
INITIALIZED → ACTIVE—activate_market. Authority-only.ACTIVE ⇄ PAUSED—pause_market/activate_market. Authority-only. Used during emergencies or oracle disputes.ACTIVE | PAUSED → DETERMINED—resolve_market(manual / Kalshi oracle / Vibe TWAP). Anyone can crank a Vibe or Kalshi resolution after the trigger condition; manual requires the authority.DETERMINED → CLOSED—close_market. Permissionless once determined.CLOSED → SETTLED—batch_settle_prediction_market. Permissionless. Cranks user redemptions in batches.SETTLED → CLEANED— cleanup instructions. Permissionless. Tears down Manifest seats, drains the collateral vault.CLEANED → ARCHIVED—archive_market. Permissionless. Closes the account.
The shortcut path INITIALIZED → CLEANED → ARCHIVED exists for markets that are cancelled before any trading occurs. The authority calls a cancel instruction, which fast-paths to CLEANED, then anyone can archive.
Resolution mechanisms
When a market reaches DETERMINED, its winning_outcome is set:
| Value | Outcome | Meaning |
|---|---|---|
| 0 | UNRESOLVED |
Default. Set during INITIALIZED/ACTIVE/PAUSED. |
| 1 | YES |
YES tokens redeem at $1; NO tokens worthless. |
| 2 | NO |
NO tokens redeem at $1; YES tokens worthless. |
| 3 | SPLIT |
Both YES and NO tokens redeem at $0.50. Used for true ties or insufficient TWAP data. |
| 4 | TWAP |
Vibe outcome. YES redeems at TWAP / 10000 of $1; NO redeems at (10000 - TWAP) / 10000. Computed TWAP in basis points stored in last_price. |
Manual
The market authority calls resolve_market with the winning outcome. The simplest mechanism — used for markets where Fora chooses the resolution criteria.
Kalshi oracle
Markets whose ticker matches a Kalshi event are resolved automatically by the Kalshi Oracle service, which subscribes to Kalshi's lifecycle WebSocket and submits a resolution instruction when the upstream Kalshi market settles. Anyone can crank the resolution once Kalshi has emitted the settlement signal.
Vibe
The market is its own oracle. After expiry_slot, the on-chain TWAP from the YES book is computed and stored as last_price (in basis points). The market resolves to TWAP outcome, and YES tokens redeem at TWAP / 10000 while NO redeems at the complement.
A Vibe market requires at least GIRARD_MIN_FILLS = 10 distinct-slot fills to resolve via TWAP. With fewer fills the market resolves to SPLIT (both sides at $0.50) — there's not enough trading history for a meaningful TWAP. This protects against last-minute manipulation by a thin order book.
See the glossary entry for the design intuition behind Vibe markets.
Why it's a state machine and not a status flag
Each state is a contract. When you query /markets/{ticker}/status and see SETTLED, you know:
- Trading is permanently over.
- The outcome is fixed.
- The collateral vault still holds funds (until CLEANED).
- Token holders can call settlement instructions to redeem.
The state machine is enforced on-chain. No instruction can transition out of order. No state can be skipped (except the cancel shortcut). Every market that enters INITIALIZED will, eventually, reach ARCHIVED — or sit in an intermediate state with funds fully accounted for.
Source
- State machine constants:
programs/fora-markets/src/state/prediction_market_state.rs—pub mod market_stateandpub mod winning_outcome. - Transition processors:
programs/fora-markets/src/program/processor/— one file per instruction. - Crank service:
market-lifecycle-processor/— the event-driven service that automates determine → close → settle → archive in production.