{s}omniamarkets

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 own subscribePrices signal, 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 with listPriceFeeds(). With no priceFeed configured, 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 in raw — never round-trip money through the number. Timestamps are unix seconds (blockTimestamp is chain time, monotonic — use it as a series x-axis; observedAt is 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.