OddMaki
Protocol

Price Markets

Pyth-powered price markets that resolve automatically from an on-chain 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 on-chain. 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

Two Modes

ModeStrikeResolves YES When
Up/DownstrikePrice = 0finalPrice > capturedOpenPrice
StrikestrikePrice > 0finalPrice >= strikePrice

In Up/Down mode, the protocol captures the current Pyth price at creation and stores it as openTime / openPrice. In Strike mode, the creator picks the target and no opening snapshot is needed.

Creating a Price Market

const tx = await client.priceMarket.createPyth({
  venueId: 1n,
  pythFeedId: '0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', // BTC/USD
  closeTime: BigInt(Math.floor(Date.now() / 1000) + 86400), // +24h
  tickSize: parseEther('0.01'),
  collateralToken: USDC_ADDRESS,
  question: {
    title: 'Will BTC close above its open price in 24h?',
    description: 'Resolves from Pyth BTC/USD at the close timestamp.',
  },
  // strikePrice: 0n,          // omit for Up/Down; set for Strike mode
  // outcomes: ['Up', 'Down'], // default
  // resolutionWindow: 60n,    // Pyth publish-time tolerance (default 60s)
  // tags: ['crypto', 'btc'],
});

closeTime must be 300s – 86400s from now (5 minutes to 24 hours). For Up/Down mode the SDK fetches a current Pyth update from Hermes and pays the small ETH update fee; Strike mode skips both.

Resolution

Anyone can trigger resolution after closeTime:

const tx = await client.priceMarket.resolvePyth(marketId);

The SDK fetches the historical Pyth price at exactly closeTime from the Hermes API, pays the update fee (ETH), and submits it to PythResolutionFacet.resolvePriceMarketPyth. The contract:

  1. Verifies the update timestamp is within resolutionWindow of closeTime
  2. Compares finalPrice against the captured open (or explicit strike)
  3. Reports the payout vector directly to the CTF — no UMA assertion involved

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);    // captured open timestamp
console.log(pm.closeTime);   // resolution time
console.log(pm.strikePrice); // 0n for Up/Down
console.log(pm.resolved);    // true once resolvePyth succeeds

Data Model

struct PriceMarket {
  bytes32 feedId;         // Pyth feed id
  FeedProvider feedProvider; // PYTH = 0, CHAINLINK = 1 (reserved)
  uint256 openTime;       // when opening price was captured
  uint256 closeTime;      // target resolution time
  int32   priceExpo;      // Pyth exponent (e.g., -8)
  int64   finalPrice;     // set on resolution
  uint256 resolutionWindow; // publish-time tolerance around closeTime
  bool    resolved;
  int64   strikePrice;    // 0 → Up/Down; >0 → Strike
}

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 path is resolvePriceMarketPyth. Conversely, binary and grouped markets cannot be resolved via Pyth.

What's Next