Skip to Content
DevelopersDrift SDKMarkets, Oracles, and Positions

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() or driftClient.getSpotMarketAccounts() to get all markets and inspect their indexes
  • Market account directly: Each market account has a marketIndex field 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

const marketIndex = 0; const spotMarket = driftClient.getSpotMarketAccount(marketIndex); console.log(spotMarket?.marketIndex);
Method DriftClient.getSpotMarketAccountReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
Returns:
SpotMarketAccount | undefined
const marketIndex = 0; const perpMarket = driftClient.getPerpMarketAccount(marketIndex); console.log(perpMarket?.marketIndex);
Method DriftClient.getPerpMarketAccountReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
Returns:
PerpMarketAccount | undefined
const spotMarkets = driftClient.getSpotMarketAccounts(); console.log(spotMarkets.length);
Method DriftClient.getSpotMarketAccountsReference ↗
Parameters:
This function does not accept any parameters.
Returns:
SpotMarketAccount[]
const perpMarkets = driftClient.getPerpMarketAccounts(); console.log(perpMarkets.length);
Method DriftClient.getPerpMarketAccountsReference ↗
Parameters:
This function does not accept any parameters.
Returns:
PerpMarketAccount[]

Oracle Price

const marketIndex = 0; const oracle = driftClient.getOracleDataForPerpMarket(marketIndex); console.log(oracle.price.toString());
Method DriftClient.getOracleDataForPerpMarketReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
Returns:
OraclePriceData
const marketIndex = 0; const oracle = driftClient.getOracleDataForSpotMarket(marketIndex); console.log(oracle.price.toString());
Method DriftClient.getOracleDataForSpotMarketReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
Returns:
OraclePriceData
const marketIndex = 0; const oracle = driftClient.getMMOracleDataForPerpMarket(marketIndex); console.log(oracle.price.toString());
Method DriftClient.getMMOracleDataForPerpMarketReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
Returns:
MMOraclePriceData

Positions and Balances

const spotPosition = driftClient.getSpotPosition(0); console.log(spotPosition);
Method DriftClient.getSpotPositionReference ↗
Parameters:
NameTypeDefault
marketIndexnumber
subAccountIdnumber
Returns:
SpotPosition | undefined
const perpPosition = driftClient.getPerpPosition(0); console.log(perpPosition);
Method DriftClient.getPerpPositionReference ↗
TypeScript docs unavailable for getPerpPosition.

Protocol State

const state = driftClient.getStateAccount(); console.log(state);
Method DriftClient.getStateAccountReference ↗
Parameters:
This function does not accept any parameters.
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.

Key Classes

ClassPurposeWhen to Use
OrderSubscriberSubscribes to all user orders in real-time via WebSocket or pollingAlways needed , this is the raw data source for the DLOB
DLOBSubscriberBuilds and maintains an aggregated orderbook from the order streamWhen you need a continuously-updated L2/L3 orderbook view
SlotSubscriberTracks the current Solana slotNeeded for timing-sensitive operations like JIT auctions and order expiry
DLOBCore data structure with bid/ask sides and query methodsUsed internally by DLOBSubscriber; access via dlobSubscriber.getDLOB()
UserMapEfficiently tracks and caches multiple user accountsUseful for bulk operations across many users (e.g., liquidation bots)

View OrderSubscriber import

import { OrderSubscriber } from "@drift-labs/sdk";
Class OrderSubscriberReference ↗
NameTypeDefault
driftClientDriftClient
usersAccountsMap<string, { slot: number; userAccount: UserAccount; }>
subscriptionPollingSubscription | WebsocketSubscription | grpcSubscription
commitmentCommitment
eventEmitterStrictEventEmitter<EventEmitter, OrderSubscriberEvents>
fetchPromisePromise<void>
fetchPromiseResolver() => void
mostRecentSlotnumber
decodeFn(name: string, data: Buffer) => UserAccount
decodeDataboolean
fetchAllNonIdleUsersboolean
subscribe() => Promise<void>
fetch() => Promise<void>
tryUpdateUserAccount(key: string, dataType: "raw" | "decoded" | "buffer", data: UserAccount | Buffer | string[], slot: number) => void
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.

getDLOB(slot: number, protectedMakerParamsMap?: ProtectMakerParamsMap | undefined) => Promise<DLOB>
getSlot() => number
addPubkey(userAccountPublicKey: PublicKey) => Promise<void>
mustGetUserAccount(key: string) => Promise<UserAccount>
unsubscribe() => Promise<void>

View DLOBSubscriber import

import { DLOBSubscriber } from "@drift-labs/sdk";
Class DLOBSubscriberReference ↗
NameTypeDefault
driftClientDriftClient
dlobSourceDLOBSource
slotSourceSlotSource
updateFrequencynumber
intervalIdTimeout
dlobDLOB
eventEmitterStrictEventEmitter<EventEmitter, DLOBSubscriberEvents>
protectedMakerViewboolean
subscribe() => Promise<void>
getProtectedMakerParamsMap() => ProtectMakerParamsMap | undefined
updateDLOB() => Promise<void>
getDLOB() => DLOB
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.

getL3({ marketName, marketIndex, marketType, }: { marketName?: string; marketIndex?: number; marketType?: MarketType; }) => L3OrderBook

Get the L3 order book for a given market.

unsubscribe() => Promise<void>

View SlotSubscriber import

import { SlotSubscriber } from "@drift-labs/sdk";
Class SlotSubscriberReference ↗
NameTypeDefault
connectionany
currentSlotnumber
subscriptionIdnumber
eventEmitterStrictEventEmitter<EventEmitter, SlotSubscriberEvents>
timeoutIdTimeout
resubTimeoutMsnumber
isUnsubscribingboolean
receivingDataboolean
subscribe() => Promise<void>
updateCurrentSlotany
setTimeoutany
getSlot() => number
unsubscribe(onResub?: boolean | undefined) => Promise<void>

View DLOB import

import { DLOB } from "@drift-labs/sdk";
Class DLOBReference ↗
NameTypeDefault
openOrdersMap<MarketTypeStr, Set<string>>
orderListsMap<MarketTypeStr, Map<number, MarketNodeLists>>
maxSlotForRestingLimitOrdersnumber
initializedboolean
protectedMakerParamsMapProtectMakerParamsMap
initany
clear() => void
initFromUserMap(userMap: UserMap, slot: number) => Promise<boolean>

initializes a new DLOB instance

