Solidity Integration
This guide shows how to integrate the OptimisticOracle into your Solidity smart contracts for fast, low-cost oracle queries with dispute mechanisms.
Contract Addresses (BSC Mainnet)
- OptimisticOracle:
0xA83689161DFa9d5992fBa658d3148C6f72E1419E - FarmTruthToken:
0x948d7a6f18511df422184360d1cd5d56f5be4444 - DecentralizedOracle (for disputes):
0xFA4595F636887CA28FCA3260486e44fdcc8c8A71
View on BSCScan:
Interface
Note: For the complete interface with all functions, events, and detailed documentation, see IOptimisticOracle Interface Reference.
The minimal interface below includes only the essential functions for basic integration:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @notice Minimal interface for OptimisticOracle on-chain integration
/// @dev For complete interface, see: /docs/interfaces/ioptimisticoracle.md
interface IOptimisticOracle {
enum QueryState { Awaiting, Proposed, Disputed, Resolved }
// Mutations
function createQuery(
string memory question,
string[] memory options
) external returns (uint256);
function proposeAnswer(
uint256 queryId,
uint8[] memory selectedOptions
) external;
function dispute(
uint256 queryId,
bool disputeAsNotClear
) external;
function resolveUndisputed(uint256 queryId) external;
function resolveDisputed(uint256 queryId) external;
// Reads
function getAnswer(uint256 queryId) external view returns (uint8[] memory);
function getQueryState(uint256 queryId) external view returns (QueryState);
function getQueryDetails(uint256 queryId) external view returns (
address creator,
string memory question,
string[] memory options,
uint256 createdAt,
QueryState state,
address proposer,
uint8[] memory proposedAnswer,
uint256 proposalBlock,
bool disputed,
address challenger,
uint256 mainOracleQueryId,
bool resolved,
uint8[] memory finalAnswer,
bool isNotClearYet
);
function getDisputeWindowRemaining(uint256 queryId) external view returns (uint256);
function canDispute(uint256 queryId) external view returns (bool);
function getQueriesByState(
QueryState state,
uint256 offset,
uint256 limit
) external view returns (uint256[] memory);
// Constants
function CREATION_COST() external view returns (uint256);
function PROPOSAL_COLLATERAL() external view returns (uint256);
function DISPUTE_COLLATERAL() external view returns (uint256);
function DISPUTE_WINDOW() external view returns (uint256);
}
Constants
- CREATION_COST: 30,000 tokens (18 decimals)
- PROPOSAL_COLLATERAL: 1,000,000 tokens
- DISPUTE_COLLATERAL: 1,000,000 tokens
- DISPUTE_WINDOW: 7,200 blocks (~1.5 hours at 0.75s/block on BSC)
Creating Queries
Basic Query Creation
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IOptimisticOracle {
function createQuery(string memory, string[] memory) external returns (uint256);
}
contract OptimisticQueryCreator {
IOptimisticOracle public immutable oracle;
IERC20 public immutable token;
uint256 constant CREATION_COST = 30_000 * 10**18;
constructor(address oracle_, address token_) {
oracle = IOptimisticOracle(oracle_);
token = IERC20(token_);
}
/// @notice Create a simple yes/no query
/// @dev Assumes contract holds tokens and has approved oracle
function createYesNoQuery(string calldata question) external returns (uint256) {
string[] memory options = new string[](2);
options[0] = "Yes";
options[1] = "No";
return oracle.createQuery(question, options);
}
/// @notice Create a query with custom options
function createCustomQuery(
string calldata question,
string[] calldata options
) external returns (uint256) {
require(options.length >= 2 && options.length <= 254, "Invalid options count");
return oracle.createQuery(question, options);
}
/// @notice Approve oracle to pull creation cost
function approveOracle() external {
token.approve(address(oracle), CREATION_COST);
}
}
Requirements
- Your contract must hold 30,000 $TRUTH tokens
- Approve OptimisticOracle to spend tokens before calling
createQuery - Provide 2-254 non-empty option strings
- Each option must be unique
Proposing Answers
Basic Proposal
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IOptimisticOracle {
function proposeAnswer(uint256, uint8[] memory) external;
function getQueryState(uint256) external view returns (QueryState);
enum QueryState { Awaiting, Proposed, Disputed, Resolved }
}
contract OptimisticProposer {
IOptimisticOracle public immutable oracle;
IERC20 public immutable token;
uint256 constant PROPOSAL_COLLATERAL = 1_000_000 * 10**18;
constructor(address oracle_, address token_) {
oracle = IOptimisticOracle(oracle_);
token = IERC20(token_);
}
/// @notice Propose a single option answer
function proposeSingleOption(uint256 queryId, uint8 optionIndex) external {
require(
oracle.getQueryState(queryId) == IOptimisticOracle.QueryState.Awaiting,
"Not awaiting proposal"
);
uint8[] memory answer = new uint8[](1);
answer[0] = optionIndex;
oracle.proposeAnswer(queryId, answer);
}
/// @notice Propose multiple options (multi-select)
function proposeMultipleOptions(
uint256 queryId,
uint8[] calldata optionIndices
) external {
oracle.proposeAnswer(queryId, optionIndices);
}
/// @notice Propose "no correct choice"
function proposeNoCorrectChoice(uint256 queryId) external {
uint8[] memory emptyAnswer = new uint8[](0);
oracle.proposeAnswer(queryId, emptyAnswer);
}
/// @notice Approve oracle for proposal collateral
function approveOracle() external {
token.approve(address(oracle), PROPOSAL_COLLATERAL);
}
}
Requirements
- Your contract must hold 1,000,000 $TRUTH tokens
- Approve OptimisticOracle before calling
proposeAnswer - Query must be in Awaiting state
- Option indices must be valid (0 to N-1, where N = number of options)
Disputing Proposals
Basic Dispute
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IOptimisticOracle {
function dispute(uint256, bool) external;
function canDispute(uint256) external view returns (bool);
function getDisputeWindowRemaining(uint256) external view returns (uint256);
}
contract OptimisticDisputer {
IOptimisticOracle public immutable oracle;
IERC20 public immutable token;
uint256 constant DISPUTE_COLLATERAL = 1_000_000 * 10**18;
constructor(address oracle_, address token_) {
oracle = IOptimisticOracle(oracle_);
token = IERC20(token_);
}
/// @notice Dispute a proposal (standard dispute)
/// @dev Use when proposer selected wrong option(s)
function disputeProposal(uint256 queryId) external {
require(oracle.canDispute(queryId), "Cannot dispute");
oracle.dispute(queryId, false);
}
/// @notice Dispute as "not clear yet"
/// @dev Use when answer isn't determinable yet
function disputeAsNotClear(uint256 queryId) external {
require(oracle.canDispute(queryId), "Cannot dispute");
oracle.dispute(queryId, true);
}
/// @notice Check if dispute window is still open
function isDisputeWindowOpen(uint256 queryId) external view returns (bool) {
return oracle.getDisputeWindowRemaining(queryId) > 0;
}
/// @notice Approve oracle for dispute collateral
function approveOracle() external {
token.approve(address(oracle), DISPUTE_COLLATERAL);
}
}
Requirements
- Your contract must hold 1,000,000 $TRUTH tokens
- Approve OptimisticOracle before calling
dispute - Query must be in Proposed state
- Dispute must be within 7,200 blocks (~1.5 hours) of proposal
Reading Query Data
Get Query Answer
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IOptimisticOracle {
function getAnswer(uint256) external view returns (uint8[] memory);
function getQueryState(uint256) external view returns (QueryState);
enum QueryState { Awaiting, Proposed, Disputed, Resolved }
}
contract OptimisticReader {
IOptimisticOracle public immutable oracle;
constructor(address oracle_) {
oracle = IOptimisticOracle(oracle_);
}
/// @notice Get the final answer for a resolved query
/// @dev Reverts if query is not resolved
function getFinalAnswer(uint256 queryId) external view returns (uint8[] memory) {
require(
oracle.getQueryState(queryId) == IOptimisticOracle.QueryState.Resolved,
"Query not resolved"
);
return oracle.getAnswer(queryId);
}
/// @notice Check if answer is a single option
function isSingleOptionAnswer(uint256 queryId) external view returns (bool, uint8) {
uint8[] memory answer = oracle.getAnswer(queryId);
if (answer.length == 1) {
return (true, answer[0]);
}
return (false, 0);
}
/// @notice Check if answer is "no correct choice"
function isNoCorrectChoice(uint256 queryId) external view returns (bool) {
uint8[] memory answer = oracle.getAnswer(queryId);
return answer.length == 0;
}
}
Get Query Details
contract OptimisticDetailsReader {
IOptimisticOracle public immutable oracle;
constructor(address oracle_) {
oracle = IOptimisticOracle(oracle_);
}
/// @notice Get complete query information
function getFullDetails(uint256 queryId) external view returns (
address creator,
string memory question,
string[] memory options,
IOptimisticOracle.QueryState state
) {
(
creator,
question,
options,
, // createdAt
state,
, // proposer
, // proposedAnswer
, // proposalBlock
, // disputed
, // challenger
, // mainOracleQueryId
, // resolved
, // finalAnswer
// isNotClearYet
) = oracle.getQueryDetails(queryId);
}
}
Complete Integration Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IOptimisticOracle {
enum QueryState { Awaiting, Proposed, Disputed, Resolved }
function createQuery(string memory, string[] memory) external returns (uint256);
function proposeAnswer(uint256, uint8[] memory) external;
function getAnswer(uint256) external view returns (uint8[] memory);
function getQueryState(uint256) external view returns (QueryState);
}
/// @title PredictionMarket
/// @notice Example prediction market using OptimisticOracle
contract PredictionMarket {
IOptimisticOracle public immutable oracle;
IERC20 public immutable token;
uint256 constant CREATION_COST = 30_000 * 10**18;
struct Market {
uint256 queryId;
string question;
bool settled;
}
mapping(uint256 => Market) public markets;
uint256 public marketCount;
event MarketCreated(uint256 indexed marketId, uint256 indexed queryId, string question);
event MarketSettled(uint256 indexed marketId, uint8[] answer);
constructor(address oracle_, address token_) {
oracle = IOptimisticOracle(oracle_);
token = IERC20(token_);
// Approve oracle for future queries
token.approve(oracle_, type(uint256).max);
}
/// @notice Create a new prediction market
function createMarket(
string calldata question,
string[] calldata options
) external returns (uint256 marketId) {
require(options.length >= 2, "Need at least 2 options");
// Transfer creation cost from user
token.transferFrom(msg.sender, address(this), CREATION_COST);
// Create optimistic query
uint256 queryId = oracle.createQuery(question, options);
// Store market
marketId = marketCount++;
markets[marketId] = Market({
queryId: queryId,
question: question,
settled: false
});
emit MarketCreated(marketId, queryId, question);
}
/// @notice Settle a market after oracle resolves
function settleMarket(uint256 marketId) external {
Market storage market = markets[marketId];
require(!market.settled, "Already settled");
// Check if oracle is resolved
require(
oracle.getQueryState(market.queryId) == IOptimisticOracle.QueryState.Resolved,
"Oracle not resolved"
);
// Get final answer
uint8[] memory answer = oracle.getAnswer(market.queryId);
market.settled = true;
emit MarketSettled(marketId, answer);
// TODO: Distribute payouts based on answer
}
/// @notice Check if market can be settled
function canSettle(uint256 marketId) external view returns (bool) {
Market storage market = markets[marketId];
return !market.settled &&
oracle.getQueryState(market.queryId) == IOptimisticOracle.QueryState.Resolved;
}
}
Common Patterns
Approve Once, Use Many Times
constructor(address oracle_, address token_) {
oracle = IOptimisticOracle(oracle_);
token = IERC20(token_);
// Approve maximum amount for unlimited queries
token.approve(oracle_, type(uint256).max);
}
Safe Resolution with Try/Catch
function tryResolveUndisputed(uint256 queryId) external returns (bool success) {
try this._resolveUndisputed(queryId) {
return true;
} catch {
return false;
}
}
function _resolveUndisputed(uint256 queryId) external {
oracle.resolveUndisputed(queryId);
}
Wait for Resolution
function waitForResolution(uint256 queryId) external view returns (
bool isResolved,
uint8[] memory answer
) {
if (oracle.getQueryState(queryId) == IOptimisticOracle.QueryState.Resolved) {
return (true, oracle.getAnswer(queryId));
}
return (false, new uint8[](0));
}
Error Handling
Common revert reasons:
- "Question cannot be empty": Provide a non-empty question string
- "Must have 2-254 options": Option array length must be 2-254
- "Option cannot be empty": All option strings must be non-empty
- "Query not in awaiting state": Can only propose to queries in Awaiting state
- "Invalid option index": Option index must be < options.length
- "Duplicate options": Cannot propose same option twice
- "Query not in proposed state": Can only dispute Proposed queries
- "Dispute window closed": Must dispute within 7,200 blocks
- "Query not resolved": Cannot get answer until resolved
Best Practices
- Approve Once: Use
type(uint256).maxfor unlimited approvals - Check State: Always verify query state before interacting
- Handle Reverts: Use try/catch for resolution attempts
- Validate Options: Ensure option indices are valid before proposing
- Monitor Timing: Track dispute windows for time-sensitive operations
- Economic Incentives: Only propose/dispute when you have high confidence
Gas Considerations
- createQuery: ~150k-300k gas (depends on question/options length)
- proposeAnswer: ~100k-150k gas
- dispute: ~300k-500k gas (creates main oracle query)
- resolveUndisputed: ~50k-100k gas
- resolveDisputed: ~100k-200k gas
Reference Documentation
- Complete IOptimisticOracle Interface - Full interface with all functions and events
- IDecentralizedOracle Interface - Main oracle interface (for dispute escalation)
- All Interfaces Overview - Complete interface documentation
Next Steps
Back to: