xtruth

Integrate OOv3 from a smart contract

The standard pattern: your contract holds funds, calls assertTruth() when a market closes, and listens for the result via callback. This is the same model used by leading prediction-market platforms.

Develop against the sandbox: deploy your integration contract to X Layer Testnet (chainId 1952), point it at the same OOv3 address (Contracts), and use app-dev.xtruth.xyz for assertion / dispute / settle UI while you iterate. Move to app.xtruth.xyz once you're done. Both environments hit the same chain today — see Environments.

Minimal prediction-market contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { OptimisticOracleV3Interface } from
  "./interfaces/OptimisticOracleV3Interface.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract YesNoMarket {
    OptimisticOracleV3Interface public immutable oo;
    IERC20 public immutable bondCurrency;
    bytes32 public constant identifier = "ASSERT_TRUTH";

    struct Market {
        bool resolved;
        bool outcome;        // true = YES
        bytes32 assertionId;
    }
    mapping(bytes32 => Market) public markets;

    constructor(address oov3, address currency) {
        oo = OptimisticOracleV3Interface(oov3);
        bondCurrency = IERC20(currency);
    }

    /// Anyone can resolve a market by posting an assertion + bond.
    function resolve(bytes32 marketId, string calldata claim, uint64 liveness) external {
        require(!markets[marketId].resolved, "already resolved");

        uint256 bond = oo.getMinimumBond(address(bondCurrency));
        bondCurrency.transferFrom(msg.sender, address(this), bond);
        bondCurrency.approve(address(oo), bond);

        bytes32 assertionId = oo.assertTruth(
            bytes(claim),
            msg.sender,            // asserter
            address(this),         // callbackRecipient
            address(0),            // escalationManager
            liveness,
            bondCurrency,
            bond,
            identifier,
            bytes32(0)
        );
        markets[marketId].assertionId = assertionId;
    }

    /// Called by OOv3 when the assertion settles (positive or negative).
    function assertionResolvedCallback(
        bytes32 assertionId,
        bool assertedTruthfully
    ) external {
        require(msg.sender == address(oo), "only OO");
        // Find the market this assertion belongs to and store the outcome.
        // Pay out winners. (Indexing details elided.)
    }

    /// Optional: a hook for when somebody disputes (escalates to the DVM).
    function assertionDisputedCallback(bytes32 assertionId) external {
        require(msg.sender == address(oo), "only OO");
        // Surface "in dispute" UI; freeze any pending payouts.
    }
}

Key choices

Bond amount

getMinimumBond(currency) returns the network minimum for that token; you can post a higher bond for stronger economic security. Higher bonds make disputes more expensive, which is exactly what you want for high-value markets.

Liveness window

Trade-off:

The default in OOv3 is 2 hours. Polymarket uses values from 2 hours up to several days depending on the market.

Identifier

For OOv3 the most common choice is ASSERT_TRUTH. It says "the claim text is a true/false proposition; resolve true if undisputed". Other identifiers exist (YES_OR_NO_QUERY, NUMERICAL, MULTIPLE_CHOICE_QUERY) but only matter if your escalation manager interprets them differently.

Custom escalation manager

Pass address(0) for the default DVM-only behavior. To customize (e.g. auto-veto certain assertions, require multi-sig before disputing), deploy a contract implementing EscalationManagerInterface and pass its address.

Callback gotchas

Approving from a contract

When the contract itself posts the bond (vs. pulling from msg.sender like the example above), you typically pre-fund the contract with bond currency and approve the OOv3 inside resolve(). To avoid wasted gas on every call, approve once at deployment with type(uint256).max.

ABI

The OptimisticOracleV3 interface for both reads and writes is bundled in xtruth-app/src/lib/contracts/oov3-abi.ts. The exact function we use:

function assertTruth(
    bytes memory claim,
    address asserter,
    address callbackRecipient,
    address escalationManager,
    uint64 liveness,
    IERC20 currency,
    uint256 bond,
    bytes32 identifier,
    bytes32 domainId
) external returns (bytes32 assertionId);

For a copy you can drop into your project, see the xtruth-app source.