Markets, Oracles, and Positions
How it works
Drift has two types of markets: perp markets (perpetual futures with funding rates) and spot markets (token deposits/borrows that serve as collateral). Each market has an onchain account storing configuration like oracle source, fees, funding rates, AMM parameters, and current open interest.
Market Indexes
Markets are identified by a numeric index starting from 0. For example:
- Perp market 0 is typically SOL-PERP
- Spot market 0 is typically USDC
- Perp market 1 might be BTC-PERP, and so on
Where to find market indexes:
- State account: Query
driftClient.getStateAccount()which contains arrays of all perp and spot market configurations - SDK methods: Use
driftClient.getPerpMarketAccounts()ordriftClient.getSpotMarketAccounts()to get all markets and inspect their indexes - Market account directly: Each market account has a
marketIndexfield you can read - Symbol lookup: Most bots maintain their own mapping from symbol (e.g., “SOL-PERP”) to market index, or query all markets and build the mapping at startup
Each market integrates with an oracle (usually Pyth or Switchboard) that provides real-time price data. The SDK lets you read oracle prices, check if they’re valid/stale, and use them for quoting or risk calculations. Prices are stored in fixed-point precision (1e6 for PRICE_PRECISION).
Perp markets track funding rates, open interest, and AMM liquidity pools. Spot markets track total deposits, borrows, and utilization rates. When you trade, you’re interacting with these market accounts, opening positions on perp markets or borrowing/depositing in spot markets.
SDK Usage
These are the most common read-path helpers you’ll use to power bots, dashboards, and risk logic.
Market Accounts
Read a single spot market account by index (e.g., market config, oracle source, and utilization state).
const marketIndex = 0;
const spotMarket = driftClient.getSpotMarketAccount(marketIndex);
console.log(spotMarket?.marketIndex);Method DriftClient.getSpotMarketAccountReference ↗
Method DriftClient.getSpotMarketAccountReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
| Returns |
|---|
SpotMarketAccount | undefined |
Read a single perp market account by index (e.g., AMM params, funding state, and open interest).
const marketIndex = 0;
const perpMarket = driftClient.getPerpMarketAccount(marketIndex);
console.log(perpMarket?.marketIndex);Method DriftClient.getPerpMarketAccountReference ↗
Method DriftClient.getPerpMarketAccountReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
| Returns |
|---|
PerpMarketAccount | undefined |
Get all spot market accounts at once, useful for startup symbol/index mapping and dashboards.
const spotMarkets = driftClient.getSpotMarketAccounts();
console.log(spotMarkets.length);Method DriftClient.getSpotMarketAccountsReference ↗
Method DriftClient.getSpotMarketAccountsReference ↗| Returns |
|---|
SpotMarketAccount[] |
Get all perp market accounts at once, useful for scanning market metadata and risk parameters.
const perpMarkets = driftClient.getPerpMarketAccounts();
console.log(perpMarkets.length);Method DriftClient.getPerpMarketAccountsReference ↗
Method DriftClient.getPerpMarketAccountsReference ↗| Returns |
|---|
PerpMarketAccount[] |
Oracle Price
Read the current oracle data for a perp market (price, confidence, and validity flags).
const marketIndex = 0;
const oracle = driftClient.getOracleDataForPerpMarket(marketIndex);
console.log(oracle.price.toString());Method DriftClient.getOracleDataForPerpMarketReference ↗
Method DriftClient.getOracleDataForPerpMarketReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
| Returns |
|---|
OraclePriceData |
Read the current oracle data for a spot market using its market index.
const marketIndex = 0;
const oracle = driftClient.getOracleDataForSpotMarket(marketIndex);
console.log(oracle.price.toString());Method DriftClient.getOracleDataForSpotMarketReference ↗
Method DriftClient.getOracleDataForSpotMarketReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
| Returns |
|---|
OraclePriceData |
Read market-maker-oriented oracle data for perp markets, typically used for DLOB/JIT pricing flows.
const marketIndex = 0;
const oracle = driftClient.getMMOracleDataForPerpMarket(marketIndex);
console.log(oracle.price.toString());Method DriftClient.getMMOracleDataForPerpMarketReference ↗
Method DriftClient.getMMOracleDataForPerpMarketReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
| Returns |
|---|
MMOraclePriceData |
Positions and Balances
Get your active subaccount’s spot position for a given market index (deposit or borrow state).
const spotPosition = driftClient.getSpotPosition(0);
console.log(spotPosition);Method DriftClient.getSpotPositionReference ↗
Method DriftClient.getSpotPositionReference ↗| Parameter | Type | Required |
|---|---|---|
marketIndex | number | Yes |
subAccountId | number | No |
| Returns |
|---|
SpotPosition | undefined |
Get your active subaccount’s perp position for a given perp market index.
const perpPosition = driftClient.getPerpPosition(0);
console.log(perpPosition);Method DriftClient.getPerpPositionReference ↗
Method DriftClient.getPerpPositionReference ↗getPerpPosition.Protocol State
const state = driftClient.getStateAccount();
console.log(state);Method DriftClient.getStateAccountReference ↗
Method DriftClient.getStateAccountReference ↗| Returns |
|---|
StateAccount |
Working With the DLOB
The Decentralized Limit Order Book (DLOB) aggregates resting limit orders from all users. If you’re building a market maker, orderbook UI, or matching bot, you’ll want to build a local DLOB by subscribing to orders.
OrderSubscriber
Subscribes to all user orders in real-time via WebSocket or polling. Always needed — this is the raw data source for the DLOB.
import { OrderSubscriber } from "@drift-labs/sdk";Class OrderSubscriberReference ↗
Class OrderSubscriberReference ↗| Property | Type | Required |
|---|---|---|
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) => DLOBCreates 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 maintains an aggregated orderbook from the order stream. Use when you need a continuously-updated L2/L3 orderbook view.
import { DLOBSubscriber } from "@drift-labs/sdk";Class DLOBSubscriberReference ↗
Class DLOBSubscriberReference ↗| Property | Type | Required |
|---|---|---|
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; }) => L3OrderBookGet the L3 order book for a given market. | Yes |
unsubscribe | () => Promise<void> | Yes |
SlotSubscriber
Tracks the current Solana slot. Needed for timing-sensitive operations like JIT auctions and order expiry.
import { SlotSubscriber } from "@drift-labs/sdk";Class SlotSubscriberReference ↗
Class SlotSubscriberReference ↗| Property | Type | Required |
|---|---|---|
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
Core data structure with bid/ask sides and query methods. Used internally by DLOBSubscriber; access via dlobSubscriber.getDLOB().
import { DLOB } from "@drift-labs/sdk";Class DLOBReference ↗
Class DLOBReference ↗| Property | Type | Required |
|---|---|---|
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; }) => L3OrderBookGet 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 multiple user accounts. Useful for bulk operations across many users (e.g., liquidation bots).
import { UserMap } from "@drift-labs/sdk";Class UserMapReference ↗
Class UserMapReference ↗| Property | Type | Required |
|---|---|---|
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 | undefinedgets 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 | undefinedgets 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 | anySyncs 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 | anySyncs 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
import { SlotSubscriber, OrderSubscriber, DLOBSubscriber } from "@drift-labs/sdk";
const slotSubscriber = new SlotSubscriber(connection);
await slotSubscriber.subscribe();
const orderSubscriber = new OrderSubscriber({
driftClient,
subscriptionConfig: { type: "websocket" },
fastDecode: true,
decodeData: true,
});
await orderSubscriber.subscribe();
const dlobSubscriber = new DLOBSubscriber({
driftClient,
dlobSource: orderSubscriber,
slotSource: slotSubscriber,
updateFrequency: 1000,
});
await dlobSubscriber.subscribe();Example DLOB setupReference ↗
Example DLOB setupReference ↗DLOB setup.Getting L2 Orderbook Data
Once the DLOB is subscribed, you can 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 ↗
Example L2 orderbookReference ↗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 ↗
Example Best bid/askReference ↗Best bid/ask.