Skip to main content
Sui-native optimistic oracle for non-price markets. Bond-backed propose/dispute, ResolverNFT-weighted vote, 30-minute auto-finalize.

Config + ledger

public struct OOConfig has key {
    id: UID,
    min_bond_micro: u64,             // OO_MIN_BOND
    challenge_window_ms: u64,        // 30 min default
    vote_window_ms: u64,             // 12 hr default
}

public struct VoteLedger has key {
    id: UID,
    /// per (proposal, voter) -> {weight, vote}
    votes: Table<vector<u8>, VoteRecord>,
}
Both shared at publish. IDs land in .env as OO_CONFIG_ID / VOTE_LEDGER_ID.

Proposal

public struct Proposal<phantom COLLATERAL> has key {
    id: UID,
    market_link_id: ID,
    state: u8,                       // OPEN | DISPUTED | FINALIZED_AUTO | FINALIZED_VOTE
    proposer: address,
    disputer: address,                // 0x0 sentinel if not disputed
    proposed_outcome: u8,            // YES | NO
    proposer_bond: Balance<COLLATERAL>,
    disputer_bond: Balance<COLLATERAL>,
    challenge_deadline_ms: u64,
    vote_deadline_ms: u64,            // 0 until disputed
    yes_weight: u64,
    no_weight: u64,
    final_outcome: u8,                // 0 if not finalized
}

Lifecycle

propose<COLLATERAL>

public fun propose<COLLATERAL>(
    config: &OOConfig,
    market_link: &MarketLink,
    outcome: u8,                        // 1 = YES, 2 = NO
    bond: Coin<COLLATERAL>,
    clock: &Clock,
    ctx: &mut TxContext,
): Proposal<COLLATERAL>
Asserts outcome \in {1,2}, coin::value(bond) >= min_bond_micro, market.resolution_kind == RESOLUTION_OPTIMISTIC. Emits ProposalCreated.

dispute<COLLATERAL>

public fun dispute<COLLATERAL>(
    proposal: &mut Proposal<COLLATERAL>,
    config: &OOConfig,
    bond: Coin<COLLATERAL>,
    clock: &Clock,
    ctx: &mut TxContext,
)
Asserts state == OPEN, clock.timestamp_ms < challenge_deadline_ms, coin::value(bond) >= proposer_bond.value. State → DISPUTED, vote_deadline_ms set to now + vote_window_ms. Emits ProposalDisputed.

cast_vote

public fun cast_vote<COLLATERAL>(
    proposal: &mut Proposal<COLLATERAL>,
    ledger: &mut VoteLedger,
    weight: u64,
    vote: u8,                       // 1 = YES, 2 = NO
    clock: &Clock,
    ctx: &mut TxContext,
)
Requires the caller hold a ResolverNFT with matching weight (v1 stub. The on-chain check is a TODO). Updates yes_weight / no_weight. Emits VoteCast.

finalize_auto<COLLATERAL> / finalize_vote<COLLATERAL>

public fun finalize_auto<COLLATERAL>(
    proposal: &mut Proposal<COLLATERAL>,
    clock: &Clock,
    ctx: &mut TxContext,
): (Coin<COLLATERAL>, Coin<COLLATERAL>, address)
Asserts state == OPEN, clock.timestamp_ms >= challenge_deadline_ms. Returns the proposer’s bond back and a zero coin for the unused disputer-bond slot; state → FINALIZED_AUTO. Returns the proposer address so the caller can transfer::public_transfer the bond back in the same PTB. finalize_vote is the post-dispute path: asserts state == DISPUTED, clock.timestamp_ms >= vote_deadline_ms. The winning side takes both bonds. The losing-side proposer/disputer’s bond is forfeited to the winner.

Frontend integration

OOPanel on EventMarketDetail exposes propose / dispute / finalize buttons with a live countdown to the deadline. State pill transitions animate via AnimatePresence. See Optimistic Oracle.

Tests

tests/oo_resolution_tests.move. State + outcome constants are distinct (placeholder; full propose-dispute-finalize harness lands post-republish).