From 2f4432d261b6dda0bbf2d278ac53ec54181e27a5 Mon Sep 17 00:00:00 2001 From: LukasDeco Date: Sat, 3 Jan 2026 07:18:29 -0700 Subject: [PATCH 1/4] fix: more null checks --- sdk/src/config.ts | 8 +- sdk/src/driftClient.ts | 284 ++++++++++++++++++++++++++++++++--------- 2 files changed, 225 insertions(+), 67 deletions(-) diff --git a/sdk/src/config.ts b/sdk/src/config.ts index 0ff967b7ad..3e64200313 100644 --- a/sdk/src/config.ts +++ b/sdk/src/config.ts @@ -136,15 +136,15 @@ export function getMarketsAndOraclesForSubscription( oracleInfos: OracleInfo[]; } { const perpMarketsToUse = - perpMarkets?.length > 0 ? perpMarkets : PerpMarkets[env]; + (perpMarkets?.length ?? 0) > 0 ? perpMarkets : PerpMarkets[env]; const spotMarketsToUse = - spotMarkets?.length > 0 ? spotMarkets : SpotMarkets[env]; + (spotMarkets?.length ?? 0) > 0 ? spotMarkets : SpotMarkets[env]; const perpMarketIndexes = []; const spotMarketIndexes = []; const oracleInfos = new Map(); - for (const market of perpMarketsToUse) { + for (const market of perpMarketsToUse ?? []) { perpMarketIndexes.push(market.marketIndex); oracleInfos.set(getOracleId(market.oracle, market.oracleSource), { publicKey: market.oracle, @@ -152,7 +152,7 @@ export function getMarketsAndOraclesForSubscription( }); } - for (const spotMarket of spotMarketsToUse) { + for (const spotMarket of spotMarketsToUse ?? []) { spotMarketIndexes.push(spotMarket.marketIndex); oracleInfos.set(getOracleId(spotMarket.oracle, spotMarket.oracleSource), { publicKey: spotMarket.oracle, diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 8b0ef4cade..39901b306d 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -4789,7 +4789,15 @@ export class DriftClient { const remainingAccounts = this.getRemainingAccounts({ userAccounts: depositToTradeArgs?.isMakingNewAccount ? [] - : [this.getUserAccount(subAccountId)], + : (() => { + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } + return [userAccount]; + })(), useMarketLastSlotCache: false, readablePerpMarketIndex: orderParams.marketIndex, readableSpotMarketIndexes: isDepositToTradeTx @@ -4797,7 +4805,7 @@ export class DriftClient { : undefined, }); - if (isUpdateHighLeverageMode(orderParams.bitFlags)) { + if (isUpdateHighLeverageMode(orderParams.bitFlags ?? 0)) { remainingAccounts.push({ pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), isWritable: true, @@ -4838,6 +4846,9 @@ export class DriftClient { const oracleAccountInfos = []; for (const marketIndex of marketIndexes) { const market = this.getPerpMarketAccount(marketIndex); + if (!market) { + continue; + } marketAccountInfos.push({ pubkey: market.pubkey, isWritable: true, @@ -4963,9 +4974,14 @@ export class DriftClient { subAccountId?: number ): Promise { const user = await this.getUserAccountPublicKey(subAccountId); - + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], useMarketLastSlotCache: true, }); @@ -5002,10 +5018,22 @@ export class DriftClient { const user = await this.getUserAccountPublicKey(subAccountId); const order = this.getOrderByUserId(userOrderId); - const oracle = this.getPerpMarketAccount(order.marketIndex).amm.oracle; + const oracle = this.getPerpMarketAccount(order?.marketIndex ?? 0)?.amm + .oracle; + if (!oracle) { + throw new Error( + `Oracle not found for market index ${order?.marketIndex ?? 0}` + ); + } + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], useMarketLastSlotCache: true, }); @@ -5075,6 +5103,11 @@ export class DriftClient { (await this.getUserAccountPublicKey(subAccountId)); const userAccount = user?.getUserAccount() ?? this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ userAccounts: [userAccount], @@ -5103,9 +5136,9 @@ export class DriftClient { const { txSig } = await this.sendTransaction( await this.buildTransaction( await this.getCancelOrdersIx( - marketType, - marketIndex, - direction, + marketType ?? null, + marketIndex ?? null, + direction ?? null, subAccountId ), txParams @@ -5135,8 +5168,15 @@ export class DriftClient { } } + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } + const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], readablePerpMarketIndex, readableSpotMarketIndexes, useMarketLastSlotCache: true, @@ -5169,9 +5209,9 @@ export class DriftClient { ): Promise { const ixs = [ await this.getCancelOrdersIx( - cancelOrderParams.marketType, - cancelOrderParams.marketIndex, - cancelOrderParams.direction, + cancelOrderParams.marketType ?? null, + cancelOrderParams.marketIndex ?? null, + cancelOrderParams.direction ?? null, subAccountId ), await this.getPlaceOrdersIx(placeOrderParams, subAccountId), @@ -5248,15 +5288,22 @@ export class DriftClient { } } + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } + const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], readablePerpMarketIndex, readableSpotMarketIndexes, useMarketLastSlotCache: true, }); for (const param of params) { - if (isUpdateHighLeverageMode(param.bitFlags)) { + if (isUpdateHighLeverageMode(param.bitFlags ?? 0)) { remainingAccounts.push({ pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), isWritable: true, @@ -5298,16 +5345,22 @@ export class DriftClient { readableSpotMarketIndexes.push(param.marketIndex); } } + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], readablePerpMarketIndex, readableSpotMarketIndexes, useMarketLastSlotCache: true, }); for (const param of params) { - if (isUpdateHighLeverageMode(param.bitFlags)) { + if (isUpdateHighLeverageMode(param.bitFlags ?? 0)) { remainingAccounts.push({ pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), isWritable: true, @@ -5362,7 +5415,7 @@ export class DriftClient { await this.getFillPerpOrderIx( userAccountPublicKey, user, - order, + order as Pick, makerInfo, referrerInfo, fillerSubAccountId, @@ -5421,7 +5474,10 @@ export class DriftClient { ? order.marketIndex : userAccount.orders.find( (order) => order.orderId === userAccount.nextOrderId - 1 - ).marketIndex; + )?.marketIndex; + if (!marketIndex) { + throw new Error(`Market index not found`); + } makerInfo = Array.isArray(makerInfo) ? makerInfo @@ -5569,9 +5625,15 @@ export class DriftClient { const userAccountPublicKey = await this.getUserAccountPublicKey( subAccountId ); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], useMarketLastSlotCache: true, readableSpotMarketIndexes: [ orderParams.marketIndex, @@ -5643,8 +5705,11 @@ export class DriftClient { const marketIndex = order ? order.marketIndex : userAccount.orders.find( - (order) => order.orderId === userAccount.nextOrderId - 1 - ).marketIndex; + (o) => o.orderId === userAccount.nextOrderId - 1 + )?.marketIndex; + if (!marketIndex) { + throw new Error(`Market index not found`); + } makerInfo = Array.isArray(makerInfo) ? makerInfo @@ -5674,7 +5739,7 @@ export class DriftClient { }); } - const orderId = order.orderId; + const orderId = order?.orderId; this.addSpotFulfillmentAccounts( marketIndex, @@ -5708,6 +5773,10 @@ export class DriftClient { | PhoenixV1FulfillmentConfigAccount | OpenbookV2FulfillmentConfigAccount ): void { + const market = this.getSpotMarketAccount(marketIndex); + if (!market) { + throw new Error(`Market not found`); + } if (fulfillmentConfig) { if ('serumProgramId' in fulfillmentConfig) { this.addSerumRemainingAccounts( @@ -5732,7 +5801,7 @@ export class DriftClient { } } else { remainingAccounts.push({ - pubkey: this.getSpotMarketAccount(marketIndex).vault, + pubkey: market.vault, isWritable: false, isSigner: false, }); @@ -5749,6 +5818,10 @@ export class DriftClient { remainingAccounts: AccountMeta[], fulfillmentConfig: SerumV3FulfillmentConfigAccount ): void { + const market = this.getSpotMarketAccount(marketIndex); + if (!market) { + throw new Error(`Market not found`); + } remainingAccounts.push({ pubkey: fulfillmentConfig.pubkey, isWritable: false, @@ -5819,7 +5892,7 @@ export class DriftClient { isSigner: false, }); remainingAccounts.push({ - pubkey: this.getSpotMarketAccount(marketIndex).vault, + pubkey: market.vault, isWritable: true, isSigner: false, }); @@ -5840,6 +5913,10 @@ export class DriftClient { remainingAccounts: AccountMeta[], fulfillmentConfig: PhoenixV1FulfillmentConfigAccount ): void { + const market = this.getSpotMarketAccount(marketIndex); + if (!market) { + throw new Error(`Market not found`); + } remainingAccounts.push({ pubkey: fulfillmentConfig.pubkey, isWritable: false, @@ -5876,7 +5953,7 @@ export class DriftClient { isSigner: false, }); remainingAccounts.push({ - pubkey: this.getSpotMarketAccount(marketIndex).vault, + pubkey: market.vault, isWritable: true, isSigner: false, }); @@ -5897,6 +5974,10 @@ export class DriftClient { remainingAccounts: AccountMeta[], fulfillmentConfig: OpenbookV2FulfillmentConfigAccount ): void { + const market = this.getSpotMarketAccount(marketIndex); + if (!market) { + throw new Error(`Market not found`); + } remainingAccounts.push({ pubkey: fulfillmentConfig.pubkey, isWritable: false, @@ -5948,7 +6029,7 @@ export class DriftClient { isSigner: false, }); remainingAccounts.push({ - pubkey: this.getSpotMarketAccount(marketIndex).vault, + pubkey: market.vault, isWritable: true, isSigner: false, }); @@ -5968,7 +6049,7 @@ export class DriftClient { isSigner: false, }); remainingAccounts.push({ - pubkey: this.getSpotMarketAccount(marketIndex).pubkey, + pubkey: market.pubkey, isWritable: true, isSigner: false, }); @@ -6149,6 +6230,9 @@ export class DriftClient { }> { const outMarket = this.getSpotMarketAccount(outMarketIndex); const inMarket = this.getSpotMarketAccount(inMarketIndex); + if (!outMarket || !inMarket) { + throw new Error(`Out or in market not found`); + } const isExactOut = swapMode === 'ExactOut'; const exactOutBufferedAmountIn = amount.muln(1001).divn(1000); // Add 10bp buffer @@ -6271,6 +6355,9 @@ export class DriftClient { }> { const outMarket = this.getSpotMarketAccount(outMarketIndex); const inMarket = this.getSpotMarketAccount(inMarketIndex); + if (!outMarket || !inMarket) { + throw new Error(`Out or in market not found`); + } if (!quote) { const fetchedQuote = await jupiterClient.getQuote({ @@ -6433,6 +6520,9 @@ export class DriftClient { const outSpotMarket = this.getSpotMarketAccount(outMarketIndex); const inSpotMarket = this.getSpotMarketAccount(inMarketIndex); + if (!outSpotMarket || !inSpotMarket) { + throw new Error(`Out or in spot market not found`); + } const outTokenProgram = this.getTokenProgramForSpotMarket(outSpotMarket); const inTokenProgram = this.getTokenProgramForSpotMarket(inSpotMarket); @@ -6555,6 +6645,9 @@ export class DriftClient { // Get market accounts to determine mints const outMarket = this.getSpotMarketAccount(outMarketIndex); const inMarket = this.getSpotMarketAccount(inMarketIndex); + if (!outMarket || !inMarket) { + throw new Error(`Out or in market not found`); + } const isExactOut = swapMode === 'ExactOut'; @@ -6674,7 +6767,10 @@ export class DriftClient { amount: BN; userAccountPublicKey?: PublicKey; }): Promise { - const wSOLMint = this.getSpotMarketAccount(1).mint; + const wSOLMint = this.getSpotMarketAccount(1)?.mint; + if (!wSOLMint) { + throw new Error(`W SOL mint not found`); + } const mSOLAccount = await this.getAssociatedTokenAccount(2); const wSOLAccount = await this.getAssociatedTokenAccount(1, false); @@ -7070,12 +7166,15 @@ export class DriftClient { placeAndTakeTx: Transaction | VersionedTransaction; cancelExistingOrdersTx: Transaction | VersionedTransaction; settlePnlTx: Transaction | VersionedTransaction; - }> { + } | null> { const placeAndTakeIxs: TransactionInstruction[] = []; type TxKeys = 'placeAndTakeTx' | 'cancelExistingOrdersTx' | 'settlePnlTx'; - const txsToSign: Record = { + const txsToSign: Record< + TxKeys, + Transaction | VersionedTransaction | undefined + > = { placeAndTakeTx: undefined, cancelExistingOrdersTx: undefined, settlePnlTx: undefined, @@ -7133,8 +7232,8 @@ export class DriftClient { await TransactionParamProcessor.getTxSimComputeUnits( placeAndTakeTxToSim, this.connection, - txParams.computeUnitsBufferMultiplier ?? 1.2, - txParams.lowerBoundCu + txParams?.computeUnitsBufferMultiplier ?? 1.2, + txParams?.lowerBoundCu ); if (shouldExitIfSimulationFails && !simulationResult.success) { @@ -7170,7 +7269,11 @@ export class DriftClient { }; const prepCancelOrderTx = async () => { - if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { + if ( + cancelExistingOrders && + orderParams.marketType && + isVariant(orderParams.marketType, 'perp') + ) { const cancelOrdersIx = await this.getCancelOrdersIx( orderParams.marketType, orderParams.marketIndex, @@ -7198,9 +7301,16 @@ export class DriftClient { subAccountId ); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } + const settlePnlIx = await this.settlePNLIx( userAccountPublicKey, - this.getUserAccount(subAccountId), + userAccount, orderParams.marketIndex ); @@ -7227,7 +7337,11 @@ export class DriftClient { return null; } - return txsToSign; + return txsToSign as { + placeAndTakeTx: Transaction | VersionedTransaction; + cancelExistingOrdersTx: Transaction | VersionedTransaction; + settlePnlTx: Transaction | VersionedTransaction; + }; } public async placeAndTakePerpWithAdditionalOrders( @@ -7244,7 +7358,7 @@ export class DriftClient { txSig: TransactionSignature; signedCancelExistingOrdersTx?: Transaction; signedSettlePnlTx?: Transaction; - }> { + } | null> { const txsToSign = await this.preparePlaceAndTakePerpOrderWithAdditionalOrders( orderParams, @@ -7308,7 +7422,13 @@ export class DriftClient { ? [makerInfo] : []; - const userAccounts = [this.getUserAccount(subAccountId)]; + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } + const userAccounts = [userAccount]; for (const maker of makerInfo) { userAccounts.push(maker.makerUserAccount); } @@ -7350,7 +7470,7 @@ export class DriftClient { } } - if (isUpdateHighLeverageMode(orderParams.bitFlags)) { + if (isUpdateHighLeverageMode(orderParams.bitFlags ?? 0)) { remainingAccounts.push({ pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), isWritable: true, @@ -7416,12 +7536,15 @@ export class DriftClient { orderParams = getOrderParams(orderParams, { marketType: MarketType.PERP }); const userStatsPublicKey = this.getUserStatsAccountPublicKey(); const user = await this.getUserAccountPublicKey(subAccountId); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [ - this.getUserAccount(subAccountId), - takerInfo.takerUserAccount, - ], + userAccounts: [userAccount, takerInfo.takerUserAccount], useMarketLastSlotCache: true, writablePerpMarketIndexes: [orderParams.marketIndex], }); @@ -7590,8 +7713,11 @@ export class DriftClient { public signMessage( message: Uint8Array, - keypair: Keypair = this.wallet.payer + keypair: Keypair | undefined = this.wallet.payer ): Buffer { + if (!keypair) { + throw new Error(`Keypair not found for signMessage`); + } return Buffer.from(nacl.sign.detached(message, keypair.secretKey)); } @@ -7788,12 +7914,15 @@ export class DriftClient { orderParams = getOrderParams(orderParams, { marketType: MarketType.PERP }); const userStatsPublicKey = this.getUserStatsAccountPublicKey(); const user = await this.getUserAccountPublicKey(subAccountId); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [ - this.getUserAccount(subAccountId), - takerInfo.takerUserAccount, - ], + userAccounts: [userAccount, takerInfo.takerUserAccount], useMarketLastSlotCache: false, writablePerpMarketIndexes: [orderParams.marketIndex], }); @@ -7927,8 +8056,14 @@ export class DriftClient { orderParams = getOrderParams(orderParams, { marketType: MarketType.SPOT }); const userStatsPublicKey = this.getUserStatsAccountPublicKey(); const user = await this.getUserAccountPublicKey(subAccountId); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } - const userAccounts = [this.getUserAccount(subAccountId)]; + const userAccounts = [userAccount]; if (makerInfo !== undefined) { userAccounts.push(makerInfo.makerUserAccount); } @@ -7942,7 +8077,7 @@ export class DriftClient { }); let makerOrderId = null; - if (makerInfo) { + if (makerInfo && makerInfo.order) { makerOrderId = makerInfo.order.orderId; remainingAccounts.push({ pubkey: makerInfo.maker, @@ -8028,12 +8163,14 @@ export class DriftClient { orderParams = getOrderParams(orderParams, { marketType: MarketType.SPOT }); const userStatsPublicKey = this.getUserStatsAccountPublicKey(); const user = await this.getUserAccountPublicKey(subAccountId); - + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [ - this.getUserAccount(subAccountId), - takerInfo.takerUserAccount, - ], + userAccounts: [userAccount, takerInfo.takerUserAccount], useMarketLastSlotCache: true, writableSpotMarketIndexes: [ orderParams.marketIndex, @@ -8258,6 +8395,9 @@ export class DriftClient { (await this.getUserAccountPublicKey(subAccountId)); const userAccount = overrides?.user?.getUserAccount() ?? this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error(`User account not found`); + } const remainingAccounts = this.getRemainingAccounts({ userAccounts: [userAccount], @@ -8277,7 +8417,7 @@ export class DriftClient { reduceOnly: reduceOnly != undefined ? reduceOnly : null, postOnly: postOnly != undefined ? postOnly : null, bitFlags: bitFlags != undefined ? bitFlags : null, - policy: policy || null, + policy: policy || undefined, maxTs: maxTs || null, }; @@ -8384,9 +8524,15 @@ export class DriftClient { subAccountId?: number ): Promise { const user = await this.getUserAccountPublicKey(subAccountId); + const userAccount = this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subaccount id ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(subAccountId)], + userAccounts: [userAccount], useMarketLastSlotCache: true, }); @@ -8403,7 +8549,7 @@ export class DriftClient { reduceOnly: reduceOnly || false, postOnly: postOnly || null, bitFlags: bitFlags || null, - policy: policy || null, + policy: policy || undefined, maxTs: maxTs || null, }; @@ -8443,7 +8589,13 @@ export class DriftClient { if (filterInvalidMarkets) { for (const marketIndex of marketIndexes) { const perpMarketAccount = this.getPerpMarketAccount(marketIndex); + if (!perpMarketAccount) { + continue; + } const oraclePriceData = this.getOracleDataForPerpMarket(marketIndex); + if (!oraclePriceData) { + continue; + } const stateAccountAndSlot = this.accountSubscriber.getStateAccountAndSlot(); const oracleGuardRails = stateAccountAndSlot.data.oracleGuardRails; @@ -8923,9 +9075,15 @@ export class DriftClient { liquidatorSubAccountId ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], useMarketLastSlotCache: true, writablePerpMarketIndexes: [marketIndex], }); @@ -10068,7 +10226,7 @@ export class DriftClient { marketIndex ); - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; this.addTokenMintToRemainingAccounts(spotMarket, remainingAccounts); if (this.isTransferHook(spotMarket)) { await this.addExtraAccountMetasToRemainingAccounts( @@ -10389,7 +10547,7 @@ export class DriftClient { } } - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; this.addTokenMintToRemainingAccounts(spotMarketAccount, remainingAccounts); if (this.isTransferHook(spotMarketAccount)) { await this.addExtraAccountMetasToRemainingAccounts( @@ -10540,7 +10698,7 @@ export class DriftClient { const spotMarketAccount = this.getSpotMarketAccount(spotMarketIndex); const tokenProgramId = this.getTokenProgramForSpotMarket(spotMarketAccount); - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; this.addTokenMintToRemainingAccounts(spotMarketAccount, remainingAccounts); if (this.isTransferHook(spotMarketAccount)) { await this.addExtraAccountMetasToRemainingAccounts( @@ -10619,7 +10777,7 @@ export class DriftClient { ): Promise { const spotMarket = await this.getSpotMarketAccount(marketIndex); - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; this.addTokenMintToRemainingAccounts(spotMarket, remainingAccounts); if (this.isTransferHook(spotMarket)) { await this.addExtraAccountMetasToRemainingAccounts( From 4d49913f9fb11038f58ce55da6ca43da8f19d77b Mon Sep 17 00:00:00 2001 From: LukasDeco Date: Tue, 30 Dec 2025 11:38:26 -0700 Subject: [PATCH 2/4] fix: many null checks fixed (#2059) * fix: many null checks fixed * fix: prettier * feat: get active markets helpers * fix: prettier --- sdk/src/constants/txConstants.ts | 2 + sdk/src/driftClient.ts | 316 ++++++++++++++++++++++++++----- sdk/src/markets.ts | 24 +++ 3 files changed, 290 insertions(+), 52 deletions(-) create mode 100644 sdk/src/markets.ts diff --git a/sdk/src/constants/txConstants.ts b/sdk/src/constants/txConstants.ts index df3e001801..c16c23eaed 100644 --- a/sdk/src/constants/txConstants.ts +++ b/sdk/src/constants/txConstants.ts @@ -4,3 +4,5 @@ export const NOT_CONFIRMED_ERROR_CODE = -1001; export const FUEL_RESET_LOG_ACCOUNT = new PublicKey( 'FuE1gqp2fzw2sDNLrbZqKsqpphJcoSW6HPaSJjGd4RZ4' ); + +export const DEFAULT_COMMITMENT_LEVEL = 'confirmed'; diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 39901b306d..46449dfb10 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -221,6 +221,7 @@ import { } from './math/builder'; import { TitanClient, SwapMode as TitanSwapMode } from './titan/titanClient'; import { UnifiedSwapClient } from './swap/UnifiedSwapClient'; +import { DEFAULT_COMMITMENT_LEVEL } from './constants'; /** * Union type for swap clients (Titan and Jupiter) - Legacy type @@ -428,19 +429,17 @@ export class DriftClient { }); } - this.marketLookupTable = config.marketLookupTable; - if (!this.marketLookupTable) { - this.marketLookupTable = new PublicKey( - configs[this.env].MARKET_LOOKUP_TABLE - ); - } + this.marketLookupTable = config.marketLookupTable + ? config.marketLookupTable + : (this.marketLookupTable = new PublicKey( + configs[this.env].MARKET_LOOKUP_TABLE + )); - this.marketLookupTables = config.marketLookupTables; - if (!this.marketLookupTables) { - this.marketLookupTables = configs[this.env].MARKET_LOOKUP_TABLES.map( - (tableAddr) => new PublicKey(tableAddr) - ); - } + this.marketLookupTables = config.marketLookupTables + ? config.marketLookupTables + : configs[this.env].MARKET_LOOKUP_TABLES.map( + (tableAddr) => new PublicKey(tableAddr) + ); const delistedMarketSetting = config.delistedMarketSetting || DelistedMarketSetting.Unsubscribe; @@ -9233,8 +9232,14 @@ export class DriftClient { ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], useMarketLastSlotCache: true, writableSpotMarketIndexes: [liabilityMarketIndex, assetMarketIndex], }); @@ -9295,7 +9300,17 @@ export class DriftClient { lookupTables: AddressLookupTableAccount[]; }> { const liabilityMarket = this.getSpotMarketAccount(liabilityMarketIndex); + if (!liabilityMarket) { + throw new Error( + `Liability spot market account not found for index ${liabilityMarketIndex}` + ); + } const assetMarket = this.getSpotMarketAccount(assetMarketIndex); + if (!assetMarket) { + throw new Error( + `Asset spot market account not found for index ${assetMarketIndex}` + ); + } if (!quote) { const fetchedQuote = await jupiterClient.getQuote({ @@ -9334,7 +9349,7 @@ export class DriftClient { outputMint: liabilityMarket.mint, }); - const preInstructions = []; + const preInstructions: TransactionInstruction[] = []; if (!liabilityTokenAccount) { const tokenProgram = this.getTokenProgramForSpotMarket(liabilityMarket); liabilityTokenAccount = await this.getAssociatedTokenAccount( @@ -9444,7 +9459,17 @@ export class DriftClient { }); const liabilitySpotMarket = this.getSpotMarketAccount(liabilityMarketIndex); + if (!liabilitySpotMarket) { + throw new Error( + `Liability spot market account not found for index ${liabilityMarketIndex}` + ); + } const assetSpotMarket = this.getSpotMarketAccount(assetMarketIndex); + if (!assetSpotMarket) { + throw new Error( + `Asset spot market account not found for index ${assetMarketIndex}` + ); + } const liabilityTokenProgram = this.getTokenProgramForSpotMarket(liabilitySpotMarket); @@ -9560,7 +9585,17 @@ export class DriftClient { }); const inSpotMarket = this.getSpotMarketAccount(inMarketIndex); + if (!inSpotMarket) { + throw new Error( + `In spot market account not found for index ${inMarketIndex}` + ); + } const outSpotMarket = this.getSpotMarketAccount(outMarketIndex); + if (!outSpotMarket) { + throw new Error( + `Out spot market account not found for index ${outMarketIndex}` + ); + } if (this.isToken2022(inSpotMarket) || this.isToken2022(outSpotMarket)) { remainingAccounts.push({ @@ -9686,9 +9721,15 @@ export class DriftClient { liquidatorSubAccountId ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], writablePerpMarketIndexes: [perpMarketIndex], writableSpotMarketIndexes: [liabilityMarketIndex], }); @@ -9761,9 +9802,15 @@ export class DriftClient { liquidatorSubAccountId ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], writablePerpMarketIndexes: [perpMarketIndex], writableSpotMarketIndexes: [assetMarketIndex], }); @@ -9825,9 +9872,15 @@ export class DriftClient { liquidatorSubAccountId ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], writablePerpMarketIndexes: [marketIndex], writableSpotMarketIndexes: [QUOTE_SPOT_MARKET_INDEX], }); @@ -9894,12 +9947,22 @@ export class DriftClient { ); const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey(); + const liquidatorUser = this.getUserAccount(liquidatorSubAccountId); + if (!liquidatorUser) { + throw new Error( + `Liquidator user account not found for subaccount id ${liquidatorSubAccountId}` + ); + } + const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount(liquidatorSubAccountId), userAccount], + userAccounts: [liquidatorUser, userAccount], writableSpotMarketIndexes: [marketIndex], }); const spotMarket = this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const tokenProgramId = this.getTokenProgramForSpotMarket(spotMarket); this.addTokenMintToRemainingAccounts(spotMarket, remainingAccounts); @@ -9979,6 +10042,11 @@ export class DriftClient { perpMarketIndex: number ): Promise { const perpMarket = this.getPerpMarketAccount(perpMarketIndex); + if (!perpMarket) { + throw new Error( + `Perp market account not found for index ${perpMarketIndex}` + ); + } if (!isVariant(perpMarket.amm.oracleSource, 'prelaunch')) { throw new Error(`Wrong oracle source ${perpMarket.amm.oracleSource}`); @@ -10014,8 +10082,13 @@ export class DriftClient { makers: [PublicKey, PublicKey][] ): Promise { const perpMarket = this.getPerpMarketAccount(perpMarketIndex); + if (!perpMarket) { + throw new Error( + `Perp market account not found for index ${perpMarketIndex}` + ); + } - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; for (const [maker, makerStats] of makers) { remainingAccounts.push({ pubkey: maker, @@ -10063,7 +10136,7 @@ export class DriftClient { userAccountPublicKey )) as UserAccount; - const writablePerpMarketIndexes = []; + const writablePerpMarketIndexes: number[] = []; for (const position of userAccount.perpPositions) { if (!positionIsAvailable(position)) { writablePerpMarketIndexes.push(position.marketIndex); @@ -10088,15 +10161,25 @@ export class DriftClient { this.eventEmitter.emit(eventName, data); } - public getOracleDataForPerpMarket(marketIndex: number): OraclePriceData { + public getOracleDataForPerpMarket( + marketIndex: number + ): OraclePriceData | undefined { return this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket( marketIndex - ).data; + )?.data; } - public getMMOracleDataForPerpMarket(marketIndex: number): MMOraclePriceData { + public getMMOracleDataForPerpMarket( + marketIndex: number + ): MMOraclePriceData | undefined { const perpMarket = this.getPerpMarketAccount(marketIndex); + if (!perpMarket) { + throw new Error(`Perp market account not found for index ${marketIndex}`); + } const oracleData = this.getOracleDataForPerpMarket(marketIndex); + if (!oracleData) { + return undefined; + } const stateAccountAndSlot = this.accountSubscriber.getStateAccountAndSlot(); const isMMOracleActive = !perpMarket.amm.mmOracleSlot.eq(ZERO); const pctDiff = perpMarket.amm.mmOraclePrice @@ -10125,7 +10208,7 @@ export class DriftClient { isExchangeOracleMoreRecent = false; } else if ( !doSlotCheckForRecency && - oracleData.sequenceId < mmOracleSequenceId + (oracleData.sequenceId ?? ZERO) < mmOracleSequenceId ) { isExchangeOracleMoreRecent = false; } @@ -10162,10 +10245,12 @@ export class DriftClient { } } - public getOracleDataForSpotMarket(marketIndex: number): OraclePriceData { + public getOracleDataForSpotMarket( + marketIndex: number + ): OraclePriceData | undefined { return this.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket( marketIndex - ).data; + )?.data; } public async initializeInsuranceFundStake( @@ -10192,9 +10277,14 @@ export class DriftClient { marketIndex ); + const spotMarket = this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } + const accounts = { insuranceFundStake: ifStakeAccountPublicKey, - spotMarket: this.getSpotMarketAccount(marketIndex).pubkey, + spotMarket: spotMarket.pubkey, userStats: getUserStatsAccountPublicKey( this.program.programId, this.wallet.publicKey // only allow payer to initialize own insurance fund stake account @@ -10220,6 +10310,9 @@ export class DriftClient { collateralAccountPublicKey: PublicKey ): Promise { const spotMarket = this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, this.wallet.publicKey, @@ -10340,9 +10433,12 @@ export class DriftClient { */ fromSubaccount?: boolean; }): Promise { - const addIfStakeIxs = []; + const addIfStakeIxs: TransactionInstruction[] = []; const spotMarketAccount = this.getSpotMarketAccount(marketIndex); + if (!spotMarketAccount) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const isSolMarket = spotMarketAccount.mint.equals(WRAPPED_SOL_MINT); const createWSOLTokenAccount = isSolMarket && collateralAccountPublicKey.equals(this.wallet.publicKey); @@ -10436,6 +10532,9 @@ export class DriftClient { txParams?: TxParams ): Promise { const spotMarketAccount = this.getSpotMarketAccount(marketIndex); + if (!spotMarketAccount) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, this.wallet.publicKey, @@ -10471,6 +10570,9 @@ export class DriftClient { txParams?: TxParams ): Promise { const spotMarketAccount = this.getSpotMarketAccount(marketIndex); + if (!spotMarketAccount) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, this.wallet.publicKey, @@ -10506,8 +10608,11 @@ export class DriftClient { collateralAccountPublicKey: PublicKey, txParams?: TxParams ): Promise { - const removeIfStakeIxs = []; + const removeIfStakeIxs: TransactionInstruction[] = []; const spotMarketAccount = this.getSpotMarketAccount(marketIndex); + if (!spotMarketAccount) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, this.wallet.publicKey, @@ -10617,6 +10722,9 @@ export class DriftClient { ): Promise { const marketIndex = QUOTE_SPOT_MARKET_INDEX; const spotMarket = this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, authority, @@ -10656,6 +10764,9 @@ export class DriftClient { ): Promise { const marketIndex = GOV_SPOT_MARKET_INDEX; const spotMarket = this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey( this.program.programId, authority, @@ -10696,6 +10807,11 @@ export class DriftClient { spotMarketIndex: number ): Promise { const spotMarketAccount = this.getSpotMarketAccount(spotMarketIndex); + if (!spotMarketAccount) { + throw new Error( + `Spot market account not found for index ${spotMarketIndex}` + ); + } const tokenProgramId = this.getTokenProgramForSpotMarket(spotMarketAccount); const remainingAccounts: AccountMeta[] = []; @@ -10744,13 +10860,24 @@ export class DriftClient { spotMarketIndex: number, perpMarketIndex: number ): Promise { + const userAccount = this.getUserAccount(); + if (!userAccount) { + throw new Error( + `User account not found. Something is wrong with driftClient config` + ); + } const remainingAccounts = this.getRemainingAccounts({ - userAccounts: [this.getUserAccount()], + userAccounts: [userAccount], writablePerpMarketIndexes: [perpMarketIndex], writableSpotMarketIndexes: [spotMarketIndex], }); const spotMarket = this.getSpotMarketAccount(spotMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${spotMarketIndex}` + ); + } const tokenProgramId = this.getTokenProgramForSpotMarket(spotMarket); return await this.program.instruction.resolvePerpPnlDeficit( @@ -10776,6 +10903,9 @@ export class DriftClient { userTokenAccountPublicKey: PublicKey ): Promise { const spotMarket = await this.getSpotMarketAccount(marketIndex); + if (!spotMarket) { + throw new Error(`Spot market account not found for index ${marketIndex}`); + } const remainingAccounts: AccountMeta[] = []; this.addTokenMintToRemainingAccounts(spotMarket, remainingAccounts); @@ -10829,9 +10959,17 @@ export class DriftClient { public getPerpMarketExtendedInfo( marketIndex: number - ): PerpMarketExtendedInfo { + ): PerpMarketExtendedInfo | undefined { const marketAccount = this.getPerpMarketAccount(marketIndex); + if (!marketAccount) { + return undefined; + } const quoteAccount = this.getSpotMarketAccount(QUOTE_SPOT_MARKET_INDEX); + if (!quoteAccount) { + throw new Error( + `Quote spot market account not found. Something is wrong with driftClient config` + ); + } const extendedInfo: PerpMarketExtendedInfo = { marketIndex, @@ -10882,18 +11020,23 @@ export class DriftClient { feeTier.makerRebateNumerator / feeTier.makerRebateDenominator; if (marketIndex !== undefined) { - let marketAccount = null; + let marketAccount: + | null + | undefined + | PerpMarketAccount + | SpotMarketAccount = null; + let feeAdjustment = 1; if (isVariant(marketType, 'perp')) { marketAccount = this.getPerpMarketAccount(marketIndex); + feeAdjustment = marketAccount?.feeAdjustment ?? 0; } else { marketAccount = this.getSpotMarketAccount(marketIndex); } - - takerFee += (takerFee * marketAccount.feeAdjustment) / 100; + takerFee += (takerFee * feeAdjustment) / 100; if (userHLM) { takerFee *= 2; } - makerFee += (makerFee * marketAccount.feeAdjustment) / 100; + makerFee += (makerFee * feeAdjustment) / 100; } return { @@ -10944,7 +11087,9 @@ export class DriftClient { return this.receiverProgram; } - public async getSwitchboardOnDemandProgram(): Promise> { + public async getSwitchboardOnDemandProgram(): Promise< + Program30 | undefined + > { if (this.sbOnDemandProgram === undefined) { this.sbOnDemandProgram = await AnchorUtils.loadProgramFromConnection( this.connection @@ -11247,6 +11392,9 @@ export class DriftClient { numSignatures = 3 ): Promise { const program = await this.getSwitchboardOnDemandProgram(); + if (!program) { + return undefined; + } const feedAccount = new PullFeed(program, feed); if (!this.sbProgramFeedConfigs) { this.sbProgramFeedConfigs = new Map(); @@ -11275,7 +11423,7 @@ export class DriftClient { feed: PublicKey, recentSlothash?: Slothash, numSignatures = 3 - ): Promise { + ): Promise { const pullIx = await this.getPostSwitchboardOnDemandUpdateAtomicIx( feed, recentSlothash, @@ -11408,7 +11556,12 @@ export class DriftClient { const signingAuthority = overrides?.signingAuthority ?? this.wallet.publicKey; const userAccount = - overrides?.user.getUserAccount() ?? this.getUserAccount(subAccountId); + overrides?.user?.getUserAccount() ?? this.getUserAccount(subAccountId); + if (!userAccount) { + throw new Error( + `User account not found for subAccountId ${subAccountId}` + ); + } const remainingAccounts = this.getRemainingAccounts({ userAccounts: depositToTradeArgs?.isMakingNewAccount ? [] : [userAccount], @@ -11548,6 +11701,11 @@ export class DriftClient { spotMarketIndex: number ): Promise { const spotMarket = await this.getSpotMarketAccount(spotMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${spotMarketIndex}` + ); + } return this.program.instruction.pauseSpotMarketDepositWithdraw({ accounts: { state: await this.getStatePublicKey(), @@ -11604,12 +11762,17 @@ export class DriftClient { data.set(oraclePrice.toArrayLike(Buffer, 'le', 8), 5); // next 8 bytes data.set(oracleSequenceId.toArrayLike(Buffer, 'le', 8), 13); // next 8 bytes + const perpMarket = this.getPerpMarketAccount(marketIndex); + if (!perpMarket) { + throw new Error(`Perp market account not found for index ${marketIndex}`); + } + // Build the instruction manually return new TransactionInstruction({ programId: this.program.programId, keys: [ { - pubkey: this.getPerpMarketAccount(marketIndex).pubkey, + pubkey: perpMarket.pubkey, isWritable: true, isSigner: false, }, @@ -11660,12 +11823,16 @@ export class DriftClient { data.set(discriminatorBuffer, 0); data.writeInt8(ammSpreadAdjustment, 5); // next byte + const perpMarket = this.getPerpMarketAccount(marketIndex); + if (!perpMarket) { + throw new Error(`Perp market account not found for index ${marketIndex}`); + } // Build the instruction manually return new TransactionInstruction({ programId: this.program.programId, keys: [ { - pubkey: this.getPerpMarketAccount(marketIndex).pubkey, + pubkey: perpMarket.pubkey, isWritable: true, isSigner: false, }, @@ -11834,12 +12001,17 @@ export class DriftClient { readablePerpMarketIndex: perpMarketIndexes, }); + const quoteMarket = this.getSpotMarketAccount(0); + if (!quoteMarket) { + throw new Error(`Quote spot market account not found for index 0`); + } + return this.program.instruction.updateAmmCache({ accounts: { state: await this.getStatePublicKey(), keeper: this.wallet.publicKey, ammCache: getAmmCachePublicKey(this.program.programId), - quoteMarket: this.getSpotMarketAccount(0).pubkey, + quoteMarket: quoteMarket.pubkey, }, remainingAccounts, }); @@ -11863,6 +12035,11 @@ export class DriftClient { constituent: ConstituentAccount ): Promise { const spotMarket = this.getSpotMarketAccount(constituent.spotMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${constituent.spotMarketIndex}` + ); + } return this.program.instruction.updateConstituentOracleInfo({ accounts: { keeper: this.wallet.publicKey, @@ -11924,12 +12101,26 @@ export class DriftClient { lpPool, outMarketIndex ); + + const inSpotMarket = this.getSpotMarketAccount(inMarketIndex); + if (!inSpotMarket) { + throw new Error( + `Spot market account not found for index ${inMarketIndex}` + ); + } + const outSpotMarket = this.getSpotMarketAccount(outMarketIndex); + if (!outSpotMarket) { + throw new Error( + `Spot market account not found for index ${outMarketIndex}` + ); + } + const userInTokenAccount = await getAssociatedTokenAddress( - this.getSpotMarketAccount(inMarketIndex).mint, + inSpotMarket.mint, userAuthority ); const userOutTokenAccount = await getAssociatedTokenAddress( - this.getSpotMarketAccount(outMarketIndex).mint, + outSpotMarket.mint, userAuthority ); const inConstituent = getConstituentPublicKey( @@ -11942,8 +12133,8 @@ export class DriftClient { lpPool, outMarketIndex ); - const inMarketMint = this.getSpotMarketAccount(inMarketIndex).mint; - const outMarketMint = this.getSpotMarketAccount(outMarketIndex).mint; + const inMarketMint = inSpotMarket.mint; + const outMarketMint = outSpotMarket.mint; const constituentTargetBase = getConstituentTargetBasePublicKey( this.program.programId, @@ -12142,6 +12333,11 @@ export class DriftClient { }); const spotMarket = this.getSpotMarketAccount(inMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${inMarketIndex}` + ); + } const inMarketMint = spotMarket.mint; const isSolMarket = inMarketMint.equals(WRAPPED_SOL_MINT); @@ -12357,6 +12553,11 @@ export class DriftClient { }); const spotMarket = this.getSpotMarketAccount(outMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${outMarketIndex}` + ); + } const outMarketMint = spotMarket.mint; const outConstituent = getConstituentPublicKey( this.program.programId, @@ -12468,6 +12669,11 @@ export class DriftClient { }); const spotMarket = this.getSpotMarketAccount(outMarketIndex); + if (!spotMarket) { + throw new Error( + `Spot market account not found for index ${outMarketIndex}` + ); + } const outMarketMint = spotMarket.mint; const outConstituent = getConstituentPublicKey( this.program.programId, @@ -12702,11 +12908,11 @@ export class DriftClient { lpPoolId: number, perpMarketIndexes: number[] ): Promise { - const remainingAccounts = []; + const remainingAccounts: AccountMeta[] = []; remainingAccounts.push( ...perpMarketIndexes.map((index) => { return { - pubkey: this.getPerpMarketAccount(index).pubkey, + pubkey: this.getPerpMarketAccount(index)?.pubkey ?? PublicKey.default, isSigner: false, isWritable: true, }; @@ -12814,7 +13020,8 @@ export class DriftClient { txVersion: txVersion ?? this.txVersion, txParams: txParams ?? this.txParams, connection: this.connection, - preFlightCommitment: this.opts.preflightCommitment, + preFlightCommitment: + this.opts?.preflightCommitment ?? DEFAULT_COMMITMENT_LEVEL, fetchAllMarketLookupTableAccounts: this.fetchAllLookupTableAccounts.bind(this), lookupTables, @@ -12831,17 +13038,20 @@ export class DriftClient { lookupTables?: AddressLookupTableAccount[], forceVersionedTransaction?: boolean ): Promise<(Transaction | VersionedTransaction)[]> { - return this.txHandler.buildBulkTransactions({ + const txns = await this.txHandler.buildBulkTransactions({ instructions, txVersion: txVersion ?? this.txVersion, txParams: txParams ?? this.txParams, connection: this.connection, - preFlightCommitment: this.opts.preflightCommitment, + preFlightCommitment: + this.opts?.preflightCommitment ?? DEFAULT_COMMITMENT_LEVEL, fetchAllMarketLookupTableAccounts: this.fetchAllLookupTableAccounts.bind(this), lookupTables, forceVersionedTransaction, }); + + return txns.filter(Boolean) as (Transaction | VersionedTransaction)[]; } async buildTransactionsMap( @@ -12859,7 +13069,8 @@ export class DriftClient { txVersion: txVersion ?? this.txVersion, txParams: txParams ?? this.txParams, connection: this.connection, - preFlightCommitment: this.opts.preflightCommitment, + preFlightCommitment: + this.opts?.preflightCommitment ?? DEFAULT_COMMITMENT_LEVEL, fetchAllMarketLookupTableAccounts: this.fetchAllLookupTableAccounts.bind(this), lookupTables, @@ -12882,7 +13093,8 @@ export class DriftClient { txVersion: txVersion ?? this.txVersion, txParams: txParams ?? this.txParams, connection: this.connection, - preFlightCommitment: this.opts.preflightCommitment, + preFlightCommitment: + this.opts?.preflightCommitment ?? DEFAULT_COMMITMENT_LEVEL, fetchAllMarketLookupTableAccounts: this.fetchAllLookupTableAccounts.bind(this), lookupTables, diff --git a/sdk/src/markets.ts b/sdk/src/markets.ts new file mode 100644 index 0000000000..298080f147 --- /dev/null +++ b/sdk/src/markets.ts @@ -0,0 +1,24 @@ +import { DriftEnv } from './config'; +import { SpotMarketConfig, SpotMarkets } from './constants'; +import { PerpMarketConfig, PerpMarkets } from './constants/perpMarkets'; +import { isOneOfVariant } from './types'; + +export const getActivePerpMarkets = ( + driftEnv: DriftEnv +): PerpMarketConfig[] => { + return PerpMarkets[driftEnv ?? 'mainnet-beta'].filter( + (market) => + !market.marketStatus || + !isOneOfVariant(market.marketStatus, ['delisted', 'settlement']) + ); +}; + +export const getActiveSpotMarkets = ( + driftEnv: DriftEnv +): SpotMarketConfig[] => { + return SpotMarkets[driftEnv ?? 'mainnet-beta'].filter( + (market) => + !market.marketStatus || + !isOneOfVariant(market.marketStatus, ['delisted', 'settlement']) + ); +}; From 03f027155963bf790e5d4d340f2958ea8f16e130 Mon Sep 17 00:00:00 2001 From: LukasDeco Date: Tue, 6 Jan 2026 20:24:25 -0700 Subject: [PATCH 3/4] fix: get oracle data will throw instead of be undefined --- sdk/src/driftClient.ts | 33 +++++++++++++++++++++------------ sdk/src/types.ts | 2 +- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 46449dfb10..dd18b3c711 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -8416,7 +8416,7 @@ export class DriftClient { reduceOnly: reduceOnly != undefined ? reduceOnly : null, postOnly: postOnly != undefined ? postOnly : null, bitFlags: bitFlags != undefined ? bitFlags : null, - policy: policy || undefined, + policy: policy || null, maxTs: maxTs || null, }; @@ -8548,7 +8548,7 @@ export class DriftClient { reduceOnly: reduceOnly || false, postOnly: postOnly || null, bitFlags: bitFlags || null, - policy: policy || undefined, + policy: policy || null, maxTs: maxTs || null, }; @@ -10163,22 +10163,27 @@ export class DriftClient { public getOracleDataForPerpMarket( marketIndex: number - ): OraclePriceData | undefined { - return this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket( - marketIndex - )?.data; + ): OraclePriceData { + try { + + return this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket( + marketIndex + ).data; + } catch (error) { + throw new Error(`Failed to get oracle data for perp market ${marketIndex}: ${error}`); + } } public getMMOracleDataForPerpMarket( marketIndex: number - ): MMOraclePriceData | undefined { + ): MMOraclePriceData { const perpMarket = this.getPerpMarketAccount(marketIndex); if (!perpMarket) { throw new Error(`Perp market account not found for index ${marketIndex}`); } const oracleData = this.getOracleDataForPerpMarket(marketIndex); if (!oracleData) { - return undefined; + throw new Error(`Oracle data not found for perp market ${marketIndex}`); } const stateAccountAndSlot = this.accountSubscriber.getStateAccountAndSlot(); const isMMOracleActive = !perpMarket.amm.mmOracleSlot.eq(ZERO); @@ -10247,10 +10252,14 @@ export class DriftClient { public getOracleDataForSpotMarket( marketIndex: number - ): OraclePriceData | undefined { - return this.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket( - marketIndex - )?.data; + ): OraclePriceData { + try { + return this.accountSubscriber.getOraclePriceDataAndSlotForSpotMarket( + marketIndex + ).data; + } catch (error) { + throw new Error(`Failed to get oracle data for spot market ${marketIndex}: ${error}`); + } } public async initializeInsuranceFundStake( diff --git a/sdk/src/types.ts b/sdk/src/types.ts index ea111ce0c5..835fb402d6 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -1315,7 +1315,7 @@ export type OptionalOrderParams = { export type ModifyOrderParams = { [Property in keyof OrderParams]?: OrderParams[Property] | null; -} & { policy?: ModifyOrderPolicy }; +} & { policy?: ModifyOrderPolicy | null }; export enum ModifyOrderPolicy { MustModify = 1, From 3d88d1931202e369a01f191d2d4d19a8ae756c19 Mon Sep 17 00:00:00 2001 From: LukasDeco Date: Wed, 7 Jan 2026 11:33:31 -0700 Subject: [PATCH 4/4] fix: a few more null checks --- sdk/src/driftClient.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index dd18b3c711..dee9713380 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -4516,7 +4516,10 @@ export class DriftClient { public getQuoteValuePerLpShare(marketIndex: number): BN { const perpMarketAccount = this.getPerpMarketAccount(marketIndex); - + if(!perpMarketAccount) { + throw new Error(`Perp market account not found for market index ${marketIndex}`); + } + const openBids = BN.max( perpMarketAccount.amm.baseAssetReserve.sub( perpMarketAccount.amm.minBaseAssetReserve @@ -10160,18 +10163,23 @@ export class DriftClient { public triggerEvent(eventName: keyof DriftClientAccountEvents, data?: any) { this.eventEmitter.emit(eventName, data); } - + + /** + * This function can potentially throw an error when the driftClient is still initializing, even if you have awaited subscription, handle accordingly. + * @param marketIndex + * @returns + */ public getOracleDataForPerpMarket( marketIndex: number ): OraclePriceData { - try { - - return this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket( - marketIndex - ).data; - } catch (error) { - throw new Error(`Failed to get oracle data for perp market ${marketIndex}: ${error}`); + const oracleDataAndSlot = this.accountSubscriber.getOraclePriceDataAndSlotForPerpMarket( + marketIndex + ) + if(!oracleDataAndSlot) { + throw new Error(`DriftClient.getOracleDataForPerpMarket: Oracle data not found for perp market ${marketIndex}. + You may not have configured the driftClient to subscribe to this market, or may be calling this function before the data is ready.`); } + return oracleDataAndSlot.data; } public getMMOracleDataForPerpMarket( @@ -10182,9 +10190,7 @@ export class DriftClient { throw new Error(`Perp market account not found for index ${marketIndex}`); } const oracleData = this.getOracleDataForPerpMarket(marketIndex); - if (!oracleData) { - throw new Error(`Oracle data not found for perp market ${marketIndex}`); - } + const stateAccountAndSlot = this.accountSubscriber.getStateAccountAndSlot(); const isMMOracleActive = !perpMarket.amm.mmOracleSlot.eq(ZERO); const pctDiff = perpMarket.amm.mmOraclePrice