Read tiers

This reference lists the sources used by SDK reads, watches, and status accessors. Use it to budget startup dependencies and requests, and to decide whether a value is fresh enough for your application. Architecture explains transport ownership.

The tiers

TierSource and freshnessStartup requirementsCost
Market live storeMain-indexer snapshot, then applied chain events. Values reflect the last applied events, not a guarantee of the current head.Market watches need the main indexer for hydration and chain RPC for subscriptions and backfill. A user watch hydrates indexed history; live updates require watched markets.Synchronous selectors make no requests. Cold hydration, backfill, ongoing subscriptions, and recovery have network cost.
ChainChain RPC, normally over the owner's WebSocket. Reads reflect the blocks served by the node; a group of reads need not share one block.A chain WebSocket endpoint and any method-specific contract addresses.One or more RPC calls, sometimes cached or batched. Parallel calls do not guarantee one round-trip.
Main indexerEnvio/Hasura GraphQL over HTTP. Values reflect indexed state and can lag the chain.The main indexer endpoint; aggregate reads can require privileged headers.One or more HTTP requests, depending on pagination and composition. No fixed freshness delay is guaranteed.
Price-feed indexer and live price storeSeparate price-feed Hasura service. HTTP reads and subscription updates reflect its indexed prices, not direct chain prices.Price-feed HTTP configuration; watches also need its Hasura WebSocket endpoint.Fetches make HTTP requests. Watches hydrate over HTTP and open one socket per watched asset key. Store selectors are synchronous and make no requests.

Client construction currently requires indexerUrl, even if the caller only uses chain reads. This configuration requirement does not mean that every method requests the main indexer. The chain socket opens lazily on chain I/O. Price-feed transport and failures are separate from the market store.

A result can combine tiers with different timestamps. The SDK does not generally compare indexed state with the current chain head to certify freshness. Check the method-specific inputs below before treating a derived value as current.

Return contract

  • An indexer point read resolves to null when the row does not exist and throws IndexerError when the request fails.
  • An indexer list read resolves to [] when there are no rows and throws IndexerError when the request fails.
  • A chain read throws on failure. It never returns null in place of a failure.
  • A live-store read returns empty data for a market with no active watch. getWatchStatus(pool) is "unwatched" in that state.
  • A live price read keeps answering with its last values after the server rejects the asset's subscription. getPriceStatus(asset) is "error" in that state, and the rejection is also emitted as a warn event on the debug channel.

Exchange API (SomniaMarkets)

MethodTier
loadMarkets, fetchMarketsIndexer, plus chain reads for perp discovery and token symbols
fetchOrderBookChain
fetchTrades, fetchOHLCV, fetchOpenOrders, fetchOrders, fetchMyTrades, fetchOpenStopOrders, fetchPortfolioAnalytics, fetchFundingRateHistoryIndexer
fetchTickerCached market metadata and main-indexer candles; on perps, also best-effort chain state. last can fall back to cached market data. timestamp is response time, not source freshness. A failed perp state read leaves candle-derived data.
fetchBalanceLoads market/currency metadata through loadMarkets, then reads chain token/native balances and main-indexer binary holdings. Cost depends on currencies and metadata discovery.
fetchFundingRate, fetchPositionsChain
fetchPrice, fetchPriceOHLCVSeparate price-feed Hasura HTTP
market, priceToPrecision, amountToPrecisionLocal
watchOrderBook, watchTrades, watchOrders, watchMyTradesLive store. The first call opens the market watch.
watchPriceLive price store. The first call opens the price watch.

A watch* call resolves with the current value on its first call and on every later call resolves when the channel's value changes.

Engine (SomniaMarketsClient)

The tables in this section are the canonical Engine inventory. Names refer to exchange.client, not similarly named Exchange methods. The inventory guard checks membership only; source and freshness claims require source review. Configuration, cleanup, escape hatches, and capability factories are outside this read inventory.

<!-- engine-inventory:start -->

Live store

GroupMethods
Market selectors and statusgetLiveMarkets, getLiveMarketByPool, getLiveMarketByAddress, getLiveFills, getLiveUserFills, getLiveUserOrders, getLiveFundingUpdates, getLiveBinaryOrderBook, getLiveBinaryOrderBookByMarket, getLiveSpotOrderBook, getLiveStatus, getWatchStatus, isTailing, subscribeLive
Price feedgetLivePrice, getLivePrices, getLivePriceTicks, getLivePriceFeedInfo, getPriceStatus, subscribePrices
WatcheswatchMarket, watchMarkets, watchUser, watchPrice, watchPrices

Chain

GroupMethods
Books and ordersgetBinaryOrderBook, getSpotOrderBook, getOrderOnchain, getOwnOpenOrdersOnchain, getAllOpenOrdersOnchain
Markets and systemgetMarketOnchain, getContractMeta, getHeadBlock, getSystemInfo, getTransactionSummary, getPoolCreator, getFreePools, getOnchainResolutionPrice, getMaxVenueFeeBps, encodeBinaryVenueFeeParams
Balances and allowancesgetErc20Balance, getErc20Metadata, getErc20Allowance, getBalances, getNativeBalance, getOutcomeBalance, getVaultBalance, getOwnLockedBalance, getLockedTokenBreakdown
Operators and buildersgetOperatorPermissionsRegistry, isOperatorAuthorized, isGloballyApproved, isApprovedForPool, getMaxBuilderFeeBpsTimes1k, getBuilderApproval, getEffectiveBuilderApproval, getStopOrderSomiPayment, getPerpStopOrderSomiPayment, getPerpStopOrder, getUnclaimedPerpStopSomi, getManualVaultMode, getAutoPullRequirement, convertToQuoteAtPriceCeil
PerpsgetPerpState, getPerpFeedStatus, getPerpPosition, getMarginAccount, getAccountHealth, getLiquidationPrice, previewPerpLiquidationPrice, getPerpLeverage, getPerpPositionAnalytics, listPerpPositionAnalytics, getMaxPerpOrderSize, previewPerpOrderMargin, previewPerpClosePnl, getPerpSideHolders, getBankruptcyPrice, getPerpSystemConfig, getInsuranceFundState, getLiquidationEngineConfig, tryGetPerpAccountEquity, getPerpCollateralBasis, listPerpPoolStatuses, listTradeablePerpPools, isPerpPoolRegistered, getPerpRiskParams, getPerpHealthSnapshot, getEffectiveImfBps, getPerpMaxLeverage, getPerpLeverageImSurcharge, tryGetPerpLeverageImSurcharge, readPerpMarketFromChain, quotePerpFundingPayer, getPerpMainFunding, getPerpLinkedWalletRegistry, getPerpWalletPullCapacity, getPerpWalletLinkage, listPerpLinkedChildren, getPerpMaxLinkedChildren, getPerpFundingPremium, meetsPerpImForFill, quoteMeetsPerpImForOrder, quotePerpOrderTopUp
Oracle hubgetSchedulingCost, earmarkedOf, creditOf, outstandingOf, withdrawableOf, payerCreditOf, payerOf, resolveReserve, quoteCreateMarketValue
SomniaLendclient.lend.listReserves, client.lend.getAccount

Indexer

