send indicative quotes to mainnet (#374)

* Bumping drift-common to ab538e0c1efbf64a3dfecf2af16e5a75aab81400

* Bumping drift-common to cf7f6ec63febe0d3b661f187e4e26e729797c4d0

* Bumping drift-common to 149f5efe5444d89b53b5370dcea4a3115fec8c5f

* Bumping drift-common to c879f8a85bfcbaef1f1c6d9c6bf4a05e8970fa0a

* Bumping drift-common to d089c9ed7e7400a0f3ea98a0184a4e3bf273fcf5

* dlob add fetch indicative quotes (#372)

* dlob add fetch indicative quotes

* indicative ws stream

* prettify

* allow for oracle orders

* Bumping drift-common to 56331c89a2285a04cc86a639e0fceeda03366f43

* add console log for debugging indicative

* add indicative in l2 queries

* add more console logging for debugging

* bump common and remove logging

* Bumping drift-common to 43104f473f43111f8cc34733a43a85b5fef7f778

* Bumping drift-common to 6d75612fa1fab858daa90cdb91f0c8c9edbc8242

* Bumping drift-common to 3a42e9665c04a27bcf92e7faa50c168690312880

* Bumping drift-common to 20351d45f2644fb7194ffce441d3d2cb8d6c6b4b

* Bumping drift-common to 04216dfc03aa659db24fc24ba42aa8a4e9a8bb9a

* Bumping drift-common to ad20bbe9d3a262c547f66ec464306fabac539fd8

* Bumping drift-common to 5539ad5f3849b19e1b7106f783a16a182fc9a411

* Bumping drift-common to 8b7e118f1aed9d1d7a786cd165f0ceef2a07297c

* Bumping drift-common to 52b7948cebb0ed6bc1f8278b0f549366a1be3ba0

* Bumping drift-common to 26a603c2d78f7f662cb872075bdff1fd6b75ec43

* Bumping drift-common to 683bfc5cefe5097a257511507f22831f185f1549

* Bumping drift-common to b66eaade382a58b563fdc5ee6c03bc98e5bb2195

* Bumping drift-common to 9a48bea13265723c5d17377e68aaf9984b24c722

* Bumping drift-common to a147ab129f6eb59d77889ac2b7a915ca910d83dd

* Bumping drift-common to 65891abfcd5f59c726aecb211ae77ca38394bdf1

* Bumping drift-common to 540cdbbc9b322990d288af8f4f3183f2c96087b7

* Bumping drift-common to 978b8a03b59d0dc24d1269f319ff6a7ff711775a

* Bumping drift-common to af74d01953db3683bca585c2111910cbbe171a72

* Bumping drift-common to cd684e22082438f0cb784ec234fd9e5d3cd63bb2

---------

Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
moosecat
2025-04-28 10:16:44 -07:00
committed by GitHub
parent 7e2ae36ee9
commit a55be980fe
8 changed files with 422 additions and 271 deletions

View File

@@ -1,11 +1,17 @@
import {
BN,
DLOBSubscriber,
DLOBSubscriptionConfig,
DriftEnv,
L2OrderBookGenerator,
MarketType,
Order,
OrderStatus,
OrderTriggerCondition,
OrderType,
PerpOperation,
PositionDirection,
ZERO,
isOperationPaused,
isVariant,
} from '@drift-labs/sdk';
@@ -37,6 +43,8 @@ const PERP_MAKRET_STALENESS_THRESHOLD = 30 * 60 * 1000;
const SPOT_MAKRET_STALENESS_THRESHOLD = 60 * 60 * 1000;
const STALE_ORACLE_REMOVE_VAMM_THRESHOLD = 160;
const INDICATIVE_QUOTES_PUBKEY = 'inDNdu3ML4vG5LNExqcwuCQtLcCU8KfK5YM2qYV3JJz';
const PERP_MARKETS_TO_SKIP_SLOT_CHECK =
process.env.PERP_MARKETS_TO_SKIP_SLOT_CHECK !== undefined
? parsePositiveIntArray(process.env.PERP_MARKETS_TO_SKIP_SLOT_CHECK)
@@ -55,6 +63,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
public marketArgs: wsMarketArgs[] = [];
public lastSeenL2Formatted: Map<MarketType, Map<number, any>>;
redisClient: RedisClient;
indicativeQuotesRedisClient?: RedisClient;
public killSwitchSlotDiffThreshold: number;
public lastMarketSlotMap: Map<
MarketType,
@@ -65,6 +74,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
config: DLOBSubscriptionConfig & {
env: DriftEnv;
redisClient: RedisClient;
indicativeQuotesRedisClient?: RedisClient;
perpMarketInfos: wsMarketInfo[];
spotMarketInfos: wsMarketInfo[];
spotMarketSubscribers: SubscriberLookup;
@@ -73,6 +83,8 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
) {
super(config);
this.redisClient = config.redisClient;
this.indicativeQuotesRedisClient = config.indicativeQuotesRedisClient;
this.killSwitchSlotDiffThreshold =
config.killSwitchSlotDiffThreshold || 200;
@@ -124,6 +136,105 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
await super.updateDLOB();
for (const marketArgs of this.marketArgs) {
try {
if (this.indicativeQuotesRedisClient) {
const marketType = isVariant(marketArgs.marketType, 'perp')
? 'perp'
: 'spot';
const mms = await this.indicativeQuotesRedisClient.smembers(
`market_mms_${marketType}_${marketArgs.marketIndex}`
);
const mmQuotes = await Promise.all(
mms.map((mm) => {
return this.indicativeQuotesRedisClient.get(
`mm_quotes_${marketType}_${marketArgs.marketIndex}_${mm}`
);
})
);
const nowMinus1000Ms = Date.now() - 1000;
mmQuotes.forEach((quote) => {
if (Number(quote['ts']) > nowMinus1000Ms) {
const indicativeBaseOrder: Order = {
status: OrderStatus.OPEN,
orderType: OrderType.LIMIT,
orderId: 0,
slot: new BN(this.slotSource.getSlot()),
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
baseAssetAmount: ZERO,
immediateOrCancel: false,
direction: PositionDirection.LONG,
oraclePriceOffset: 0,
maxTs: new BN(quote['ts'] + 1000),
reduceOnly: false,
triggerCondition: OrderTriggerCondition.ABOVE,
price: ZERO,
userOrderId: 0,
postOnly: true,
auctionDuration: 0,
auctionStartPrice: ZERO,
auctionEndPrice: ZERO,
// Rest are not necessary and set for type conforming
existingPositionDirection: PositionDirection.LONG,
triggerPrice: ZERO,
baseAssetAmountFilled: ZERO,
quoteAssetAmountFilled: ZERO,
quoteAssetAmount: ZERO,
bitFlags: 0,
postedSlotTail: 0,
};
if (quote['bid_size'] && quote['bid_price']) {
// Sanity check bid price and size
const indicativeBid: Order = Object.assign(
{},
indicativeBaseOrder,
{
oraclePriceOffset: quote['is_oracle_offset']
? quote['bid_price']
: 0,
price: quote['is_oracle_offset']
? 0
: new BN(quote['bid_price']),
baseAssetAmount: new BN(quote['bid_size']),
direction: PositionDirection.LONG,
}
);
this.dlob.insertOrder(
indicativeBid,
INDICATIVE_QUOTES_PUBKEY,
this.slotSource.getSlot(),
false
);
}
if (quote['ask_size'] && quote['ask_price']) {
const indicativeAsk: Order = Object.assign(
{},
indicativeBaseOrder,
{
oraclePriceOffset: quote['is_oracle_offset']
? quote['ask_price']
: 0,
price: quote['is_oracle_offset']
? 0
: new BN(quote['ask_price']),
baseAssetAmount: new BN(quote['ask_size']),
direction: PositionDirection.SHORT,
}
);
this.dlob.insertOrder(
indicativeAsk,
INDICATIVE_QUOTES_PUBKEY,
this.slotSource.getSlot(),
false
);
}
}
});
}
this.getL2AndSendMsg(marketArgs);
this.getL3AndSendMsg(marketArgs);
} catch (error) {
@@ -162,6 +273,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
logger.info('Oracle is stale, removing vamm orders');
includeVamm = false;
}
const l2 = this.getL2({ ...l2FuncArgs, includeVamm });
const slot = l2.slot;
const lastMarketSlotAndTime = this.lastMarketSlotMap
@@ -264,44 +376,43 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
this.redisClient.publish(
`${clientPrefix}orderbook_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l2Formatted
);
this.redisClient.set(
`last_update_orderbook_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l2Formatted_depth100
);
const oraclePriceData =
marketType === 'spot'
? this.driftClient.getOracleDataForSpotMarket(marketArgs.marketIndex)
: this.driftClient.getOracleDataForPerpMarket(marketArgs.marketIndex);
const bids = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.LONG,
slot: slot,
oraclePriceData,
numMakers: 4,
})
.map((x) => x.toString());
const asks = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.SHORT,
slot,
oraclePriceData,
numMakers: 4,
})
.map((x) => x.toString());
this.redisClient.set(
`last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`,
{ bids, asks, slot }
);
if (!this.indicativeQuotesRedisClient) {
const bids = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.LONG,
slot: slot,
oraclePriceData: oracleData,
numMakers: 4,
})
.map((x) => x.toString());
const asks = this.dlob
.getBestMakers({
marketIndex: marketArgs.marketIndex,
marketType: marketArgs.marketType,
direction: PositionDirection.SHORT,
slot,
oraclePriceData: oracleData,
numMakers: 4,
})
.map((x) => x.toString());
this.redisClient.set(
`last_update_orderbook_best_makers_${marketType}_${marketArgs.marketIndex}`,
{ bids, asks, slot }
);
}
}
getL3AndSendMsg(marketArgs: wsMarketArgs): void {
@@ -355,7 +466,7 @@ export class DLOBSubscriberIO extends DLOBSubscriber {
this.redisClient.set(
`last_update_orderbook_l3_${marketType}_${marketArgs.marketIndex}${
this.protectedMakerView ? '_pmm' : ''
this.indicativeQuotesRedisClient ? '_indicative' : ''
}`,
l3
);

View File

@@ -1,9 +1,18 @@
import { DLOB, OrderSubscriber, UserAccount, UserMap } from '@drift-labs/sdk';
import {
DLOB,
OrderSubscriber,
ProtectMakerParamsMap,
UserAccount,
UserMap,
} from '@drift-labs/sdk';
import { PublicKey } from '@solana/web3.js';
export type DLOBProvider = {
subscribe(): Promise<void>;
getDLOB(slot: number, protectedMakerView: boolean): Promise<DLOB>;
getDLOB(
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
): Promise<DLOB>;
getUniqueAuthorities(): PublicKey[];
getUserAccounts(): Generator<{
userAccount: UserAccount;
@@ -20,8 +29,11 @@ export function getDLOBProviderFromUserMap(userMap: UserMap): DLOBProvider {
subscribe: async () => {
await userMap.subscribe();
},
getDLOB: async (slot: number, protectedMakerView: boolean) => {
return await userMap.getDLOB(slot, protectedMakerView);
getDLOB: async (
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
) => {
return await userMap.getDLOB(slot, protectedMakerParamsMap);
},
getUniqueAuthorities: () => {
return userMap.getUniqueAuthorities();
@@ -56,8 +68,11 @@ export function getDLOBProviderFromOrderSubscriber(
subscribe: async () => {
await orderSubscriber.subscribe();
},
getDLOB: async (slot: number, protectedMakerView: boolean) => {
return await orderSubscriber.getDLOB(slot, protectedMakerView);
getDLOB: async (
slot: number,
protectedMakerParamsMap?: ProtectMakerParamsMap
) => {
return await orderSubscriber.getDLOB(slot, protectedMakerParamsMap);
},
getUniqueAuthorities: () => {
const authorities = new Set<string>();

View File

@@ -521,7 +521,7 @@ const main = async (): Promise<void> => {
app.get('/l2', async (req, res, next) => {
try {
const { marketName, marketIndex, marketType, depth, includePmm } =
const { marketName, marketIndex, marketType, depth, includeIndicative } =
req.query;
const { normedMarketType, normedMarketIndex, error } = validateDlobQuery(
@@ -537,14 +537,15 @@ const main = async (): Promise<void> => {
}
const isSpot = isVariant(normedMarketType, 'spot');
const includePmmStr = (includePmm as string)?.toLowerCase() === 'true';
const includeIndicativeStr =
(includeIndicative as string)?.toLowerCase() === 'true';
const adjustedDepth = depth ?? '100';
let l2Formatted: any;
const redisL2 = await fetchFromRedis(
`last_update_orderbook_${
isSpot ? 'spot' : 'perp'
}_${normedMarketIndex}${includePmmStr ? '_pmm' : ''}`,
}_${normedMarketIndex}${includeIndicativeStr ? '_indicative' : ''}`,
selectMostRecentBySlot
);
const depthToUse = Math.min(parseInt(adjustedDepth as string) ?? 1, 100);
@@ -606,7 +607,7 @@ const main = async (): Promise<void> => {
includePhoenix,
includeOpenbook,
includeOracle,
includePmm,
includeIndicative,
} = req.query;
const normedParams = normalizeBatchQueryParams({
@@ -618,7 +619,7 @@ const main = async (): Promise<void> => {
includePhoenix: includePhoenix as string | undefined,
includeOpenbook: includeOpenbook as string | undefined,
includeOracle: includeOracle as string | undefined,
includePmm: includePmm as string | undefined,
includeIndicative: includeIndicative as string | undefined,
});
if (normedParams === undefined) {
@@ -649,14 +650,17 @@ const main = async (): Promise<void> => {
}
const isSpot = isVariant(normedMarketType, 'spot');
const normedIncludePmm = normedParam['includePmm'] == 'true';
const normedIncludeIndicative =
normedParam['includeIndicative'] == 'true';
const adjustedDepth = normedParam['depth'] ?? '100';
let l2Formatted: any;
const redisL2 = await fetchFromRedis(
`last_update_orderbook_${
isSpot ? 'spot' : 'perp'
}_${normedMarketIndex}${normedIncludePmm ? '_pmm' : ''}`,
}_${normedMarketIndex}${
normedIncludeIndicative ? '_indicative' : ''
}`,
selectMostRecentBySlot
);
const depth = Math.min(parseInt(adjustedDepth as string) ?? 1, 100);
@@ -726,7 +730,8 @@ const main = async (): Promise<void> => {
app.get('/l3', async (req, res, next) => {
try {
const { marketName, marketIndex, marketType, includePmm } = req.query;
const { marketName, marketIndex, marketType, includeIndicative } =
req.query;
const { normedMarketType, normedMarketIndex, error } = validateDlobQuery(
driftClient,
@@ -741,11 +746,12 @@ const main = async (): Promise<void> => {
}
const marketTypeStr = getVariant(normedMarketType);
const normedIncludePmm = (includePmm as string)?.toLowerCase() === 'true';
const normedIncludeIndicative =
(includeIndicative as string)?.toLowerCase() === 'true';
const redisL3 = await fetchFromRedis(
`last_update_orderbook_l3_${marketTypeStr}_${normedMarketIndex}${
normedIncludePmm ? '_pmm' : ''
normedIncludeIndicative ? '_indicative' : ''
}`,
selectMostRecentBySlot
);

View File

@@ -313,6 +313,9 @@ const main = async () => {
});
await redisClient.connect();
const indicativeRedisClient = new RedisClient({});
await indicativeRedisClient.connect();
const connection = new Connection(endpoint, {
wsEndpoint: wsEndpoint,
commitment: stateCommitment,
@@ -506,7 +509,7 @@ const main = async () => {
});
await dlobSubscriber.subscribe();
const dlobSubscriberPmm = new DLOBSubscriberIO({
const dlobSubscriberIndicative = new DLOBSubscriberIO({
driftClient,
env: driftEnv,
dlobSource: dlobProvider,
@@ -518,8 +521,9 @@ const main = async () => {
spotMarketInfos,
killSwitchSlotDiffThreshold: KILLSWITCH_SLOT_DIFF_THRESHOLD,
protectedMakerView: true,
indicativeQuotesRedisClient: indicativeRedisClient,
});
await dlobSubscriberPmm.subscribe();
await dlobSubscriberIndicative.subscribe();
if (useWebsocket && !FEATURE_FLAGS.DISABLE_GPA_REFRESH) {
const recursiveFetch = (delay = WS_FALLBACK_FETCH_INTERVAL) => {
@@ -595,7 +599,6 @@ const main = async () => {
const handleDebug = async (req: Request, res: Response) => {
const marketIndex = +req.query.marketIndex;
const protectedMakerView = req.query.includePmm === 'true';
let marketType: MarketType = MarketType.PERP;
let oraclePriceData: OraclePriceData;
if (req.query.marketType === 'spot') {
@@ -606,7 +609,7 @@ const main = async () => {
}
try {
const slot = slotSource.getSlot();
const dlob = await dlobProvider.getDLOB(slot, protectedMakerView);
const dlob = await dlobProvider.getDLOB(slot);
const l2 = dlob.getL2({
marketIndex,
marketType,
@@ -629,7 +632,6 @@ const main = async () => {
},
l2: l2WithBNToStrings(l2),
l3,
includePmm: protectedMakerView,
};
res.json(state);

View File

@@ -95,6 +95,8 @@ const getRedisChannelFromMessage = (message: any): string => {
return `trades_${marketType}_${marketIndex}`;
case 'orderbook':
return `orderbook_${marketType}_${marketIndex}`;
case 'orderbook_indicative':
return `orderbook_${marketType}_${marketIndex}_indicative`;
case 'priorityfees':
return `priorityFees_${marketType}_${marketIndex}`;
case undefined: