Skip to Content
DevelopersDrift SDKDLOB (Decentralized Limit Order Book)

DLOB (Decentralized Limit Order Book)

What is the DLOB?

The Decentralized Limit Order Book (DLOB) is Drift’s on-chain representation of all resting limit orders across all users. Unlike a traditional centralized order book maintained by an exchange, the DLOB is constructed locally by reading on-chain user accounts and aggregating their open limit orders into a price-ordered book.

When a new order arrives, keepers and market makers query the DLOB to find matching resting orders. Drift’s matching engine then executes fills between the incoming taker and the resting makers on the DLOB, or routes to the AMM as a fallback.

When you’d use the DLOB:

  • Market makers: quote against the current best bid/ask and respond to order flow
  • Keeper/filler bots: identify and fill matchable orders for fee rewards
  • Orderbook UIs: display a live aggregated L2 or L3 view of the market

SDK Usage

The SDK provides several classes to subscribe to and query the DLOB.

OrderSubscriber

Subscribes to all open user orders in real-time via WebSocket or polling. This is the raw data feed that the DLOB is built from. You need this running before you can maintain a local DLOB.

import { OrderSubscriber } from "@drift-labs/sdk"; const orderSubscriber = new OrderSubscriber({ driftClient, subscriptionConfig: { type: "websocket" }, fastDecode: true, decodeData: true, }); await orderSubscriber.subscribe();
Class OrderSubscriberReference ↗
PropertyTypeRequired
driftClient
DriftClient
Yes
usersAccounts
Map<string, { slot: number; userAccount: UserAccount; }>
Yes
subscription
PollingSubscription | WebsocketSubscription | grpcSubscription
Yes
commitment
Commitment
Yes
eventEmitter
StrictEventEmitter<EventEmitter, OrderSubscriberEvents>
Yes
fetchPromise
Promise<void>
No
fetchPromiseResolver
() => void
Yes
mostRecentSlot
number
Yes
decodeFn
(name: string, data: Buffer) => UserAccount
Yes
decodeData
boolean
No
fetchAllNonIdleUsers
boolean
No
subscribe
() => Promise<void>
Yes
fetch
() => Promise<void>
Yes
tryUpdateUserAccount
(key: string, dataType: "raw" | "decoded" | "buffer", data: UserAccount | Buffer | string[], slot: number) => void
Yes
createDLOB
(protectedMakerParamsMap?: ProtectMakerParamsMap | undefined) => DLOB
Creates a new DLOB for the order subscriber to fill. This will allow a caller to extend the DLOB Subscriber with a custom DLOB type.
Yes
getDLOB
(slot: number, protectedMakerParamsMap?: ProtectMakerParamsMap | undefined) => Promise<DLOB>
Yes
getSlot
() => number
Yes
addPubkey
(userAccountPublicKey: PublicKey) => Promise<void>
Yes
mustGetUserAccount
(key: string) => Promise<UserAccount>
Yes
unsubscribe
() => Promise<void>
Yes

DLOBSubscriber

Builds and continuously maintains an aggregated orderbook from the order stream. Use this when you need a live L2/L3 view without manually managing the DLOB state.

