# Sources

> The three source types, Website, JSON and Contract, what each one returns and how to encode its params.

A source is where the oracle looks for the answer. Each question has 1 to 20 of them, in any mix of three types, and each source is one vote.

```solidity
struct QuestionSource {
    QuestionSourceType sourceType; // 0 = Website, 1 = JSON, 2 = Contract
    bytes params;                  // abi.encode(...) of the fields for that type
}
```

| Type | `sourceType` | Read by | `params` |
| --- | --- | --- | --- |
| Website | `0` | An agent subcommittee that reads the page with an LLM | `abi.encode(string url, bool websiteSearch, uint8 decimals)` |
| JSON | `1` | An agent subcommittee that fetches the API and follows a path | `abi.encode(string url, string jsonQuery, uint8 decimals)` |
| Contract | `2` | OracleCore, with a `staticcall` at resolution time | `abi.encode(address target, string signature, bytes args, uint8 decimals)` |

Every type carries `decimals`: the scale the source's value is expressed at. The oracle rescales each value from the source's `decimals` to the question's `numericDecimals` before aggregating. Discrete questions ignore it; pass `0`.

## Website

A Website source sends the question to the [LLM Parse Website](/agents/somnia-agents/base-agents/llm-parse-website) agent. Each agent fetches the page, converts it to text and extracts the answer to `questionText`.

| Field | Meaning |
| --- | --- |
| `url` | The page to read, or a domain to search when `websiteSearch` is true. |
| `websiteSearch` | `false`: read `url` directly. `true`: search `url`'s domain using the question text and read the page it finds. Use it when you know the site but not the page, for example `espn.com`. |
| `decimals` | The scale of the extracted number. |

- **Discrete:** agents are given the outcome labels and must pick one.
- **Numeric:** agents return a whole, non-negative number. If you need fractions, ask for a whole-number unit in the question text ("in US cents") and set `decimals` to match (`2`).
- Agents only answer when they are at least 70% confident; below that, the agent counts as failed.

```solidity
bytes memory params = abi.encode("https://www.bbc.com/sport/football/teams/arsenal", false, uint8(0));
```

## JSON

A JSON source sends the URL and a path to the [JSON API Request](/agents/somnia-agents/base-agents/json-api-request) agent. Each agent fetches the URL and returns the value at the path. There is no LLM involved, so results are exact.

| Field | Meaning |
| --- | --- |
| `url` | A JSON endpoint the agents can fetch, with no API key. |
| `jsonQuery` | A plain dot path into the response: `data.price`, `rates.EUR`, `data[0].intensity.actual`, or `[0][4]` into nested arrays. **It is not JSONPath:** a `$.` prefix fails. |
| `decimals` | For numeric questions, the agent multiplies the value by 10^`decimals`, so `97123.45` at `decimals = 2` becomes `9712345`. Numbers sent as strings, such as `"97123.45"`, work too. |

- **Discrete:** the value at the path is read as a string and must equal one of the labels exactly.
- **Numeric:** negative values are supported.

```solidity
// Binance's one-minute BTC/USDT candle starting at 2026-12-01 00:00 UTC; [0][4] is its close price.
bytes memory params = abi.encode(
    "https://data-api.binance.vision/api/v3/klines?symbol=BTCUSDT&interval=1m&startTime=1796083200000&endTime=1796083259999&limit=1",
    "[0][4]",
    uint8(2)
);
```

Prefer endpoints with the time baked into the URL, like the candle above, over live tickers. They return the same answer whenever they're read, which helps agents agree and keeps the question auditable afterwards.

## Contract

A Contract source calls a view function on any contract on the same chain, at the moment the question resolves. No agents are involved, so it costs nothing extra and is trusted as-is.

| Field | Meaning |
| --- | --- |
| `target` | The contract to call. |
| `signature` | The canonical function signature, such as `"latestAnswer()"` or `"balanceOf(address)"`. Must not be empty. The selector is `bytes4(keccak256(signature))`. |
| `args` | The ABI-encoded arguments, without the selector. Empty bytes for no arguments. |
| `decimals` | The scale of the returned number. For an 8-decimal price feed, `8`. |

How the return value is read:

- **Numeric:** the first 32-byte word, as an `int256`. Functions that return `uint256` or `int256` work. For a function that returns a tuple, the first field is used: Chainlink's `latestRoundData()` would give the round ID, so call `latestAnswer()` instead.
- **Discrete:** the return data must be a single ABI-encoded `string` that equals one of the labels.
- A revert, empty return data or a value of implausible magnitude counts as a failed source. The call gets a gas limit of 200,000.

```solidity
bytes memory params = abi.encode(
    address(0x1234567890123456789012345678901234567890), // your token
    "balanceOf(address)",
    abi.encode(address(0xAbCdEf0123456789aBcDeF0123456789AbCdEf01)), // the holder
    uint8(18)
);
```

## Encoding with viem

The same encodings in TypeScript:

```ts
import { encodeAbiParameters, encodeFunctionData, parseAbi } from "viem"

const website = encodeAbiParameters(
  [{ type: "string" }, { type: "bool" }, { type: "uint8" }],
  ["espn.com", true, 0],
)

const json = encodeAbiParameters(
  [{ type: "string" }, { type: "string" }, { type: "uint8" }],
  ["https://api.frankfurter.dev/v1/2026-12-01?base=USD&symbols=EUR", "rates.EUR", 4],
)

// args are the encoded arguments only: encodeFunctionData's output without its 4-byte selector.
const call = encodeFunctionData({
  abi: parseAbi(["function balanceOf(address) view returns (uint256)"]),
  args: ["0xAbCdEf0123456789aBcDeF0123456789AbCdEf01"],
})
const contract = encodeAbiParameters(
  [{ type: "address" }, { type: "string" }, { type: "bytes" }, { type: "uint8" }],
  ["0x1234567890123456789012345678901234567890", "balanceOf(address)", `0x${call.slice(10)}`, 18],
)
```

## Sources are shared

A source's ID is its content hash: `uint256(keccak256(abi.encode(sourceType, params)))`. The same URL, path and decimals used by many questions is one source, with one usage and reliability record. The Sources page in the [Somnia Explorer](./explorer#sources) shows that record. Each question still sends its own agent requests; results are never reused between questions.

The same source can't appear twice in one question, so one read can't be counted as two votes.

---

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.
