UNPKG

@drift-labs/sdk-browser

Version:
225 lines (208 loc) 6.24 kB
import { DLOB } from './DLOB'; import { EventEmitter } from 'events'; import StrictEventEmitter from 'strict-event-emitter-types'; import { DLOBSource, DLOBSubscriberEvents, DLOBSubscriptionConfig, ProtectMakerParamsMap, SlotSource, } from './types'; import { DriftClient } from '../driftClient'; import { isVariant, MarketType } from '../types'; import { DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS, MAJORS_TOP_OF_BOOK_QUOTE_AMOUNTS, getVammL2Generator, L2OrderBook, L2OrderBookGenerator, L3OrderBook, } from './orderBookLevels'; import { getProtectedMakerParamsMap } from '../math/protectedMakerParams'; import { BN } from '@coral-xyz/anchor'; export class DLOBSubscriber { driftClient: DriftClient; dlobSource: DLOBSource; slotSource: SlotSource; updateFrequency: number; intervalId?: ReturnType<typeof setTimeout>; dlob: DLOB; public eventEmitter: StrictEventEmitter<EventEmitter, DLOBSubscriberEvents>; protectedMakerView: boolean; constructor(config: DLOBSubscriptionConfig) { this.driftClient = config.driftClient; this.dlobSource = config.dlobSource; this.slotSource = config.slotSource; this.updateFrequency = config.updateFrequency; this.protectedMakerView = config.protectedMakerView || false; this.dlob = new DLOB(this.getProtectedMakerParamsMap()); this.eventEmitter = new EventEmitter(); } public async subscribe(): Promise<void> { if (this.intervalId) { return; } await this.updateDLOB(); this.intervalId = setInterval(async () => { try { await this.updateDLOB(); this.eventEmitter.emit('update', this.dlob); } catch (e) { this.eventEmitter.emit('error', e); } }, this.updateFrequency); } getProtectedMakerParamsMap(): ProtectMakerParamsMap | undefined { return this.protectedMakerView ? getProtectedMakerParamsMap(this.driftClient.getPerpMarketAccounts()) : undefined; } async updateDLOB(): Promise<void> { this.dlob = await this.dlobSource.getDLOB( this.slotSource.getSlot(), this.getProtectedMakerParamsMap() ); } public getDLOB(): DLOB { return this.dlob; } /** * Get the L2 order book for a given market. * * @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided. * @param marketIndex * @param marketType * @param depth Number of orders to include in the order book. Defaults to 10. * @param includeVamm Whether to include the VAMM orders in the order book. Defaults to false. If true, creates vAMM generator {@link getVammL2Generator} and adds it to fallbackL2Generators. * @param fallbackL2Generators L2 generators for fallback liquidity e.g. vAMM {@link getVammL2Generator}, openbook {@link SerumSubscriber} * @param latestSlot Latest slot observed via slot subscriber or similar for accuarate vamm quotes (if including the vAMM). */ public getL2({ marketName, marketIndex, marketType, depth = 10, includeVamm = false, numVammOrders, fallbackL2Generators = [], latestSlot, }: { marketName?: string; marketIndex?: number; marketType?: MarketType; depth?: number; includeVamm?: boolean; numVammOrders?: number; fallbackL2Generators?: L2OrderBookGenerator[]; latestSlot?: BN; }): L2OrderBook { if (marketName) { const derivedMarketInfo = this.driftClient.getMarketIndexAndType(marketName); if (!derivedMarketInfo) { throw new Error(`Market ${marketName} not found`); } marketIndex = derivedMarketInfo.marketIndex; marketType = derivedMarketInfo.marketType; } else { if (marketIndex === undefined || marketType === undefined) { throw new Error( 'Either marketName or marketIndex and marketType must be provided' ); } } let oraclePriceData; const isPerp = isVariant(marketType, 'perp'); if (isPerp) { const perpMarketAccount = this.driftClient.getPerpMarketAccount(marketIndex); oraclePriceData = this.driftClient.getOracleDataForPerpMarket( perpMarketAccount.marketIndex ); } else { oraclePriceData = this.driftClient.getOracleDataForSpotMarket(marketIndex); } if (isPerp && includeVamm) { if (fallbackL2Generators.length > 0) { throw new Error( 'includeVamm can only be used if fallbackL2Generators is empty' ); } fallbackL2Generators = [ getVammL2Generator({ marketAccount: this.driftClient.getPerpMarketAccount(marketIndex), mmOraclePriceData: this.driftClient.getMMOracleDataForPerpMarket(marketIndex), numOrders: numVammOrders ?? depth, topOfBookQuoteAmounts: marketIndex < 3 ? MAJORS_TOP_OF_BOOK_QUOTE_AMOUNTS : DEFAULT_TOP_OF_BOOK_QUOTE_AMOUNTS, latestSlot, }), ]; } return this.dlob.getL2({ marketIndex, marketType, depth, oraclePriceData, slot: this.slotSource.getSlot(), fallbackL2Generators: fallbackL2Generators, }); } /** * Get the L3 order book for a given market. * * @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided. * @param marketIndex * @param marketType */ public getL3({ marketName, marketIndex, marketType, }: { marketName?: string; marketIndex?: number; marketType?: MarketType; }): L3OrderBook { if (marketName) { const derivedMarketInfo = this.driftClient.getMarketIndexAndType(marketName); if (!derivedMarketInfo) { throw new Error(`Market ${marketName} not found`); } marketIndex = derivedMarketInfo.marketIndex; marketType = derivedMarketInfo.marketType; } else { if (marketIndex === undefined || marketType === undefined) { throw new Error( 'Either marketName or marketIndex and marketType must be provided' ); } } let oraclePriceData; const isPerp = isVariant(marketType, 'perp'); if (isPerp) { oraclePriceData = this.driftClient.getOracleDataForPerpMarket(marketIndex); } else { oraclePriceData = this.driftClient.getOracleDataForSpotMarket(marketIndex); } return this.dlob.getL3({ marketIndex, marketType, oraclePriceData, slot: this.slotSource.getSlot(), }); } public async unsubscribe(): Promise<void> { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = undefined; } } }