GroupMethods
MarketslistMarkets, listRegistryMarkets, listRegistryMarketsChecked, countMarkets, countMarketsBounded, getMarket, getMarketByPool, listMarketsByPool, listBinaryMarkets, listLiveBinaryMarkets, listPastBinaryMarkets, getBinaryMarket, getBinaryMarketByAddress, listBinaryVenueIds, listBinaryAssets, countBinaryMarkets, countBinaryMarketsBounded, listSpotMarkets, getSpotMarket, listPerpMarkets, getPerpMarket, getMarketFees, getMarketStatusHistory
HistorygetCandles, getFills, getFill, getUserFills, countUserFills, getOrderFills, getOrder, getOrders, getOpenOrders, countOrders, listSweepableOrders, getMarketActivity, getTradeContext, getRouterActions, getUserFillsPage, getLatestActiveBlock
PortfoliosgetPortfolio, getSpotPortfolio, getPerpPortfolio, getOutcomeBalances, getSpotStopOrders, listPerpStopOrders, listPerpOrderHistory, listPerpPositions
ResolutiongetMarketResolution, getOpeningPrices, getResolutionPrices, getBookTops
Fees and perps historylistProtocolFees, listBuilderFees, listSettlementFees, listBuilderApprovals, getVaultPayoutFallbacks, getFundingPayments, getMarginEvents, listLiquidations, getLiquidations, listFundingRateHistory, listFundingRateCandles, listPerpFees, getOpenInterestHistory, getFundingRateHistory, listPerpOrderRejections, listPerpInsuranceFundEvents, listPerpWalletLinkEvents, listPerpMarginPulls, listPerpMainFundingEvents
Control planelistOperators, getOperator, countOperators, listVenues, getVenue, countVenues, listMarketCreators, getMarketCreator, listOracleAdapters, getOracleAdapter, listSeries, getSeries, getOracleQuestion, listOracleQuestions, getOperatorHubAccount, listOperatorHubAccounts, listOracleBinds, listOracleCallbacks, getSyncStatus, getPool, getPoolBindings

Price-feed HTTP reads

GroupMethods
Current prices and feed metadatafetchPrice, fetchPrices, fetchPriceFeedInfo, listPriceFeeds
Price historyfetchPriceHistory, fetchPriceCandles

Mixed and derived reads

Inputs and behaviorMethods
Main indexer plus a mandatory chain block timestamp lookup. The timestamp anchors indexed activity; it does not make indexed rows current.getBlockActivity, getAdjacentActiveBlocks
Main-indexer activity. Without a caller-supplied anchor, tries a chain receipt and block lookup first. Failure to obtain that anchor leaves the indexer-only path available.getTransactionActivity
Synchronous live binary book and stored market decimals. Needs a hydrated market watch for useful depth.quoteBinaryOrder
Live binary book plus cached chain tick/lot grid. A market-id target can add an indexer lookup when the store cannot resolve its pool. An empty live book is not replaced by an indexer book.quoteBinaryStake, quoteBinarySell
Chain grid read cached per pool for the client lifetime. Later administrative grid changes do not invalidate this cache.getBinaryBookParams
Chain closing-price view, including its capture state.getClosingPrice
Main-indexer candles; a market-id target also resolves its pool through the main indexer.getMarketStats24h
Main-indexer market metadata, fills, router actions, and outcome balances, plus chain book top for the mark clamp. Without configured chain access, uses indexed last price alone; an operational chain failure propagates.getBinaryPositionPnL
Main-indexer positions and market metadata, fills, router actions, and indexed book tops. All inputs can lag; this does not use the live book or a chain book.getOpenPositionsWithPnL
Main-indexer positions and resolution data, plus indexed settlement fees for winning markets. This estimates claimable amounts from indexed state.getClaimable

| Independent RPC head, then main-indexer metadata plus processed-block time. Raw measurements, no SDK stale threshold. | getIndexerFreshness | | One independent head for response-associated reads, using the same owner. | createObservedReads | | Per-asset price watch lifecycle, without I/O. | getPriceHealth, listPriceHealth |

<!-- engine-inventory:end -->

Watch startup and updates

Engine watches return a handle with stop(). Repeated handles for the same scope share its work. The scope normally remains for about 30 seconds after the last handle stops, so a quick remount can reuse it. After teardown, a new watch hydrates again.

Market watches hydrate a main-indexer snapshot before chain backfill and live updates. watchUser hydrates the account's indexed order/fill history. It opens no subscription of its own. Subsequent account updates come only from market scopes watched by this client; it is not an account-wide chain subscription.

Price watches hydrate metadata and recent prices from the separate price-feed HTTP service. Each asset key owns a Hasura socket for its price and tick subscriptions. A resolved watch means its snapshot landed, not that the socket has connected. Check getPriceStatus for state. Rejected subscriptions report error, while synchronous price reads retain stale values. These failures do not change market-watch status.

React hooks

Hook familyTier
useLive*, useWatchMarket, useWatchUser, useLiveStatus, useIsTailingLive store. The pool-keyed hooks open the market watch while mounted.
useLivePrice, useLivePriceTicks, useLivePriceFeedInfo, useWatchPriceLive price store
usePortfolio, useMarkets, useCandles, useFundingRateSeries, useMarketFees, useOperators, useMarketCreators, useOracleAdaptersIndexer
useLendReserves, useLendAccountChain

useIndexerQuery runs the supplied callback on mount, owner or dependency changes, and explicit refetch. It does not poll or automatically refresh on live events. The callback determines its source and freshness. The hook passes an abort signal and fences superseded responses; cancellation reaches the request only if the callback forwards that signal to an operation that supports it.

Automatic hooks preserve background acquisition semantics: failures use the configured debug/console warning channel and do not throw during render. This includes useFundingRateSeries; its state.error still contains history query failures. Opt into useWatchMarketResult, useWatchUserResult, or useWatchPriceResult for a typed acquisition error, which a component may explicitly throw into an ErrorBoundary. Missing-provider validation still throws InvalidInputError.

Response-associated indexer observations

client.createObservedReads() observes one independent RPC head. Its read and batch methods accept the operations in ObservedReadOperation. The roster includes every ordinary main-indexer domain read, including mixed-tier derived reads. getSyncStatus and getIndexerFreshness are source diagnostics, not row reads. Live-store reads, chain-only reads, write capabilities and the separate price service are excluded.

The envelope keeps the domain value in data. observation.latestProcessedBlock is the minimum metadata watermark across successful contributing responses. Missing metadata in any response leaves it null. startedAt, completedAt and responseCount describe the observation span. The metadata is selected beside the rows in the same GraphQL request, without a separate metadata request. A rejected privileged aggregate attempt does not contribute; its successful fallback does. Observed portfolio reads refresh their scope inside the logical read so a cached pool set cannot hide an older contributing response.

These are indexing watermarks. They do not claim each entity changed at that block, that the RPC itself is current, or that separate requests share an atomic historical snapshot. Keep an envelope with its value; later reads cannot update its observation. Create a new observed capability to refresh the shared head. Closing the owner cancels pending observations; a stopped capability cannot reopen transports.

fetchStatus() remains a local-only three-field chain-tail snapshot. Opt into fetchDataStatus() to read RPC and the main indexer once. Its top-level status retains chain-tail meaning; indexer contains independent measurements and prices lists precise per-asset health. API-only monitors keep using client.getSyncStatus(chainId) with no RPC dependency.

The concrete exchange.client exposes the additive SomniaMarketsClientWithObservations contract. The original SomniaMarketsClient interface and existing providers require no extra members. Ordinary reads retain their original document selections and role permissions. Only observed reads select the response watermark. stopLive() invalidates observed capabilities without aborting ordinary pending indexer reads; caller cancellation still reaches those reads.

Consumer example

Use the configured exchange in dreamDEX or another consumer. The downstream dreamDEX hook edit remains outside this repository.

ts
const observed = await exchange.client.createObservedReads();
const [markets, count] = await observed.batch([
  { operation: "listMarkets", args: [{ limit: 20 }] },
  { operation: "countMarkets", args: [] },
]);
console.log(markets.data, markets.observation.latestProcessedBlock);
console.log(count.data, count.observation.independentHead.blockNumber);
const freshness = await exchange.client.getIndexerFreshness(exchange.client.config.chain.id);
console.log(freshness?.lagBlocks, freshness?.lagSeconds);

For a local end-to-end observation, start ./demo-clob.sh up, construct the exchange with the local chain and deployment configuration used by the explorer, and run this example against http://127.0.0.1:8085/v1/graphql and ws://127.0.0.1:8545. Use chain id 31337. No production configuration or transaction is required.