Skip to main content

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Quick Start

In the next five minutes you will: post a question to OMY, propose an answer with a bond, and react to the resolved truth in your own contract.

:::tip This guide assumes

  • A TON wallet with some TON for gas
  • A USDT jetton wallet for posting bonds
  • Your own consumer contract on TON that will receive the result callback :::

1 — Install the SDK

The SDK is one file you copy into your project:

terminal
mkdir -p src/omy
curl -L https://raw.githubusercontent.com/Omy-network/omy/main/sdk/index.ts \
-o src/omy/index.ts
npm i @ton/core @ton/ton

That's it. No build step, no peer-dep gymnastics.

2 — Contract addresses

Network-specific contract addresses ship at launch. Plug them into your environment (see Installation → Environment variables).

3 — Ask a question

The flow: you send CreateAssertion to the OracleFactory. The factory deploys a child Assertion contract (one per question), discovers + binds its bond jetton wallet via TEP-89, and replies to you with AssertionCreated{id, assertion}.

src/oracle/create.ts
import {Address, beginCell, toNano} from '@ton/core';
import {buildMeta, buildCreateAssertion} from '../omy';
import {FACTORY, RESOLVER} from '../config'; // your env-bound addresses

const meta = buildMeta({
identifier: 1n, // your data identifier
factTimestamp: Math.floor(Date.now() / 1000), // when the fact occurred
callbackRecipient: myConsumerContract, // where OracleResult lands
question: beginCell()
.storeStringTail('Did event X happen by 2026-06-01?')
.endCell(),
});

const body = buildCreateAssertion({
id: 1n,
resolver: RESOLVER,
bondAmount: 1_000_000n, // 1 USDT (6 decimals)
liveness: 3600, // 1-hour challenge window
meta,
});

// Send `body` to FACTORY with value = toNano('0.6')
// — enough TON for: deploy + TEP-89 round-trip + SetJettonWallet
next: send the transaction
import {TonConnectUI} from '@tonconnect/ui';

const tonConnect = new TonConnectUI({...});

await tonConnect.sendTransaction({
validUntil: Math.floor(Date.now() / 1000) + 60,
messages: [{
address: FACTORY.toString(),
amount: toNano('0.6').toString(),
payload: body.toBoc().toString('base64'),
}],
});

:::info What just happened?

  1. The factory accepted the message + paid for the Assertion deploy
  2. The factory asked the USDT jetton master "what's the assertion's wallet address?" (TEP-89)
  3. The master replied; factory then sent SetJettonWallet to the new assertion
  4. Factory notified your address with AssertionCreated{id, assertion} — that's where you find the spawned assertion's address

The whole round-trip typically takes 6-10 seconds. :::

4 — Propose an answer (with a bond)

Anyone can propose. Send a TEP-74 jetton transfer to the assertion's address with an inline op (0x01 = propose-true) and an amount of bondAmount + protocolFee.

src/oracle/propose.ts
import {buildBondPayload, OP_PROPOSE_TRUE} from '../omy';

const payload = buildBondPayload(OP_PROPOSE_TRUE);

// Send via your USDT wallet's AskToTransfer:
// amount = 3_000_000n (= 1 USDT bond + 2 USDT fee, Phase A)
// destination = assertionAddress (from AssertionCreated)
// forwardPayload = payload
// forwardTon = 0.05 TON (so the assertion receives the notification)
// value = 0.2 TON (gas for the intake + fee forward)

The assertion will:

  1. Verify the amount covers bondAmount + protocolFee
  2. Store proposerBond = msg.amount - protocolFee
  3. Forward protocolFee to the factory's bound treasury
  4. Transition status to Proposed with deadline = now + liveness
caution

The Phase A protocolFee is small (~2 USDT-units). See the Reference for current values.

:::caution Underfunded? You'll get refunded If you send less than bondAmount + protocolFee, the assertion does not throw. It refunds your full transfer and stays in Open status. This is the "M-1 refund-and-return" pattern — funds can never strand on a guard. :::

5 — Handle the resolution callback

Once finalize() (no dispute) or Resolve (after dispute) fires, the assertion sends OracleResult{questionId, answer} to your callbackRecipient.

my-consumer.tolk
import "@omy/oracle-messages" // shared opcodes: OracleResult, OracleDisputed

struct (0x0a02) OracleResult {
questionId: uint64
answer: bool
}

fun onInternalMessage(in: InMessage) {
val msg = lazy MyMessage.fromSlice(in.body);
match (msg) {
OracleResult => {
// ⚠️ Delivery is at-least-once. ALWAYS dedupe by questionId.
var st = Storage.load();
if (st.settled.contains(msg.questionId)) {
return; // already processed — drop quietly
}
st.settled.set(msg.questionId, true);
st.save();

// Settle YOUR contract based on msg.answer
if (msg.answer) {
payoutWinners();
} else {
refundHolders();
}
}
// ... your other handlers ...
}
}
src/oracle/listen.ts
import {parseOracleResult} from '../omy';

// In your message handler (e.g., subscribed to your consumer's inbound traces):
const parsed = parseOracleResult(msg.body);
if (parsed) {
// parsed.questionId : bigint
// parsed.answer : boolean
console.log(`Question ${parsed.questionId} resolved to ${parsed.answer}`);

// Dedupe in your DB before acting
if (await alreadySettled(parsed.questionId)) return;
await markSettled(parsed.questionId);
await settleBet(parsed.questionId, parsed.answer);
}

:::danger Always dedupe OracleResult may be delivered more than once: anyone can pay gas to call ReemitResult and re-trigger the callback (covers the case where the first delivery hit a bounced message). Your consumer MUST dedupe by questionId — otherwise an attacker can trigger double payouts at the cost of 0.02 TON.

This is the single most common integration bug. See Patterns → Dedupe. :::

What you just built

┌──────────────────┐
│ Your consumer │
│ contract │
└────────┬─────────┘

propose() ─┴─► Assertion ──── liveness ─┐
│ │
│ (rare) dispute │ (common) finalize
▼ ▼
Resolver ──► OracleResult ──► You

you dedupe + settle

Where to go from here

import {DocCards, DocCard} from '@site/src/components/docs/DocCards';