insertOrder(order: Order, userAccount: string, slot: number, isUserProtectedMaker: boolean, baseAssetAmount: BN, onInsert?: OrderBookCallback | undefined) => void
insertSignedMsgOrder(order: Order, userAccount: string, isUserProtectedMaker: boolean, baseAssetAmount?: any, onInsert?: OrderBookCallback | undefined) => void
addOrderList(marketType: MarketTypeStr, marketIndex: number) => void
delete(order: Order, userAccount: PublicKey, slot: number, isUserProtectedMaker: boolean, onDelete?: OrderBookCallback | undefined) => void
getListForOnChainOrder(order: Order, slot: number, isProtectedMaker: boolean) => NodeList<any> | undefined
updateRestingLimitOrders(slot: number) => void
updateRestingLimitOrdersForMarketType(slot: number, marketTypeStr: MarketTypeStr) => void
getOrder(orderId: number, userAccount: PublicKey) => Order | undefined
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...
getMakerRebate(marketType: MarketType, stateAccount: StateAccount, marketAccount: SpotMarketAccount | PerpMarketAccount) => { ...; }
mergeNodesToFill(restingLimitOrderNodesToFill: NodeToFill[], takingOrderNodesToFill: NodeToFill[]) => NodeToFill[]
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...
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...
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...
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...
findExpiredNodesToFill(marketIndex: number, ts: number, marketType: MarketType, slot?: any) => NodeToFill[]
findUnfillableReduceOnlyOrdersToCancel(marketIndex: number, marketType: MarketType, stepSize: BN) => NodeToFill[]
getTakingBids<T extends MarketType>(marketIndex: number, marketType: T, slot: number, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
getTakingAsks<T extends MarketType>(marketIndex: number, marketType: T, slot: number, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
signedMsgGenerator(signedMsgOrderList: NodeList<"signedMsg">, filter: (x: DLOBNode) => boolean) => Generator<DLOBNode, any, any>
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...
getRestingLimitAsks<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
getRestingLimitBids<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData, filterFcn?: DLOBFilterFcn | undefined) => Generator<...>
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

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

findCrossingRestingLimitOrders<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => NodeToFill[]
determineMakerAndTaker(askNode: DLOBNode, bidNode: DLOBNode) => { takerNode: DLOBNode; makerNode: DLOBNode; } | undefined
getBestAsk<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => any
getBestBid<T extends MarketType>(marketIndex: number, slot: number, marketType: T, oraclePriceData: T extends { spot: unknown; } ? OraclePriceData : MMOraclePriceData) => any
getStopLosses(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
getStopLossMarkets(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
getStopLossLimits(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
getTakeProfits(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
getTakeProfitMarkets(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
getTakeProfitLimits(marketIndex: number, marketType: MarketType, direction: PositionDirection) => Generator<DLOBNode, any, any>
findNodesToTrigger(marketIndex: number, slot: number, triggerPrice: BN, marketType: MarketType, stateAccount: StateAccount) => NodeToTrigger[]
printTop(driftClient: DriftClient, slotSubscriber: SlotSubscriber, marketIndex: number, marketType: MarketType) => void
getDLOBOrders() => DLOBOrders
getNodeLists() => Generator<NodeList<DLOBNodeType>, any, any>
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.

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

estimateFillExactBaseAmountInForSideany
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
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[]

View UserMap import

import { UserMap } from "@drift-labs/sdk";
Class UserMapReference ↗
NameTypeDefault
userMapany
driftClientDriftClient
eventEmitterStrictEventEmitter<EventEmitter, UserEvents>
connectionany
commitmentany
includeIdleany
filterByPoolIdany
additionalFiltersany
disableSyncOnTotalAccountsChangeany
lastNumberOfSubAccountsany
subscriptionany
stateAccountUpdateCallbackany
decodeany
mostRecentSlotany
syncConfigany
syncPromiseany
syncPromiseResolverany
throwOnFailedSyncany
subscribe() => Promise<void>
addPubkey(userAccountPublicKey: PublicKey, userAccount?: UserAccount | undefined, slot?: number | undefined, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<...>
has(key: string) => boolean
get(key: string) => User | undefined

gets the User for a particular userAccountPublicKey, if no User exists, undefined is returned

getWithSlot(key: string) => DataAndSlot<User> | undefined
mustGet(key: string, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<User>

gets the User for a particular userAccountPublicKey, if no User exists, new one is created

mustGetWithSlot(key: string, accountSubscription?: UserSubscriptionConfig | undefined) => Promise<DataAndSlot<User>>
mustGetUserAccount(key: string) => Promise<UserAccount>
getUserAuthority(key: string) => PublicKey | undefined

gets the Authority for a particular userAccountPublicKey, if no User exists, undefined is returned

getDLOB(slot: number, protectedMakerParamsMap?: ProtectMakerParamsMap | undefined) => Promise<DLOB>

implements the DLOBSource interface create a DLOB from all the subscribed users

updateWithOrderRecord(record: OrderRecord) => Promise<void>
updateWithEventRecord(record: any) => Promise<void>
values() => IterableIterator<User>
valuesWithSlot() => IterableIterator<DataAndSlot<User>>
entries() => IterableIterator<[string, User]>
entriesWithSlot() => IterableIterator<[string, DataAndSlot<User>]>
size() => number
getUniqueAuthorities(filterCriteria?: UserAccountFilterCriteria | undefined) => PublicKey[]

Returns a unique list of authorities for all users in the UserMap that meet the filter criteria

sync() => Promise<void>
getFiltersany
defaultSyncany

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)

paginatedSyncany

Syncs the UserMap using the paginated sync method (multiple getMultipleAccounts calls with filters). This method is more reliable when drift has many users.

unsubscribe() => Promise<void>
updateUserAccount(key: string, userAccount: UserAccount, slot: number) => Promise<void>
updateLatestSlot(slot: number) => void
getSlot() => number

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();

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));

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)); }
Last updated on