instrument channel, wrapping sdk.ws.instruments(). No authentication is required.
On subscribe the channel delivers a snapshot of every listed market (inactive ones flagged isActive: false), then live change frames. Every frame carries a market’s full current configuration rather than a delta, so the hook keeps one terminal-state entry per trading pair: each live event replaces that market’s entry outright, and each snapshot replaces the whole map — it is the authoritative market set at the moment it was taken.
Three change kinds are emitted today: listing (a market became tradable), delisting (deactivated), and config_change. config_change covers a narrow set of edits — tick size, quantity step, order-size bounds, category, and a perp market’s margin ladder (riskTiers, with changedFields naming risk_tiers). An edit to fees emits nothing. halt and unhalt are defined on InstrumentChangeKind for wire-forward-compatibility with a future market-regime producer and are not emitted by the admin trading-pair path — keep a default branch, but do not build behavior on them yet. The subscription is torn down on unmount, and re-established when sdk.ws or tradingPairId changes.
Detected broadcast lag closes the entire socket with code 1013, including an instrument-only connection. A dropped update may belong to a market that never emits again, so continuing from the retained tail cannot repair every key. With automatic reconnect enabled, the core client resubscribes and this hook replaces its market set when a fresh instrument snapshot arrives.
A reconnect request or onResync callback is not proof that the snapshot succeeded. If the snapshot is unavailable, or a decision requires independently refreshed configuration, reconcile over REST. No single endpoint carries every field this channel does, so an unfiltered hook requires multiple requests:
sdk.market.getPaginatedTradingPairs(), walkingpageuntil you have every market (one page can omit some), coversisActive,tickSize,quantityStepSize,minOrderSize,maxOrderSize,category,marketType, andmaxLeverage.sdk.market.getPerpMarketConfig(tradingPairId)covers the two remaining perp fields, which the pair list does not carry — and note the rename: this channel’sinitialMarginRate/maintenanceMarginRateareinitialMarginRatio/maintenanceMarginRatiothere. It is scoped to one pair, so following every market costs one request per perp market.
useInstruments call keeps its own state, so mounting the hook in two components subscribes twice. If both resolve to the same channel — both omitting tradingPairId, or both passing the same id — the second joins a channel the client already holds, which the server does not re-snapshot; the core helper replays its cached baseline to it instead. Mounts with different filters are distinct subscriptions and each get their own server snapshot. Either way the baseline arrives asynchronously, like a real snapshot, so instruments is empty on the first render — render an empty list, not a spinner keyed on subscribed.
Parameters
string
Optional trading pair UUID to filter on. Omit it to follow every market. An empty string is rejected by the underlying core helper and surfaces through
error.Return Values
InstrumentEventData[]
Current configuration of every market seen on the channel, ordered by
symbol and then by tradingPairId — a spot and a perp market sharing a symbol keep a stable order. Sorting uses plain codepoint comparison, so the order does not shift with the runtime locale.An inactive market stays in the list carrying isActive: false rather than disappearing — whether it was delisted while the subscription was live or arrived inactive in the snapshot — so a consumer can tell “no longer tradable” from “never seen” and still resolve the metadata of positions, orders and history on it. Filter on isActive for the tradable set. A market absent from a snapshot is a different case — it is no longer listed at all, so it leaves the list with that snapshot.Record<string, InstrumentEventData>
The same entries keyed by trading pair UUID, for direct lookup.
boolean
Whether a subscription to the instrument channel is currently registered. This is not a connection indicator: the client accepts subscriptions while the socket is down and sends them on the next open, so
true means “registered”, not “receiving”.Error | null
A rejected subscription request. The core helper validates its arguments synchronously — passing an empty
tradingPairId is the reachable case — so treat this as a bad-usage signal.It is deliberately not a transport signal: the client registers handlers regardless of socket state and logs malformed-frame failures internally, so connection loss and unparseable frames never appear here. Drive connection UI from the client’s own status and resync callbacks instead.() => void
Clear the current error state.
InstrumentEventData from @0xmonaco/types: change, tradingPairId, symbol, isActive, tickSize, quantityStepSize, minOrderSize, maxOrderSize, marketType, category, changedFields, updatedAt, plus the perp-only maxLeverage, initialMarginRate, and maintenanceMarginRate (omitted on spot markets). See the channel reference for the wire shape.
What makes the reconnect self-repairing is that the core helper delivers the snapshot as its own array rather than as per-market events, so the hook can tell where the baseline ends and drop entries missing from it. If a fresh snapshot is unavailable, the REST reconciliation above remains application-owned; reconnect does not itself fetch those endpoints.

