# Reference

> Every Somnia Price Feeds read, what it returns, and the GraphQL indexer for historical prices.

Every read below is a `view` on the one proxy address. Prices are scaled by `decimals` (18), timestamps are Unix **milliseconds**, and **nothing reverts** except the `*OrRevert` helpers: an untracked, removed or never-written pair or index returns zero in every position.

## Spot, by pair: `IPriceFeed`

| Call | Returns |
| --- | --- |
| `latest(string symbol)` | `(uint256 price, uint256 updatedAtMs)` |
| `latestWithSource(string symbol)` | `(uint256 price, uint256 updatedAtMs, uint256 sourceUpdatedAtMs)` |
| `latestBatch(string[] symbols)` | `(uint256[] prices, uint256[] updatedAtsMs)`, in the input's order |
| `latestBatchWithSource(string[] symbols)` | `(uint256[] prices, uint256[] updatedAtsMs, uint256[] sourceUpdatedAtsMs)` |
| `symbols()` | `string[]`: the current pairs, in the order they were added |
| `isTracked(string symbol)` | `bool` |

## Mark, by pair: `IMarkPriceFeed`

| Call | Returns |
| --- | --- |
| `latestMark(string symbol)` | `(uint256 markPrice, uint256 updatedAtMs)`; `updatedAtMs` always matches the spot cell |
| `latestMarkBatch(string[] symbols)` | `(uint256[] markPrices, uint256[] updatedAtsMs)` |

## By stable index: `IIndexedPriceFeed`

One SLOAD cheaper. Resolve `index → pair` once from `SymbolAdded`.

| Call | Returns |
| --- | --- |
| `latestByIndex(uint256 index)` | `(uint256 price, uint256 updatedAtMs)` |
| `latestByIndexWithSource(uint256 index)` | `(uint256 price, uint256 updatedAtMs, uint256 sourceUpdatedAtMs)` |
| `latestMarkByIndex(uint256 index)` | `(uint256 markPrice, uint256 updatedAtMs)` |
| `latestBatchByIndex(uint256[] indices)` | `(uint256[] prices, uint256[] updatedAtsMs)` |
| `latestMarkBatchByIndex(uint256[] indices)` | `(uint256[] markPrices, uint256[] updatedAtsMs)` |
| `symbolOf(uint256 index)` | `(string symbol, bool active)`; `active` is `false` once removed |
| `activeIndices()` | `uint256[]`: the live set, in the order sent to the agents each tick |

## Safe reads, quality and liveness: `IQualifiedPriceFeed`

| Call | Returns |
| --- | --- |
| `latestOrRevert(string symbol, uint256 maxAgeMs)` | `uint256 price`; **reverts** `NoPrice()` or `StalePrice(ageMs, maxAgeMs)` |
| `latestByIndexOrRevert(uint256 index, uint256 maxAgeMs)` | `uint256 price`, with the same reverts |
| `latestMarkOrRevert(string symbol, uint256 maxAgeMs)` | `uint256 markPrice`, with the same reverts |
| `latestWithQuality(string symbol)` | `(uint256 price, uint256 markPrice, uint256 updatedAtMs, uint256 sourceUpdatedAtMs, uint256 validatorCount, uint256 medianControlCount, uint256 flags)` |
| `latestByIndexWithQuality(uint256 index)` | The same seven values |
| `feedHealth()` | `(uint256 lastTickMs, uint256 lastKickMs, uint256 lastIngestMs, uint256 lastFailureMs, uint8 lastFailureCode, bool subscriptionArmed, uint256 balance)` |
| `livenessStatus(uint256 maxTickAgeMs)` | `(Liveness status, uint256 lastTickMs, uint256 lastIngestMs)`; `status` is `0 Unknown`, `1 Healthy`, `2 GlobalStale` or `3 ChainDead` |
| `FLAG_MARK_RESYNCED()` / `FLAG_DEVIATION()` | `uint256` bit masks (`1` and `2`) |

## Configuration (on the scheduler)

| Call | Returns |
| --- | --- |
| `decimals()` | `uint8`: the price scale exponent (18) |
| `emaAlpha()` | `uint256`: the mark's smoothing, in 1e18 fixed point |
| `emaMaxAgeMs()` | `uint64`: how old a mark can be before the next one resyncs to spot |
| `symbolsVersion()` | `uint64`: increases with every change to the pair set |
| `intervalMs()` | `uint64`: the tick period in milliseconds (0 when the schedule isn't running) |
| `phaseCount()` | `uint64`: 1 while the schedule runs, 0 when it doesn't |

## What comes back

```bash
cast call 0x1998C88D54a240b8C671493f27f58eC416b81D7F \
  'latestWithQuality(string)(uint256,uint256,uint256,uint256,uint256,uint256,uint256)' \
  "BTC/USDC" --rpc-url https://api.infra.mainnet.somnia.network
```

```
64984750000000000000000   price              → 64,984.75 USDC (÷ 1e18)
64985417937502157000642   markPrice          → 64,985.42 USDC
        1786359310000     updatedAtMs        → when the cell was written (always ...000)
        1786359308831     sourceUpdatedAtMs  → the agent took in the data 1.17 s before the write
                    3     validatorCount     → three validators backed this median
                    0     medianControlCount → unknown (not "no venues")
                    0     flags              → no mark resync this tick
```

Divide by `10 ** decimals()` for a human-readable price. In Solidity, keep the fixed point and scale at the last step; never go through a float. The typed equivalent is one line:

```solidity
uint256 btc = FEED.latestOrRevert("BTC/USDC", 5_000); // 64984750000000000000000
```

## Historical data

An indexer serves the feed's history over GraphQL, for charts, backtests and monitoring:

| Network | Endpoint |
| --- | --- |
| Mainnet | `https://price-feed.prd.oracle.somnia.host/v1/graphql` |
| Testnet | `https://price-feed.dev.oracle.somnia.host/v1/graphql` |

> [!WARNING]
> **For observation only.** Don't settle, liquidate or price anything from GraphQL: it lags the chain, can be resynced, and isn't part of what the feed guarantees. Anything that moves value reads the feed onchain.

| Entity | One row per |
| --- | --- |
| `Feed` | Pair: its latest state, like Chainlink's `latestRoundData` |
| `PricePoint` | Tick and pair: every price written |
| `Candle` | Pair and period: OHLC at `M1`, `H1` and `D1` |
| `Symbol` | Index: the index → pair registry |

---

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.
