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: