Concepts
Before you write integration code, the four ideas below are worth ten minutes of your time. They make everything else in the SDK obvious.
Assertion
An assertion is a single yes/no claim about the world, bound to a specific
question and resolver, with collateral attached. One assertion = one
on-chain contract, deployed per question by the OracleFactory.
Question: "Did event X happen by date Y?"
Asserter: @user_who_proposed
Answer: true
Bond: 1 USDT
Deadline: in 1 hour (if undisputed → accepted)
Status: Proposed
Assertions are immutable. Once spawned, their config — bond size, liveness window, resolver address, monetization snapshot — never changes. You can't "update" an assertion; if you want different terms, you spawn a new one.
Lifecycle
Every assertion is a tiny state machine with five states:
┌────────────┐
│ Open │ ← initial state after deploy
└─────┬──────┘
propose() │
▼
┌────────────┐
│ Proposed │ ← answer is on-chain, challenge window open
└──┬──────┬──┘
finalize() │ │ dispute()
(no dispute) │ │
▼ ▼
┌──────────┐ ┌────────────┐
│ Resolved │ │ Disputed │ ← someone challenged
└──────────┘ └─────┬──────┘
│ Resolve from resolver
│ or TimeoutRefund after window
▼
┌──────────┐ ┌─────────────┐
│ Resolved │ │ Cancelled │
└──────────┘ └─────────────┘
Three terminal states:
- Resolved (Finalize) — common case. Proposer was unchallenged, gets bond back, consumer notified.
- Resolved (Resolve) — dispute path. Resolver picked a winner; loser bond split 50/50 between winner and treasury.
- Cancelled — arbitration timed out. Both bonds refunded; consumer notified that the question went unanswered.
Your consumer needs to handle all three terminal outcomes — see Patterns.
Bond
The bond is refundable collateral that the proposer locks at propose().
It's there for one reason: make lying expensive.
| Bond is... | Used for | |
|---|---|---|
| Proposer | bondAmount + protocolFee | bond is refunded if right; fee is burned |
| Disputer | bondAmount (no fee) | bond is refunded if right; lost if wrong |
In the dispute path, the loser's bond gets split 50/50 between the winner and the treasury. So if you propose, lose, and your bond was 1 USDT:
- You posted 1 USDT bond + 2 USDT fee at propose time → 3 USDT out of pocket
- The fee (2 USDT) was burned at propose; you never see it again
- The bond (1 USDT) goes 0.5 to the disputer, 0.5 to treasury
- Net: you lose 3 USDT, disputer earns 0.5 USDT + their own bond back, treasury earns 2.5 USDT
The math is asymmetric on purpose. Disputer-watcher bots will only bother to dispute when they're highly confident. Proposers only bother to lie when they're so confident they'll win against any disputer — which is exactly the problem the bond is designed to make irrational.
:::tip Bond sizing
For consumer contracts that auto-request resolution (e.g., prediction markets),
the bond should scale to TVL. Our reference Market contract clamps the bond to
2% of total collateral, floored at the configured minimum and capped at
10 000 USDT. See tolk/contracts/Market.tolk
RequestResolution.
:::
Callbacks
Your consumer contract is a callback recipient on the assertion. OMY sends you exactly two message opcodes:
import {DocFeatures, DocFeature} from '@site/src/components/docs/DocFeatures';
Resolved — either via
finalize() (common) or Resolve (dispute path).
Carries {`{ questionId, answer }`}.
Disputed status. Lets you pause
your consumer logic if you want to wait for resolution. Carries
{`{ questionId }`}. Optional to handle.
That's the whole interface from OMY → you. Two messages, both with a predictable shape.
At-least-once delivery
This is the single most important integration property to internalize:
:::danger Callbacks may fire more than once
OracleResult delivery is at-least-once, not exactly-once.
ReemitResult is a permissionless message that any address can send to a
resolved assertion to re-trigger the callback. Use case: the original
delivery hit a bounced message or your consumer rejected it; someone pays gas
to retry.
Your consumer MUST dedupe by questionId. Otherwise an attacker can
trigger double-credit at gas cost. See Patterns →
Dedupe.
:::
The dedupe pattern is two lines of Tolk:
if (st.settled.contains(msg.questionId)) return;
st.settled.set(msg.questionId, true);
Always do this. Even if you're sure your consumer is the only target — assume the message will land twice and design for it.
The four things to remember
import {DocCards, DocCard} from '@site/src/components/docs/DocCards';