Overview
The DlobWebsocketClient provides a reactive interface for streaming real-time Level 2 (L2) orderbook data from Drift’s DLOB (Decentralized Limit Order Book) server via websocket.
Key Features
Reactive Streams : RxJS-based observable streams for orderbook data
Multiple Markets : Subscribe to multiple markets simultaneously
Grouping Support : Optional price grouping for aggregated orderbook views
Slot Filtering : Automatic filtering of out-of-order or stale data
Tab Return Handling : Prevents “speed run” through queued messages after tab becomes active
Automatic Deserialization : Raw orderbook data automatically deserialized
Subscription Management : Declarative subscription management with automatic cleanup
Constructor
new DlobWebsocketClient ( config : DlobWebsocketClientConfig )
Parameters
config
DlobWebsocketClientConfig
required
Configuration object Show DlobWebsocketClientConfig properties
DLOB server websocket URL (e.g., ‘wss://dlob.drift.trade/ws’)
enableIndicativeOrderbook
Enable indicative orderbook channel (default: false)
Custom slot tracker (optional, created automatically if not provided)
onFallback
(marketId: MarketId) => void
Callback when websocket connection fails for a market
Example
import { DlobWebsocketClient } from '@drift-labs/common' ;
const dlobClient = new DlobWebsocketClient ({
websocketUrl: 'wss://dlob.drift.trade/ws' ,
enableIndicativeOrderbook: false ,
onFallback : ( marketId ) => {
console . error ( `Websocket failed for ${ marketId . key } , falling back to polling` );
// Implement fallback logic here
}
});
Methods
subscribeToMarkets
Subscribe to orderbook data for one or more markets.
subscribeToMarkets (
markets : {
marketId: MarketId ;
grouping ?: OrderbookGrouping ;
}[]
): void
Parameters
Array of market subscription configurations Show Market config properties
Market identifier (perp or spot)
Optional price grouping level (e.g., 0.01, 0.1, 1)
Calling this method replaces all existing subscriptions. To add markets incrementally, include all desired markets in each call.
Example
import { MarketId , OrderbookGrouping } from '@drift-labs/common' ;
// Subscribe to multiple markets with different groupings
dlobClient . subscribeToMarkets ([
{
marketId: MarketId . createPerpMarketId ( 0 ), // SOL-PERP
grouping: 0.01 // Group by $0.01
},
{
marketId: MarketId . createPerpMarketId ( 1 ), // BTC-PERP
grouping: 1 // Group by $1
},
{
marketId: MarketId . createSpotMarketId ( 0 ), // USDC spot
// No grouping - raw orderbook
}
]);
getMarketDataStream
Get an observable stream of processed orderbook data for specific markets.
getMarketDataStream ( marketIds : MarketId []): Observable < ProcessedMarketData >
Parameters
Array of market IDs to include in the stream
Returns
RxJS Observable that emits ProcessedMarketData objects.
Show ProcessedMarketData structure
interface ProcessedMarketData {
marketId : MarketId ;
rawData : RawL2Output ;
deserializedData : {
bids : Array <{ price : number ; size : number }>;
asks : Array <{ price : number ; size : number }>;
};
slot : number ;
}
Example
import { MarketId } from '@drift-labs/common' ;
const solPerpId = MarketId . createPerpMarketId ( 0 );
const btcPerpId = MarketId . createPerpMarketId ( 1 );
// Subscribe to markets
dlobClient . subscribeToMarkets ([
{ marketId: solPerpId },
{ marketId: btcPerpId }
]);
// Get data stream
const dataStream = dlobClient . getMarketDataStream ([ solPerpId , btcPerpId ]);
const subscription = dataStream . subscribe ({
next : ( data ) => {
console . log ( `Market: ${ data . marketId . key } , Slot: ${ data . slot } ` );
console . log ( 'Best bid:' , data . deserializedData . bids [ 0 ]);
console . log ( 'Best ask:' , data . deserializedData . asks [ 0 ]);
},
error : ( err ) => console . error ( 'Stream error:' , err ),
complete : () => console . log ( 'Stream completed' )
});
// Later: cleanup
subscription . unsubscribe ();
unsubscribeAll
Unsubscribe from all markets.
Example
dlobClient . unsubscribeAll ();
handleTabReturn
Handle tab return to prevent processing backlog of queued messages.
Call this method when the browser tab becomes active again. It prevents the “speed run” effect where hundreds of queued messages are processed rapidly, potentially causing UI freezes.
Example
// Browser environment
document . addEventListener ( 'visibilitychange' , () => {
if ( ! document . hidden ) {
dlobClient . handleTabReturn ();
}
});
resetSlotTracking
Reset slot tracking for all active subscriptions.
resetSlotTracking (): void
Call this after reconnection events to ensure clean slot tracking state.
Example
// After reconnection
connection . on ( 'reconnect' , () => {
dlobClient . resetSlotTracking ();
});
destroy
Destroy the client and clean up all resources.
Always call destroy() when done with the client to prevent memory leaks.
Example
// React component cleanup
useEffect (() => {
const dlobClient = new DlobWebsocketClient ( config );
return () => {
dlobClient . destroy ();
};
}, []);
Types
OrderbookChannelTypes
type OrderbookChannelTypes = 'orderbook' | 'orderbook_indicative' ;
MarketSubscription
interface MarketSubscription {
marketId : MarketId ;
channel : OrderbookChannelTypes ;
grouping ?: OrderbookGrouping ;
}
OrderbookGrouping
type OrderbookGrouping = number ; // e.g., 0.01, 0.1, 1, 10
Advanced Usage
import { filter , map } from 'rxjs/operators' ;
const dataStream = dlobClient . getMarketDataStream ([ marketId ]);
// Only process updates with spread < $1
const filteredStream = dataStream . pipe (
filter ( data => {
const bestBid = data . deserializedData . bids [ 0 ]?. price || 0 ;
const bestAsk = data . deserializedData . asks [ 0 ]?. price || Infinity ;
const spread = bestAsk - bestBid ;
return spread < 1 ;
}),
map ( data => ({
market: data . marketId . key ,
midpoint: ( data . deserializedData . bids [ 0 ]. price + data . deserializedData . asks [ 0 ]. price ) / 2
})
));
filteredStream . subscribe (({ market , midpoint }) => {
console . log ( ` ${ market } midpoint: $ ${ midpoint } ` );
});
Multiple Orderbook Views with Different Groupings
import { MarketId } from '@drift-labs/common' ;
const marketId = MarketId . createPerpMarketId ( 0 ); // SOL-PERP
// Create three clients with different groupings
const clients = [
{ grouping: 0.01 , name: 'Fine' },
{ grouping: 0.1 , name: 'Medium' },
{ grouping: 1 , name: 'Coarse' }
]. map ( config => {
const client = new DlobWebsocketClient ({
websocketUrl: 'wss://dlob.drift.trade/ws'
});
client . subscribeToMarkets ([{
marketId ,
grouping: config . grouping
}]);
client . getMarketDataStream ([ marketId ]). subscribe ( data => {
console . log ( ` ${ config . name } view:` , data . deserializedData . bids . slice ( 0 , 5 ));
});
return client ;
});
// Cleanup
clients . forEach ( client => client . destroy ());
Combining Multiple Market Streams
import { combineLatest } from 'rxjs' ;
import { map } from 'rxjs/operators' ;
const solId = MarketId . createPerpMarketId ( 0 );
const btcId = MarketId . createPerpMarketId ( 1 );
dlobClient . subscribeToMarkets ([
{ marketId: solId },
{ marketId: btcId }
]);
const solStream = dlobClient . getMarketDataStream ([ solId ]);
const btcStream = dlobClient . getMarketDataStream ([ btcId ]);
// Get combined updates
combineLatest ([ solStream , btcStream ]). pipe (
map (([ solData , btcData ]) => ({
sol: solData . deserializedData . bids [ 0 ]?. price || 0 ,
btc: btcData . deserializedData . bids [ 0 ]?. price || 0
}))
). subscribe ( prices => {
console . log ( `SOL: $ ${ prices . sol } , BTC: $ ${ prices . btc } ` );
console . log ( `BTC/SOL ratio: ${ prices . btc / prices . sol } ` );
});
Error Handling and Fallback
import { retry , catchError } from 'rxjs/operators' ;
import { EMPTY } from 'rxjs' ;
const dlobClient = new DlobWebsocketClient ({
websocketUrl: 'wss://dlob.drift.trade/ws' ,
onFallback : ( marketId ) => {
console . error ( `WebSocket failed for ${ marketId . key } ` );
// Switch to polling fallback
startPollingFallback ( marketId );
}
});
const dataStream = dlobClient . getMarketDataStream ([ marketId ]). pipe (
retry ( 3 ), // Retry up to 3 times
catchError ( error => {
console . error ( 'Stream error after retries:' , error );
// Return empty stream to complete gracefully
return EMPTY ;
})
);
String Caching
The client implements internal string caching to reduce memory allocation:
// Internally cached strings (max 1000 entries):
// - Subscription keys: `${marketKey}_${channel}_${grouping}`
// - Result keys: `${channel}_${marketKey}`
Slot Filtering
The ResultSlotIncrementer automatically:
Filters out-of-order messages (lower slot numbers)
Handles tab return scenario (prevents processing backlog)
Tracks message timestamps for accurate filtering
Subscription Management
The reactive subscription pipeline:
Uses distinctUntilChanged to avoid redundant re-subscriptions
switchMap cancels previous subscription management when markets change
Automatically cleans up websocket connections when no longer needed
Common Patterns
React Integration
import { useEffect , useState } from 'react' ;
import { DlobWebsocketClient , MarketId } from '@drift-labs/common' ;
function OrderbookComponent ({ marketId }) {
const [ orderbook , setOrderbook ] = useState ( null );
useEffect (() => {
const client = new DlobWebsocketClient ({
websocketUrl: 'wss://dlob.drift.trade/ws'
});
client . subscribeToMarkets ([{ marketId }]);
const subscription = client . getMarketDataStream ([ marketId ]). subscribe ({
next : ( data ) => setOrderbook ( data . deserializedData ),
error : ( err ) => console . error ( err )
});
return () => {
subscription . unsubscribe ();
client . destroy ();
};
}, [ marketId ]);
if ( ! orderbook ) return < div > Loading ...</ div > ;
return (
< div >
< h3 > Best Bid : $ {orderbook.bids [ 0 ]?.price}</h3>
<h3>Best Ask: $ {orderbook.asks [ 0 ]?.price}</h3>
</div>
);
}
Calculating Orderbook Depth
const dataStream = dlobClient . getMarketDataStream ([ marketId ]);
dataStream . subscribe ( data => {
const bids = data . deserializedData . bids ;
const asks = data . deserializedData . asks ;
// Calculate total bid/ask liquidity
const bidLiquidity = bids . reduce (( sum , level ) =>
sum + ( level . price * level . size ), 0
);
const askLiquidity = asks . reduce (( sum , level ) =>
sum + ( level . price * level . size ), 0
);
console . log ( 'Bid liquidity:' , bidLiquidity );
console . log ( 'Ask liquidity:' , askLiquidity );
console . log ( 'Imbalance:' , ( bidLiquidity - askLiquidity ) / ( bidLiquidity + askLiquidity ));
});
MultiplexWebSocket Underlying websocket multiplexing infrastructure
ResultSlotIncrementer Slot tracking and filtering logic