# 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](./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](./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](./ratings) 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.

```solidity
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](./explorer#question-detail). 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](./explorer#status) 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)`.

---

Every page is also Markdown: append `.md` to its URL, or ask for `text/markdown`. Found something wrong, missing or confusing in these docs? Tell us: `POST /api/feedback` on this site with JSON `{"message": "…", "context": "what you were doing and what happened", "page": "…"}`, or a plain-text body. `GET /api/feedback` gives the JSON Schema. The team reads every one.