import { DLOBSubscriber } from "@drift-labs/sdk"; const dlobSubscriber = new DLOBSubscriber({ driftClient, dlobSource: orderSubscriber, // feeds from your OrderSubscriber slotSource: slotSubscriber, // needed for order expiry/timing updateFrequency: 1000, // rebuild the book every 1000ms }); await dlobSubscriber.subscribe();
Class DLOBSubscriberReference ↗
PropertyTypeRequired
driftClient
DriftClient
Yes
dlobSource
DLOBSource
Yes
slotSource
SlotSource
Yes
updateFrequency
number
Yes
intervalId
Timeout
No
dlob
DLOB
Yes
eventEmitter
StrictEventEmitter<EventEmitter, DLOBSubscriberEvents>
Yes
protectedMakerView
boolean
Yes
subscribe
() => Promise<void>
Yes
getProtectedMakerParamsMap
() => ProtectMakerParamsMap | undefined
Yes
updateDLOB
() => Promise<void>
Yes
getDLOB
() => DLOB
Yes
getL2
({ marketName, marketIndex, marketType, depth, includeVamm, numVammOrders, fallbackL2Generators, latestSlot, }: { marketName?: string; marketIndex?: number; marketType?: MarketType; depth?: number; includeVamm?: boolean; numVammOrders?: number; fallbackL2Generators?: L2OrderBookGenerator[]; latestSlot?: any; }) => L...
Get the L2 order book for a given market.
Yes
getL3
({ marketName, marketIndex, marketType, }: { marketName?: string; marketIndex?: number; marketType?: MarketType; }) => L3OrderBook
Get the L3 order book for a given market.
Yes
unsubscribe
() => Promise<void>
Yes

SlotSubscriber

Tracks the current Solana slot. Required for timing-sensitive operations like JIT auction windows and order expiry checks.

import { SlotSubscriber } from "@drift-labs/sdk"; const slotSubscriber = new SlotSubscriber(connection); await slotSubscriber.subscribe(); const currentSlot = slotSubscriber.getSlot();
Class SlotSubscriberReference ↗
PropertyTypeRequired
connection
any
Yes
currentSlot
number
Yes
subscriptionId
number
Yes
eventEmitter
StrictEventEmitter<EventEmitter, SlotSubscriberEvents>
Yes
timeoutId
Timeout
No
resubTimeoutMs
number
No
isUnsubscribing
boolean
Yes
receivingData
boolean
Yes
subscribe
() => Promise<void>
Yes
updateCurrentSlot
any
Yes
setTimeout
any
Yes
getSlot
() => number
Yes
unsubscribe
(onResub?: boolean | undefined) => Promise<void>
Yes

DLOB

The core data structure with bid/ask sides and query methods. Under normal usage you access this via dlobSubscriber.getDLOB() rather than instantiating it directly.

import { DLOB } from "@drift-labs/sdk"; // Access via DLOBSubscriber (recommended) const dlob = dlobSubscriber.getDLOB();
Class DLOBReference ↗
PropertyTypeRequired
openOrders
Map<MarketTypeStr, Set<string>>
Yes
orderLists
Map<MarketTypeStr, Map<number, MarketNodeLists>>
Yes
maxSlotForRestingLimitOrders
number
Yes
initialized
boolean
Yes
protectedMakerParamsMap
ProtectMakerParamsMap
Yes
init
any
Yes
clear
() => void
Yes
initFromUserMap
(userMap: UserMap, slot: number) => Promise<boolean>
initializes a new DLOB instance
Yes
insertOrder
(order: Order, userAccount: string, slot: number, isUserProtectedMaker: boolean, baseAssetAmount: BN, onInsert?: OrderBookCallback | undefined) => void
Yes
insertSignedMsgOrder
(order: Order, userAccount: string, isUserProtectedMaker: boolean, baseAssetAmount?: any, onInsert?: OrderBookCallback | undefined) => void
Yes
addOrderList
(marketType: MarketTypeStr, marketIndex: number) => void
Yes
delete
(order: Order, userAccount: PublicKey, slot: number, isUserProtectedMaker: boolean, onDelete?: OrderBookCallback | undefined) => void
Yes
getListForOnChainOrder
(order: Order, slot: number, isProtectedMaker: boolean) => NodeList<any> | undefined
Yes
updateRestingLimitOrders
(slot: number) => void
Yes
updateRestingLimitOrdersForMarketType
(slot: number, marketTypeStr: MarketTypeStr) => void
Yes
getOrder
(orderId: number, userAccount: PublicKey) => Order | undefined
Yes
findNodesToFill
<T extends MarketType>(marketIndex: number, fallbackBid: any, fallbackAsk: any, slot: number, ts: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, stateAccount: StateAccount, marketAccount: T extends { ...; } ? SpotMarketAccount : PerpMarketAccount) => NodeT...
Yes
getMakerRebate
(marketType: MarketType, stateAccount: StateAccount, marketAccount: SpotMarketAccount | PerpMarketAccount) => { ...; }
Yes
mergeNodesToFill
(restingLimitOrderNodesToFill: NodeToFill[], takingOrderNodesToFill: NodeToFill[]) => NodeToFill[]
Yes
findRestingLimitOrderNodesToFill
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, isAmmPaused: boolean, stateAccount: StateAccount, marketAccount: T extends { ...; } ? SpotMarketAccount : PerpMarketAccount, makerRebateNumerator: number, make...
Yes
findTakingNodesToFill
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, isAmmPaused: boolean, state: StateAccount, marketAccount: T extends { ...; } ? SpotMarketAccount : PerpMarketAccount, fallbackAsk: any, fallbackBid?: any) => N...
Yes
findTakingNodesCrossingMakerNodes
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, takerNodeGenerator: Generator<...>, makerNodeGeneratorFn: (marketIndex: number, slot: number, marketType: MarketType, oraclePriceData: T extends { ...; } ? Ora...
Yes
findNodesCrossingFallbackLiquidity
<T extends MarketType>(marketType: T, slot: number, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, nodeGenerator: Generator<DLOBNode, any, any>, doesCross: (nodePrice: any) => boolean, state: StateAccount, marketAccount: T extends { ...; } ? SpotMarketAccount : PerpMarketAccount...
Yes
findExpiredNodesToFill
(marketIndex: number, ts: number, marketType: MarketType, slot?: any) => NodeToFill[]
Yes
findUnfillableReduceOnlyOrdersToCancel
(marketIndex: number, marketType: MarketType, stepSize: BN) => NodeToFill[]
Yes
getTakingBids
<T extends MarketType>(marketIndex: number, marketType: T, slot: number, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
Yes
getTakingAsks
<T extends MarketType>(marketIndex: number, marketType: T, slot: number, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
Yes
signedMsgGenerator
(signedMsgOrderList: NodeList<"signedMsg">, filter: (x: DLOBNode) => boolean) => Generator<DLOBNode, any, any>
Yes
getBestNode
<T extends MarketTypeStr>(generatorList: Generator<DLOBNode, any, any>[], oraclePriceData: T extends "spot" ? OraclePriceData : MMOraclePriceData, slot: number, compareFcn: (bestDLOBNode: DLOBNode, currentDLOBNode: DLOBNode, slot: number, oraclePriceData: T extends "spot" ? OraclePriceData : MMOraclePriceData) => bo...
Yes
getRestingLimitAsks
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
Yes
getRestingLimitBids
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
Yes
getAsks
<T extends MarketType>(marketIndex: number, _fallbackAsk: any, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
This will look at both the taking and resting limit asks
Yes
getBids
<T extends MarketType>(marketIndex: number, _fallbackBid: any, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
This will look at both the taking and resting limit bids
Yes
findCrossingRestingLimitOrders
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => NodeToFill[]
Yes
determineMakerAndTaker
(askNode: DLOBNode, bidNode: DLOBNode) => { takerNode: DLOBNode; makerNode: DLOBNode; } | undefined
Yes
getBestAsk
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => any
Yes
getBestBid
<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => any
Yes
getStopLosses
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
getStopLossMarkets
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
getStopLossLimits
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
getTakeProfits
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
getTakeProfitMarkets
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
getTakeProfitLimits
(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
Yes
findNodesToTrigger
(marketIndex: number, slot: number, triggerPrice: BN, marketType: MarketType, stateAccount: StateAccount) => NodeToTrigger[]
Yes
printTop
(driftClient: DriftClient, slotSubscriber: SlotSubscriber, marketIndex: number, marketType: MarketType) => void
Yes
getDLOBOrders
() => DLOBOrders
Yes
getNodeLists
() => Generator<NodeList<DLOBNodeType>, any, any>
Yes
getL2
<T extends MarketType>({ marketIndex, marketType, slot, oraclePriceData, depth, fallbackL2Generators, }: { marketIndex: number; marketType: T; slot: number; oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData; depth: number; fallbackL2Generators?: L2OrderBookGenerator[]; }) => L2Order...
Get an L2 view of the order book for a given market.
Yes
getL3
<T extends MarketType>({ marketIndex, marketType, slot, oraclePriceData, }: { marketIndex: number; marketType: T; slot: number; oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData; }) => L3OrderBook
Get an L3 view of the order book for a given market. Does not include fallback liquidity sources
Yes
estimateFillExactBaseAmountInForSide
any
Yes
estimateFillWithExactBaseAmount
<T extends MarketType>({ marketIndex, marketType, baseAmount, orderDirection, slot, oraclePriceData, }: { marketIndex: number; marketType: T; baseAmount: BN; orderDirection: PositionDirection; slot: number; oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData; }) => BN
Yes
getBestMakers
<T extends MarketType>({ marketIndex, marketType, direction, slot, oraclePriceData, numMakers, }: { marketIndex: number; marketType: T; direction: PositionDirection; slot: number; oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData; numMakers: number; }) => PublicKey[]
Yes

UserMap

Efficiently tracks and caches the accounts of many users simultaneously. Used by liquidation bots and other applications that need to monitor positions across the whole protocol, rather than just orders.

import { UserMap } from "@drift-labs/sdk";
Class UserMapReference ↗
PropertyTypeRequired
userMap
any
Yes
driftClient
DriftClient
Yes
eventEmitter
StrictEventEmitter<EventEmitter, UserEvents>
Yes
connection
any
Yes
commitment
any
Yes
includeIdle
any
Yes
filterByPoolId
any
No
additionalFilters
any
No
disableSyncOnTotalAccountsChange
any
Yes
lastNumberOfSubAccounts
any
Yes
subscription
any
Yes
stateAccountUpdateCallback
any
Yes
decode
any
Yes
mostRecentSlot
any
Yes
syncConfig
any
Yes
syncPromise
any
No
syncPromiseResolver
any
Yes
throwOnFailedSync
any
Yes
subscribe
() => Promise<void>
Yes
addPubkey
(userAccountPublicKey: PublicKey, userAccount?: UserAccount | undefined, slot?: number | undefined, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<...>
Yes
has
(key: string) => boolean
Yes
get
(key: string) => User | undefined
gets the User for a particular userAccountPublicKey, if no User exists, undefined is returned
Yes
getWithSlot
(key: string) => DataAndSlot<User> | undefined
Yes
mustGet
(key: string, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<User>
gets the User for a particular userAccountPublicKey, if no User exists, new one is created
Yes
mustGetWithSlot
(key: string, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<DataAndSlot<User>>
Yes
mustGetUserAccount
(key: string) => Promise<UserAccount>
Yes
getUserAuthority
(key: string) => PublicKey | undefined
gets the Authority for a particular userAccountPublicKey, if no User exists, undefined is returned
Yes
getDLOB
(slot: number, protectedMakerParamsMap?: ProtectMakerParamsMap | undefined) => Promise<DLOB>
implements the DLOBSource interface create a DLOB from all the subscribed users
Yes
updateWithOrderRecord
(record: OrderRecord) => Promise<void>
Yes
updateWithEventRecord
(record: any) => Promise<void>
Yes
values
() => IterableIterator<User>
Yes
valuesWithSlot
() => IterableIterator<DataAndSlot<User>>
Yes
entries
() => IterableIterator<[string, User]>
Yes
entriesWithSlot
() => IterableIterator<[string, DataAndSlot<User>]>
Yes
size
() => number
Yes
getUniqueAuthorities
(filterCriteria?: UserAccountFilterCriteria | undefined) => PublicKey[]
Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
Yes
sync
() => Promise<void>
Yes
getFilters
any
Yes
defaultSync
any
Syncs the UserMap using the default sync method (single getProgramAccounts call with filters). This method may fail when drift has too many users. (nodejs response size limits)
Yes
paginatedSync
any
Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters). This method is more reliable when drift has many users.
Yes
unsubscribe
() => Promise<void>
Yes
updateUserAccount
(key: string, userAccount: UserAccount, slot: number) => Promise<void>
Yes
updateLatestSlot
(slot: number) => void
Yes
getSlot
() => number
Yes

Setting Up a Local DLOB

This is the full setup sequence to get a live, continuously-updated orderbook running:

import { SlotSubscriber, OrderSubscriber, DLOBSubscriber } from "@drift-labs/sdk"; // 1. Track the current slot (needed for order expiry) const slotSubscriber = new SlotSubscriber(connection); await slotSubscriber.subscribe(); // 2. Subscribe to all open orders across all users const orderSubscriber = new OrderSubscriber({ driftClient, subscriptionConfig: { type: "websocket" }, fastDecode: true, decodeData: true, }); await orderSubscriber.subscribe(); // 3. Build and maintain the DLOB from the order stream const dlobSubscriber = new DLOBSubscriber({ driftClient, dlobSource: orderSubscriber, slotSource: slotSubscriber, updateFrequency: 1000, }); await dlobSubscriber.subscribe();
Example DLOB setupReference ↗
TypeScript docs unavailable for DLOB setup.

Getting L2 Orderbook Data

Once subscribed, query the aggregated L2 orderbook (price levels with cumulative size):

import { MarketType, PRICE_PRECISION, BASE_PRECISION, convertToNumber } from "@drift-labs/sdk"; const dlob = dlobSubscriber.getDLOB(); const marketIndex = 0; // SOL-PERP // For perp markets, use getMMOracleDataForPerpMarket (returns MMOraclePriceData) const oraclePriceData = driftClient.getMMOracleDataForPerpMarket(marketIndex); const slot = slotSubscriber.getSlot(); const l2 = dlob.getL2({ marketIndex, marketType: MarketType.PERP, oraclePriceData, slot, depth: 10, // number of price levels per side }); // l2.bids and l2.asks are arrays of { price: BN, size: BN } console.log("Top bid:", convertToNumber(l2.bids[0].price, PRICE_PRECISION), "size:", convertToNumber(l2.bids[0].size, BASE_PRECISION)); console.log("Top ask:", convertToNumber(l2.asks[0].price, PRICE_PRECISION), "size:", convertToNumber(l2.asks[0].size, BASE_PRECISION));
Example L2 orderbookReference ↗
TypeScript docs unavailable for L2 orderbook.

Getting Best Bid/Ask

For quick access to the best bid and ask prices without fetching the full orderbook:

import { MarketType, PRICE_PRECISION, convertToNumber } from "@drift-labs/sdk"; const dlob = dlobSubscriber.getDLOB(); const marketIndex = 0; const oraclePriceData = driftClient.getMMOracleDataForPerpMarket(marketIndex); const slot = slotSubscriber.getSlot(); // Returns BN | undefined (undefined if no orders on that side) const bestBid = dlob.getBestBid(marketIndex, slot, MarketType.PERP, oraclePriceData); const bestAsk = dlob.getBestAsk(marketIndex, slot, MarketType.PERP, oraclePriceData); if (bestBid && bestAsk) { console.log("Best bid:", convertToNumber(bestBid, PRICE_PRECISION)); console.log("Best ask:", convertToNumber(bestAsk, PRICE_PRECISION)); console.log("Spread:", convertToNumber(bestAsk.sub(bestBid), PRICE_PRECISION)); }
Example Best bid/askReference ↗
TypeScript docs unavailable for Best bid/ask.
Last updated on