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.
Setup
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.
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:
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 bymarketId. 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 aMarketContext 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 optionaldepth 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’sportfolioValue 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 forsize 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 concurrentorderbook 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 declareddisconnected. - 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 terminaldisconnectedto 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 newsubscribe()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.
Type Safety
Thesubscribe 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').