@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
225 lines (208 loc) • 6.24 kB
text/typescript
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;
}
}
}