Commit–Reveal
Commit–reveal is what makes the agent honest. Before staking on a market opens, the agent posts a hash of the market parameters; the actual parameters stay secret. After staking closes, the agent reveals the pre-image. The contract recomputes the hash and verifies it matches. If it doesn't, the reveal reverts and the market refunds.
Without this, the agent could look at how money has flowed onto each side of a market mid-stake and rewrite the question to favor the heavier side. With it, the question text is bound before the agent knows the flow.
Why it matters
A live-feed-driven market is uniquely exposed to a class of attacks where the operator silently reshapes the question to bias the outcome. Two examples:
- Window slip. The agent declares "clean sheet in the next 15 minutes," then quietly extends to 20 minutes after seeing OVER over-stake. Commit–reveal blocks this because the window is in the committed hash.
- Param swap. The agent declares "possession > 55%," then swaps to 52% once UNDER dominates. The pre-image of the hash pins the threshold.
The hash
Every market commits exactly one 32-byte value to the chain. It is a domain-separated keccak of three fields:
question— the human-readable question string.templateParams— the encoded template parameters (e.g. window seconds, threshold, side). One canonical encoding per template.salt— a 32-byte random the agent generates per market. Prevents pre-image discovery by guessing.
// On-chain check inside reveal()
bytes32 expected = keccak256(abi.encodePacked(
question,
templateParams,
salt
));
if (expected != s.commitHash) revert RevealHashMismatch();The agent side
Off-chain, the agent generates the salt with a CSPRNG, encodes the template params canonically, then publishes the hash:
// packages/agent/src/markets/commit.ts (excerpt)
import { encodePacked, keccak256, toHex } from "viem";
import { randomBytes } from "node:crypto";
export function buildCommit(question: string, templateParams: Hex) {
const salt = toHex(randomBytes(32));
const commitHash = keccak256(
encodePacked(["string", "bytes", "bytes32"], [question, templateParams, salt])
);
return { commitHash, salt };
}
// at commit time, only commitHash goes on chain.
// the agent stores { question, templateParams, salt } in its local
// SQLite to use later in reveal().What if reveal fails?
Two ways reveal can fail: (1) the agent reveals different templateParams than it committed, or (2) the agent never reveals at all. Both cases route into the refund window:
- If
reveal()reverts because the hash doesn't match, the market goes straight toRefunded— stakers withdraw their original deposit in USDT0, minus relayer gas cost. - If the agent never calls
reveal()within therevealDeadline, anyone can callrefund()on the market — same outcome for stakers.