Price feeds
A realtime index price for an asset (BTC, ETH) from the on-chain EMA price
oracle — streamed the same way the order books are: hydrate a snapshot to get
roughly up to speed, then tail live. It's a read-only feed (an index price, not
a tradable market), so there are no orders here — just watch/fetch. The
shared client mechanics (read tiers, the reactive store, React wiring) are in
the engine guide.
The mental model
- A separate service. Prices come from a standalone EMA price-feed indexer
(Hasura GraphQL), not the markets indexer — its own endpoint, its own
WebSocket. So price watches are independent of
watchMarket: their own store, their ownsubscribePricessignal, their own health. - One endpoint, every asset. A single endpoint (
config.priceFeed) serves every tracked asset ("BTC","ETH", …); callers select an asset by symbol (case-insensitive). Discover what's tracked withlistPriceFeeds(). With nopriceFeedconfigured, the price methods throw with guidance. - Snapshot, then live tail. A watch first pulls an HTTP snapshot (current price + recent ticks), then a Hasura subscription streams updates. Unlike the order-book tail there's no seam to stitch — a price subscription is a full-state stream, so a reconnect just re-delivers current state (handled with backoff internally).
- Human numbers + exact raw. Every price is a 1e18-scaled integer on the
wire; the structs carry a display
number(price,ema) and the exact 1e18 string inraw— never round-trip money through thenumber. Timestamps are unix seconds (blockTimestampis chain time, monotonic — use it as a series x-axis;observedAtis the oracle's own source time and can drift).
Configuration
import { SomniaMarkets, SOMNIA_TESTNET_PRICE_FEED } from "@somnia-chain/markets-sdk";
const exchange = new SomniaMarkets({
indexerUrl, chain, wsRpcUrl,
priceFeed: SOMNIA_TESTNET_PRICE_FEED, // { url } — the deployed dev feed, all assets
// or your own: priceFeed: { url: "https://…/v1/graphql" }
});
priceFeed is { url, wsUrl? }; wsUrl is derived from url (http→ws,
https→wss) when omitted. Live subscriptions use the global WebSocket
(browsers, Node ≥ 21).
Reading — live
const watch = await client.watchPrice("BTC"); // snapshot + subscribe; ref-counted
const now = client.getLivePrice("BTC"); // LivePrice | null — { price, ema, blockTimestamp, raw, … }
const tape = client.getLivePriceTicks("BTC", { limit: 120 }); // recent ticks, newest first
const info = client.getLivePriceFeedInfo("BTC"); // decimals, symbol, description, latest
const state = client.getPriceStatus("BTC"); // "unwatched" | "hydrating" | "live"
watch.stop(); // release (the last release tears the socket down)
Batch variants take/return arrays and are the ergonomic choice for a multi-asset ticker — one watch, one read:
const handle = await client.watchPrices(["BTC", "ETH", "SOL"]); // snapshot + subscribe all; ref-counted
const rows = client.getLivePrices(["BTC", "ETH", "SOL"]); // (LivePrice | null)[], index-aligned
const tailing = client.isTailing(); // true while the price socket is live-tailing
handle.stop(); // release all in one call
Reads are synchronous, zero-round-trip, and current to the last pushed tick;
subscribe to changes with client.subscribePrices(listener) (independent of the
order-book subscribeLive). In React the hooks auto-watch while mounted:
import { useLivePrice, useLivePriceTicks, useWatchPrice } from "@somnia-chain/markets-sdk/react";
function Ticker() {
const price = useLivePrice("BTC"); // updates the moment a tick lands
const status = useWatchPrice("BTC"); // "hydrating" | "live" — render loading off this
const ticks = useLivePriceTicks("BTC", 120); // for a sparkline
return <span>{price ? `$${price.price.toLocaleString()}` : "…"}</span>;
}
Reading — one-shot
No watch, one HTTP round-trip each — for history, charts, or a server render:
const info = await client.fetchPriceFeedInfo("BTC"); // metadata + current price
const price = await client.fetchPrice("BTC"); // LivePrice | null
const history = await client.fetchPriceHistory("BTC", { limit: 500, from, to }); // ticks, newest first
const candles = await client.fetchPriceCandles("BTC", "M1", { from, to }); // OHLC, oldest first
Candle resolutions are "M1" / "H1" / "D1" (60s / 3600s / 86400s). count
on a candle is the number of oracle updates in the bucket (update density), not
trade volume. Like every indexer read these throw on request failure — an
empty result means "no rows", never "request failed".
From the exchange
The unified SomniaMarkets surface exposes prices by asset
(these don't need loadMarkets — a price feed isn't a symbol-keyed market):
const px = await exchange.fetchPrice("BTC"); // { symbol, price, ema, timestamp, … } | null
for await (const _ of forever) {
const tick = await exchange.watchPrice("BTC"); // resolves on each new tick
}
const ohlcv = await exchange.fetchPriceOHLCV("BTC", "1m"); // [ms, o, h, l, c, count][] — "1m"/"1h"/"1d"
The native LivePrice (raw strings + block metadata) rides on each struct's
info.