Skip to main content

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
ProposerbondAmount + protocolFeebond is refunded if right; fee is burned
DisputerbondAmount (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';

Sent when the assertion reaches Resolved — either via finalize() (common) or Resolve (dispute path). Carries {`{ questionId, answer }`}. Sent when the assertion enters 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';

What's next​

  • Tutorial — apply these concepts in a 30-minute walk-through.
  • Patterns — production-ready snippets for the gotchas above.
  • Reference — every export, opcode, and constant.