OddMaki
Markets

Resolution

How markets resolve — UMA's Optimistic Oracle for discretionary markets and Pyth for price markets. Assertion, dispute, settlement, reporting, and redemption.

OddMaki has two resolution paths:

  • UMA Optimistic Oracle V3 — used for binary markets and market groups. Anyone can assert an outcome; anyone can dispute; disputes escalate to UMA's DVM.
  • Pyth — used for price markets. Anyone can trigger resolution after the close time, and the outcome is deterministic from the Pyth historical price.

Both paths end the same way: a payout vector is reported to the CTF and winning token holders redeem for collateral.

Where to drive resolution

Resolution steps (assert, settle, report, redeem, Pyth resolve) are permissionless — anyone can call them — so venue operators rarely need to automate them. Traders and keepers typically act through the UI; the SDK is there when you want to script it.

  • Venue Starter — every market page renders a Resolution Panel with buttons for the current phase (assert an outcome, settle an expired assertion, report to the CTF). Price markets render a dedicated Auto-resolve button instead. Once resolved, a Redeem panel lets winning holders claim collateral. This is where most end-user resolution happens.
  • OddMaki App — the market detail view exposes the same resolution actions for the venue operator and any other wallet.
  • SDK — the client.uma and client.priceMarket modules expose the same operations for scripted flows or integration into custom UIs.

The sections below document the SDK calls. Both UI surfaces wire directly to these same functions.

UMA Resolution Lifecycle

Under the hood, UMA resolution progresses through these phases. They are inferred from activeAssertionId, AssertionData.settled, and the market's resolved flag rather than an explicit enum:

PhaseDescriptionWho Acts
ACTIVE_NO_ASSERTIONMarket is active, no assertion existsAnyone can assert
ASSERTION_PENDINGAssertion posted, liveness window runningAnyone can dispute via UMA
ASSERTION_EXPIREDLiveness expired without disputeAnyone can settle
SETTLED_NOT_REPORTEDUMA confirmed the assertionAnyone can report to CTF
RESOLVEDPayouts recorded in CTFWinning token holders can redeem

Making an Assertion

Anyone can assert a market's outcome by calling assertMarketOutcome. The asserter posts a bond — if the assertion goes undisputed, the bond is returned plus any reward. If disputed and wrong, the bond is lost.

UI (Venue Starter or OddMaki App): on the market's Resolution Panel, pick the outcome and click Assert — the app handles the bond-token approval and assertion in one flow.

SDK:

const tx = await client.uma.assertMarketOutcome({
  marketId: 42n,
  outcome: 'Yes',
  autoApprove: true, // auto-approves bond token if needed
});

Preconditions:

  • No active assertion on this market
  • Caller must hold the effective bond (USDC)
  • USDC approved to the Diamond contract (autoApprove: true handles this)

The outcome string must exactly match one of the market's defined outcomes ("Yes", "No", or the market group's specific outcome labels).

Challenge Period

During the liveness window, anyone can dispute an assertion by posting a counter-bond directly through UMA's Optimistic Oracle V3. Disputes are not handled by OddMaki contracts — they go through UMA's standard dispute mechanism.

  1. Disputer calls disputeAssertion() on the UMA Oracle (not the Diamond)
  2. UMA escalates the dispute to its DVM for human arbitration
  3. DVM voters resolve the dispute
  4. If upheld: assertion accepted, disputer loses bond
  5. If rejected: assertion rejected, asserter loses bond, a new assertion can be submitted

You can check assertion status at any time:

const details = await client.uma.getAssertionDetails(assertionId);

console.log(details.expirationTime);  // Unix timestamp when liveness expires
console.log(details.canSettle);       // true if liveness expired
console.log(details.isDisputed);      // true if disputed

Settlement

After the liveness window expires without dispute, anyone can settle the assertion.

UI (Venue Starter or OddMaki App): the Resolution Panel displays a Settle button once liveness expires.

SDK:

const tx = await client.uma.settleAssertion(assertionId);

