Skip to main content

Resolution Patterns

Resolving an outcome means fixing the value a market pays out against. Two kinds of outcome need two different approaches, and the difference is whether the answer is a number a price feed already reports.

Price-based resolution for parametric outcomes

A parametric outcome is a deterministic function of a price: a settlement at expiry, whether the price crossed a strike, whether it touched a barrier. An oracle already reports the number, so resolution is reading it at the right moment and freezing it.

The pattern is a market that settles exactly once, at or after a defined expiry, through the same staleness-checked read a liquidation uses. The resolver holds the expiry and the frozen settlement:

public struct Market has key, store {
id: UID,
expiry_ms: u64,
max_age_secs: u64,
settlement: Option<u64>,
}
/// Freeze the settlement price from the oracle. The adapter's staleness bound
/// applies, so a market cannot settle against a price the transaction did not
/// refresh. After this, the settlement price is fixed and readable forever.
public fun resolve(market: &mut Market, price_info: &PriceInfoObject, clock: &Clock) {
assert_resolvable(market, clock);
let p = price_adapter::price_from_pyth(price_info, clock, market.max_age_secs);
market.settlement.fill(price_adapter::magnitude(&p));
}

Two failure modes stay separate from any oracle failure: settling before expiry and settling twice, both rejected before the oracle is read. Because resolve goes through the adapter's price_from_pyth, a market cannot settle against a stale price.

For a production worked example, DeepBook Predict settles each market through a permissionless try_settle transition that anyone can call after expiry. It reads the exact price stamped at the expiry timestamp, records that price and the payout liability once, and returns false rather than inventing a mark when no exact price exists yet. Every later read, including settled redemption, consumes the recorded price instead of reading an oracle. The mechanism, resolve once at expiry and read recorded state afterward, is the same.

Optimistic resolution for subjective outcomes

A subjective outcome is not a price: did a team win, did a proposal pass, did an event occur? No price feed reports it, so no oracle resolves it for you. The established design is optimistic: someone proposes an answer and posts a bond, a challenge window opens, and if nobody disputes, the answer stands; if someone disputes, a higher authority decides and the loser's bond pays the winner.

caution

No canonical optimistic-oracle protocol exists on Sui as of this writing. UMA's Optimistic Oracle, the reference design that resolves Polymarket, is an EVM protocol with no Sui Move equivalent. The interface below is an illustrative pattern to build against your own governance, not a deployable dependency you can import.

A minimal optimistic resolver on Sui is a state machine over proposal, bond, dispute, and escalation:

// ILLUSTRATIVE PATTERN, NOT A DEPLOYABLE PACKAGE. Sketch of an optimistic
// resolver you would implement and audit yourself; no canonical Sui protocol
// provides this.
module example::optimistic_resolver {
public enum Status has store { Proposed, Disputed, Resolved }

public struct Question<phantom T> has key {
id: UID,
proposer: address,
answer: vector<u8>,
bond: Balance<T>,
challenge_ends_ms: u64,
status: Status,
}

// Propose an answer and lock a bond. Starts the challenge window.
public fun propose<T>(/* question, answer, bond, clock */) { /* ... */ }

// Dispute within the window, matching the bond. Routes to escalation.
public fun dispute<T>(/* question, counter_bond, clock */) { /* ... */ }

// After an undisputed window, the proposed answer stands and the bond returns.
public fun settle_undisputed<T>(/* question, clock */) { /* ... */ }

// A disputed question is decided by an escalation authority you define
// (a multisig, a token vote, a court); the loser's bond pays the winner.
public fun resolve_disputed<T>(/* question, ruling, authority_cap */) { /* ... */ }
}

The load-bearing decisions are the escalation authority and the bond sizing, and both are yours to design: a multisig for a small market, a token vote for a large one, and a bond large enough that a false proposal is unprofitable. Build and audit this yourself rather than assuming a protocol provides it.

Choosing

If the outcome is a number a feed reports, resolve it against the oracle with a staleness-checked read and settle once. If the outcome is a judgment, no oracle helps: implement an optimistic process with a bond, a dispute window, and an escalation authority you control.