Docs·Metrics reference

Metrics reference

Prediction markets

Per-currency volume and open interest, the three end states a market can reach, and the predicate that makes a question machine-readable.

Version
1.2
Updated
2026-09-28
Source
Metric definitions v1.2

marketVolume — per currency#

marketVolume
unitbase units of the market's own currency
derivationΣ Staked.amountCredited, grouped by ParimutuelMarket.stakeToken
sourceParimutuelMarket.Staked(address indexed user, bool indexed side, uint256 amountRequested, uint256 amountCredited, uint256 newTotalYes, uint256 newTotalNo)
freshnessIndexed. Carries asOfBlock.
coverageFULL, per currency. Never summed across currencies.

There is no unstake, so for a parimutuel market lifetime volume and current stake coincide until settlement. Say so rather than relying on it silently — a consumer computing “volume minus open interest” on that assumption gets zero, and would not know whether that was a fact or a bug.

openInterest — per currency#

openInterest
unitbase units of the market's own currency
derivationΣ ParimutuelMarket.totalPool() = totalYes + totalNo, over markets the oracle reports as resolved == false
sourceParimutuelMarket.totalPool()
freshnessLive chain read, exact at a block.
coverageFULL, per currency

The fee is not deducted. It is not held separately — it comes out of the same pot at settlement.

The related quantities, all exact reads
idRead
stakedYes / stakedNototalYes() / totalNo()
payoutPoolpot − pot×feeBps/10_000 − pot×marketingBps/10_000. Both truncate independently and the shaved dust stays in the contract.
outstandingObligationtotalPool() − (totalPaidOut + feeSweptAmount() + marketingFeeSweptAmount()), floored at 0
heldBalanceaddress(this).balance, or IERC20(stakeToken).balanceOf(market)

Refund mode zeroes both fees. When voided || totalYes == 0 || totalNo == 0, entitlement is userYes + userNo, the protocol fee is 0 and the marketing fee is 0. A one-sided market therefore has openInterest == payoutPool.

Settlements and outcomes#

The three events an outcome can produce
EventCarries
LaunchOutcomeOracle.MarketSettled(bytes32 indexed marketId, bool indexed outcome, uint256 observedValue, uint256 threshold, address indexed settler, uint64 settledAt, uint64 deadline) — the outcome, the value it was judged on, the threshold, and who settled
LaunchOutcomeOracle.MarketVoided(bytes32 indexed marketId, string reason, address indexed caller). "EXPIRED" — permissionless, after deadline + settlementWindow. "FACTORY" — the factory’s emergency hatch
LaunchOutcomeOracle.MarketRegisteredThe question: family, metric, subject, comparator, threshold, deadline, settlementWindow, curve

No settlement fee, no ETH to the settler. settle() transfers nothing. The settler earns THD per settlement from the settlers bucket, published as settlerRewardThd on /api/v1/settlements/rewards.

Three end states, not two#

A dashboard that collapses these is wrong
StateoutcomeOf()What holders get
Settled, both sides stakedresolved=true, voided=falseWinners share the payout pool; fees taken
Settled, one side emptyresolved=true, voided=falseEveryone refunded at par, zero fee — this is not a void
Voidedresolved=true, voided=trueEveryone refunded at par, zero fee

resolved is true for a void — that is what makes refunds claimable. Reading it as “an outcome was decided” would report a voided market as settled.

The predicate — what a market is about#

observedValue is produced by the oracle reading one metric view. The ids, so a published question is machine-readable:

idmetricRead
0GRADUATED1 when complete() || graduated() — not graduated() alone
1CURVE_COMPLETEcomplete()
2NET_RAISED_WEIvirtualEthReserve() − initialVirtualEthReserve(), saturating
3TOKENS_SOLD_BASE_UNITStokensSold()
4PROGRESS_BPSForced to 10000 once graduated
5CURVE_FDV_WEIfdvWei()
6CREATOR_LAUNCH_COUNTCreatorRegistry.statsOf().launches
7CREATOR_GRADUATED_COUNTCreatorRegistry.statsOf().graduated
8CREATOR_TIERCreatorRegistry.tierOf()
9SUPPLY_BURNED1e27 − token.totalSupply(), graduated launches only
10CURVE_TRADE_FEES_WEITradeFeeSplitter.totalReceivedFrom(curve) + curve.tradeFeesAccrued(), ETH launches only
11PROTOCOL_FEES_METERED_WEIProtocolFeeSplitter.totalProtocolFeesReceived()
Comparators
IdName
0EQ
1NEQ
2GT
3GTE
4LT
5LTE
Families
IdName
0lifecycle
1curve
2creator

The metric, not the family, is what pins down the question.

marketId = keccak256(abi.encode(block.chainid, address(factory), predicate, currency, salt))

abi.encode, not abi.encodePacked, and the same value is the CREATE2 salt — see ABIs and clone templates.