Skip to main content
The PerpsWsClient provides streaming data over WebSocket. Connections are made directly to the provider for lowest latency — the SDK discovers the WebSocket URL from GET /providers automatically. All incoming data is normalized to the same types used by the account and market reads, so your application logic works identically whether data comes from a read or a WebSocket event.
WebSocket support is provider-specific. Not every provider serves every channel — see Channel support and check ws.streamsCandles() / ws.streamsAvailableToTrade() before subscribing.

Setup

Register only the providers you use. See Lighter WebSocket for lighter-rh and Ondo WebSocket for the authenticated channels. The PerpsWsClient lazily initializes connections — no WebSocket is opened until you call subscribe(). If you trade with a PerpsClient, build the PerpsWsClient from its low-level client: new PerpsWsClient(perps.client, { wsProviders }). Lighter and Ondo resolve their auth tokens from the providers registered on that client.
ws.close() is terminal. Afterwards subscribe() and subscribeQuote() reject with an SDKError (“PerpsWsClient is closed”). Create the client once, close it only when the app shuts down, and create a new instance to subscribe again.

Subscribe

subscribe() is async and returns a Promise<() => void> — awaiting it yields the unsubscribe function. The descriptor’s dex field holds the provider key — the key from getProviders(), e.g., 'hyperliquid' or 'lighter'. dex is the literal field name; the value is always a provider key.
subscribe() accepts an optional third argument, onStatus, a listener for the underlying connection’s health. It fires 'reconnecting' on a transient drop and the terminal 'disconnected' once auto-reconnect is abandoned, so consumers can surface a reconnecting/disconnected state instead of silently showing stale data.
The exported types WsConnectionStatus ('connected' | 'reconnecting' | 'disconnected'), WsStatusListener (the onStatus callback) and EventForSubscription<S> (the event type for a subscription descriptor S) come from @lifi/perps-sdk. subscribe() resolves only after the channel is open. If a component unmounts before the promise resolves, call the unsubscribe function as soon as it arrives:
Unsupported channels and providers without a WebSocket URL reject with a plain Error (no PerpsErrorCode). A provider key with no registered factory rejects with a PerpsError.

Channel support

A rejected subscription fails with "<Provider> WS does not support channel: <channel>.". The synchronous probes read the registered factory, make no network call, and return false when no factory is registered for the provider:

Available Channels

marketsContext

Full context for every market on the provider, keyed by marketId. Use this when a UI needs mark price, oracle price, 24h volume, open interest, or funding for the whole market list.

marketContext

Full context for a specific market. The event data is a MarketContext snapshot, so consumers should replace their cached context for that market when a new event arrives.

orderbook

L2 orderbook for a specific market. Fires on every book update. Pass an optional depth to limit price levels (provider default if omitted), and an optional priceStep to request a price granularity — the desired bucket width in quote currency (e.g. 10 buckets a BTC book into $10-wide levels). Providers that aggregate the book server-side honour priceStep best-effort; providers that stream the full book ignore it, and undefined requests full precision.

candle

OHLCV candle updates for a specific market and interval.

trades

Public trade prints for a specific market. Fires as trades execute on the venue.

orderUpdates

Live order updates for a user’s orders. Requires the user’s wallet address.

fills

Live fill notifications for a user’s trades. The first frame can be empty (the subscribe snapshot).

positions

Live positions for a user. Each frame is the complete set of open positions: replace your state, never merge. A closed position is the market’s absence from the next frame.

spotBalances

Live spot balance updates for a user (e.g., USDC collateral held on the venue).

accountSummary

Live account roll-up for a user — portfolio value, available margin, margin used, and unrealized PnL. Requires the user’s wallet address. Field coverage matches the venue’s own stream (e.g. Hyperliquid’s portfolioValue covers perps equity only; spot balances have their own spotBalances channel).

availableToTrade

Live per-market available-to-trade updates for a user — the amount the account can still buy and sell on one market. The venue recomputes both sides whenever the account’s margin or position changes. Hyperliquid is the only provider that streams this channel; on Lighter and Ondo the subscription rejects.

Quotes

getQuote

One-shot fill quote for size USD notional of symbol on a single provider: VWAP expected fill, price impact (bps), base-tier taker fee, and funding (perps only). getQuote is a plain request/response service (not a WS subscription) — the streaming counterpart is subscribeQuote, and both share the GetQuoteParams shape. Cross-venue comparison is a consumer-side loop over the providers from getProviders().
GetQuoteParams: Throws a PerpsError when the provider plugin is not registered or on network / parsing errors. It throws MarketNotFound (see Error Codes) both when no market matches the symbol and type and when the market is known but the provider returned no live price for it. Quote fields:

subscribeQuote

Stream live fill quotes for a market. The provider’s WS plugin layers the quote on its orderbook channel, so a concurrent orderbook subscription on the same market shares a single wire subscription. Async; returns a Promise<() => void> unsubscribe function.
onQuote is a QuoteListener: (quote: Quote) => void. subscribeQuote throws a PerpsError when no WS provider factory is registered for params.provider, and MarketNotFound when no market matches the symbol and type.

Multiple Subscriptions

You can subscribe to multiple channels simultaneously. Each returns an independent unsubscribe function.

Connection Lifecycle

  • Lazy connection — The WebSocket connects on the first subscribe() call for each provider
  • Automatic reconnection — Jittered exponential backoff on disconnect. The first-retry delay is drawn once per socket from [500, 1500)ms (to de-synchronize reconnect storms) and grows exponentially, capped at 10s. After 10 attempts the socket is declared disconnected.
  • Resubscription — All active subscriptions are automatically re-sent after reconnection
  • Keepalive — A ping every 30 seconds, for providers that define a ping payload (all three shipped providers do). If no inbound frame arrives within three ping intervals (90 seconds), the stale-stream watchdog forces a reconnect.
  • Connect timeout and send buffer — A connection attempt times out after 4 seconds. Messages sent while the socket is not open are buffered, up to 1000, and replayed on open.
  • Refcounted — Duplicate subscriptions to the same channel are deduplicated; the upstream subscription is removed only when the last listener unsubscribes
  • Manual reconnect — ws.reconnect(provider) forces an already-created provider whose socket reached terminal disconnected to reconnect with a fresh retry budget. It is a safe no-op when the provider is unknown or not terminal. subscribe() also re-arms a terminal socket, so a new subscribe() call revives it.

Reads lag writes

A venue’s account, order and fill reads can return pre-write data for a couple of seconds after a write (order placement, updateLeverage, sendAsset, margin updates). A refetch started before a stream frame arrives can also overwrite that newer frame. Apply the write optimistically, let stream frames replace state, and re-read once after a short delay rather than immediately.

Cleanup

Close the client when the app shuts down to release WebSocket connections. close() is terminal — see Setup.
Individual subscriptions can be cleaned up by calling the returned unsubscribe function. The underlying WebSocket connection stays open as long as at least one subscription is active on that provider.

Type Safety

The subscribe method is fully typed — the callback receives the correct event type based on the subscription channel:

Exported types

@lifi/perps-types exports Subscription and SubscriptionEvent, the unions of every channel. It also exports one subscription type and one event type for each channel: MarketsContextSubscription and MarketsContextEvent, then MarketContext*, Orderbook*, Candle*, Trades*, OrderUpdates*, Fills*, Positions*, SpotBalances*, AccountSummary*, and AvailableToTrade* in the same pattern. ProviderId is a plain string. MetaProvider is the type of the constant META_PROVIDER ('meta').