Solidity: Create / Vote / Resolve / Claim Flows

End-to-end patterns for integrating with the DecentralizedOracle from smart contracts. This page focuses purely on on-chain usage (no deployment, governance, or meta info).

Key mechanics:

  • The oracle pulls ERC‑20 tokens from msg.sender on:
    • createQuery(...) — fixed creation deposit
    • voteOnQuery(...) — collateral amount
  • Time is block-based. Resolving too early reverts.
  • Ties extend the voting period and cause resolve to revert; retry after the extension.

Prereqs:

  • Your contract holds the governance token and has approved the oracle (see “Approvals and Token Handling”).
  • You have the oracle address and token address.

Flow 1 — Create

Create a new query scheduled to start in the near future. The query remains pending until the first vote.

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

interface IOracle {
    function createQuery(uint256, string calldata, string calldata, uint8) external returns (uint256);
}

contract FlowCreate {
    IOracle public immutable oracle;

    constructor(address oracle_) {
        oracle = IOracle(oracle_);
    }

    function openQuery(
        uint256 startOffsetBlocks,
        string calldata question,
        string calldata metadata,
        uint8 totalOptions
    ) external returns (uint256 queryId) {
        // Requires allowance to cover the creation deposit
        uint256 startBlock = block.number + startOffsetBlocks;
        queryId = oracle.createQuery(startBlock, question, metadata, totalOptions);
    }
}

Constraints:

  • startBlock must be >= current block.
  • totalOptions must be in 2..255.

Flow 2 — Vote

Stake collateral on a selected set of options. The oracle aggregates weight by identical answer-hash (keccak256 of the encoded uint8).

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

interface IOracleVote {
    function voteOnQuery(uint256, uint8[] calldata, uint256) external;
}

contract FlowVote {
    IOracleVote public immutable oracle;

    constructor(address oracle_) {
        oracle = IOracleVote(oracle_);
    }

    function voteSingle(uint256 queryId, uint8 option, uint256 collateral) external {
        // Requires allowance to cover `collateral`
        uint8[] memory opts = new uint8[](1);
        opts[0] = option;
        oracle.voteOnQuery(queryId, opts, collateral);
    }

    function voteMulti(uint256 queryId, uint8[] calldata options, uint256 collateral) external {
        // The oracle enforces: non-empty, unique values, each < totalOptions
        oracle.voteOnQuery(queryId, options, collateral);
    }
}

Rules:

  • One vote per address per query.
  • Duplicate options revert.
  • Collateral must be > 0.
  • Voting must occur in startBlock, currentEndBlock.

Flow 3 — Resolve (with tie handling)

Resolve after the voting window ends. If top answers tie, the oracle:

  • sets currentEndBlock to block.number + tieExtensionPeriod()
  • emits VotingExtended
  • reverts the transaction

Use a helper + try/catch to handle reverts cleanly.

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

interface IOracleResolve {
    function resolveQuery(uint256) external;
}

contract FlowResolve {
    IOracleResolve public immutable oracle;

    constructor(address oracle_) {
        oracle = IOracleResolve(oracle_);
    }

    function tryResolve(uint256 queryId) external returns (bool) {
        // Reverts if too-early or tie. Use try/catch to translate into a boolean.
        try this._resolve(queryId) {
            return true;
        } catch {
            return false;
        }
    }

    function _resolve(uint256 queryId) external {
        oracle.resolveQuery(queryId);
    }
}

Tips:

  • Off-chain, call getQueryDetails(queryId) to check currentEndBlock and canBeResolved before attempting resolve.
  • After a tie, wait until the extended end block to retry.

Flow 4 — Claim Rewards

If your contract voted on the winning answer, it can claim rewards after resolution.

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

interface IOracleClaim {
    function canClaimRewards(uint256, address) external view returns (bool);
    function claimRewards(uint256) external;
}

contract FlowClaim {
    IOracleClaim public immutable oracle;

    constructor(address oracle_) {
        oracle = IOracleClaim(oracle_);
    }

    function claimIfWinner(uint256 queryId) external {
        if (oracle.canClaimRewards(queryId, address(this))) {
            oracle.claimRewards(queryId);
        }
    }
}

Reward math (for reference):

  • totalPot = initialDeposit + (totalCollateral - totalWinningCollateral)
  • userShare = (userCollateral * totalPot) / totalWinningCollateral

Orchestrating all flows

A single contract can wrap approve + create + vote + resolve + claim patterns. Keep security in mind (access control, withdrawals, etc.).

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

interface IOracleFull {
    function createQuery(uint256, string calldata, string calldata, uint8) external returns (uint256);
    function voteOnQuery(uint256, uint8[] calldata, uint256) external;
    function resolveQuery(uint256) external;
    function canClaimRewards(uint256, address) external view returns (bool);
    function claimRewards(uint256) external;

    function getQueryDetails(uint256) external view returns (
        address, uint256, uint256, uint256, string memory, string memory, uint8, bool, uint8[] memory,
        uint256, uint256, uint256, uint256, uint256, bool, bool
    );
}

contract OracleFlows {
    IOracleFull public immutable oracle;

    constructor(address oracle_) {
        oracle = IOracleFull(oracle_);
    }

    function createThenVote(
        uint256 startOffset,
        string calldata question,
        string calldata metadata,
        uint8 totalOptions,
        uint8 voteOption,
        uint256 collateral
    ) external returns (uint256 queryId) {
        // Assumes allowance covers deposit and collateral
        queryId = oracle.createQuery(block.number + startOffset, question, metadata, totalOptions);
        uint8[] memory opts = new uint8[](1);
        opts[0] = voteOption;
        oracle.voteOnQuery(queryId, opts, collateral);
    }

    function resolveIfReady(uint256 queryId) external returns (bool resolved) {
        ( , , , uint256 currentEndBlock, , , , , , , , , , , , ) = oracle.getQueryDetails(queryId);
        if (block.number > currentEndBlock) {
            // Reverts on tie → extension. Off-chain callers should handle retries.
            try this._resolve(queryId) { return true; } catch { return false; }
        }
        return false;
    }

    function _resolve(uint256 queryId) external {
        oracle.resolveQuery(queryId);
    }

    function claimIfWinner(uint256 queryId) external {
        if (oracle.canClaimRewards(queryId, address(this))) {
            oracle.claimRewards(queryId);
        }
    }
}

Operational guidance

  • Approvals: Prefer a generous one-time allowance; rotate only if you must change oracle addresses.
  • Timing: Always check currentEndBlock off-chain before resolving; surface countdowns in your UI.
  • Ties: Expect occasional reverts on resolve; show “extended until block X” to users.
  • Inactivity: Avoid creating queries that won’t attract votes; inactive zero‑vote queries can be slashed after grace.

Back to: