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.
marketVolume — per currency#
| unit | base units of the market's own currency |
|---|---|
| derivation | Σ Staked.amountCredited, grouped by ParimutuelMarket.stakeToken |
| source | ParimutuelMarket.Staked(address indexed user, bool indexed side, uint256 amountRequested, uint256 amountCredited, uint256 newTotalYes, uint256 newTotalNo) |
| freshness | Indexed. Carries asOfBlock. |
| coverage | FULL, 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#
| unit | base units of the market's own currency |
|---|---|
| derivation | Σ ParimutuelMarket.totalPool() = totalYes + totalNo, over markets the oracle reports as resolved == false |
| source | ParimutuelMarket.totalPool() |
| freshness | Live chain read, exact at a block. |
| coverage | FULL, per currency |
The fee is not deducted. It is not held separately — it comes out of the same pot at settlement.
| id | Read |
|---|---|
stakedYes / stakedNo | totalYes() / totalNo() |
payoutPool | pot − pot×feeBps/10_000 − pot×marketingBps/10_000. Both truncate independently and the shaved dust stays in the contract. |
outstandingObligation | totalPool() − (totalPaidOut + feeSweptAmount() + marketingFeeSweptAmount()), floored at 0 |
heldBalance | address(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#
| Event | Carries |
|---|---|
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.MarketRegistered | The 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#
| State | outcomeOf() | What holders get |
|---|---|---|
| Settled, both sides staked | resolved=true, voided=false | Winners share the payout pool; fees taken |
| Settled, one side empty | resolved=true, voided=false | Everyone refunded at par, zero fee — this is not a void |
| Voided | resolved=true, voided=true | Everyone 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:
| id | metric | Read |
|---|---|---|
| 0 | GRADUATED | 1 when complete() || graduated() — not graduated() alone |
| 1 | CURVE_COMPLETE | complete() |
| 2 | NET_RAISED_WEI | virtualEthReserve() − initialVirtualEthReserve(), saturating |
| 3 | TOKENS_SOLD_BASE_UNITS | tokensSold() |
| 4 | PROGRESS_BPS | Forced to 10000 once graduated |
| 5 | CURVE_FDV_WEI | fdvWei() |
| 6 | CREATOR_LAUNCH_COUNT | CreatorRegistry.statsOf().launches |
| 7 | CREATOR_GRADUATED_COUNT | CreatorRegistry.statsOf().graduated |
| 8 | CREATOR_TIER | CreatorRegistry.tierOf() |
| 9 | SUPPLY_BURNED | 1e27 − token.totalSupply(), graduated launches only |
| 10 | CURVE_TRADE_FEES_WEI | TradeFeeSplitter.totalReceivedFrom(curve) + curve.tradeFeesAccrued(), ETH launches only |
| 11 | PROTOCOL_FEES_METERED_WEI | ProtocolFeeSplitter.totalProtocolFeesReceived() |
| Id | Name |
|---|---|
| 0 | EQ |
| 1 | NEQ |
| 2 | GT |
| 3 | GTE |
| 4 | LT |
| 5 | LTE |
| Id | Name |
|---|---|
| 0 | lifecycle |
| 1 | curve |
| 2 | creator |
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.