Settlement triggers UMA to finalize the result. UMA synchronously calls back into the Diamond to record whether the assertion was truthful. If the assertion was rejected (via dispute), the oracle lock is cleared and a new assertion can be made.

Reporting

After settlement, the resolved outcome must be reported to the Conditional Token Framework. This records the payout vector and enables redemption.

UI (Venue Starter or OddMaki App): once the assertion is settled, the Resolution Panel shows a Report button that writes the outcome to the CTF.

SDK:

const tx = await client.uma.reportResolution({
  marketId: 42n,
  outcome: 'Yes',
});

The outcome parameter must exactly match the settled assertion's outcome.

Payout vectors:

Resolved AsPayout VectorMeaning
"Yes"[1, 0]YES holders win
"No"[0, 1]NO holders win
"Invalid"[1, 1]Equal split — both sides redeem proportionally

Market group cascade: If a market in a group resolves YES, all sibling markets automatically resolve as NO in the same transaction — no separate assertion needed for the others.

Pyth Resolution

Price markets skip UMA entirely. After the close time, anyone triggers resolution.

UI (Venue Starter or OddMaki App): price market pages render a Resolve button (in the Price Market Resolution Panel) once the close time has passed.

SDK:

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

The market is resolved deterministically by comparing the close price against strikePrice:

  • Up/Down (deferred) — open price is captured at resolution from a Hermes VAA in [openTime, openTime + resolutionWindow], then compared against the close VAA. YES if finalPrice ≥ capturedOpenPrice.
  • Explicit Strike — strike was set at creation. YES if finalPrice ≥ strikePrice.

For deferred markets, the SDK fetches both the open-window and close-window Hermes VAAs in a single call and submits them together. The contract picks the earliest in-range VAA per window (anti-cherry-pick) and rejects the resolution if either window has no available VAA. The resolver pays the Pyth update fee (typically a few wei per VAA).

There is no bond, no liveness, no dispute path. Once resolved, the payout vector is set on the CTF and winners redeem. See Price Markets for scheduling, deferred-capture, and the projected-strike helper.

Invalidation Backstop

If Hermes has no in-range VAA for the open or close window — feed outage, deprecated feed, off-hours metals/equity gap — resolvePyth reverts. After closeTime + 7 days (the protocol grace period), anyone can invalidate the market so traders get refunded 50/50:

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

This reports payouts [1, 1] to the CTF — every YES and every NO token redeems for half the underlying collateral. Use only as a last-resort cleanup; the 7-day window is long enough for any temporary Hermes outage to recover.

Redemption

After resolution is reported (by either path), holders of winning outcome tokens can redeem them for collateral.

UI (Venue Starter or OddMaki App): resolved markets show a Redeem panel summarising the user's winning balance with a single-click redeem button. Partial redemptions happen automatically as the user redeems across separate sessions.

SDK:

const tx = await client.uma.redeemWinnings(42n);

The CTF burns winning tokens and returns collateral (USDC). Partial redemptions are supported — you don't have to redeem all at once.

Check resolution status before redeeming:

const status = await client.uma.getResolutionStatus(42n);

console.log(status.resolved);         // boolean
console.log(status.winningOutcome);   // "YES" | "NO" | "INVALID" | null
console.log(status.payouts);          // [1n, 0n]

Cancelling Orders After Resolution

Orders resting on a resolved market can be cancelled in bulk to refund their locked collateral:

await client.trade.cancelOrdersOnResolvedMarket(42n, [orderId1, orderId2, …]);

Dispute & Edge Cases (UMA)

If a UMA assertion is disputed and rejected by UMA's DVM, the oracle lock is cleared. A new assertion can then be submitted — the market doesn't get stuck. There is no admin override or emergency resolution mechanism; UMA resolution is fully governed by the UMA Oracle lifecycle.

Key edge cases:

  • Expired but unsettled: An expired assertion stays pending until someone calls settleAssertion() — there's no auto-settlement
  • No assertion: A market with no assertion stays active indefinitely until someone posts one
  • Double assertion prevention: Only one assertion can be active per market at a time

What's Next