From 3a9954343fbdde607b7819fc5027e178985638bf Mon Sep 17 00:00:00 2001 From: wphan Date: Mon, 23 Dec 2024 23:46:22 -0800 Subject: [PATCH] mainnet - incl esbuild (#329) * Bumping sdk and jit dependencies to 2.104.0-beta.31 and 0.12.18 * Bumping sdk and jit dependencies to 2.104.0-beta.32 and 0.12.19 * Nour/pyth lazer cranker (#321) * add pyth lazer cranker * rm unncessary libraries * remove unnecessary code * add entrypoint for the cranker * added improvements * increase the chunk size * Bumping sdk and jit dependencies to 2.104.0-beta.33 and 0.12.20 * Bumping sdk and jit dependencies to 2.104.0-beta.34 and 0.12.21 * liquidator: use SOL routes when swapping LSTs (#322) * Bumping sdk and jit dependencies to 2.104.0-beta.35 and 0.12.22 * fillers: fix multimaker retry logic (#325) * fillers: fix multimaker retry logic * add missing tip ix * extend auction duration for filler examples * Bumping sdk and jit dependencies to 2.104.0-beta.36 and 0.12.23 * Bumping sdk and jit dependencies to 2.104.0-beta.37 and 0.12.24 * dont throw on timeout * ignore settled markets (#326) * Nour/swift filler (#300) * swift subscriber process * integrate dlob builder * everything working but txs too large * protected maker * fix fill ix bug * prettify * working with filling swift orders * final ixs working * revert taker example * fix weird merge conflict error * uncomment error code to suppress * makerBidAskTwapCrank: skip simulation if updating switchboard oracle (#328) * Bumping sdk and jit dependencies to 2.104.0-beta.38 and 0.12.25 * Bumping sdk and jit dependencies to 2.104.0-beta.39 and 0.12.26 * Bumping sdk and jit dependencies to 2.105.0-beta.0 and 0.12.27 * Chore/esbuild (#316) * increase swift maker heartbeat window * esbuild it * fix * optimize --------- Co-authored-by: wphan --------- Co-authored-by: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: moosecat Co-authored-by: jordy25519 --- .dockerignore | 5 + Dockerfile | 21 +- esbuild.config.js | 27 + package.json | 8 +- src/bots/makerBidAskTwapCrank.ts | 32 +- src/config.ts | 2 + src/experimental-bots/entrypoint.ts | 111 ++-- .../filler-common/dlobBuilder.ts | 160 ++++-- .../filler-common/swiftOrderSubscriber.ts | 197 ++++++++ src/experimental-bots/filler-common/types.ts | 8 +- src/experimental-bots/filler-common/utils.ts | 68 +-- .../filler/fillerMultithreaded.ts | 476 +++++++++++++----- src/utils.ts | 15 +- yarn.lock | 247 +++++++-- 14 files changed, 1084 insertions(+), 293 deletions(-) create mode 100644 .dockerignore create mode 100644 esbuild.config.js create mode 100644 src/experimental-bots/filler-common/swiftOrderSubscriber.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..18e997d0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +*/**/node_modules +*/**/lib +*/**/dist +.git/ +.husky/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index edb683c1..66f47936 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,21 @@ -FROM public.ecr.aws/bitnami/node:20.18.1 -RUN apt-get install git -ENV NODE_ENV=production -RUN npm install -g typescript -RUN npm install -g ts-node +FROM node:20.18.1 AS builder +RUN npm install -g husky + +COPY package.json yarn.lock ./ WORKDIR /app COPY . . RUN yarn install -RUN yarn build -RUN yarn install --production +RUN node esbuild.config.js + +FROM node:20.18.1-alpine +# 'bigint-buffer' native lib for performance +RUN apk add python3 make g++ --virtual .build &&\ + npm install -C /lib bigint-buffer &&\ + apk del .build +COPY --from=builder /app/lib/ ./lib/ EXPOSE 9464 -CMD [ "yarn", "start:all" ] +CMD ["node", "./lib/index.js"] diff --git a/esbuild.config.js b/esbuild.config.js new file mode 100644 index 00000000..09e84738 --- /dev/null +++ b/esbuild.config.js @@ -0,0 +1,27 @@ +const esbuild = require('esbuild'); +const glob = require('tiny-glob'); + +const commonConfig = { + bundle: true, + platform: 'node', + target: 'es2020', + sourcemap: false, + // minify: true, makes messy debug/error output + treeShaking: true, + legalComments: 'none', + mainFields: ['module', 'main'], + metafile: true, + format: 'cjs', + external: [ + 'bigint-buffer' + ] +}; + +(async () => { + let entryPoints = await glob("./src/*.ts", { filesOnly: true }); + await esbuild.build({ + ...commonConfig, + entryPoints, + outdir: 'lib', + }); +})().catch(() => process.exit(1)); \ No newline at end of file diff --git a/package.json b/package.json index 18d15bc8..fc50864c 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "lib/index.js", "license": "Apache-2.0", "dependencies": { - "@drift-labs/jit-proxy": "0.12.24", - "@drift-labs/sdk": "2.104.0-beta.37", + "@drift-labs/jit-proxy": "0.12.27", + "@drift-labs/sdk": "2.105.0-beta.0", "@opentelemetry/api": "1.7.0", "@opentelemetry/auto-instrumentations-node": "0.31.2", "@opentelemetry/exporter-prometheus": "0.31.0", @@ -44,17 +44,19 @@ "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "4.33.0", "chai": "4.3.10", + "esbuild": "0.24.0", "eslint": "7.32.0", "eslint-config-prettier": "8.10.0", "eslint-plugin-prettier": "3.4.1", "husky": "7.0.4", "mocha": "10.2.0", "prettier": "3.0.1", + "tiny-glob": "0.2.9", "ts-node": "10.9.1" }, "scripts": { "prepare": "husky install", - "build": "yarn clean && tsc", + "build": "yarn clean && node esbuild.config.js", "clean": "rm -rf lib", "start": "node lib/index.js", "dev": "NODE_OPTIONS=--max-old-space-size=8192 ts-node src/index.ts", diff --git a/src/bots/makerBidAskTwapCrank.ts b/src/bots/makerBidAskTwapCrank.ts index 82a153f1..d97d957e 100644 --- a/src/bots/makerBidAskTwapCrank.ts +++ b/src/bots/makerBidAskTwapCrank.ts @@ -365,7 +365,8 @@ export class MakerBidAskTwapCrank implements Bot { private async buildTransaction( marketIndex: number, - ixs: TransactionInstruction[] + ixs: TransactionInstruction[], + doSimulation = true ): Promise { const recentBlockhash = await this.driftClient.connection.getLatestBlockhash('confirmed'); @@ -377,7 +378,7 @@ export class MakerBidAskTwapCrank implements Bot { lookupTableAccounts: this.lookupTableAccounts, cuLimitMultiplier: CU_EST_MULTIPLIER, minCuLimit: TWAP_CRANK_MIN_CU, - doSimulation: true, + doSimulation, recentBlockhash: recentBlockhash.blockhash, }); logger.info( @@ -476,9 +477,15 @@ export class MakerBidAskTwapCrank implements Bot { let txsToBundle: VersionedTransaction[] = []; for (const mi of crankMarkets) { + const usingSwitchboardOnDemand = isVariant( + this.driftClient.getPerpMarketAccount(mi)!.amm.oracleSource, + 'switchboardOnDemand' + ); const ixs = [ ComputeBudgetProgram.setComputeUnitLimit({ - units: 1_400_000, // will be overwritten by simulateAndGetTxWithCUs + units: usingSwitchboardOnDemand + ? 350_000 // switchboard simulation is unreliable, use hardcoded CU limit + : 1_400_000, // will be overwritten by simulateAndGetTxWithCUs }), ]; @@ -547,12 +554,7 @@ export class MakerBidAskTwapCrank implements Bot { const pythIxs = await this.getPythIxsFromTwapCrankInfo(mi); ixs.push(...pythIxs); pythIxsPushed = true; - } else if ( - isVariant( - this.driftClient.getPerpMarketAccount(mi)!.amm.oracleSource, - 'switchboardOnDemand' - ) - ) { + } else if (usingSwitchboardOnDemand) { const switchboardIx = await this.driftClient.getPostSwitchboardOnDemandUpdateAtomicIx( this.driftClient.getPerpMarketAccount(mi)!.amm.oracle, @@ -596,7 +598,11 @@ export class MakerBidAskTwapCrank implements Bot { if (isFirstTxInBundle) { ixs.push(this.bundleSender!.getTipIx()); } - const txToSend = await this.buildTransaction(mi, ixs); + const txToSend = await this.buildTransaction( + mi, + ixs, + !usingSwitchboardOnDemand + ); if (txToSend) { // @ts-ignore; txToSend.sign(jitoSigners); @@ -605,7 +611,11 @@ export class MakerBidAskTwapCrank implements Bot { logger.error(`[${this.name}] failed to build tx for market: ${mi}`); } } else { - const txToSend = await this.buildTransaction(mi, ixs); + const txToSend = await this.buildTransaction( + mi, + ixs, + !usingSwitchboardOnDemand + ); if (txToSend) { await this.sendSingleTx(mi, txToSend); } else { diff --git a/src/config.ts b/src/config.ts index d74de0ee..784e381b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -159,6 +159,8 @@ export interface GlobalConfig { /// ws endpoint to use (inferred from endpoint using web3.js rules, only provide if you want to use a different one) wsEndpoint?: string; hermesEndpoint?: string; + lazerEndpoint?: string; + lazerToken?: string; numNonActiveOraclesToPush?: number; // Optional to specify markets loaded by drift client diff --git a/src/experimental-bots/entrypoint.ts b/src/experimental-bots/entrypoint.ts index 511ef750..1bf5ee64 100644 --- a/src/experimental-bots/entrypoint.ts +++ b/src/experimental-bots/entrypoint.ts @@ -39,6 +39,8 @@ import { setGlobalDispatcher, Agent } from 'undici'; import { PythPriceFeedSubscriber } from '../pythPriceFeedSubscriber'; import { SwiftMaker } from './swift/makerExample'; import { SwiftTaker } from './swift/takerExample'; +import * as net from 'net'; +import { PythLazerClient } from '@pythnetwork/pyth-lazer-sdk'; setGlobalDispatcher( new Agent({ @@ -307,6 +309,19 @@ const runBot = async () => { throw new Error('fillerMultithreaded bot config not found'); } + let pythLazerClient: PythLazerClient | undefined; + if (config.global.driftEnv! === 'devnet') { + if (!config.global.lazerEndpoint || !config.global.lazerToken) { + throw new Error( + 'Must set environment variables LAZER_ENDPOINT and LAZER_TOKEN' + ); + } + pythLazerClient = new PythLazerClient( + config.global.lazerEndpoint, + config.global.lazerToken + ); + } + // Ensure that there are no duplicate market indexes in the Array marketIndexes config const marketIndexes = new Set(); for (const marketIndexList of config.botConfigs.fillerMultithreaded @@ -335,6 +350,7 @@ const runBot = async () => { }, bundleSender, pythPriceSubscriber, + pythLazerClient, [] ); bots.push(fillerMultithreaded); @@ -423,46 +439,50 @@ const runBot = async () => { // start http server listening to /health endpoint using http package const startupTime = Date.now(); - http - .createServer(async (req, res) => { - if (req.url === '/health') { - if (config.global.testLiveness) { - if (Date.now() > startupTime + 60 * 1000) { - res.writeHead(500); - res.end('Testing liveness test fail'); - return; - } - } - - /* @ts-ignore */ - if (!driftClient.connection._rpcWebSocketConnected) { - logger.error(`Connection rpc websocket disconnected`); + const createServerCallback = async (req: any, res: any) => { + if (req.url === '/health') { + if (config.global.testLiveness) { + if (Date.now() > startupTime + 60 * 1000) { res.writeHead(500); - res.end(`Connection rpc websocket disconnected`); + res.end('Testing liveness test fail'); return; } + } - // check all bots if they're live - for (const bot of bots) { - const healthCheck = await promiseTimeout(bot.healthCheck(), 1000); - if (!healthCheck) { - logger.error(`Health check failed for bot`); - res.writeHead(503); - res.end(`Bot is not healthy`); - return; - } - } + /* @ts-ignore */ + if (!driftClient.connection._rpcWebSocketConnected) { + logger.error(`Connection rpc websocket disconnected`); + res.writeHead(500); + res.end(`Connection rpc websocket disconnected`); + return; + } - // liveness check passed - res.writeHead(200); - res.end('OK'); - } else { - res.writeHead(404); - res.end('Not found'); + // check all bots if they're live + for (const bot of bots) { + const healthCheck = await promiseTimeout(bot.healthCheck(), 1000); + if (!healthCheck) { + logger.error(`Health check failed for bot`); + res.writeHead(503); + res.end(`Bot is not healthy`); + return; + } } - }) - .listen(healthCheckPort); - logger.info(`Health check server listening on port ${healthCheckPort}`); + + // liveness check passed + res.writeHead(200); + res.end('OK'); + } else { + res.writeHead(404); + res.end('Not found'); + } + }; + + let healthCheckPortToUse = Number(healthCheckPort); + while (await isPortInUse(healthCheckPortToUse)) { + healthCheckPortToUse++; + } + http.createServer(createServerCallback).listen(healthCheckPortToUse); + logger.info(`Server listening on port ${healthCheckPortToUse}`); }; recursiveTryCatch(() => runBot()); @@ -476,3 +496,26 @@ async function recursiveTryCatch(f: () => void) { await recursiveTryCatch(f); } } + +function isPortInUse(port: number, host = '127.0.0.1'): Promise { + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', (err) => { + if ( + err.name?.includes('EADDRINUSE') || + err.message?.includes('EADDRINUSE') + ) { + resolve(true); + } else { + resolve(false); + } + }); + + server.once('listening', () => { + server.close(() => resolve(false)); + }); + + server.listen(port, host); + }); +} diff --git a/src/experimental-bots/filler-common/dlobBuilder.ts b/src/experimental-bots/filler-common/dlobBuilder.ts index cacbd2a2..384e7132 100644 --- a/src/experimental-bots/filler-common/dlobBuilder.ts +++ b/src/experimental-bots/filler-common/dlobBuilder.ts @@ -15,12 +15,20 @@ import { PhoenixSubscriber, BN, ClockSubscriber, - PhoenixV1FulfillmentConfigAccount, OpenbookV2Subscriber, - OpenbookV2FulfillmentConfigAccount, + SwiftOrderParamsMessage, + getUserAccountPublicKey, + SwiftOrderNode, + Order, + OrderType, + getAuctionPrice, + ZERO, + OrderTriggerCondition, + PositionDirection, + UserStatus, isUserProtectedMaker, } from '@drift-labs/sdk'; -import { Connection } from '@solana/web3.js'; +import { Connection, PublicKey } from '@solana/web3.js'; import dotenv from 'dotenv'; import parseArgs from 'minimist'; import { logger } from '../../logger'; @@ -36,6 +44,7 @@ import { serializeNodeToTrigger, } from './utils'; 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) @@ -54,17 +63,28 @@ class DLOBBuilder { // only used for spot filler private phoenixSubscribers?: Map; private openbookSubscribers?: Map; - private phoenixFulfillmentConfigMap?: Map< - number, - PhoenixV1FulfillmentConfigAccount - >; - private openbookFulfillmentConfigMap?: Map< - number, - OpenbookV2FulfillmentConfigAccount - >; - private clockSubscriber: ClockSubscriber; + private swiftUserAuthorities = new Map(); + + // Swift orders to keep track of + private swiftOrders = new LRUCache({ + max: 5000, + dispose: (_, key) => { + console.log('disposing', key); + if (typeof process.send === 'function') { + process.send({ + type: 'swiftOrderParamsMessage', + data: { + uuid: key, + type: 'delete', + }, + }); + } + return; + }, + }); + constructor( driftClient: DriftClient, marketType: MarketType, @@ -81,14 +101,6 @@ class DLOBBuilder { if (marketTypeString.toLowerCase() === 'spot') { this.phoenixSubscribers = new Map(); this.openbookSubscribers = new Map(); - this.phoenixFulfillmentConfigMap = new Map< - number, - PhoenixV1FulfillmentConfigAccount - >(); - this.openbookFulfillmentConfigMap = new Map< - number, - OpenbookV2FulfillmentConfigAccount - >(); } this.clockSubscriber = new ClockSubscriber(driftClient.connection, { @@ -108,8 +120,6 @@ class DLOBBuilder { private async initializeSpotMarkets() { ({ - phoenixFulfillmentConfigs: this.phoenixFulfillmentConfigMap, - openbookFulfillmentConfigs: this.openbookFulfillmentConfigMap, phoenixSubscribers: this.phoenixSubscribers, openbookSubscribers: this.openbookSubscribers, } = await initializeSpotFulfillmentAccounts( @@ -146,7 +156,7 @@ class DLOBBuilder { logger.debug( `${logPrefix} Building DLOB with ${this.userAccountData.size} users` ); - this.dlob = new DLOB(); + const dlob = new DLOB(); let counter = 0; this.userAccountData.forEach((userAccount, pubkey) => { userAccount.orders.forEach((order) => { @@ -165,8 +175,87 @@ class DLOBBuilder { counter++; }); }); + for (const swiftNode of this.swiftOrders.values()) { + dlob.insertSwiftOrder(swiftNode.order, swiftNode.userAccount, false); + counter++; + } logger.debug(`${logPrefix} Built DLOB with ${counter} orders`); - return this.dlob; + this.dlob = dlob; + return dlob; + } + + public async insertSwiftOrder(orderData: any, uuid: number) { + // Deserialize and store + const { + swiftOrderParams, + subAccountId: takerSubaccountId, + slot, + }: SwiftOrderParamsMessage = this.driftClient.program.coder.types.decode( + 'SwiftOrderParamsMessage', + Buffer.from(orderData['order_message'], 'base64') + ); + + const takerAuthority = new PublicKey(orderData['taker_authority']); + const takerUserPubkey = await getUserAccountPublicKey( + this.driftClient.program.programId, + takerAuthority, + takerSubaccountId + ); + this.swiftUserAuthorities.set( + takerUserPubkey.toString(), + orderData['taker_authority'] + ); + + const maxSlot = slot.addn(swiftOrderParams.auctionDuration ?? 0); + if (maxSlot.toNumber() < this.slotSubscriber.getSlot()) { + logger.warn( + `${logPrefix} Received expired swift order with uuid: ${uuid}` + ); + return; + } + + const swiftOrder: Order = { + status: 'open', + orderType: OrderType.MARKET, + orderId: uuid, + slot, + marketIndex: swiftOrderParams.marketIndex, + marketType: MarketType.PERP, + baseAssetAmount: swiftOrderParams.baseAssetAmount, + auctionDuration: swiftOrderParams.auctionDuration!, + auctionStartPrice: swiftOrderParams.auctionStartPrice!, + auctionEndPrice: swiftOrderParams.auctionEndPrice!, + immediateOrCancel: true, + direction: swiftOrderParams.direction, + postOnly: false, + oraclePriceOffset: swiftOrderParams.oraclePriceOffset ?? 0, + // Rest are not required for DLOB + price: ZERO, + maxTs: ZERO, + triggerPrice: ZERO, + triggerCondition: OrderTriggerCondition.ABOVE, + existingPositionDirection: PositionDirection.LONG, + reduceOnly: false, + baseAssetAmountFilled: ZERO, + quoteAssetAmountFilled: ZERO, + quoteAssetAmount: ZERO, + userOrderId: 0, + }; + swiftOrder.price = getAuctionPrice( + swiftOrder, + this.slotSubscriber.getSlot(), + this.driftClient.getOracleDataForPerpMarket(swiftOrder.marketIndex).price + ); + + const swiftOrderNode = new SwiftOrderNode( + swiftOrder, + takerUserPubkey.toString() + ); + + const ttl = (maxSlot.toNumber() - this.slotSubscriber.getSlot()) * 500; + this.swiftOrders.set(uuid, swiftOrderNode, { + ttl, + }); } public getNodesToTriggerAndNodesToFill(): [ @@ -277,21 +366,28 @@ class DLOBBuilder { return nodesToFill .map((node) => { const buffer = this.getUserBuffer(node.node.userAccount!); - if (!buffer) { + if (!buffer && !node.node.isSwift) { + console.log(node.node); + console.log(`Received node to fill without user account`); return undefined; } const makerBuffers = new Map(); for (const makerNode of node.makerNodes) { - // @ts-ignore - const makerBuffer = this.getUserBuffer(makerNode.userAccount); + const makerBuffer = this.getUserBuffer(makerNode.userAccount!); if (!makerBuffer) { return undefined; } - // @ts-ignore - makerBuffers.set(makerNode.userAccount, makerBuffer); + makerBuffers.set(makerNode.userAccount!, makerBuffer); } - return serializeNodeToFill(node, buffer, makerBuffers); + return serializeNodeToFill( + node, + makerBuffers, + this.userAccountData.get(node.node.userAccount!)?.status === + UserStatus.PROTECTED_MAKER, + buffer, + this.swiftUserAuthorities.get(node.node.userAccount!) + ); }) .filter((node): node is SerializedNodeToFill => node !== undefined); } @@ -424,12 +520,14 @@ const main = async () => { } process.on('message', (msg: any) => { - // console.log("received msg"); if (!msg.data || typeof msg.data.type === 'undefined') { logger.warn(`${logPrefix} Received message without data.type field.`); return; } switch (msg.data.type) { + case 'swiftOrderParamsMessage': + dlobBuilder.insertSwiftOrder(msg.data.swiftOrder, msg.data.uuid); + break; case 'update': dlobBuilder.deserializeAndUpdateUserAccountData( msg.data.userAccount, diff --git a/src/experimental-bots/filler-common/swiftOrderSubscriber.ts b/src/experimental-bots/filler-common/swiftOrderSubscriber.ts new file mode 100644 index 00000000..61d08467 --- /dev/null +++ b/src/experimental-bots/filler-common/swiftOrderSubscriber.ts @@ -0,0 +1,197 @@ +import { + DevnetPerpMarkets, + DriftEnv, + loadKeypair, + MainnetPerpMarkets, +} from '@drift-labs/sdk'; +import { Keypair } from '@solana/web3.js'; +import nacl from 'tweetnacl'; +import { decodeUTF8 } from 'tweetnacl-util'; +import WebSocket from 'ws'; +import { sleepMs } from '../../utils'; +import dotenv from 'dotenv'; +import parseArgs from 'minimist'; + +export type SwiftOrderSubscriberConfig = { + driftEnv: DriftEnv; + endpoint: string; + marketIndexes: number[]; + keypair: Keypair; +}; + +export class SwiftOrderSubscriber { + private heartbeatTimeout: NodeJS.Timeout | null = null; + private readonly heartbeatIntervalMs = 60000; + private ws: WebSocket | null = null; + subscribed: boolean = false; + + constructor(private config: SwiftOrderSubscriberConfig) {} + + getSymbolForMarketIndex(marketIndex: number) { + const markets = + this.config.driftEnv === 'devnet' + ? DevnetPerpMarkets + : MainnetPerpMarkets; + return markets[marketIndex].symbol; + } + + generateChallengeResponse(nonce: string) { + const messageBytes = decodeUTF8(nonce); + const signature = nacl.sign.detached( + messageBytes, + this.config.keypair.secretKey + ); + const signatureBase64 = Buffer.from(signature).toString('base64'); + return signatureBase64; + } + + handleAuthMessage(message: any) { + if (message['channel'] === 'auth' && message['nonce'] != null) { + const signatureBase64 = this.generateChallengeResponse(message['nonce']); + this.ws?.send( + JSON.stringify({ + pubkey: this.config.keypair.publicKey.toBase58(), + signature: signatureBase64, + }) + ); + } + + if ( + message['channel'] === 'auth' && + message['message']?.toLowerCase() === 'authenticated' + ) { + this.subscribed = true; + this.config.marketIndexes.forEach(async (marketIndex) => { + this.ws?.send( + JSON.stringify({ + action: 'subscribe', + market_type: 'perp', + market_name: this.getSymbolForMarketIndex(marketIndex), + }) + ); + await sleepMs(100); + }); + } + } + + async subscribe() { + const ws = new WebSocket( + this.config.endpoint + + '?pubkey=' + + this.config.keypair.publicKey.toBase58() + ); + this.ws = ws; + ws.on('open', async () => { + console.log('Connected to the server'); + + ws.on('message', async (data: WebSocket.Data) => { + const message = JSON.parse(data.toString()); + this.startHeartbeatTimer(); + + if (message['channel'] === 'auth') { + this.handleAuthMessage(message); + } + + if (message['order']) { + const order = JSON.parse(message['order']); + if (typeof process.send === 'function') { + process.send({ + type: 'swiftOrderParamsMessage', + data: { + type: 'swiftOrderParamsMessage', + swiftOrder: order, + marketIndex: order.market_index, + uuid: this.convertUuidToNumber(order.uuid), + }, + }); + } + } + }); + + ws.on('close', () => { + console.log('Disconnected from the server'); + this.reconnect(); + }); + + ws.on('error', (error: Error) => { + console.error('WebSocket error:', error); + this.reconnect(); + }); + }); + } + + private startHeartbeatTimer() { + if (this.heartbeatTimeout) { + clearTimeout(this.heartbeatTimeout); + } + this.heartbeatTimeout = setTimeout(() => { + console.warn('No heartbeat received within 30 seconds, reconnecting...'); + this.reconnect(); + }, this.heartbeatIntervalMs); + } + + private reconnect() { + if (this.ws) { + this.ws.removeAllListeners(); + this.ws.terminate(); + } + + console.log('Reconnecting to WebSocket...'); + setTimeout(() => { + this.subscribe(); + }, 1000); + } + + private convertUuidToNumber(uuid: string): number { + return uuid + .split('') + .reduce( + (n, c) => + n * 64 + + '_~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.indexOf( + c + ), + 0 + ); + } +} + +async function main() { + process.on('disconnect', () => process.exit()); + + dotenv.config(); + + const args = parseArgs(process.argv.slice(2)); + const driftEnv = args['drift-env'] ?? 'devnet'; + const marketIndexesStr = String(args['market-indexes']); + const marketIndexes = marketIndexesStr.split(',').map(Number); + + const endpoint = process.env.ENDPOINT; + const privateKey = process.env.KEEPER_PRIVATE_KEY; + + if (!endpoint || !privateKey) { + throw new Error('ENDPOINT and KEEPER_PRIVATE_KEY must be provided'); + } + + if (driftEnv !== 'devnet') { + throw new Error('Only devnet is supported'); + } + + const keypair = loadKeypair(privateKey); + const swiftOrderSubscriberConfig: SwiftOrderSubscriberConfig = { + driftEnv, + endpoint: + driftEnv === 'devnet' + ? 'wss://master.swift.drift.trade/ws' + : 'wss://swift.drift.trade/ws', + marketIndexes, + keypair, + }; + + const swiftOrderSubscriber = new SwiftOrderSubscriber( + swiftOrderSubscriberConfig + ); + await swiftOrderSubscriber.subscribe(); +} + +main(); diff --git a/src/experimental-bots/filler-common/types.ts b/src/experimental-bots/filler-common/types.ts index 6d680cef..54718b62 100644 --- a/src/experimental-bots/filler-common/types.ts +++ b/src/experimental-bots/filler-common/types.ts @@ -111,18 +111,21 @@ export type SerializedNodeToFill = { fallbackBidSource?: FallbackLiquiditySource; node: SerializedDLOBNode; makerNodes: SerializedDLOBNode[]; + authority?: string; }; export type SerializedDLOBNode = { type: string; order: SerializedOrder; - userAccountData: Buffer; + userAccountData?: Buffer; userAccount: string; sortValue: string; haveFilled: boolean; haveTrigger?: boolean; fallbackAskSource?: FallbackLiquiditySource; fallbackBidSource?: FallbackLiquiditySource; + isSwift?: boolean; + isUserProtectedMaker: boolean; }; export type FallbackLiquiditySource = 'serum' | 'phoenix' | 'openbook'; @@ -132,10 +135,11 @@ export type NodeToFillWithContext = NodeToFill & { }; export type NodeToFillWithBuffer = { - userAccountData: Buffer; + userAccountData?: Buffer; makerAccountData: string; node: DLOBNode; fallbackAskSource?: FallbackLiquiditySource; fallbackBidSource?: FallbackLiquiditySource; makerNodes: DLOBNode[]; + authority?: string; }; diff --git a/src/experimental-bots/filler-common/utils.ts b/src/experimental-bots/filler-common/utils.ts index 7db49308..44bb99ab 100644 --- a/src/experimental-bots/filler-common/utils.ts +++ b/src/experimental-bots/filler-common/utils.ts @@ -24,17 +24,10 @@ import { UserStatsAccount, isVariant, StateAccount, - PRICE_PRECISION, DriftEnv, - isUserProtectedMaker, - decodeUser, + SwiftOrderNode, } from '@drift-labs/sdk'; -import { - ComputeBudgetProgram, - Connection, - LAMPORTS_PER_SOL, - PublicKey, -} from '@solana/web3.js'; +import { ComputeBudgetProgram, Connection, PublicKey } from '@solana/web3.js'; import { SerializedUserAccount, SerializedOrder, @@ -244,23 +237,27 @@ const serializeTriggerOrderNode = ( export const serializeNodeToFill = ( node: NodeToFillWithContext, - userAccountData: Buffer, - makerAccountDatas: Map + makerAccountDatas: Map, + isUserProtectedMaker: boolean, + userAccountData?: Buffer, + authority?: string ): SerializedNodeToFill => { return { - node: serializeDLOBNode(node.node, userAccountData), + node: serializeDLOBNode(node.node, isUserProtectedMaker, userAccountData), makerNodes: node.makerNodes.map((node) => { // @ts-ignore return serializeDLOBNode(node, makerAccountDatas.get(node.userAccount)); }), fallbackAskSource: node.fallbackAskSource, fallbackBidSource: node.fallbackBidSource, + authority, }; }; const serializeDLOBNode = ( node: DLOBNode, - userAccountData: Buffer + isUserProtectedMaker: boolean, + userAccountData?: Buffer ): SerializedDLOBNode => { if (node instanceof OrderNode) { return { @@ -271,6 +268,8 @@ const serializeDLOBNode = ( sortValue: node.sortValue.toString('hex'), haveFilled: node.haveFilled, haveTrigger: 'haveTrigger' in node ? node.haveTrigger : undefined, + isSwift: 'isSwift' in node ? node.isSwift : undefined, + isUserProtectedMaker, }; } else { throw new Error( @@ -298,37 +297,40 @@ export const deserializeNodeToFill = ( makerNodes: serializedNode.makerNodes.map(deserializeDLOBNode), fallbackAskSource: serializedNode.fallbackAskSource, fallbackBidSource: serializedNode.fallbackBidSource, + authority: serializedNode.authority, }; return node; }; const deserializeDLOBNode = (node: SerializedDLOBNode): DLOBNode => { - // @ts-ignore - const userAccount = decodeUser(Buffer.from(node.userAccountData.data)); const order = deserializeOrder(node.order); - const isProtectedMaker = isUserProtectedMaker(userAccount); - switch (node.type) { case 'TakingLimitOrderNode': return new TakingLimitOrderNode( order, node.userAccount, - isProtectedMaker + node.isUserProtectedMaker ); case 'RestingLimitOrderNode': return new RestingLimitOrderNode( order, node.userAccount, - isProtectedMaker + node.isUserProtectedMaker ); case 'FloatingLimitOrderNode': return new FloatingLimitOrderNode( order, node.userAccount, - isProtectedMaker + node.isUserProtectedMaker ); case 'MarketOrderNode': - return new MarketOrderNode(order, node.userAccount, isProtectedMaker); + return new MarketOrderNode( + order, + node.userAccount, + node.isUserProtectedMaker + ); + case 'SwiftOrderNode': + return new SwiftOrderNode(order, node.userAccount); default: throw new Error(`Invalid node type: ${node.type}`); } @@ -464,28 +466,8 @@ export const spawnChild = ( return child; }; -export const getPriorityFeeInstruction = ( - priorityFeeMicroLamports: number, - solPrice: BN, - fillerRewardEstimate?: BN, - feeMultiplier = 1.0 -) => { - let microLamports = priorityFeeMicroLamports; - if (fillerRewardEstimate !== undefined) { - const fillerRewardMicroLamports = Math.floor( - fillerRewardEstimate - .mul(PRICE_PRECISION) - .mul(new BN(LAMPORTS_PER_SOL)) - .div(solPrice) - .div(QUOTE_PRECISION) - .toNumber() * feeMultiplier - ); - logger.info(` - fillerRewardEstimate microLamports: ${fillerRewardMicroLamports} - priority fee subscriber micro lamports: ${priorityFeeMicroLamports}`); - - microLamports = Math.max(microLamports, fillerRewardMicroLamports); - } +export const getPriorityFeeInstruction = (priorityFeeMicroLamports: number) => { + const microLamports = priorityFeeMicroLamports; return ComputeBudgetProgram.setComputeUnitPrice({ microLamports, }); diff --git a/src/experimental-bots/filler/fillerMultithreaded.ts b/src/experimental-bots/filler/fillerMultithreaded.ts index 8a224775..95eae72b 100644 --- a/src/experimental-bots/filler/fillerMultithreaded.ts +++ b/src/experimental-bots/filler/fillerMultithreaded.ts @@ -1,9 +1,7 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { - BASE_PRECISION, BlockhashSubscriber, BN, - BulkAccountLoader, DataAndSlot, decodeUser, DLOBNode, @@ -12,6 +10,8 @@ import { FeeTier, getOrderSignature, getUserAccountPublicKey, + getUserStatsAccountPublicKey, + getUserWithoutOrderFilter, isFillableByVAMM, isOneOfVariant, isOrderExpired, @@ -21,14 +21,14 @@ import { MarketType, NodeToFill, PerpMarkets, - PRICE_PRECISION, PriorityFeeSubscriberMap, QUOTE_PRECISION, ReferrerInfo, + ReferrerMap, SlotSubscriber, TxSigAndSlot, UserAccount, - UserStatsMap, + UserMap, } from '@drift-labs/sdk'; import { FillerMultiThreadedConfig, GlobalConfig } from '../../config'; import { JITO_METRIC_TYPES, BundleSender } from '../../bundleSender'; @@ -54,6 +54,7 @@ import { } from '../filler-common/types'; import { assert } from 'console'; import { + chunks, getAllPythOracleUpdateIxs, getFillSignatureFromUserAccountAndOrderId, getNodeToFillSignature, @@ -73,7 +74,6 @@ import { spawnChild, deserializeNodeToFill, deserializeOrder, - getUserFeeTier, getPriorityFeeInstruction, } from '../filler-common/utils'; import { @@ -108,6 +108,7 @@ import { import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'; import { ChildProcess } from 'child_process'; import { PythPriceFeedSubscriber } from 'src/pythPriceFeedSubscriber'; +import { PythLazerClient } from '@pythnetwork/pyth-lazer-sdk'; const logPrefix = '[Filler]'; export type MakerNodeMap = Map; @@ -121,7 +122,7 @@ const MAX_ACCOUNTS_PER_TX = 64; // solana limit, track https://github.com/solana const MAX_POSITIONS_PER_USER = 8; export const SETTLE_POSITIVE_PNL_COOLDOWN_MS = 60_000; export const CONFIRM_TX_INTERVAL_MS = 5_000; -const SIM_CU_ESTIMATE_MULTIPLIER = 1.35; +const SIM_CU_ESTIMATE_MULTIPLIER = 3; const SLOTS_UNTIL_JITO_LEADER_TO_SEND = 4; export const TX_CONFIRMATION_BATCH_SIZE = 100; export const CACHED_BLOCKHASH_OFFSET = 5; @@ -177,7 +178,8 @@ export class FillerMultithreaded { private subaccount: number; private fillTxId: number = 0; - private userStatsMap: UserStatsMap; + private userMap: UserMap; + private referrerMap: ReferrerMap; private throttledNodes = new Map(); private fillingNodes = new Map(); private triggeringNodes = new Map(); @@ -191,8 +193,12 @@ export class FillerMultithreaded { private dlobHealthy = true; private orderSubscriberHealthy = true; + private swiftOrderSubscriberHealth = true; private simulateTxForCUEstimate?: boolean; + // Swift orders + private swiftOrderMessages: Map = new Map(); + private intervalIds: NodeJS.Timeout[] = []; protected txConfirmationConnection: Connection; @@ -254,6 +260,10 @@ export class FillerMultithreaded { protected latestPythVaas?: Map; // priceFeedId -> vaa protected marketIndexesToPriceIds = new Map(); + protected pythLazerClient?: PythLazerClient; + protected latestPythLazerUpdates?: Map; // priceFeedId -> vaa + protected marketIndexesToPythLazerGroups?: Map; // priceFeedId -> vaa + constructor( globalConfig: GlobalConfig, config: FillerMultiThreadedConfig, @@ -262,6 +272,7 @@ export class FillerMultithreaded { runtimeSpec: RuntimeSpec, bundleSender?: BundleSender, pythPriceSubscriber?: PythPriceFeedSubscriber, + pythLazerClient?: PythLazerClient, lookupTableAccounts: AddressLookupTableAccount[] = [] ) { this.globalConfig = globalConfig; @@ -290,14 +301,20 @@ export class FillerMultithreaded { } this.lookupTableAccounts = lookupTableAccounts; - this.userStatsMap = new UserStatsMap( - this.driftClient, - new BulkAccountLoader( - new Connection(this.driftClient.connection.rpcEndpoint), - 'confirmed', - 0 - ) - ); + this.userMap = new UserMap({ + driftClient, + fastDecode: true, + includeIdle: false, + subscriptionConfig: { + type: 'websocket', + resubTimeoutMs: 10_000, + commitment: 'processed', + }, + additionalFilters: [getUserWithoutOrderFilter()], + skipInitialLoad: true, + }); + this.referrerMap = new ReferrerMap(this.driftClient, true); + this.blockhashSubscriber = new BlockhashSubscriber({ connection: driftClient.connection, }); @@ -383,6 +400,63 @@ export class FillerMultithreaded { ttl: TX_TIMEOUT_THRESHOLD_MS, ttlResolution: 1000, }); + + // Pyth lazer + this.pythLazerClient = pythLazerClient; + this.latestPythLazerUpdates = new Map(); + this.marketIndexesToPythLazerGroups = new Map(); + let subscriptionId = 0; + const subscribeToPythLazer = () => { + for (const marketIndexes of chunks(this.marketIndexesFlattened, 6)) { + const markets = PerpMarkets[this.globalConfig.driftEnv!] + .filter((market) => marketIndexes.includes(market.marketIndex)) + .filter((market) => market.pythLazerId !== undefined); + const pythLazerIds = markets.map((m) => m.pythLazerId!); + marketIndexes.forEach( + (marketIndex) => + this.marketIndexesToPythLazerGroups?.set(marketIndex, pythLazerIds) + ); + this.pythLazerClient?.send({ + type: 'subscribe', + subscriptionId, + priceFeedIds: pythLazerIds, + properties: ['price'], + chains: ['solana'], + deliveryFormat: 'json', + channel: 'fixed_rate@200ms', + jsonBinaryEncoding: 'hex', + }); + subscriptionId++; + } + }; + + if (this.pythLazerClient?.ws.readyState === 1) { + subscribeToPythLazer(); + } else { + this.pythLazerClient?.ws.addEventListener('open', () => { + subscribeToPythLazer(); + }); + } + + this.pythLazerClient?.addMessageListener((message) => { + switch (message.type) { + case 'json': { + if (message.value.type == 'streamUpdated') { + if (message.value?.solana?.data) { + const data = message.value?.solana?.data; + const pythLazerIds = message.value.parsed!.priceFeeds.map( + (priceFeed) => priceFeed.priceFeedId + ); + this.latestPythLazerUpdates?.set( + JSON.stringify(pythLazerIds), + data + ); + } + } + break; + } + } + }); } async init() { @@ -402,6 +476,9 @@ export class FillerMultithreaded { `${this.name}: hasEnoughSolToFill: ${this.hasEnoughSolToFill}, balance: ${fillerSolBalance}` ); + await this.userMap.subscribe(); + await this.referrerMap.subscribe(); + this.lookupTableAccounts.push( await this.driftClient.fetchMarketLookupTableAccount() ); @@ -541,17 +618,50 @@ export class FillerMultithreaded { process.exit(code || 1); }); + logger.info( + `orderSubscriber spawned with pid: ${orderSubscriberProcess.pid}` + ); + + // Swift Subscriber process + const swiftOrderSubscriberProcess = spawnChild( + './src/experimental-bots/filler-common/swiftOrderSubscriber.ts', + orderSubscriberArgs, + 'swiftOrderSubscriber', + (msg: any) => { + switch (msg.type) { + case 'swiftOrderParamsMessage': + if (msg.data.type === 'swiftOrderParamsMessage') { + this.swiftOrderMessages.set(msg.data.uuid, msg.data.swiftOrder); + routeMessageToDlobBuilder(msg); + } else if (msg.data.type === 'delete') { + console.log(`received delete message for ${msg.data.uuid}`); + this.swiftOrderMessages.delete(msg.data.uuid); + } + break; + case 'health': + this.swiftOrderSubscriberHealth = msg.data.healthy; + break; + } + } + ); + + swiftOrderSubscriberProcess.on('exit', (code) => { + logger.error(`swiftOrderSubscriber exited with code ${code}`); + process.exit(code || 1); + }); + process.on('SIGINT', () => { logger.info(`${logPrefix} Received SIGINT, killing children`); this.dlobBuilders.forEach((value: DLOBBuilderWithProcess, _: number) => { value.process.kill(); }); orderSubscriberProcess.kill(); + swiftOrderSubscriberProcess.kill(); process.exit(0); }); logger.info( - `orderSubscriber spawned with pid: ${orderSubscriberProcess.pid}` + `swiftOrderSubscriber process spawned with pid: ${swiftOrderSubscriberProcess.pid}` ); this.intervalIds.push( @@ -725,7 +835,14 @@ export class FillerMultithreaded { if (!this.orderSubscriberHealthy) { logger.error(`${logPrefix} Order subscriber not healthy`); } - return this.dlobHealthy && this.orderSubscriberHealthy; + if (!this.swiftOrderSubscriberHealth) { + logger.error(`${logPrefix} Swift order subscriber not healthy`); + } + return ( + this.dlobHealthy && + this.orderSubscriberHealthy && + this.swiftOrderSubscriberHealth + ); } protected recordJitoBundleStats() { @@ -979,7 +1096,9 @@ export class FillerMultithreaded { } private async getPythIxsFromNode( - node: NodeToFillWithBuffer | SerializedNodeToTrigger + node: NodeToFillWithBuffer | SerializedNodeToTrigger, + precedingIdxs: TransactionInstruction[] = [], + isSwift = false ): Promise { const marketIndex = node.node.order?.marketIndex; if (marketIndex === undefined) { @@ -998,15 +1117,50 @@ export class FillerMultithreaded { if (!this.pythPriceSubscriber) { throw new Error('Pyth price subscriber not initialized'); } - const pythIxs = await getAllPythOracleUpdateIxs( - this.runtimeSpec.driftEnv as DriftEnv, - marketIndex, - MarketType.PERP, - this.pythPriceSubscriber!, - this.driftClient, - this.globalConfig.numNonActiveOraclesToPush ?? 0, - this.marketIndexesFlattened - ); + + let pythIxs: TransactionInstruction[] = []; + if ( + isVariant( + this.driftClient.getPerpMarketAccount(marketIndex)?.amm.oracleSource, + 'pythLazer' + ) + ) { + const pythLazerIds = + this.marketIndexesToPythLazerGroups?.get(marketIndex); + if (!pythLazerIds) { + logger.error( + `Pyth lazer ids not found for marketIndex: ${marketIndex}` + ); + return pythIxs; + } + + const latestLazerUpdate = this.latestPythLazerUpdates?.get( + JSON.stringify(pythLazerIds) + ); + if (!latestLazerUpdate) { + logger.error( + `Latest lazer update not found for marketIndex: ${marketIndex}, pythLazerIds: ${pythLazerIds}` + ); + return pythIxs; + } + + pythIxs = await this.driftClient.getPostPythLazerOracleUpdateIxs( + pythLazerIds, + latestLazerUpdate, + precedingIdxs + ); + } else if (!isSwift) { + pythIxs = await getAllPythOracleUpdateIxs( + this.runtimeSpec.driftEnv as DriftEnv, + marketIndex, + MarketType.PERP, + this.pythPriceSubscriber!, + this.driftClient, + this.globalConfig.numNonActiveOraclesToPush ?? 0, + this.marketIndexesFlattened + ); + } + return pythIxs; } @@ -1259,7 +1413,11 @@ export class FillerMultithreaded { let removeLastIxPostSim = this.revertOnFailure; if (this.pythPriceSubscriber) { - const pythIxs = await this.getPythIxsFromNode(nodeToTrigger); + const pythIxs = await this.getPythIxsFromNode( + nodeToTrigger, + ixs, + false + ); ixs.push(...pythIxs); // do not strip the last ix if we have pyth ixs removeLastIxPostSim = false; @@ -1291,6 +1449,7 @@ export class FillerMultithreaded { ); if (this.revertOnFailure) { + console.log(user.userAccountPublicKey.toString()); ixs.push( await this.driftClient.getRevertFillIx(user.userAccountPublicKey) ); @@ -1405,6 +1564,7 @@ export class FillerMultithreaded { const deserializedNodesToFill = serializedNodesToFill.map( deserializeNodeToFill ); + // console.log(deserializedNodesToFill); const seenFillableNodes = new Set(); const filteredFillableNodes = deserializedNodesToFill.filter((node) => { @@ -1546,7 +1706,27 @@ export class FillerMultithreaded { fillTxId: number, nodeToFill: NodeToFillWithBuffer ): Promise { + const ixs = [ + ComputeBudgetProgram.setComputeUnitLimit({ + units: 1_400_000, // will be overridden by simulateTx + }), + ]; + try { + const priorityFeePrice = Math.floor( + this.priorityFeeSubscriber.getPriorityFees( + 'perp', + nodeToFill.node.order!.marketIndex! + )!.high * this.driftClient.txSender.getSuggestedPriorityFeeMultiplier() + ); + const buildForBundle = this.shouldBuildForBundle(); + + if (buildForBundle) { + ixs.push(this.bundleSender!.getTipIx()); + } else { + ixs.push(getPriorityFeeInstruction(priorityFeePrice)); + } + const { makerInfos, takerUser, @@ -1554,10 +1734,55 @@ export class FillerMultithreaded { takerUserSlot, referrerInfo, marketType, - fillerRewardEstimate, + takerStatsPubKey, + isSwift, + authority, } = await this.getNodeFillInfo(nodeToFill); - const buildForBundle = this.shouldBuildForBundle(); + let removeLastIxPostSim = this.revertOnFailure; + if ( + this.pythPriceSubscriber && + ((makerInfos.length === 2 && !referrerInfo) || makerInfos.length < 2) + ) { + const pythIxs = await this.getPythIxsFromNode(nodeToFill, ixs, isSwift); + ixs.push(...pythIxs); + removeLastIxPostSim = false; + } + + logMessageForNodeToFill( + nodeToFill, + takerUserPubKey, + takerUserSlot, + makerInfos, + this.slotSubscriber.getSlot(), + fillTxId, + 'multiMakerFill', + this.revertOnFailure ?? false, + removeLastIxPostSim ?? false + ); + + if (!isVariant(marketType, 'perp')) { + throw new Error('expected perp market type'); + } + + if (isSwift) { + const swiftOrderMessageParams = this.swiftOrderMessages.get( + nodeToFill.node.order!.orderId + ); + ixs.push( + ...(await this.driftClient.getPlaceSwiftTakerPerpOrderIxs( + Buffer.from(swiftOrderMessageParams['order_message'], 'base64'), + Buffer.from(swiftOrderMessageParams['order_signature'], 'base64'), + nodeToFill.node.order!.marketIndex, + { + taker: new PublicKey(takerUserPubKey), + takerStats: takerStatsPubKey, + takerUserAccount: takerUser, + }, + authority + )) + ); + } let makerInfosToUse = makerInfos; const buildTxWithMakerInfos = async ( @@ -1594,14 +1819,7 @@ export class FillerMultithreaded { if (buildForBundle) { ixs.push(this.bundleSender!.getTipIx()); } else { - ixs.push( - getPriorityFeeInstruction( - priorityFeePrice, - this.driftClient.getOracleDataForPerpMarket(0).price, - this.config.bidToFillerReward ? fillerRewardEstimate : undefined, - this.globalConfig.priorityFeeMultiplier - ) - ); + ixs.push(getPriorityFeeInstruction(priorityFeePrice)); } logMessageForNodeToFill( @@ -1624,14 +1842,15 @@ export class FillerMultithreaded { await this.driftClient.getFillPerpOrderIx( await getUserAccountPublicKey( this.driftClient.program.programId, - takerUser.authority, - takerUser.subAccountId + takerUser!.authority, + takerUser!.subAccountId ), - takerUser, + takerUser!, nodeToFill.node.order!, makers.map((m) => m.data), referrerInfo, - this.subaccount + this.subaccount, + isSwift ) ); @@ -1639,6 +1858,7 @@ export class FillerMultithreaded { const user = this.driftClient.getUser(this.subaccount); if (this.revertOnFailure) { + console.log(user.userAccountPublicKey.toString()); ixs.push( await this.driftClient.getRevertFillIx(user.userAccountPublicKey) ); @@ -1663,7 +1883,7 @@ export class FillerMultithreaded { takerUser.perpPositions.length + takerUser.spotPositions.length } marketIndex: ${nodeToFill.node.order!.marketIndex} - taker has position in market: ${takerUser.perpPositions.some( + taker has position in market: ${takerUser!.perpPositions.some( (pos) => pos.marketIndex === nodeToFill.node.order!.marketIndex )} makers have position in market: ${makerInfos.some((maker) => @@ -1795,6 +2015,13 @@ export class FillerMultithreaded { units: 1_400_000, }), ]; + + if (buildForBundle) { + ixs.push(this.bundleSender!.getTipIx()); + } else { + ixs.push(getPriorityFeeInstruction(priorityFeePrice)); + } + const fillTxId = this.fillTxId++; const { @@ -1804,29 +2031,18 @@ export class FillerMultithreaded { takerUserSlot, referrerInfo, marketType, - fillerRewardEstimate, + takerStatsPubKey, + isSwift, + authority, } = await this.getNodeFillInfo(nodeToFill); let removeLastIxPostSim = this.revertOnFailure; if (this.pythPriceSubscriber && makerInfos.length <= 2) { - const pythIxs = await this.getPythIxsFromNode(nodeToFill); + const pythIxs = await this.getPythIxsFromNode(nodeToFill, ixs, isSwift); ixs.push(...pythIxs); removeLastIxPostSim = false; } - if (buildForBundle) { - ixs.push(this.bundleSender!.getTipIx()); - } else { - ixs.push( - getPriorityFeeInstruction( - priorityFeePrice, - this.driftClient.getOracleDataForPerpMarket(0).price, - this.config.bidToFillerReward ? fillerRewardEstimate : undefined, - this.globalConfig.priorityFeeMultiplier - ) - ); - } - logMessageForNodeToFill( nodeToFill, takerUserPubKey, @@ -1843,22 +2059,38 @@ export class FillerMultithreaded { throw new Error('expected perp market type'); } - const ix = await this.driftClient.getFillPerpOrderIx( - await getUserAccountPublicKey( - this.driftClient.program.programId, - takerUser.authority, - takerUser.subAccountId - ), - takerUser, + if (isSwift) { + const swiftOrderMessageParams = this.swiftOrderMessages.get( + nodeToFill.node.order!.orderId + ); + ixs.push( + ...(await this.driftClient.getPlaceSwiftTakerPerpOrderIxs( + Buffer.from(swiftOrderMessageParams['order_message'], 'base64'), + Buffer.from(swiftOrderMessageParams['order_signature'], 'base64'), + nodeToFill.node.order!.marketIndex, + { + taker: new PublicKey(takerUserPubKey), + takerStats: takerStatsPubKey, + takerUserAccount: takerUser, + }, + authority + )) + ); + } + const fillIx = await this.driftClient.getFillPerpOrderIx( + new PublicKey(nodeToFill.node.userAccount!), + takerUser!, nodeToFill.node.order!, makerInfos.map((m) => m.data), referrerInfo, - this.subaccount + this.subaccount, + isSwift ); + ixs.push(fillIx); - ixs.push(ix); const user = this.driftClient.getUser(this.subaccount); if (this.revertOnFailure) { + console.log(user.userAccountPublicKey.toString()); ixs.push( await this.driftClient.getRevertFillIx(user.userAccountPublicKey) ); @@ -1866,28 +2098,36 @@ export class FillerMultithreaded { const txSize = getSizeOfTransaction(ixs, true, this.lookupTableAccounts); if (txSize > PACKET_DATA_SIZE) { - logger.info(`tx too large, removing pyth ixs. - keys: ${ixs.map((ix) => ix.keys.map((key) => key.pubkey.toString()))} - total number of maker positions: ${makerInfos.reduce( - (acc, maker) => - acc + - (maker.data.makerUserAccount.perpPositions.length + - maker.data.makerUserAccount.spotPositions.length), - 0 - )} - total taker positions: ${ - takerUser.perpPositions.length + takerUser.spotPositions.length - } - marketIndex: ${nodeToFill.node.order!.marketIndex} - taker has position in market: ${takerUser.perpPositions.some( - (pos) => pos.marketIndex === nodeToFill.node.order!.marketIndex - )} - makers have position in market: ${makerInfos.some((maker) => - maker.data.makerUserAccount.perpPositions.some( - (pos) => pos.marketIndex === nodeToFill.node.order!.marketIndex - ) - )} - `); + const lutAccounts = this.lookupTableAccounts[0].state.addresses.map((a) => + a.toBase58() + ); + logger.info(`tx too large: ${txSize} bytes, removing pyth ixs. + keys: ${ixs.map((ix) => ix.keys.map((key) => key.pubkey.toString()))} + keys not in LUT: ${ixs + .map((ix) => ix.keys.map((key) => key.pubkey.toString())) + .flat() + .filter((key) => !lutAccounts.includes(key))} + total number of maker positions: ${makerInfos.reduce( + (acc, maker) => + acc + + (maker.data.makerUserAccount.perpPositions.length + + maker.data.makerUserAccount.spotPositions.length), + 0 + )} + total taker positions: ${ + takerUser.perpPositions.length + takerUser.spotPositions.length + } + marketIndex: ${nodeToFill.node.order!.marketIndex} + taker has position in market: ${takerUser.perpPositions.some( + (pos) => pos.marketIndex === nodeToFill.node.order!.marketIndex + )} + makers have position in market: ${makerInfos.some((maker) => + maker.data.makerUserAccount.perpPositions.some( + (pos) => pos.marketIndex === nodeToFill.node.order!.marketIndex + ) + )} + `); + ixs = removePythIxs(ixs); } @@ -2260,10 +2500,12 @@ export class FillerMultithreaded { makerInfos: Array>; takerUserPubKey: string; takerUser: UserAccount; + takerStatsPubKey: PublicKey; takerUserSlot: number; referrerInfo: ReferrerInfo | undefined; marketType: MarketType; - fillerRewardEstimate: BN; + isSwift: boolean | undefined; + authority?: PublicKey; }> { const makerInfos: Array> = []; @@ -2299,9 +2541,10 @@ export class FillerMultithreaded { Buffer.from(makerInfoMap.get(makerAccount)!.data) ); const makerAuthority = makerUserAccount.authority; - const makerUserStats = ( - await this.userStatsMap!.mustGet(makerAuthority.toString()) - ).userStatsAccountPublicKey; + const makerUserStats = getUserStatsAccountPublicKey( + this.driftClient.program.programId, + new PublicKey(makerAuthority) + ); makerInfos.push({ slot: this.slotSubscriber.getSlot(), data: { @@ -2315,39 +2558,38 @@ export class FillerMultithreaded { } const takerUserPubKey = nodeToFill.node.userAccount!.toString(); - const takerUserAccount = decodeUser( - // @ts-ignore - Buffer.from(nodeToFill.userAccountData.data) - ); - const referrerInfo = ( - await this.userStatsMap!.mustGet(takerUserAccount.authority.toString()) - ).getReferrerInfo(); - const fillerReward = this.calculateFillerRewardEstimate( - getUserFeeTier( - MarketType.PERP, - this.driftClient.getStateAccount(), - ( - await this.userStatsMap.mustGet(takerUserAccount.authority.toString()) - ).getAccount() - ), - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain - nodeToFill.node - .order!.price.mul(nodeToFill.node.order!.baseAssetAmount) - .mul(QUOTE_PRECISION) - .div(PRICE_PRECISION) - .div(BASE_PRECISION) - .sub(nodeToFill.node.order!.quoteAssetAmountFilled) - ); + // @ts-ignore + const takerUserAccount = nodeToFill.userAccountData?.data + ? decodeUser( + // @ts-ignore + Buffer.from(nodeToFill.userAccountData.data) + ) + : (await this.userMap.mustGet(takerUserPubKey)).getUserAccount(); + + const authority = nodeToFill.authority + ? nodeToFill.authority + : getUserStatsAccountPublicKey( + this.driftClient.program.programId, + takerUserAccount!.authority + ).toString(); + const referrerInfo = await this.referrerMap.mustGet(authority); return Promise.resolve({ makerInfos, takerUserPubKey, takerUser: takerUserAccount, + takerStatsPubKey: getUserStatsAccountPublicKey( + this.driftClient.program.programId, + new PublicKey(authority) + ), takerUserSlot: this.slotSubscriber.getSlot(), referrerInfo, marketType: nodeToFill.node.order!.marketType, - fillerRewardEstimate: fillerReward, + isSwift: nodeToFill.node.isSwift, + authority: nodeToFill.authority + ? new PublicKey(nodeToFill.authority) + : undefined, }); } diff --git a/src/utils.ts b/src/utils.ts index d442f263..f335c537 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -749,6 +749,7 @@ export function logMessageForNodeToFill( fillId, revertOnFailure, removeLastIxPreSim, + isSwift: node.node.isSwift, }) ); @@ -1065,8 +1066,9 @@ export function validRebalanceSettledPnlThreshold( export const getPythPriceFeedIdForMarket = ( marketIndex: number, markets: Array, - throwOnNotFound = true -): string | undefined => { + throwOnNotFound = true, + lazer = false +): string | number | undefined => { const market = markets.find((market) => market.marketIndex === marketIndex); if (!market) { if (throwOnNotFound) { @@ -1076,7 +1078,7 @@ export const getPythPriceFeedIdForMarket = ( return undefined; } } - return market.pythFeedId; + return lazer ? market.pythLazerId : market.pythFeedId; }; export const getPythUpdateIxsForVaa = async ( @@ -1164,12 +1166,15 @@ export const getAllPythOracleUpdateIxs = async ( .filter((feedId) => feedId !== undefined) .map((feedId) => { if (!feedId) return; - const vaa = pythPriceSubscriber.getLatestCachedVaa(feedId); + const vaa = pythPriceSubscriber.getLatestCachedVaa(feedId as string); if (!vaa) { logger.debug('No VAA found for feedId', feedId); return; } - return driftClient.getPostPythPullOracleUpdateAtomicIxs(vaa, feedId); + return driftClient.getPostPythPullOracleUpdateAtomicIxs( + vaa, + feedId as string + ); }) .filter((ix) => ix !== undefined) as Promise[]; const postOracleUpdateIxs = await Promise.all(postOracleUpdateIxsPromises); diff --git a/yarn.lock b/yarn.lock index 63a13045..622a640f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,7 +28,7 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.2", "@babel/runtime@^7.23.4": +"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.5", "@babel/runtime@^7.23.4": version "7.24.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.4.tgz#de795accd698007a66ba44add6cc86542aff1edd" integrity sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA== @@ -42,6 +42,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.17.2": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" @@ -168,19 +175,19 @@ enabled "2.0.x" kuler "^2.0.0" -"@drift-labs/jit-proxy@0.12.24": - version "0.12.24" - resolved "https://registry.yarnpkg.com/@drift-labs/jit-proxy/-/jit-proxy-0.12.24.tgz#9baa0017c8d28d50875419929416fbf7a8fe5a55" - integrity sha512-16eE2zipSR5HvWVHyK5kh5yzmqt3E/5af0d98stgE9XMjOY7xewrScjb5PLP5v2k0ktt7Gbaz43atoCbGe4RZw== +"@drift-labs/jit-proxy@0.12.27": + version "0.12.27" + resolved "https://registry.yarnpkg.com/@drift-labs/jit-proxy/-/jit-proxy-0.12.27.tgz#b2f5ac65e58e755a8807c240fba7a4a84cfbc1f2" + integrity sha512-JBs42mm5QOh3iPl+j1cxWG7jx5JMl7kaHsG/C2OtdNv7M6rAvaop1hbgiyWFsJMfDgTgsuDJZqs+0huwJr5k1Q== dependencies: "@coral-xyz/anchor" "0.26.0" - "@drift-labs/sdk" "2.104.0-beta.37" + "@drift-labs/sdk" "2.105.0-beta.0" "@solana/web3.js" "1.91.7" -"@drift-labs/sdk@2.104.0-beta.37": - version "2.104.0-beta.37" - resolved "https://registry.yarnpkg.com/@drift-labs/sdk/-/sdk-2.104.0-beta.37.tgz#c62cfcc6d5048019acebec03ee2518ad24ea5ec4" - integrity sha512-D/RIfK1RiudrNpE/ITiuB7aGanhOdJCElan+n99nNzoFNZIyN1/ImhjotuF9QmZRx4hGVKV2f4LvCnUq6pvJ6w== +"@drift-labs/sdk@2.105.0-beta.0": + version "2.105.0-beta.0" + resolved "https://registry.yarnpkg.com/@drift-labs/sdk/-/sdk-2.105.0-beta.0.tgz#cea9ba6608669990f2d1539231af931f6a87b6fc" + integrity sha512-5Vw+zjcm2YBlGOuRFuRAjKXWtWmLdBckFyyvD7fx9ByeHNhR9/lP8NmBl36yuYAItPlc7JkAVxxwpd+nEEl8hA== dependencies: "@coral-xyz/anchor" "0.29.0" "@coral-xyz/anchor-30" "npm:@coral-xyz/anchor@0.30.1" @@ -220,6 +227,126 @@ borsh "^0.7.0" bs58 "^5.0.0" +"@esbuild/aix-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz#b57697945b50e99007b4c2521507dc613d4a648c" + integrity sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw== + +"@esbuild/android-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz#1add7e0af67acefd556e407f8497e81fddad79c0" + integrity sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w== + +"@esbuild/android-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.0.tgz#ab7263045fa8e090833a8e3c393b60d59a789810" + integrity sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew== + +"@esbuild/android-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.0.tgz#e8f8b196cfdfdd5aeaebbdb0110983460440e705" + integrity sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ== + +"@esbuild/darwin-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz#2d0d9414f2acbffd2d86e98253914fca603a53dd" + integrity sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw== + +"@esbuild/darwin-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz#33087aab31a1eb64c89daf3d2cf8ce1775656107" + integrity sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA== + +"@esbuild/freebsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz#bb76e5ea9e97fa3c753472f19421075d3a33e8a7" + integrity sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA== + +"@esbuild/freebsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz#e0e2ce9249fdf6ee29e5dc3d420c7007fa579b93" + integrity sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ== + +"@esbuild/linux-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz#d1b2aa58085f73ecf45533c07c82d81235388e75" + integrity sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g== + +"@esbuild/linux-arm@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz#8e4915df8ea3e12b690a057e77a47b1d5935ef6d" + integrity sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw== + +"@esbuild/linux-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz#8200b1110666c39ab316572324b7af63d82013fb" + integrity sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA== + +"@esbuild/linux-loong64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz#6ff0c99cf647504df321d0640f0d32e557da745c" + integrity sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g== + +"@esbuild/linux-mips64el@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz#3f720ccd4d59bfeb4c2ce276a46b77ad380fa1f3" + integrity sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA== + +"@esbuild/linux-ppc64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz#9d6b188b15c25afd2e213474bf5f31e42e3aa09e" + integrity sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ== + +"@esbuild/linux-riscv64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz#f989fdc9752dfda286c9cd87c46248e4dfecbc25" + integrity sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw== + +"@esbuild/linux-s390x@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz#29ebf87e4132ea659c1489fce63cd8509d1c7319" + integrity sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g== + +"@esbuild/linux-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz#4af48c5c0479569b1f359ffbce22d15f261c0cef" + integrity sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA== + +"@esbuild/netbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz#1ae73d23cc044a0ebd4f198334416fb26c31366c" + integrity sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg== + +"@esbuild/openbsd-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz#5d904a4f5158c89859fd902c427f96d6a9e632e2" + integrity sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg== + +"@esbuild/openbsd-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz#4c8aa88c49187c601bae2971e71c6dc5e0ad1cdf" + integrity sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q== + +"@esbuild/sunos-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz#8ddc35a0ea38575fa44eda30a5ee01ae2fa54dd4" + integrity sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA== + +"@esbuild/win32-arm64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz#6e79c8543f282c4539db684a207ae0e174a9007b" + integrity sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA== + +"@esbuild/win32-ia32@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz#057af345da256b7192d18b676a02e95d0fa39103" + integrity sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw== + +"@esbuild/win32-x64@0.24.0": + version "0.24.0" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz#168ab1c7e1c318b922637fad8f339d48b01e1244" + integrity sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA== + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1604,11 +1731,11 @@ integrity sha512-zA2oZluZmVvgZEDjF243KWD1S2J+1SH1MVynI0O1KRgDt1lU8nqk7AK3oQfW/WpwT51L5waGSU0xKF/9BTP5Cw== "@swc/helpers@^0.5.11": - version "0.5.11" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.11.tgz#5bab8c660a6e23c13b2d23fcd1ee44a2db1b0cb7" - integrity sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A== + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== dependencies: - tslib "^2.4.0" + tslib "^2.8.0" "@switchboard-xyz/common@^2.5.0": version "2.5.0" @@ -2089,7 +2216,14 @@ dependencies: "@types/node" "*" -"@types/ws@^8.2.2", "@types/ws@^8.5.3": +"@types/ws@^8.2.2": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.3": version "8.5.10" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== @@ -3003,6 +3137,36 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +esbuild@^0.24.0: + version "0.24.0" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.0.tgz#f2d470596885fcb2e91c21eb3da3b3c89c0b55e7" + integrity sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ== + optionalDependencies: + "@esbuild/aix-ppc64" "0.24.0" + "@esbuild/android-arm" "0.24.0" + "@esbuild/android-arm64" "0.24.0" + "@esbuild/android-x64" "0.24.0" + "@esbuild/darwin-arm64" "0.24.0" + "@esbuild/darwin-x64" "0.24.0" + "@esbuild/freebsd-arm64" "0.24.0" + "@esbuild/freebsd-x64" "0.24.0" + "@esbuild/linux-arm" "0.24.0" + "@esbuild/linux-arm64" "0.24.0" + "@esbuild/linux-ia32" "0.24.0" + "@esbuild/linux-loong64" "0.24.0" + "@esbuild/linux-mips64el" "0.24.0" + "@esbuild/linux-ppc64" "0.24.0" + "@esbuild/linux-riscv64" "0.24.0" + "@esbuild/linux-s390x" "0.24.0" + "@esbuild/linux-x64" "0.24.0" + "@esbuild/netbsd-x64" "0.24.0" + "@esbuild/openbsd-arm64" "0.24.0" + "@esbuild/openbsd-x64" "0.24.0" + "@esbuild/sunos-x64" "0.24.0" + "@esbuild/win32-arm64" "0.24.0" + "@esbuild/win32-ia32" "0.24.0" + "@esbuild/win32-x64" "0.24.0" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -3461,6 +3625,11 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" +globalyzer@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465" + integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q== + globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -3473,6 +3642,11 @@ globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -4586,7 +4760,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rpc-websockets@7.11.0: +rpc-websockets@7.11.0, rpc-websockets@^7.5.1: version "7.11.0" resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.11.0.tgz#05451975963a7d1a4cf36d54e200bfc4402a56d7" integrity sha512-IkLYjayPv6Io8C/TdCL5gwgzd1hFz2vmBZrjMw/SPEXo51ETOhnzgS4Qy5GWi2JQN7HKHa66J3+2mv0fgNh/7w== @@ -4611,23 +4785,10 @@ rpc-websockets@7.5.1: bufferutil "^4.0.1" utf-8-validate "^5.0.2" -rpc-websockets@^7.5.1: - version "7.9.0" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-7.9.0.tgz#a3938e16d6f134a3999fdfac422a503731bf8973" - integrity sha512-DwKewQz1IUA5wfLvgM8wDpPRcr+nWSxuFxx5CbrI2z/MyyZ4nXLM86TvIA+cI1ZAdqC8JIBR1mZR55dzaLU+Hw== - dependencies: - "@babel/runtime" "^7.17.2" - eventemitter3 "^4.0.7" - uuid "^8.3.2" - ws "^8.5.0" - optionalDependencies: - bufferutil "^4.0.1" - utf-8-validate "^5.0.2" - rpc-websockets@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-8.0.1.tgz#fa76db08badc0b2f5cd66b5496debd2c404c94b1" - integrity sha512-PptrPRK40uQvifq5sCcObmqInVcZXhy+RRrirzdE5KUPvDI47y1wPvfckD2QzqngOU9xaPW/dT+G+b+wj6M1MQ== + version "8.0.2" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-8.0.2.tgz#c91cb680d6806101bdf7862897bcc43732e20da6" + integrity sha512-QZ8lneJTtIZTf9JBcdUn/im2qDynWRYPKtmF6P9DqtdzqSLebcllYWVQr5aQacAp7LBYPReOW9Ses98dNfO7cA== dependencies: eventemitter3 "^4.0.7" uuid "^8.3.2" @@ -4637,9 +4798,9 @@ rpc-websockets@^8.0.1: utf-8-validate "^5.0.2" rpc-websockets@^9.0.0, rpc-websockets@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.2.tgz#4c1568d00b8100f997379a363478f41f8f4b242c" - integrity sha512-YzggvfItxMY3Lwuax5rC18inhbjJv9Py7JXRHxTIi94JOLrqBsSsUUc5bbl5W6c11tXhdfpDPK0KzBhoGe8jjw== + version "9.0.4" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" + integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== dependencies: "@swc/helpers" "^0.5.11" "@types/uuid" "^8.3.4" @@ -4989,6 +5150,14 @@ thread-stream@^0.15.1: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-glob@^0.2.9: + version "0.2.9" + resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2" + integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg== + dependencies: + globalyzer "0.1.0" + globrex "^0.1.2" + tiny-lru@^8.0.1: version "8.0.2" resolved "https://registry.yarnpkg.com/tiny-lru/-/tiny-lru-8.0.2.tgz#812fccbe6e622ded552e3ff8a4c3b5ff34a85e4c" @@ -5055,10 +5224,10 @@ tslib@^2.0.3, tslib@^2.3.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tslib@^2.4.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== tsutils@^3.21.0: version "3.21.0"