Market Lifecycle
A SportsPerp market exists in one of several states. This page documents each state, the transitions between them, and the permissionless and admin operations that drive them.
The state diagram
initialize_market
β
βΌ
ββββββββββββββββ pause_market ββββββββββββββββ
β ACTIVE β βββββββββββββββββββββββββΊ β PAUSED β
β β βββββββββββββββββββββββββ β β
ββββββββ¬ββββββββ unpause_market ββββββββββββββββ
β
β (oracle stale > 72 hours)
β sunset_market (permissionless)
β
β β OR β
β
β force_settle_market (admin)
βΌ
ββββββββββββββββ
β SUNSETTING β positions can still close normally
ββββββββ¬ββββββββ
β (all positions closed, or admin calls settle)
βΌ
ββββββββββββββββ
β SETTLED β settle_position unwinds any remaining positions
β β at the settlement price
ββββββββββββββββState 1: Active
A market that is open for normal trading. paused = false, oracle is fresh (< 2h staleness), sunset_requested = false. This is the steady-state for the 68 launch markets.
All 27 instructions (open, close, place trigger, add/remove collateral, crank funding, run liquidations) are valid.
State 2: Paused
Admin-triggered emergency halt. paused = true.
- Allowed: close existing positions, cancel trigger orders, remove collateral, apply funding, execute liquidations on underwater positions
- Blocked: open new positions, place new trigger orders, add collateral
Triggered by:
- Admin pause (
pause_marketinstruction). Used for anything requiring caution: suspected oracle manipulation, discovery of a new risk scenario, coordinated response to a data-feed incident.
Unpaused by unpause_market (admin only). The market resumes from the same state β positions, triggers, insurance accounting all preserved.
Emergency pause is market-specific. Pausing market 3 has no effect on market 4. This lets admin narrowly target the affected surface without halting unrelated trading.
State 3: Sunsetting
A market heading toward formal settlement. Triggered by one of:
- Stale oracle sunset (permissionless). If the oracle hasnβt received an update in > 72 hours, anyone can call
sunset_market. This is the mechanism for retiring a player who has been dropped from the roster β the crank stops pushing updates, 72 hours pass, sunset is called. - Admin force-sunset.
force_settle_marketimmediately puts a market into sunsetting state without waiting for staleness. Used for planned market retirement (end-of-season player rotation, team relegation handling).
During sunsetting:
- Allowed: close existing positions, withdraw collateral, cancel triggers
- Blocked: open new positions, place triggers, add collateral
The market continues to accrue funding and remains liquidatable β existing traders can still be liquidated if they let their positions go underwater during sunset.
A settlement price is fixed at sunset time. Specifically, the current mark_price_ema at the moment sunset_market or force_settle_market was called becomes the canonical price for any residual positions.
State 4: Settled
All remaining positions have closed, or admin has force-settled the market. The market is effectively terminated.
- Allowed:
settle_positionβ permissionless instruction that closes any position still open in a settled market, at the fixed settlement price. This is a trader-protection mechanism: even if you forget about a position in a settled market, anyone can close it for you (at the settlement price, to your wallet). - Not allowed: everything else. The market is read-only after settlement except for
settle_position.
The 72-hour sunset rule
The 72-hour oracle-staleness trigger is specifically designed to handle roster rotations:
- A player is dropped from the weekly roster regeneration (
generate-roster.mjs). - The crank reads
roster.json, notices the player is no longer listed, stops pushing updates for that market. - 72 hours pass.
- A permissionless keeper (or any account) calls
sunset_market. The sunset price is locked. - Traders with open positions have a generous window to close voluntarily at any price during sunset.
- Any stragglers eventually get
settle_positionβd at the sunset price.
The 72-hour window is long enough that accidental stale pushes (e.g., a 2-hour network outage) never trigger sunset, but short enough that stale markets clear promptly.
How settlement price is chosen
sunset_market locks the settlement price to the last good oracle_price stored on the market β the price that was current immediately before staleness began. This is deliberate: staleness is detected by elapsed time, not by oracle content, so the most recent oracle value is still the freshest signal the protocol has of the marketβs true level.
| Signal | Used by sunset_market? | Notes |
|---|---|---|
Last oracle_price on the market | β yes | Frozen at sunset; no new oracle pushes are accepted afterwards |
| Raw composite mark | β no | Mark price blends in vAMM impact β too easily gamed in a thin market right before sunset |
| 150 s mark EMA | β no | Reasonable alternative, but the canonical sunset price is the last oracle_price for auditability and simplicity |
force_settle_market is different: the admin passes an explicit settlement_price parameter, which is written directly to the market. That instruction exists for emergencies (e.g., a market that needs to be retired ahead of the 72h window) and is the only path by which an admin can choose the settlement price.
Insurance fund during sunset
Sunsetting does not release insurance exposure associated with absorbed positions on that market. BackstopPosition accounts remain and are unwound against the sunset price during settlement. This ensures the fundβs balance is correctly adjusted regardless of market lifecycle state.
Admin capabilities and their guardrails
The admin keypair has several lifecycle instructions available, each with specific protections:
| Instruction | What it does | Guardrail |
|---|---|---|
pause_market / unpause_market | Toggle paused state | Cannot skip settlement or invalidate positions |
force_settle_market | Skip the 72 h wait and settle immediately at an admin-supplied settlement_price | Settlement is irreversible; the market is moved to Settled, no further oracle pushes are accepted, and remaining positions are closed at the supplied price by settle_position. Use only for genuine emergencies (e.g., retiring a market ahead of a roster rotation when waiting 72 h is unsafe). |
update_market_params | Adjust fees, max leverage, funding interval, oracle weights, vAMM impact factor | Cannot change market_id or settlement state |
configure_insurance | Adjust max_backstop_exposure and target_balance | Cannot pull insurance below the configured balance via this instruction |
There is no βcancel all positionsβ instruction. Admin can accelerate a market toward settlement and, in force_settle_market, supply the settlement price; in the normal sunset_market path the price is fixed to the last oracle_price and is not admin-chosen.
Further reading
- Markets β how the 68 initial markets are structured.
- Mark Price β the EMA that becomes the settlement price.
- Insurance Fund β how backstop exposures are handled through settlement.
- Oracle Design β the 2h staleness limit (vs the 72h sunset threshold).