From 4a9798fc517d8bc47942fa6e656a8e6da1fecd89 Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:39:45 -0800 Subject: [PATCH 1/6] Add trigger+fill to filler --- src/bots/filler.ts | 144 +++++++++++++++++-- src/experimental-bots/filler-common/types.ts | 9 ++ 2 files changed, 139 insertions(+), 14 deletions(-) diff --git a/src/bots/filler.ts b/src/bots/filler.ts index c558689f..f8b4acce 100644 --- a/src/bots/filler.ts +++ b/src/bots/filler.ts @@ -32,6 +32,9 @@ import { QUOTE_PRECISION, ClockSubscriber, DriftEnv, + OraclePriceData, + StateAccount, + getUserStatsAccountPublicKey, PerpMarkets, } from '@drift-labs/sdk'; import { Mutex, tryAcquire, E_ALREADY_LOCKED } from 'async-mutex'; @@ -97,6 +100,7 @@ import { LRUCache } from 'lru-cache'; import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { PythPriceFeedSubscriber } from '../pythPriceFeedSubscriber'; import { TxThreaded } from './common/txThreaded'; +import { NodeToTriggerWithMakers } from 'src/experimental-bots/filler-common/types'; import { PythLazerSubscriber } from '../pythLazerSubscriber'; const TX_COUNT_COOLDOWN_ON_BURST = 10; // send this many tx before resetting burst mode @@ -775,7 +779,7 @@ export class FillerBot extends TxThreaded implements Bot { dlob: DLOB ): { nodesToFill: Array; - nodesToTrigger: Array; + nodesToTrigger: Array; } { const marketIndex = market.marketIndex; @@ -786,6 +790,14 @@ export class FillerBot extends TxThreaded implements Bot { const vBid = calculateBidPrice(market, oraclePriceData); const fillSlot = this.getMaxSlot(); + const nodesToTrigger = this.findTriggerableNodesWithMakers( + dlob, + marketIndex, + fillSlot, + oraclePriceData, + MarketType.PERP, + this.driftClient.getStateAccount() + ); return { nodesToFill: dlob.findNodesToFill( @@ -799,13 +811,7 @@ export class FillerBot extends TxThreaded implements Bot { this.driftClient.getStateAccount(), this.driftClient.getPerpMarketAccount(marketIndex)! ), - nodesToTrigger: dlob.findNodesToTrigger( - marketIndex, - fillSlot, - oraclePriceData.price, - MarketType.PERP, - this.driftClient.getStateAccount() - ), + nodesToTrigger, }; } @@ -1881,10 +1887,10 @@ export class FillerBot extends TxThreaded implements Bot { protected filterPerpNodesForMarket( fillableNodes: Array, - triggerableNodes: Array + triggerableNodes: Array ): { filteredFillableNodes: Array; - filteredTriggerableNodes: Array; + filteredTriggerableNodes: Array; } { const seenFillableNodes = new Set(); const filteredFillableNodes = fillableNodes.filter((node) => { @@ -1920,7 +1926,7 @@ export class FillerBot extends TxThreaded implements Bot { } protected async executeTriggerablePerpNodesForMarket( - triggerableNodes: Array, + triggerableNodes: Array, buildForBundle: boolean ) { for (const nodeToTrigger of triggerableNodes) { @@ -1929,9 +1935,11 @@ export class FillerBot extends TxThreaded implements Bot { nodeToTrigger.node.userAccount.toString() ); logger.info( - `trying to trigger (account: ${nodeToTrigger.node.userAccount.toString()}, slot: ${ + `Processing node: account=${nodeToTrigger.node.userAccount.toString()}, slot=${ user.slot - }) order ${nodeToTrigger.node.order.orderId.toString()}` + }, orderId=${nodeToTrigger.node.order.orderId.toString()}, isFillable=${ + nodeToTrigger.isFillable + }, numMakers=${nodeToTrigger.makers?.length ?? 0}` ); const nodeSignature = getNodeToTriggerSignature(nodeToTrigger); @@ -1970,6 +1978,46 @@ export class FillerBot extends TxThreaded implements Bot { ) ); + if (nodeToTrigger.isFillable) { + logger.info( + `Node is fillable - processing ${nodeToTrigger.makers.length} makers` + ); + const makerInfos = await Promise.all( + nodeToTrigger.makers.map(async (m) => { + const maker = new PublicKey(m); + logger.info(`Getting maker info for ${maker.toString()}`); + const { data } = await this.getUserAccountAndSlotFromMap( + maker.toString() + ); + const makerUserAccount = data; + const makerAuthority = makerUserAccount.authority; + const makerStats = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + makerAuthority + ); + logger.info( + `Got maker info: authority=${makerAuthority}, slot=${user.slot}` + ); + return { + maker, + makerStats, + makerUserAccount, + }; + }) + ); + + logger.info( + `Adding fill perp order ix for ${makerInfos.length} makers` + ); + const fillIx = await this.driftClient.getFillPerpOrderIx( + new PublicKey(nodeToTrigger.node.userAccount), + user.data, + nodeToTrigger.node.order, + makerInfos + ); + ixs.push(fillIx); + } + if (this.revertOnFailure) { ixs.push(await this.driftClient.getRevertFillIx()); } @@ -2296,6 +2344,74 @@ export class FillerBot extends TxThreaded implements Bot { return true; } + public findTriggerableNodesWithMakers( + dlob: DLOB, + marketIndex: number, + slot: number, + oraclePriceData: OraclePriceData, + marketType: MarketType, + stateAccount: StateAccount + ): NodeToTriggerWithMakers[] { + const baseTriggerable = dlob.findNodesToTrigger( + marketIndex, + slot, + oraclePriceData.price, + marketType, + stateAccount + ); + + const triggerWithMaker: NodeToTriggerWithMakers[] = []; + + for (const nodeObj of baseTriggerable) { + const order = nodeObj.node.order; + let isFillable = false; + + if ( + isVariant(order.orderType, 'market') || + isVariant(order.orderType, 'triggerMarket') + ) { + isFillable = true; + } else if (isVariant(order.orderType, 'triggerLimit')) { + if ( + isVariant(order.triggerCondition, 'triggeredAbove') || + isVariant(order.triggerCondition, 'above') + ) { + isFillable = order.price.lte(oraclePriceData.price); + } else if ( + isVariant(order.triggerCondition, 'triggeredBelow') || + isVariant(order.triggerCondition, 'below') + ) { + isFillable = order.price.gte(oraclePriceData.price); + } + } + + const NUM_MAKERS = 4; + if (isFillable) { + const makers = dlob.getBestMakers({ + marketIndex, + marketType, + direction: order.direction, + slot, + oraclePriceData, + numMakers: NUM_MAKERS, + }); + triggerWithMaker.push({ + ...nodeObj, + isFillable, + makers, + }); + } else { + triggerWithMaker.push({ + ...nodeObj, + isFillable, + makers: [], + }); + } + } + + return triggerWithMaker; + } + protected async tryFill() { const startTime = Date.now(); let ran = false; @@ -2325,7 +2441,7 @@ export class FillerBot extends TxThreaded implements Bot { // 1) get all fillable nodes let fillableNodes: Array = []; - let triggerableNodes: Array = []; + let triggerableNodes: Array = []; for (const market of this.driftClient.getPerpMarketAccounts()) { try { const { nodesToFill, nodesToTrigger } = this.getPerpNodesForMarket( diff --git a/src/experimental-bots/filler-common/types.ts b/src/experimental-bots/filler-common/types.ts index 3e658db6..32bd69c6 100644 --- a/src/experimental-bots/filler-common/types.ts +++ b/src/experimental-bots/filler-common/types.ts @@ -7,6 +7,8 @@ import { SpotBalanceType, NodeToFill, DLOBNode, + NodeToTrigger, + PublicKey, } from '@drift-labs/sdk'; export type SerializedUserAccount = { @@ -95,6 +97,8 @@ export type SerializedPerpPosition = { export type SerializedNodeToTrigger = { node: SerializedTriggerOrderNode; + isFillable: boolean; + makers: string[]; }; export type SerializedTriggerOrderNode = { @@ -143,3 +147,8 @@ export type NodeToFillWithBuffer = { makerNodes: DLOBNode[]; authority?: string; }; + +export type NodeToTriggerWithMakers = NodeToTrigger & { + isFillable: boolean; + makers: PublicKey[]; +}; From 45bfac60e1fc5fc7d4bc587921e0d99852d927b3 Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:48:49 -0800 Subject: [PATCH 2/6] Add trigger and fill for multithreaded experimental bot --- .../filler-common/dlobBuilder.ts | 99 ++++++++++++++++--- src/experimental-bots/filler-common/utils.ts | 11 ++- .../filler/fillerMultithreaded.ts | 76 +++++++++++--- 3 files changed, 156 insertions(+), 30 deletions(-) diff --git a/src/experimental-bots/filler-common/dlobBuilder.ts b/src/experimental-bots/filler-common/dlobBuilder.ts index ac5040e6..bf98d9bb 100644 --- a/src/experimental-bots/filler-common/dlobBuilder.ts +++ b/src/experimental-bots/filler-common/dlobBuilder.ts @@ -10,7 +10,6 @@ import { UserAccount, isVariant, decodeUser, - NodeToTrigger, Wallet, PhoenixSubscriber, BN, @@ -27,6 +26,8 @@ import { PositionDirection, UserStatus, isUserProtectedMaker, + OraclePriceData, + StateAccount, } from '@drift-labs/sdk'; import { Connection, PublicKey } from '@solana/web3.js'; import dotenv from 'dotenv'; @@ -37,6 +38,7 @@ import { SerializedNodeToFill, SerializedNodeToTrigger, NodeToFillWithContext, + NodeToTriggerWithMakers, } from './types'; import { getDriftClientFromArgs, @@ -47,6 +49,7 @@ import { initializeSpotFulfillmentAccounts, sleepMs } from '../../utils'; import { LRUCache } from 'lru-cache'; const EXPIRE_ORDER_BUFFER_SEC = 30; // add an extra 30 seconds before trying to expire orders (want to avoid 6252 error due to clock drift) +const NUM_MAKERS = 5; // number of makers to consider for triggerable nodes const logPrefix = '[DLOBBuilder]'; class DLOBBuilder { @@ -260,14 +263,14 @@ class DLOBBuilder { public getNodesToTriggerAndNodesToFill(): [ NodeToFillWithContext[], - NodeToTrigger[], + NodeToTriggerWithMakers[], ] { const dlob = this.build(); const nodesToFill: NodeToFillWithContext[] = []; - const nodesToTrigger: NodeToTrigger[] = []; + const nodesToTrigger: NodeToTriggerWithMakers[] = []; for (const marketIndex of this.marketIndexes) { let market; - let oraclePriceData; + let oraclePriceData: OraclePriceData; let fallbackAsk: BN | undefined = undefined; let fallbackBid: BN | undefined = undefined; let fallbackAskSource: FallbackLiquiditySource | undefined = undefined; @@ -343,13 +346,16 @@ class DLOBBuilder { stateAccount, market ); - const nodesToTriggerForMarket = dlob.findNodesToTrigger( - marketIndex, - slot, - oraclePriceData.price, - this.marketType, - stateAccount - ); + const nodesToTriggerForMarket: NodeToTriggerWithMakers[] = + this.findTriggerableNodesWithMakers( + dlob, + marketIndex, + slot, + oraclePriceData, + this.marketType, + stateAccount + ); + nodesToFill.push( ...nodesToFillForMarket.map((node) => { return { ...node, fallbackAskSource, fallbackBidSource }; @@ -360,6 +366,73 @@ class DLOBBuilder { return [nodesToFill, nodesToTrigger]; } + public findTriggerableNodesWithMakers( + dlob: DLOB, + marketIndex: number, + slot: number, + oraclePriceData: OraclePriceData, + marketType: MarketType, + stateAccount: StateAccount + ): NodeToTriggerWithMakers[] { + const baseTriggerable = dlob.findNodesToTrigger( + marketIndex, + slot, + oraclePriceData.price, + marketType, + stateAccount + ); + + const triggerWithMaker: NodeToTriggerWithMakers[] = []; + + for (const nodeObj of baseTriggerable) { + const order = nodeObj.node.order; + let isFillable = false; + + if ( + isVariant(order.orderType, 'market') || + isVariant(order.orderType, 'triggerMarket') + ) { + isFillable = true; + } else if (isVariant(order.orderType, 'triggerLimit')) { + if ( + isVariant(order.triggerCondition, 'triggeredAbove') || + isVariant(order.triggerCondition, 'above') + ) { + isFillable = order.price.lte(oraclePriceData.price); + } else if ( + isVariant(order.triggerCondition, 'triggeredBelow') || + isVariant(order.triggerCondition, 'below') + ) { + isFillable = order.price.gte(oraclePriceData.price); + } + } + + if (isFillable) { + const makers = dlob.getBestMakers({ + marketIndex, + marketType, + direction: order.direction, + slot, + oraclePriceData, + numMakers: NUM_MAKERS, + }); + triggerWithMaker.push({ + ...nodeObj, + isFillable, + makers, + }); + } else { + triggerWithMaker.push({ + ...nodeObj, + isFillable, + makers: [], + }); + } + } + + return triggerWithMaker; + } + public serializeNodesToFill( nodesToFill: NodeToFillWithContext[] ): SerializedNodeToFill[] { @@ -393,7 +466,7 @@ class DLOBBuilder { } public serializeNodesToTrigger( - nodesToTrigger: NodeToTrigger[] + nodesToTrigger: NodeToTriggerWithMakers[] ): SerializedNodeToTrigger[] { return nodesToTrigger .map((node) => { @@ -401,7 +474,7 @@ class DLOBBuilder { if (!buffer) { return undefined; } - return serializeNodeToTrigger(node, buffer); + return serializeNodeToTrigger(node, buffer, node.makers); }) .filter((node): node is SerializedNodeToTrigger => node !== undefined); } diff --git a/src/experimental-bots/filler-common/utils.ts b/src/experimental-bots/filler-common/utils.ts index b62f926c..68ebae09 100644 --- a/src/experimental-bots/filler-common/utils.ts +++ b/src/experimental-bots/filler-common/utils.ts @@ -3,7 +3,6 @@ import { UserAccount, SpotPosition, PerpPosition, - NodeToTrigger, TriggerOrderNode, DLOBNode, OrderNode, @@ -39,6 +38,7 @@ import { SerializedDLOBNode, NodeToFillWithBuffer, NodeToFillWithContext, + NodeToTriggerWithMakers, } from './types'; import { ChildProcess, fork } from 'child_process'; import { logger } from '../../logger'; @@ -213,11 +213,14 @@ const deserializePerpPosition = ( }; export const serializeNodeToTrigger = ( - node: NodeToTrigger, - userAccountData: Buffer + node: NodeToTriggerWithMakers, + userAccountData: Buffer, + makers: PublicKey[] ): SerializedNodeToTrigger => { return { node: serializeTriggerOrderNode(node.node, userAccountData), + isFillable: node.isFillable, + makers: makers.map((maker) => maker.toString()), }; }; @@ -322,7 +325,7 @@ export const deserializeNodeToFill = ( return node; }; -const deserializeDLOBNode = (node: SerializedDLOBNode): DLOBNode => { +export const deserializeDLOBNode = (node: SerializedDLOBNode): DLOBNode => { const order = deserializeOrder(node.order); switch (node.type) { case 'TakingLimitOrderNode': diff --git a/src/experimental-bots/filler/fillerMultithreaded.ts b/src/experimental-bots/filler/fillerMultithreaded.ts index 99fe9a78..07b046dc 100644 --- a/src/experimental-bots/filler/fillerMultithreaded.ts +++ b/src/experimental-bots/filler/fillerMultithreaded.ts @@ -1414,36 +1414,86 @@ export class FillerMultithreaded { } const nodeSignature = getNodeToTriggerSignature(nodeToTrigger); - if (this.seenTriggerableOrders.has(nodeSignature)) { - if (!this.pythPriceSubscriber) { - // no pyth subscriber, tx will be empty - return; - } + if (!this.seenTriggerableOrders.has(nodeSignature)) { + this.seenTriggerableOrders.add(nodeSignature); + this.triggeringNodes.set(nodeSignature, Date.now()); + const deserializedOrder = deserializeOrder(nodeToTrigger.node.order); logger.info( - `${logPrefix} already triggered order (account: ${ + `${logPrefix} adding trigger order ix for user ${ nodeToTrigger.node.userAccount - }, order ${nodeToTrigger.node.order.orderId.toString()}. - Just going to pull oracles` + } order ${deserializedOrder.orderId.toString()}` ); - } else { - this.seenTriggerableOrders.add(nodeSignature); - this.triggeringNodes.set(nodeSignature, Date.now()); ixs.push( await this.driftClient.getTriggerOrderIx( new PublicKey(nodeToTrigger.node.userAccount), userAccount, - deserializeOrder(nodeToTrigger.node.order), + deserializedOrder, user.userAccountPublicKey ) ); + if (nodeToTrigger.isFillable) { + logger.info( + `${logPrefix} order is fillable, processing ${nodeToTrigger.makers.length} makers` + ); + // TODO: Check for Swift? + const makerInfos = await Promise.all( + nodeToTrigger.makers.map(async (m) => { + const maker = new PublicKey(m); + logger.info( + `${logPrefix} getting maker info for ${maker.toString()}` + ); + const user = await this.userMap!.mustGetWithSlot( + m, + this.driftClient.userAccountSubscriptionConfig + ); + const makerUserAccount = user.data.getUserAccount(); + const makerAuthority = makerUserAccount.authority; + const makerStats = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + makerAuthority + ); + return { + maker, + makerStats, + makerUserAccount, + }; + }) + ); + + logger.info( + `${logPrefix} adding fill perp order ix for ${makerInfos.length} makers` + ); + const fillIx = await this.driftClient.getFillPerpOrderIx( + new PublicKey(nodeToTrigger.node.userAccount), + user.getUserAccount(), + deserializedOrder, + makerInfos, + undefined, + this.subaccount + ); + ixs.push(fillIx); + } if (this.revertOnFailure) { - console.log(user.userAccountPublicKey.toString()); + logger.info( + `${logPrefix} adding revert fill ix for user ${user.userAccountPublicKey.toString()}` + ); ixs.push( await this.driftClient.getRevertFillIx(user.userAccountPublicKey) ); } + } else { + if (!this.pythPriceSubscriber) { + logger.info(`${logPrefix} no pyth price subscriber, returning`); + return; + } + logger.info( + `${logPrefix} already triggered order (account: ${ + nodeToTrigger.node.userAccount + }, order ${nodeToTrigger.node.order.orderId.toString()}). + Just going to pull oracles` + ); } const txSize = getSizeOfTransaction(ixs, true, this.lookupTableAccounts); From 59a3979ed33674f3908946f3941567710788bfab Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:39:30 -0800 Subject: [PATCH 3/6] Address comments: always attempt fill --- src/bots/filler.ts | 137 +++++++----------- .../filler-common/dlobBuilder.ts | 55 ++----- src/experimental-bots/filler-common/types.ts | 2 - .../filler/fillerMultithreaded.ts | 118 +++++++-------- 4 files changed, 116 insertions(+), 196 deletions(-) diff --git a/src/bots/filler.ts b/src/bots/filler.ts index f8b4acce..cee86c27 100644 --- a/src/bots/filler.ts +++ b/src/bots/filler.ts @@ -35,6 +35,7 @@ import { OraclePriceData, StateAccount, getUserStatsAccountPublicKey, + PositionDirection, PerpMarkets, } from '@drift-labs/sdk'; import { Mutex, tryAcquire, E_ALREADY_LOCKED } from 'async-mutex'; @@ -100,7 +101,7 @@ import { LRUCache } from 'lru-cache'; import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { PythPriceFeedSubscriber } from '../pythPriceFeedSubscriber'; import { TxThreaded } from './common/txThreaded'; -import { NodeToTriggerWithMakers } from 'src/experimental-bots/filler-common/types'; +import { NodeToTriggerWithMakers } from '../experimental-bots/filler-common/types'; import { PythLazerSubscriber } from '../pythLazerSubscriber'; const TX_COUNT_COOLDOWN_ON_BURST = 10; // send this many tx before resetting burst mode @@ -122,6 +123,7 @@ export const CACHED_BLOCKHASH_OFFSET = 5; const DUMP_TXS_IN_SIM = false; const EXPIRE_ORDER_BUFFER_SEC = 60; // add extra time before trying to expire orders (want to avoid 6252 error due to clock drift) +const NUM_MAKERS = 4; // number of makers to pull for triggerable orders const errorCodesToSuppress = [ 6004, // 0x1774 Error Number: 6004. Error Message: SufficientCollateral. @@ -1937,9 +1939,9 @@ export class FillerBot extends TxThreaded implements Bot { logger.info( `Processing node: account=${nodeToTrigger.node.userAccount.toString()}, slot=${ user.slot - }, orderId=${nodeToTrigger.node.order.orderId.toString()}, isFillable=${ - nodeToTrigger.isFillable - }, numMakers=${nodeToTrigger.makers?.length ?? 0}` + }, orderId=${nodeToTrigger.node.order.orderId.toString()}, numMakers=${ + nodeToTrigger.makers?.length ?? 0 + }` ); const nodeSignature = getNodeToTriggerSignature(nodeToTrigger); @@ -1978,45 +1980,35 @@ export class FillerBot extends TxThreaded implements Bot { ) ); - if (nodeToTrigger.isFillable) { - logger.info( - `Node is fillable - processing ${nodeToTrigger.makers.length} makers` - ); - const makerInfos = await Promise.all( - nodeToTrigger.makers.map(async (m) => { - const maker = new PublicKey(m); - logger.info(`Getting maker info for ${maker.toString()}`); - const { data } = await this.getUserAccountAndSlotFromMap( - maker.toString() - ); - const makerUserAccount = data; - const makerAuthority = makerUserAccount.authority; - const makerStats = getUserStatsAccountPublicKey( - this.driftClient.program.programId, - makerAuthority - ); - logger.info( - `Got maker info: authority=${makerAuthority}, slot=${user.slot}` - ); - return { - maker, - makerStats, - makerUserAccount, - }; - }) - ); + logger.info(`Triggrable node has ${nodeToTrigger.makers.length} makers`); + const makerInfos = await Promise.all( + nodeToTrigger.makers.map(async (m) => { + const maker = new PublicKey(m); + logger.info(`Getting maker info for ${maker.toString()}`); + const { data } = await this.getUserAccountAndSlotFromMap( + maker.toString() + ); + const makerUserAccount = data; + const makerAuthority = makerUserAccount.authority; + const makerStats = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + makerAuthority + ); + return { + maker, + makerStats, + makerUserAccount, + }; + }) + ); - logger.info( - `Adding fill perp order ix for ${makerInfos.length} makers` - ); - const fillIx = await this.driftClient.getFillPerpOrderIx( - new PublicKey(nodeToTrigger.node.userAccount), - user.data, - nodeToTrigger.node.order, - makerInfos - ); - ixs.push(fillIx); - } + const fillIx = await this.driftClient.getFillPerpOrderIx( + new PublicKey(nodeToTrigger.node.userAccount), + user.data, + nodeToTrigger.node.order, + makerInfos + ); + ixs.push(fillIx); if (this.revertOnFailure) { ixs.push(await this.driftClient.getRevertFillIx()); @@ -2359,54 +2351,31 @@ export class FillerBot extends TxThreaded implements Bot { marketType, stateAccount ); + if (baseTriggerable.length > 0) { + logger.info( + `Found ${baseTriggerable.length} nodes to trigger for market ${marketIndex}` + ); + } const triggerWithMaker: NodeToTriggerWithMakers[] = []; for (const nodeObj of baseTriggerable) { const order = nodeObj.node.order; - let isFillable = false; - - if ( - isVariant(order.orderType, 'market') || - isVariant(order.orderType, 'triggerMarket') - ) { - isFillable = true; - } else if (isVariant(order.orderType, 'triggerLimit')) { - if ( - isVariant(order.triggerCondition, 'triggeredAbove') || - isVariant(order.triggerCondition, 'above') - ) { - isFillable = order.price.lte(oraclePriceData.price); - } else if ( - isVariant(order.triggerCondition, 'triggeredBelow') || - isVariant(order.triggerCondition, 'below') - ) { - isFillable = order.price.gte(oraclePriceData.price); - } - } - const NUM_MAKERS = 4; - if (isFillable) { - const makers = dlob.getBestMakers({ - marketIndex, - marketType, - direction: order.direction, - slot, - oraclePriceData, - numMakers: NUM_MAKERS, - }); - triggerWithMaker.push({ - ...nodeObj, - isFillable, - makers, - }); - } else { - triggerWithMaker.push({ - ...nodeObj, - isFillable, - makers: [], - }); - } + const makers = dlob.getBestMakers({ + marketIndex, + marketType, + direction: isVariant(order.direction, 'long') + ? PositionDirection.SHORT + : PositionDirection.LONG, + slot, + oraclePriceData, + numMakers: NUM_MAKERS, + }); + triggerWithMaker.push({ + ...nodeObj, + makers, + }); } return triggerWithMaker; diff --git a/src/experimental-bots/filler-common/dlobBuilder.ts b/src/experimental-bots/filler-common/dlobBuilder.ts index bf98d9bb..faf6b0df 100644 --- a/src/experimental-bots/filler-common/dlobBuilder.ts +++ b/src/experimental-bots/filler-common/dlobBuilder.ts @@ -386,48 +386,21 @@ class DLOBBuilder { for (const nodeObj of baseTriggerable) { const order = nodeObj.node.order; - let isFillable = false; - - if ( - isVariant(order.orderType, 'market') || - isVariant(order.orderType, 'triggerMarket') - ) { - isFillable = true; - } else if (isVariant(order.orderType, 'triggerLimit')) { - if ( - isVariant(order.triggerCondition, 'triggeredAbove') || - isVariant(order.triggerCondition, 'above') - ) { - isFillable = order.price.lte(oraclePriceData.price); - } else if ( - isVariant(order.triggerCondition, 'triggeredBelow') || - isVariant(order.triggerCondition, 'below') - ) { - isFillable = order.price.gte(oraclePriceData.price); - } - } - if (isFillable) { - const makers = dlob.getBestMakers({ - marketIndex, - marketType, - direction: order.direction, - slot, - oraclePriceData, - numMakers: NUM_MAKERS, - }); - triggerWithMaker.push({ - ...nodeObj, - isFillable, - makers, - }); - } else { - triggerWithMaker.push({ - ...nodeObj, - isFillable, - makers: [], - }); - } + const makers = dlob.getBestMakers({ + marketIndex, + marketType, + direction: isVariant(order.direction, 'long') + ? PositionDirection.SHORT + : PositionDirection.LONG, + slot, + oraclePriceData, + numMakers: NUM_MAKERS, + }); + triggerWithMaker.push({ + ...nodeObj, + makers, + }); } return triggerWithMaker; diff --git a/src/experimental-bots/filler-common/types.ts b/src/experimental-bots/filler-common/types.ts index 32bd69c6..91014a7f 100644 --- a/src/experimental-bots/filler-common/types.ts +++ b/src/experimental-bots/filler-common/types.ts @@ -97,7 +97,6 @@ export type SerializedPerpPosition = { export type SerializedNodeToTrigger = { node: SerializedTriggerOrderNode; - isFillable: boolean; makers: string[]; }; @@ -149,6 +148,5 @@ export type NodeToFillWithBuffer = { }; export type NodeToTriggerWithMakers = NodeToTrigger & { - isFillable: boolean; makers: PublicKey[]; }; diff --git a/src/experimental-bots/filler/fillerMultithreaded.ts b/src/experimental-bots/filler/fillerMultithreaded.ts index 07b046dc..91da9ebf 100644 --- a/src/experimental-bots/filler/fillerMultithreaded.ts +++ b/src/experimental-bots/filler/fillerMultithreaded.ts @@ -1378,7 +1378,6 @@ export class FillerMultithreaded { ) * this.driftClient.txSender.getSuggestedPriorityFeeMultiplier() ); const buildForBundle = this.shouldBuildForBundle(); - if (buildForBundle) { ixs.push(this.bundleSender!.getTipIx()); } else { @@ -1395,12 +1394,6 @@ export class FillerMultithreaded { // @ts-ignore const userAccount = decodeUser(buffer); - logger.debug( - `${logPrefix} trying to trigger (account: ${ - nodeToTrigger.node.userAccount - }, order ${nodeToTrigger.node.order.orderId.toString()}` - ); - let removeLastIxPostSim = this.revertOnFailure; if (this.pythPriceSubscriber) { const pythIxs = await this.getPythIxsFromNode( @@ -1409,7 +1402,6 @@ export class FillerMultithreaded { false ); ixs.push(...pythIxs); - // do not strip the last ix if we have pyth ixs removeLastIxPostSim = false; } @@ -1418,11 +1410,6 @@ export class FillerMultithreaded { this.seenTriggerableOrders.add(nodeSignature); this.triggeringNodes.set(nodeSignature, Date.now()); const deserializedOrder = deserializeOrder(nodeToTrigger.node.order); - logger.info( - `${logPrefix} adding trigger order ix for user ${ - nodeToTrigger.node.userAccount - } order ${deserializedOrder.orderId.toString()}` - ); ixs.push( await this.driftClient.getTriggerOrderIx( @@ -1432,73 +1419,66 @@ export class FillerMultithreaded { user.userAccountPublicKey ) ); - if (nodeToTrigger.isFillable) { - logger.info( - `${logPrefix} order is fillable, processing ${nodeToTrigger.makers.length} makers` - ); - // TODO: Check for Swift? - const makerInfos = await Promise.all( - nodeToTrigger.makers.map(async (m) => { - const maker = new PublicKey(m); - logger.info( - `${logPrefix} getting maker info for ${maker.toString()}` - ); - const user = await this.userMap!.mustGetWithSlot( - m, - this.driftClient.userAccountSubscriptionConfig - ); - const makerUserAccount = user.data.getUserAccount(); - const makerAuthority = makerUserAccount.authority; - const makerStats = getUserStatsAccountPublicKey( - this.driftClient.program.programId, - makerAuthority - ); - return { - maker, - makerStats, - makerUserAccount, - }; - }) - ); - logger.info( - `${logPrefix} adding fill perp order ix for ${makerInfos.length} makers` - ); - const fillIx = await this.driftClient.getFillPerpOrderIx( - new PublicKey(nodeToTrigger.node.userAccount), - user.getUserAccount(), - deserializedOrder, - makerInfos, - undefined, - this.subaccount - ); - ixs.push(fillIx); - } + logger.info( + `executeTriggerablePerpNodes: processing fillable order with ${nodeToTrigger.makers.length} makers` + ); + const makerInfos = await Promise.all( + nodeToTrigger.makers.map(async (m) => { + const maker = new PublicKey(m); + logger.info( + `executeTriggerablePerpNodes: getting maker info for ${maker.toString()}` + ); + const user = await this.userMap!.mustGetWithSlot( + m, + this.driftClient.userAccountSubscriptionConfig + ); + const makerUserAccount = user.data.getUserAccount(); + const makerAuthority = makerUserAccount.authority; + const makerStats = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + makerAuthority + ); + return { + maker, + makerStats, + makerUserAccount, + }; + }) + ); + + const fillIx = await this.driftClient.getFillPerpOrderIx( + new PublicKey(nodeToTrigger.node.userAccount), + user.getUserAccount(), + deserializedOrder, + makerInfos, + undefined, + this.subaccount + ); + ixs.push(fillIx); if (this.revertOnFailure) { - logger.info( - `${logPrefix} adding revert fill ix for user ${user.userAccountPublicKey.toString()}` - ); ixs.push( await this.driftClient.getRevertFillIx(user.userAccountPublicKey) ); } } else { if (!this.pythPriceSubscriber) { - logger.info(`${logPrefix} no pyth price subscriber, returning`); + logger.info( + `executeTriggerablePerpNodes: no pyth price subscriber, skipping` + ); return; } logger.info( - `${logPrefix} already triggered order (account: ${ - nodeToTrigger.node.userAccount - }, order ${nodeToTrigger.node.order.orderId.toString()}). - Just going to pull oracles` + `executeTriggerablePerpNodes: already triggered order ${nodeSignature}, only pulling oracles` ); } const txSize = getSizeOfTransaction(ixs, true, this.lookupTableAccounts); if (txSize > PACKET_DATA_SIZE) { - logger.warn(`tx too large, removing pyth ixs.`); + logger.warn( + `executeTriggerablePerpNodes: tx too large, removing pyth ixs` + ); ixs = removePythIxs(ixs); } @@ -1530,14 +1510,14 @@ export class FillerMultithreaded { }); logger.info( - `executeTriggerablePerpNodesForMarket (${nodeSignature}) estimated CUs: ${simResult.cuEstimate}, finalIxCount: ${ixs.length}). revertTx: ${this.revertOnFailure}}, buildForBundle: ${buildForBundle}` + `executeTriggerablePerpNodes: simulation complete - CUs: ${simResult.cuEstimate}, ixCount: ${ixs.length}, revertTx: ${this.revertOnFailure}, buildForBundle: ${buildForBundle}` ); if (simResult.simError) { logger.error( - `executeTriggerablePerpNodesForMarket simError: (simError: ${JSON.stringify( + `executeTriggerablePerpNodes: simulation error: ${JSON.stringify( simResult.simError - )})` + )}` ); } else { if (this.hasEnoughSolToFill) { @@ -1550,7 +1530,7 @@ export class FillerMultithreaded { .sendTransaction(simResult.tx) .then((txSig) => { logger.info( - `${logPrefix} Triggered user (account: ${nodeToTrigger.node.userAccount.toString()}) order: ${nodeToTrigger.node.order.orderId.toString()} Tx: ${ + `executeTriggerablePerpNodes: triggered successfully - user: ${nodeToTrigger.node.userAccount.toString()}, order: ${nodeToTrigger.node.order.orderId.toString()}, tx: ${ txSig.txSig }` ); @@ -1567,7 +1547,7 @@ export class FillerMultithreaded { ) ) { logger.error( - `Error (${errorCode}) triggering order for user (account: ${nodeToTrigger.node.userAccount.toString()}) order: ${nodeToTrigger.node.order.orderId.toString()}` + `executeTriggerablePerpNodes: error (${errorCode}) triggering order for user ${nodeToTrigger.node.userAccount.toString()}, order: ${nodeToTrigger.node.order.orderId.toString()}` ); logger.error(error); } @@ -1578,7 +1558,7 @@ export class FillerMultithreaded { } } else { logger.info( - `Not enough SOL to fill, skipping executeTriggerablePerpNodes` + `executeTriggerablePerpNodes: insufficient SOL balance to fill` ); } } From 788725d3ab4d301fea116c8857f412e924a9d304 Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:54:47 -0800 Subject: [PATCH 4/6] Update serialize node --- src/experimental-bots/filler-common/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/experimental-bots/filler-common/utils.ts b/src/experimental-bots/filler-common/utils.ts index 68ebae09..64670696 100644 --- a/src/experimental-bots/filler-common/utils.ts +++ b/src/experimental-bots/filler-common/utils.ts @@ -219,7 +219,6 @@ export const serializeNodeToTrigger = ( ): SerializedNodeToTrigger => { return { node: serializeTriggerOrderNode(node.node, userAccountData), - isFillable: node.isFillable, makers: makers.map((maker) => maker.toString()), }; }; From 3b41614827a3cbc71cfe048c138ed28b7e5f9359 Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Thu, 23 Jan 2025 02:53:22 -0800 Subject: [PATCH 5/6] Update serializing and deserializing of triggernode account with maker --- src/experimental-bots/filler-common/types.ts | 2 ++ src/experimental-bots/filler-common/utils.ts | 31 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/experimental-bots/filler-common/types.ts b/src/experimental-bots/filler-common/types.ts index 91014a7f..28ed1fb1 100644 --- a/src/experimental-bots/filler-common/types.ts +++ b/src/experimental-bots/filler-common/types.ts @@ -107,6 +107,8 @@ export type SerializedTriggerOrderNode = { sortValue: string; haveFilled: boolean; haveTrigger: boolean; + isSwift: boolean; + isUserProtectedMaker: boolean; }; export type SerializedNodeToFill = { diff --git a/src/experimental-bots/filler-common/utils.ts b/src/experimental-bots/filler-common/utils.ts index 64670696..1017dab3 100644 --- a/src/experimental-bots/filler-common/utils.ts +++ b/src/experimental-bots/filler-common/utils.ts @@ -234,6 +234,37 @@ const serializeTriggerOrderNode = ( sortValue: node.sortValue.toString('hex'), haveFilled: node.haveFilled, haveTrigger: node.haveTrigger, + isSwift: node.isSwift, + isUserProtectedMaker: node.isUserProtectedMaker, + }; +}; + +export const deserializeNodeToTriggerWithMakers = ( + serializedNode: SerializedNodeToTrigger +): NodeToTriggerWithMakers => { + return { + node: deserializeTriggerOrderNode(serializedNode.node), + makers: serializedNode.makers.map((m) => new PublicKey(m)), + }; +}; + +const deserializeTriggerOrderNode = ( + serializedNode: SerializedTriggerOrderNode +): TriggerOrderNode => { + const order = deserializeOrder(serializedNode.order); + return { + order, + userAccount: serializedNode.userAccount, + sortValue: new BN(serializedNode.sortValue, 'hex'), + haveFilled: serializedNode.haveFilled, + haveTrigger: serializedNode.haveTrigger, + isUserProtectedMaker: serializedNode.isUserProtectedMaker, + isSwift: serializedNode.isSwift, + isVammNode: () => false, + isBaseFilled: () => false, + getSortValue: () => new BN(0), + getLabel: () => '', + getPrice: () => new BN(0), }; }; From cc6d3c82b936fe2f219cc07511df0da08e29bf79 Mon Sep 17 00:00:00 2001 From: sina <20732540+SinaKhalili@users.noreply.github.com> Date: Thu, 23 Jan 2025 03:12:59 -0800 Subject: [PATCH 6/6] Update multithreaded filler --- .../filler-common/dlobBuilder.ts | 2 +- .../filler/fillerMultithreaded.ts | 153 +++++++++++------- 2 files changed, 92 insertions(+), 63 deletions(-) diff --git a/src/experimental-bots/filler-common/dlobBuilder.ts b/src/experimental-bots/filler-common/dlobBuilder.ts index faf6b0df..ea71c025 100644 --- a/src/experimental-bots/filler-common/dlobBuilder.ts +++ b/src/experimental-bots/filler-common/dlobBuilder.ts @@ -49,7 +49,7 @@ import { initializeSpotFulfillmentAccounts, sleepMs } from '../../utils'; import { LRUCache } from 'lru-cache'; const EXPIRE_ORDER_BUFFER_SEC = 30; // add an extra 30 seconds before trying to expire orders (want to avoid 6252 error due to clock drift) -const NUM_MAKERS = 5; // number of makers to consider for triggerable nodes +const NUM_MAKERS = 3; // number of makers to consider for triggerable nodes const logPrefix = '[DLOBBuilder]'; class DLOBBuilder { diff --git a/src/experimental-bots/filler/fillerMultithreaded.ts b/src/experimental-bots/filler/fillerMultithreaded.ts index 91da9ebf..3985c1c5 100644 --- a/src/experimental-bots/filler/fillerMultithreaded.ts +++ b/src/experimental-bots/filler/fillerMultithreaded.ts @@ -20,6 +20,7 @@ import { MakerInfo, MarketType, NodeToFill, + NodeToTrigger, PerpMarkets, PriorityFeeSubscriberMap, QUOTE_PRECISION, @@ -51,6 +52,7 @@ import { NodeToFillWithBuffer, SerializedNodeToTrigger, SerializedNodeToFill, + NodeToTriggerWithMakers, } from '../filler-common/types'; import { assert } from 'console'; import { @@ -73,9 +75,9 @@ import { import { spawnChild, deserializeNodeToFill, - deserializeOrder, getPriorityFeeInstruction, isTsRuntime, + deserializeNodeToTriggerWithMakers, } from '../filler-common/utils'; import { CounterValue, @@ -1085,7 +1087,7 @@ export class FillerMultithreaded { } private async getPythIxsFromNode( - node: NodeToFillWithBuffer | SerializedNodeToTrigger, + node: NodeToFillWithBuffer | NodeToTrigger | SerializedNodeToTrigger, precedingIxs: TransactionInstruction[] = [], isSwift = false ): Promise { @@ -1223,8 +1225,12 @@ export class FillerMultithreaded { this.throttledNodes.set(signature, Date.now()); } - protected removeTriggeringNodes(node: SerializedNodeToTrigger) { - this.triggeringNodes.delete(getNodeToTriggerSignature(node)); + protected removeTriggeringNodes(node: NodeToTriggerWithMakers) { + const nodeSignature = getOrderSignature( + node.node.order.orderId, + node.node.userAccount + ); + this.triggeringNodes.delete(nodeSignature); } protected pruneThrottledNode() { @@ -1358,9 +1364,41 @@ export class FillerMultithreaded { return true; } - async executeTriggerablePerpNodes(nodesToTrigger: SerializedNodeToTrigger[]) { - const user = this.driftClient.getUser(this.subaccount); - for (const nodeToTrigger of nodesToTrigger) { + protected async getUserAccountAndSlotFromMap( + key: string + ): Promise> { + const user = await this.userMap!.mustGetWithSlot( + key, + this.driftClient.userAccountSubscriptionConfig + ); + return { + data: user.data.getUserAccount(), + slot: user.slot, + }; + } + + protected async executeTriggerablePerpNodes( + nodesToTrigger: SerializedNodeToTrigger[] + ) { + const triggerableNodes = nodesToTrigger.map((node) => + deserializeNodeToTriggerWithMakers(node) + ); + const buildForBundle = this.shouldBuildForBundle(); + const subaccountUser = this.driftClient.getUser(this.subaccount); + + for (const nodeToTrigger of triggerableNodes) { + nodeToTrigger.node.haveTrigger = true; + const user = await this.getUserAccountAndSlotFromMap( + nodeToTrigger.node.userAccount.toString() + ); + logger.info( + `multithreaded: Processing node: account=${nodeToTrigger.node.userAccount.toString()}, slot=${ + user.slot + }, orderId=${nodeToTrigger.node.order.orderId.toString()}, numMakers=${ + nodeToTrigger.makers?.length ?? 0 + }` + ); + let ixs = [ ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000, @@ -1377,7 +1415,7 @@ export class FillerMultithreaded { }) ) * this.driftClient.txSender.getSuggestedPriorityFeeMultiplier() ); - const buildForBundle = this.shouldBuildForBundle(); + if (buildForBundle) { ixs.push(this.bundleSender!.getTipIx()); } else { @@ -1388,52 +1426,35 @@ export class FillerMultithreaded { ); } - nodeToTrigger.node.haveTrigger = true; - // @ts-ignore - const buffer = Buffer.from(nodeToTrigger.node.userAccountData.data); - // @ts-ignore - const userAccount = decodeUser(buffer); - let removeLastIxPostSim = this.revertOnFailure; if (this.pythPriceSubscriber) { - const pythIxs = await this.getPythIxsFromNode( - nodeToTrigger, - ixs, - false - ); + const pythIxs = await this.getPythIxsFromNode(nodeToTrigger, ixs); ixs.push(...pythIxs); removeLastIxPostSim = false; } - const nodeSignature = getNodeToTriggerSignature(nodeToTrigger); + const nodeSignature = getOrderSignature( + nodeToTrigger.node.order.orderId, + nodeToTrigger.node.userAccount + ); if (!this.seenTriggerableOrders.has(nodeSignature)) { this.seenTriggerableOrders.add(nodeSignature); this.triggeringNodes.set(nodeSignature, Date.now()); - const deserializedOrder = deserializeOrder(nodeToTrigger.node.order); ixs.push( await this.driftClient.getTriggerOrderIx( new PublicKey(nodeToTrigger.node.userAccount), - userAccount, - deserializedOrder, - user.userAccountPublicKey + user.data, + nodeToTrigger.node.order ) ); - logger.info( - `executeTriggerablePerpNodes: processing fillable order with ${nodeToTrigger.makers.length} makers` - ); const makerInfos = await Promise.all( - nodeToTrigger.makers.map(async (m) => { - const maker = new PublicKey(m); - logger.info( - `executeTriggerablePerpNodes: getting maker info for ${maker.toString()}` + nodeToTrigger.makers.map(async (maker) => { + const { data } = await this.getUserAccountAndSlotFromMap( + maker.toString() ); - const user = await this.userMap!.mustGetWithSlot( - m, - this.driftClient.userAccountSubscriptionConfig - ); - const makerUserAccount = user.data.getUserAccount(); + const makerUserAccount = data; const makerAuthority = makerUserAccount.authority; const makerStats = getUserStatsAccountPublicKey( this.driftClient.program.programId, @@ -1449,8 +1470,8 @@ export class FillerMultithreaded { const fillIx = await this.driftClient.getFillPerpOrderIx( new PublicKey(nodeToTrigger.node.userAccount), - user.getUserAccount(), - deserializedOrder, + user.data, + nodeToTrigger.node.order, makerInfos, undefined, this.subaccount @@ -1459,25 +1480,25 @@ export class FillerMultithreaded { if (this.revertOnFailure) { ixs.push( - await this.driftClient.getRevertFillIx(user.userAccountPublicKey) + await this.driftClient.getRevertFillIx( + subaccountUser.userAccountPublicKey + ) ); } } else { if (!this.pythPriceSubscriber) { - logger.info( - `executeTriggerablePerpNodes: no pyth price subscriber, skipping` - ); + logger.info(`No pyth price subscriber, skipping`); return; } logger.info( - `executeTriggerablePerpNodes: already triggered order ${nodeSignature}, only pulling oracles` + `Already triggered order ${nodeSignature}, only pulling oracles` ); } const txSize = getSizeOfTransaction(ixs, true, this.lookupTableAccounts); if (txSize > PACKET_DATA_SIZE) { logger.warn( - `executeTriggerablePerpNodes: tx too large, removing pyth ixs` + `Transaction too large (${txSize}), removing pyth instructions` ); ixs = removePythIxs(ixs); } @@ -1496,28 +1517,28 @@ export class FillerMultithreaded { type: 'trigger', simError: simResult.simError !== null, ...metricAttrFromUserAccount( - user.userAccountPublicKey, - user.getUserAccount() + subaccountUser.userAccountPublicKey, + subaccountUser.getUserAccount() ), }); this.estTxCuHistogram?.record(simResult.cuEstimate, { type: 'trigger', simError: simResult.simError !== null, ...metricAttrFromUserAccount( - user.userAccountPublicKey, - user.getUserAccount() + subaccountUser.userAccountPublicKey, + subaccountUser.getUserAccount() ), }); logger.info( - `executeTriggerablePerpNodes: simulation complete - CUs: ${simResult.cuEstimate}, ixCount: ${ixs.length}, revertTx: ${this.revertOnFailure}, buildForBundle: ${buildForBundle}` + `Simulation complete - CUs: ${simResult.cuEstimate}, ixCount: ${ixs.length}, revertTx: ${this.revertOnFailure}, buildForBundle: ${buildForBundle}` ); if (simResult.simError) { logger.error( - `executeTriggerablePerpNodes: simulation error: ${JSON.stringify( - simResult.simError - )}` + `Simulation error: ${JSON.stringify(simResult.simError)}, ${ + simResult.simTxLogs + }` ); } else { if (this.hasEnoughSolToFill) { @@ -1530,7 +1551,7 @@ export class FillerMultithreaded { .sendTransaction(simResult.tx) .then((txSig) => { logger.info( - `executeTriggerablePerpNodes: triggered successfully - user: ${nodeToTrigger.node.userAccount.toString()}, order: ${nodeToTrigger.node.order.orderId.toString()}, tx: ${ + `Triggered successfully - user: ${nodeToTrigger.node.userAccount.toString()}, order: ${nodeToTrigger.node.order.orderId.toString()}, tx: ${ txSig.txSig }` ); @@ -1546,9 +1567,6 @@ export class FillerMultithreaded { 'Transaction was not confirmed' ) ) { - logger.error( - `executeTriggerablePerpNodes: error (${errorCode}) triggering order for user ${nodeToTrigger.node.userAccount.toString()}, order: ${nodeToTrigger.node.order.orderId.toString()}` - ); logger.error(error); } }) @@ -1557,17 +1575,15 @@ export class FillerMultithreaded { }); } } else { - logger.info( - `executeTriggerablePerpNodes: insufficient SOL balance to fill` - ); + logger.info(`Insufficient SOL balance to fill`); } } } this.attemptedTriggersCounter?.add( nodesToTrigger.length, metricAttrFromUserAccount( - user.userAccountPublicKey, - user.getUserAccount() + subaccountUser.userAccountPublicKey, + subaccountUser.getUserAccount() ) ); } @@ -2606,7 +2622,20 @@ export class FillerMultithreaded { this.driftClient.program.programId, takerUserAccount!.authority ).toString(); - const referrerInfo = await this.referrerMap.mustGet(authority); + + // Add try-catch around referrer lookup + let referrerInfo: ReferrerInfo | undefined; + try { + // Existing referrer lookup code + if (nodeToFill.node.userAccount) { + referrerInfo = await this.referrerMap?.mustGet( + nodeToFill.node.userAccount + ); + } + } catch (e) { + logger.warn(`Failed to get referrer info: ${e}`); + referrerInfo = undefined; + } return Promise.resolve({ makerInfos,