Somnia Oracle

Asking questions

Write questions that resolve cleanly: question text, resolution time, answer types, agreement rules, costs and why questions void.

A question is a QuestionDefinition: the question text, the sources that answer it, the set of valid answers, when to resolve, and how much agreement is needed. This page covers how to fill each part in so the question resolves the way you expect. For the call itself, see From Solidity.

Field Type What it is
questionText string The question, in plain language. Must not be empty.
sources QuestionSource[] 1 to 20 sources. Each one is one vote. See Sources.
validAnswers ValidAnswers The outcomes the question can resolve to: discrete labels or numeric intervals.
resolutionTime uint256 Unix seconds. Sources are read at this time, not at submission.
minAgreement uint256 How many sources must produce a usable result. From 1 up to the number of sources.
subcommitteeSize uint256 Agents queried per Website or JSON source. At least 3.
subcommitteeThreshold uint256 How many of those agents must agree for the source to count. At least 2, at most subcommitteeSize.

Ask about a moment, not a period

The oracle reads every source once, at resolutionTime. It does not watch a value over time. So:

  • "What is the price of BTC in USD at 2026-12-01 00:00 UTC?" resolves: it is one reading at one instant.
  • "Did Arsenal beat Chelsea on 2026-11-14?" resolves: the final result is fixed by resolutionTime.
  • "Did BTC trade above $120,000 at any point in November?" does not resolve as written. Reduce it to one reading first, such as an API that reports the month's high, or a contract that tracks it.

Pick sources that still show the answer at resolutionTime. A live ticker shows the price when it is read; a historical endpoint with the timestamp in its URL, such as a one-minute candle, always shows the price at that minute.

Write the question text

questionText is the prompt the Website agents answer, and the term they search with when a source asks them to search. JSON and Contract sources ignore it, but the moderation and naming committees and the Caliber rating all read it, so write it for every source.

  • Name the instant and the time zone. "at 2026-12-01 00:00 UTC", not "on December 1st".
  • Name the unit. "in USD", "in degrees Celsius", "in millimetres".
  • Name the exact thing. "the closing price of AAPL on NASDAQ", not "Apple's stock".
  • Say what counts. "including extra time", "as first published", "the official result".
  • Avoid judgement words such as "significant", "popular" or "likely". Agents can only report what the page says.

Pick the resolution time

Set resolutionTime after the answer is published, with some margin: after the final whistle, after the data release, after the candle closes. If the answer isn't on the page yet, the agents can't find it.

A resolutionTime in the past is accepted; the question fires about a second after submission. When many questions share the same second, the trigger can move a few seconds later.

Answer types

validAnswers.answerType is 0 for Numeric or 1 for Discrete. Fill in only the fields for your type; the other list must be empty.

struct ValidAnswers {
    AnswerType answerType;      // 0 = NumericAnswer, 1 = DiscreteAnswer
    string[] discreteOutcomes;  // Discrete only
    Interval[] numericIntervals; // Numeric only: { int256 low; int256 high; }
    uint64 numericDecimals;     // Numeric only, at most 18; 0 for Discrete
}

Discrete

List 1 to 64 labels in discreteOutcomes, for example ["Yes", "No"] or ["Home", "Draw", "Away"]. The answer's outcomeIdx is the index of the winning label.

  • Website agents are given the list and must pick one of the labels.
  • JSON and Contract sources must return a string that matches a label exactly, byte for byte. "yes" does not match "Yes".
  • A source that returns anything else doesn't count.
  • The winner is the label with the most votes, as long as it has at least minAgreement votes and no other label ties it. A tie voids the question.

Numeric

List 1 to 64 intervals in numericIntervals. Each is an inclusive [low, high] range of int256 values, with low <= high, and no two ranges may overlap. The answer's outcomeIdx is the index of the interval that contains the result, and numericValue holds the result itself.

  • numericDecimals is the fixed-point scale of the bounds and the result. At numericDecimals = 2, the bound 10050 means 100.50.
  • Each source declares its own decimals, and the oracle rescales its value to numericDecimals before aggregating. Scaling down truncates toward zero.
  • The result is the median of the values from every usable source. With an even count, it is the mean of the middle two.
  • For open-ended ranges, use type(int256).min as the lowest bound or type(int256).max as the highest.
  • If the median falls outside every interval, the question voids with NoBucketMatch. Cover the whole range you expect, and make adjacent intervals meet: at numericDecimals = 0, [0, 99999] and [100000, max] leave no gap.

