Market Lifecycle
Signals v1 markets run on a timestamped state machine. Every market has a trading window, a settlement window keyed by a fixed settlement timestamp, and a claim gate. The point of the lifecycle is not presentation; it is a hard on-chain schedule that makes timing rules explicit and makes invalid actions revert.
The lifecycle is easiest to understand as two coupled systems:
- A trading schedule keyed by
startTimestampandendTimestamp. - A settlement schedule keyed by
settlementTimestamp(denoted ) plus global window parameters.
The trading schedule decides when positions can change. The settlement schedule decides when oracle samples are admissible, when settlement can be finalized, and when claims can be executed.
State machine at a glance

Figure: settlement finality has a primary path (candidate-based) and a secondary path (manual value after explicit failure).
Pre-Tset -> SettlementOpen -> PendingOps -> Settled
\\-> FailedPendingManual -> Settled
The settlement schedule is exposed by a derived helper (getMarketState) with
labels:
Trading(pre- stage)SettlementOpenPendingOpsFinalizedPrimary(timeline stage where primary finalization is permitted)FailedPendingManual
In the current release, the helper does not reliably distinguish primary vs secondary settlement after a market is settled. The distinction is surfaced by events emitted at finalization, not by the derived label.
Time model
Each market defines three timestamps:
startTimestamp(): trading becomes valid.endTimestamp(): trading closes (trades revert after this).settlementTimestamp(): settlement schedule anchor and day key.
Creation enforces the relationship:
Settlement uses three global window lengths:
- SettlementOpen window (sample submissions)
- PendingOps window (finalize or mark failed)
- Claim delay with the enforced relationship
The four derived boundaries returned by getSettlementWindows are:
The claim gate is time-based, but claimability is still conditional on finalization. A claim can only execute after:
- the market has been finalized on-chain (
market.settled == true), and - the block timestamp is at or after
claimOpenTime.
Lifecycle semantics
This section treats the lifecycle as "what transitions are admitted at a given time", ignoring UI flow. The contract enforces these rules through reverts.
Creation and seeding
A market is created with its tick grid, timestamps, depth , a fee policy,
and a prior encoded as seed factors stored in a SeedData contract. At creation
time, the market's pricing tree is initialized and the prior is recorded, but
the market is not yet tradable.
Seeding is performed in chunks via seedNextChunks. Each call applies a slice of
the prior factors to the segment tree and advances a cursor. When the cursor
reaches the market's bin count, market.isSeeded flips to true.
Trading entrypoints require market.isSeeded == true. This makes seeding an
explicit prerequisite rather than an implicit background step.
Trading window
Open, increase, decrease, and close share the same trading gate:
- market exists and has not been settled
- market is seeded
The important detail is that the settlement schedule does not replace the
trading schedule. The derived settlement label Trading means "pre-",
not "trades are currently valid". Trade validity is always checked against
and at the point of execution.
SettlementOpen window
SettlementOpen is the window where oracle samples are admissible for candidate selection:
During this window, settlement samples can be submitted permissionlessly. Each sample contains:
- a scaled settlement value, and
- the oracle's price timestamp (seconds).
The oracle module maintains a single candidate per market. A new sample replaces the candidate only if it is strictly closer to in absolute distance, with an explicit tie-break rule toward the past:
Samples outside the oracle timing constraints (future tolerance and maximum distance) revert and do not update the candidate.
PendingOps window and failure flag
PendingOps begins when SettlementOpen ends:
During PendingOps, settlement is decided by privileged transactions:
- primary settlement can be finalized using the current candidate, or
- the market can be explicitly marked as failed.
Marking a market as failed clears any candidate and flips market.failed to true.
The failure flag indicates that primary settlement should not be used for that
market day.
After PendingOps ends, the allowed failure behavior tightens: failure can only be marked if there was no candidate at all. This separates two classes of outcomes:
- a candidate existed and was not rejected during the decision window, or
- no candidate existed and the market needs a manual settlement value.
Finalization
Finalization is the act that makes the market settled. Both primary and secondary finalization write the same end object:
- a scaled settlement value,
- a settlement tick on the market's grid, and
- a
settlementFinalizedAttimestamp (when the finalization transaction was mined).
Tick mapping clamps and aligns the settlement tick to the market's grid:
- convert value to a raw tick by dividing by ,
- clamp to ,
- align to
tickSpacingfromminTick.
Finalization also starts the day boundary accounting work. It computes maker-side P&L, records gross fees, escrows a payout reserve for open positions, and marks the market as resolved for the daily batch keyed by .
In addition, settlement snapshot work can be chunked. A settled market with a
large number of positions can request settlement chunks. Each request emits
SettlementChunkRequested events up to a bounded per-transaction limit and
advances snapshotChunkCursor until snapshotChunksDone is true.
Claims and post-settlement behavior
Claiming a payout is not a view-level computation. It is a state transition that burns the position token and transfers ctUSD from escrow.
Two gates apply:
- The market must already be settled.
- The block timestamp must be at or after .
Formally:
Claims draw from the payout reserve escrowing performed at finalization. This is an accounting constraint: payouts are not sourced from future maker value or from later batches. The reserve is decremented on each claim, and the system reverts if a claim would exceed the remaining reserve.
Edge cases
This list focuses on boundary conditions that are easy to miss when reading the state machine as a UI flow.
- Trading end at the boundary: market creation allows . In that boundary case, a block timestamp equal to is inside the trade gate () and inside SettlementOpen (). Most deployments avoid this equality, but the code permits it.
- No candidate: if no admissible oracle sample is submitted during SettlementOpen, the candidate is missing. Primary finalization reverts in this case. After PendingOps ends, failure can still be marked explicitly, which enables secondary finalization.
- Candidate rejected: if a candidate exists but failure is marked during PendingOps, the candidate is discarded and primary finalization is no longer available for that market day.
- Time passed without finalization:
claimOpenTimeis derived from timestamps, but claims remain impossible until finalization writesmarket.settled = true. This is a liveness property: timestamps define admissibility, and mined transactions realize the transition. - Primary vs secondary observability: primary and secondary finalization emit distinct events. For settled markets, those events are the reliable surface for the finalization path.
Related sections: