Examples

This page shows a minimal Solidity consumer that resolves a single player-prop market against a finalized Props Oracle assertion. It is intentionally stripped down — no access control, no fee math, no multi-market logic — to isolate the oracle interaction.

Minimal consumer contract

The pattern below locks a market at creation, then resolves it by checking the UMA assertion result and reading the UMA assertion log off-chain. On-chain, the consumer stores only what it needs to verify later: the canonical event ID, the oracle player ID, the stat type, and the locked line.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
 
interface IOptimisticOracleV3 {
    function getAssertionResult(bytes32 assertionId) external view returns (bool);
}
 
contract SimplePropConsumer {
    IOptimisticOracleV3 public immutable oo;
 
    struct Market {
        bytes32 canonicalEventId;
        bytes32 oraclePlayerId;
        string statType;       // e.g. "batter_hits"
        int256 lockedLine;     // scaled by 10 to keep half-points as integers
        address bettor;
        uint256 stake;
        bool isOver;
        bool resolved;
    }
 
    mapping(uint256 => Market) public markets;
 
    constructor(address optimisticOracleV3) {
        oo = IOptimisticOracleV3(optimisticOracleV3);
    }
 
    function canResolve(uint256 marketId, bytes32 assertionId) external view returns (bool) {
        Market memory m = markets[marketId];
        if (m.resolved) return false;
        return oo.getAssertionResult(assertionId);
    }
 
    // resolveMarket takes the statValue the consumer parsed from the
    // UMA AssertionMade log off-chain. The off-chain step also verifies
    // the decoded claim matches m.canonicalEventId, m.oraclePlayerId,
    // and m.statType.
    function resolveMarket(uint256 marketId, bytes32 assertionId, int256 statValueX10) external {
        Market storage m = markets[marketId];
        require(!m.resolved, "Already resolved");
        require(oo.getAssertionResult(assertionId), "Not truthful");
 
        bool over = statValueX10 > m.lockedLine;
        bool won = (over == m.isOver);
 
        m.resolved = true;
        if (won) {
            payable(m.bettor).transfer(m.stake * 2);
        }
    }
}

Two things are worth flagging. First, resolveMarket trusts whatever statValueX10 the caller passes in. In production you want to either require a signed proof from a trusted relayer, or pull the log data on-chain using an archival node or a light-client proof. The simplest production-ready path is to have the consumer's own backend watch for AssertionMade events, parse the JSON, and call resolveMarket with the value — the on-chain check still guarantees the UMA assertion resolved truthfully, which is the part that matters for dispute safety.

Second, the statType field here is a string for readability. In real contracts, hash it to bytes32 at market creation and store the hash, both to save gas and to avoid subtle issues with string comparison.

Off-chain parsing

The JavaScript side of the integration is a log filter. Pseudocode:

const logs = await oo.queryFilter(oo.filters.AssertionMade());
const claim = JSON.parse(ethers.toUtf8String(logs[0].args.claim));
 
if (claim.assertion_type !== "MARKET_RESOLUTIONS") throw new Error("wrong claim type");
if (claim.canonical_event_id !== canonicalEventId) throw new Error("wrong event");
 
const entry = claim.player_stats.find(
  p => p.oracle_player_id === oraclePlayerId && p.stat_type === statType
);
 
const statValueX10 = Math.round(entry.stat_value * 10);
await consumer.resolveMarket(marketId, logs[0].args.assertionId, statValueX10);

In production, verify the canonical event ID and oracle player ID locally before trusting the claim — recompute both hashes from the inputs you stored at market creation and compare. If either does not match, something is wrong and the resolution should be rejected.

Dispute awareness

If a MARKET_RESOLUTIONS assertion is disputed and the disputer wins, UMA marks that assertion untruthful and the consumer should continue polling for a corrected assertion. The relayer, or any party, can repost corrected values after a fresh validation cycle. Markets should not pay out and should not refund on a lost dispute; they should simply wait for the next truthful assertion.