Skip to content

Oracle-Settled Open Challenges (spec 041)

Open challenges (spec 024) with Polymarket as the settlement source: the creator links a live Polymarket market at creation, picks a side, and shares the four-word claim code; the taker gets the opposite side and the wager settles automatically via autoResolveFromPolymarket. No contract changes — createOpenWager already validates oracle linkage on-chain (_checkOracleLinkage: non-zero condition id, adapter configured, condition unresolved), and the post-accept resolution path is identical to named-opponent oracle wagers. The lifecycle proof lives in test/integration/oracle/WagerRegistry_PolymarketOpenChallenge.test.js.

Frontend pieces

Piece File Notes
Create flow frontend/src/components/fairwins/OracleOpenChallengeModal.jsx Picker-first: PolymarketBrowser (inline) → side picker → stake → derived timeline. Gated on capabilities.polymarketSidebets + isOracleModelExposed(Polymarket).
Claimant view frontend/src/components/fairwins/TakeChallengePanel.jsx Bet summary (stake/payout for all open challenges) + oracle block: question, taker's side, "Settled automatically by Polymarket" badge, live context, accept gate.
Shared result panel frontend/src/components/fairwins/ClaimCodeResultPanel.jsx Extracted from OpenChallengeModal — one code/QR/deep-link/vault-backup UX for both create flows. Don't fork it.
Timeline derivation frontend/src/lib/openChallenge/oracleTimeline.js Pure. accept = min(marketEnd, now+30d−1h), resolve = min(marketEnd+7d, now+180d−1h), 1h minimum lead. Derived values can never fail the contract's _checkDeadlines.
Live market fetch frontend/src/hooks/usePolymarketMarket.js One Gamma market by condition_ids; normalized via normaliseGammaMarket; errors degrade, never gate.

The sealed oracle terms block

useOpenChallengeCreate seals (code-keyed envelope, spec 024 format unchanged):

{
  "description": "…human-readable, names market + side + Polymarket…",
  "createdAt": "<ISO>",
  "oracle": {
    "source": "polymarket", "conditionId": "0x…", "question": "…",
    "outcomes": ["Yes", "No"], "creatorSide": 0, "endDate": "<ISO>", "slug": "…"
  }
}

It exists so a code-holder can read the bet when the Gamma API is down (FR-014). Never put the market reference in on-chain plaintext — it would break the spec-024 indistinguishability of code-gated challenges.

Chain-authoritative display rule (Constitution III)

The on-chain wager fields — resolutionType, polymarketConditionId, creatorIsYes — are the only authority for what is bet. The sealed oracle block is display metadata: the claimant view cross-checks terms.oracle.conditionId against wager.polymarketConditionId and, on mismatch, flags the stored description and falls back to live data. Side labels index the adapter's outcome ordering: index 0 = YES = creatorIsYes: true; the taker always holds the opposite.

Accept gating is app-level honesty (the contract cannot query the oracle at accept): a closed market warns; only a positively known public outcome (closed + ~certain price) disables accept. Unreachable live data discloses itself and never blocks.