Price Markets
Pyth-powered price markets that resolve automatically from an onchain price feed — no UMA bond, no assertion, no dispute path.
Price markets ask "is the price above X at time T?" and resolve deterministically from a price oracle. OddMaki integrates Pyth Network as the first feed provider; the architecture is provider-agnostic and reserves room for additional providers (e.g., Chainlink) as separate resolution facets.
Why Price Markets
The UMA path is ideal for discretionary questions ("Will Team A win?") but overkill when the answer is already onchain. Price markets use the PriceMarketFacet + PythResolutionFacet pair to:
- Skip the UMA bond, liveness window, and dispute flow entirely
- Resolve at the known close time — no waiting for a human asserter
- Let anyone permissionlessly trigger resolution with a single call
Three Shapes, One Entry Point
A single createPriceMarketPyth function covers all three:
| Shape | strikePrice | openTime | What it asks |
|---|---|---|---|
| Immediate Up/Down | 0 | 0 | Will the price be higher at closeTime than at creation time? |
| Scheduled Up/Down | 0 | future unix ts | Will the price be higher at closeTime than at the scheduled openTime? |
| Explicit Strike | > 0 | (ignored) | Will the price be ≥ strikePrice at closeTime? |
The protocol enforces only one duration invariant: closeTime > effectiveOpenTime. No five-minute floor, no 24-hour ceiling — venues can be as short or long as they want.
Deferred open-price capture
For Up/Down markets the open price is captured at resolution time, not at creation. Creation never touches Pyth — no VAA submission, no Pyth fee. When the resolver fires resolvePriceMarketPyth, the facet fetches a Hermes VAA published in [openTime, openTime + resolutionWindow], stores it as the strike, and immediately compares against the close VAA in the same transaction.
This decoupling is what enables scheduled markets. The bot (or a UI) can pre-list "BTC Up/Down for 5–6 pm UTC" hours in advance; the open price is captured at 5 pm via Hermes when resolution fires at 6 pm — not at the moment the market was created.
Explicit-strike markets keep the old semantics: the strike is set at creation and never changes.
Creating a Price Market
const tx = await client.priceMarket.createPyth({
venueId: 1n,
pythFeedId: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', // BTC/USD
closeTime: BigInt(Math.floor(Date.now() / 1000) + 3600), // +1h
tickSize: parseEther('0.01'),
collateralToken: USDC_ADDRESS,
question: {
title: 'BTC Up/Down for the next hour?',
description: 'Resolves Up if Pyth BTC/USD at the close timestamp is higher than at creation.',
},
// openTime: 0n, // 0 = immediate; > now = scheduled
// strikePrice: 0n, // 0 = Up/Down; > 0 = Explicit Strike (Above/Below)
// outcomes: ['Up', 'Down'],
// resolutionWindow: 60n, // Hermes publish-time tolerance (default 60s, max 300s)
// tags: ['crypto', 'btc'],
});The call is non-payable — no ETH required. The only onchain cost at creation is the venue's marketCreationFee (if set) in USDC.
pythFeedId accepts any feed ID from the Pyth price feed catalog.
Scheduling a market
To pre-list a market that opens at a specific future time, set openTime:
const fivePmUtc = BigInt(Math.floor(Date.parse('2026-06-01T17:00:00Z') / 1000));
const sixPmUtc = fivePmUtc + 3600n;
await client.priceMarket.createPyth({
venueId: 1n,
pythFeedId: BTC_USD_FEED,
openTime: fivePmUtc, // open price captured here at resolution
closeTime: sixPmUtc, // close price captured here at resolution
tickSize: parseEther('0.01'),
collateralToken: USDC_ADDRESS,
question: { title: 'BTC Up/Down 17:00–18:00 UTC', description: '…' },
});The market is tradable from the moment of creation. Between creation and openTime, the strike is unknown — UIs display a "pending" state or a live Pyth spot as a projection (see Showing the projected strike below).
Resolution
Anyone can trigger resolution after closeTime:
const tx = await client.priceMarket.resolvePyth(marketId);The SDK reads the market's openTime, closeTime, and strikePrice, then:
- For explicit-strike markets: fetches one Hermes VAA in the close window
- For Up/Down markets (deferred): fetches two VAAs — one in the open window, one in the close window — and submits them together
The contract picks the earliest in-range VAA in each window (anti-cherry-pick rule), compares finalPrice against strikePrice, and reports the payout vector directly to the CTF.
The resolver pays the Pyth update fee for the submitted VAAs (typically a few wei). Refunds excess via refundExcess.
Checking Resolvability
const canResolve = await client.priceMarket.canResolve(marketId);
const pm = await client.priceMarket.get(marketId);
console.log(pm.feedId); // Pyth feed hex id
console.log(pm.openTime); // effective open timestamp
console.log(pm.closeTime); // resolution time
console.log(pm.strikePrice); // 0 until resolution for deferred Up/Down
console.log(pm.openPriceTime); // 0 until resolution for deferred Up/Down
console.log(pm.resolved); // true once resolvePyth or markInvalid succeedsShowing the projected strike
For deferred markets between openTime and resolution, the onchain strikePrice is still 0. The SDK provides a helper that fetches the same Hermes VAA the contract will use at resolution, so the UI can render the strike before resolution lands:
const projected = await client.priceMarket.fetchProjectedOpenPrice(marketId);
if (projected === null) {
// Resolved, explicit-strike, or not yet past openTime — read pm.strikePrice instead.
} else {
console.log(projected.price); // bigint, in Pyth feed-native units (use priceExpo)
console.log(projected.publishTime); // unix seconds of the VAA
console.log(projected.canonical); // true once openTime + window has elapsed —
// the value will not change before resolution
}Use canonical: false as the trigger to render a "pending" hint; canonical: true means the projection equals the onchain strike that will be set when resolution fires.
Invalidation Backstop
If Hermes has no VAA in the open or close window (feed outage, deprecated feed, market closed in an off-hours metals/equity gap), resolvePriceMarketPyth reverts. After closeTime + 7 days (the protocol grace period), anyone can call:
const tx = await client.priceMarket.markInvalid(marketId);This reports payouts [1, 1] to the CTF — every YES and every NO position redeems for half the underlying collateral. Traders recover their cost basis pro-rata; nobody wins or loses.
The grace period is intentional: 7 days is enough for any temporary Hermes outage to resolve naturally. Use markInvalid only as a last-resort cleanup.
Data Model
struct PriceMarket {
bytes32 feedId; // Pyth feed id
FeedProvider feedProvider; // PYTH = 0, CHAINLINK = 1 (reserved)
uint256 openTime; // effective open time (block.timestamp at creation for immediate)
uint256 closeTime; // target resolution time
int32 priceExpo; // Pyth exponent (e.g., -8)
int64 finalPrice; // set on resolution
uint256 resolutionWindow; // Hermes publish-time tolerance (default 60s, max 300s)
bool resolved;
int64 strikePrice; // 0 for deferred Up/Down (until resolution); > 0 for Explicit Strike
uint256 openPriceTime; // VAA publishTime of the captured open price (0 until resolution for deferred)
}Storage lives in LibPriceMarketStorage under its own namespaced slot, separate from the UMA resolution storage used by binary markets.
Trading
Price markets trade exactly like binary markets — same orderbook, same settlement paths, same outcome tokens. Outcomes default to ["Up", "Down"] but can be overridden (e.g., ["Above", "Below"] for Strike mode).
Holders of the winning side redeem through the CTF after resolution, just like UMA-resolved markets.
Interplay With UMA
Price markets have no UMA assertion, no oracle lock, and no dispute window. ResolutionFacet.assertMarketOutcome reverts for price markets — the only resolution paths are resolvePriceMarketPyth and markPriceMarketInvalid. Conversely, binary and grouped markets cannot be resolved via Pyth.
What's Next
- Creating Markets → — full parameter reference, including scheduling
- Resolution → — how both resolution paths end in a CTF payout vector
- Orderbook → — CLOB mechanics shared across all market types
Creating Markets
How to create binary markets, market groups, and Pyth price markets — UI flows in the OddMaki App and the Venue Starter, plus SDK reference.
Resolution
How markets resolve — UMA's Optimistic Oracle for discretionary markets and Pyth for price markets. Assertion, dispute, settlement, reporting, and redemption.