🚧 SportsPerp is currently live on devnet. Mainnet target: before Jun 12, 2026 (World Cup kickoff).
TradingMarket Lifecycle

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_market instruction). 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_market immediately 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:

  1. A player is dropped from the weekly roster regeneration (generate-roster.mjs).
  2. The crank reads roster.json, notices the player is no longer listed, stops pushing updates for that market.
  3. 72 hours pass.
  4. A permissionless keeper (or any account) calls sunset_market. The sunset price is locked.
  5. Traders with open positions have a generous window to close voluntarily at any price during sunset.
  6. 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.

SignalUsed by sunset_market?Notes
Last oracle_price on the marketβœ… yesFrozen at sunset; no new oracle pushes are accepted afterwards
Raw composite mark❌ noMark price blends in vAMM impact β€” too easily gamed in a thin market right before sunset
150 s mark EMA❌ noReasonable 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:

InstructionWhat it doesGuardrail
pause_market / unpause_marketToggle paused stateCannot skip settlement or invalidate positions
force_settle_marketSkip the 72 h wait and settle immediately at an admin-supplied settlement_priceSettlement 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_paramsAdjust fees, max leverage, funding interval, oracle weights, vAMM impact factorCannot change market_id or settlement state
configure_insuranceAdjust max_backstop_exposure and target_balanceCannot 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).