getOutcomeLabels(questionId) renders the intervals as text: "<= X" and ">= X" for open-ended ranges, the bare value for a single-value range, and "low - high" otherwise, with numericDecimals applied.

Agreement

Agreement works at two levels.

Within a source. Each Website or JSON source is answered by a subcommittee of subcommitteeSize agents. The source counts only if at least subcommitteeThreshold of them succeed: for numbers the source's value is the median of their answers, and for labels at least that many must return the same label. The protocol minimum is a subcommittee of 3 with a threshold of 2 (read minSubcommitteeSize() and minSubcommitteeThreshold() on OracleCore); the maximum size is 32. Contract sources are a single deterministic read with no subcommittee.

Across sources. minAgreement is how many sources must produce a usable result, from 1 up to the number of sources. Below it, the question voids with InsufficientSources. For discrete questions, the winning label also needs at least minAgreement votes.

A good default is three independent sources with minAgreement of 2: one broken page or API can't void the question, and one wrong source can't decide it.

Why questions void

A voided question has voided = true and outcomeIdx = 255 on its Answer. The AnswerPosted event carries the reason.

Reason What happened
InsufficientSources Fewer than minAgreement sources produced a usable result. The usual causes are an unreachable URL, a JSON path that doesn't match the response, or a page that doesn't show the answer.
NoBucketMatch The numeric median fell outside every interval.
NoDiscreteWinner Labels tied, or the leading label had fewer than minAgreement votes.
Expired Nobody resolved the question within the void timeout after resolutionTime, and someone called voidExpired.
Administrative The protocol owner force-voided the question.

To find out why a source failed, open the question in the Somnia Explorer. Each agent request links to every validator's receipt, which records each step (fetch, extract, respond) and any error.

Costs

You pay the whole fee in the native token (SOMI on mainnet, STT on testnet) with submitQuestion. Ask the contract for the live quote with getSubmissionCost(def) and send at least that much. Any excess is refunded in the same transaction.

The fee has these parts. The figures are the current mainnet and testnet settings, with a 3-agent subcommittee:

Part Charged Current cost
Moderation Always 0.24
Display name Always 0.24
Website source Per source 0.33 (agent platform deposit + 3 × 0.10)
JSON source Per source 0.12 (agent platform deposit + 3 × 0.03)
Contract source Per source 0
Reactivity trigger Always 5,000,000 gas × (basefee + 10 gwei) × 1.2, about 0.096 at a 6 gwei basefee

So a question with one JSON source costs about 0.70, one Website source about 0.91, and three JSON sources about 0.94. Larger subcommittees cost more per source. The reactivity part follows the basefee at submission, so quote close to when you submit. The fee isn't refunded if the question voids.

Timing and stuck questions

Contract-only questions resolve in the same transaction as the trigger. Questions with Website or JSON sources resolve once every subcommittee has answered, usually within seconds to a few minutes. Agent requests time out after about 10 minutes, and a timed-out source counts as failed.

If the Reactivity trigger is ever missed, the question stays Scheduled. Anyone can fix that:

  • triggerMissedSchedule(questionId) on OracleCore, once resolutionTime has passed, dispatches the question as the trigger would have.
  • voidExpired(questionId) on OracleCore voids a question that is still unresolved one hour (voidTimeout()) after resolutionTime, so no question stays open forever.

The Status page lists questions whose trigger has failed.

Moderation and display names

When you submit, a moderation committee returns an advisory Accepted or Rejected verdict, and a naming committee writes a short display name. Neither affects resolution: a rejected question still resolves. Consumers decide whether to honour the verdict. Read them with getModerationVerdict(questionId) and getName(questionId) on the entry point.

Metadata

submitQuestion(def, metadata) takes an extra bytes argument for your own data, such as a category tag or an image hash. The oracle stores and emits it but never reads it, and it adds nothing to the fee. Read it back with getQuestionMetadata(questionId).

Edit this page