@drift-labs/sdk 2.149.1 → 2.150.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.env +4 -0
  2. package/VERSION +1 -1
  3. package/lib/browser/constants/perpMarkets.js +11 -0
  4. package/lib/browser/constants/spotMarkets.js +13 -0
  5. package/lib/browser/decode/user.js +2 -2
  6. package/lib/browser/driftClient.d.ts +20 -8
  7. package/lib/browser/driftClient.js +216 -17
  8. package/lib/browser/idl/drift.json +225 -21
  9. package/lib/browser/math/margin.js +2 -1
  10. package/lib/browser/math/position.d.ts +1 -0
  11. package/lib/browser/math/position.js +10 -2
  12. package/lib/browser/math/superStake.d.ts +3 -2
  13. package/lib/browser/types.d.ts +12 -6
  14. package/lib/browser/types.js +11 -6
  15. package/lib/browser/user.d.ts +3 -2
  16. package/lib/browser/user.js +24 -8
  17. package/lib/node/constants/perpMarkets.d.ts.map +1 -1
  18. package/lib/node/constants/perpMarkets.js +11 -0
  19. package/lib/node/constants/spotMarkets.d.ts.map +1 -1
  20. package/lib/node/constants/spotMarkets.js +13 -0
  21. package/lib/node/decode/user.d.ts.map +1 -1
  22. package/lib/node/decode/user.js +2 -2
  23. package/lib/node/driftClient.d.ts +20 -8
  24. package/lib/node/driftClient.d.ts.map +1 -1
  25. package/lib/node/driftClient.js +216 -17
  26. package/lib/node/idl/drift.json +225 -21
  27. package/lib/node/math/margin.d.ts.map +1 -1
  28. package/lib/node/math/margin.js +2 -1
  29. package/lib/node/math/position.d.ts +1 -0
  30. package/lib/node/math/position.d.ts.map +1 -1
  31. package/lib/node/math/position.js +10 -2
  32. package/lib/node/math/spotBalance.d.ts.map +1 -1
  33. package/lib/node/math/superStake.d.ts +3 -2
  34. package/lib/node/math/superStake.d.ts.map +1 -1
  35. package/lib/node/types.d.ts +12 -6
  36. package/lib/node/types.d.ts.map +1 -1
  37. package/lib/node/types.js +11 -6
  38. package/lib/node/user.d.ts +3 -2
  39. package/lib/node/user.d.ts.map +1 -1
  40. package/lib/node/user.js +24 -8
  41. package/package.json +1 -1
  42. package/scripts/deposit-isolated-positions.ts +110 -0
  43. package/scripts/single-grpc-client-test.ts +71 -21
  44. package/scripts/withdraw-isolated-positions.ts +174 -0
  45. package/src/constants/perpMarkets.ts +11 -0
  46. package/src/constants/spotMarkets.ts +14 -0
  47. package/src/decode/user.ts +2 -3
  48. package/src/driftClient.ts +464 -41
  49. package/src/idl/drift.json +226 -22
  50. package/src/margin/README.md +143 -0
  51. package/src/math/margin.ts +3 -4
  52. package/src/math/position.ts +12 -2
  53. package/src/math/spotBalance.ts +0 -1
  54. package/src/types.ts +15 -7
  55. package/src/user.ts +49 -15
  56. package/tests/amm/test.ts +1 -1
  57. package/tests/dlob/helpers.ts +1 -1
  58. package/tests/user/test.ts +0 -7
@@ -57,7 +57,6 @@ import {
57
57
  StateAccount,
58
58
  SwapReduceOnly,
59
59
  SignedMsgOrderParamsMessage,
60
- TakerInfo,
61
60
  TxParams,
62
61
  UserAccount,
63
62
  UserStatsAccount,
@@ -140,6 +139,7 @@ import {
140
139
  BASE_PRECISION,
141
140
  GOV_SPOT_MARKET_INDEX,
142
141
  MARGIN_PRECISION,
142
+ MIN_I64,
143
143
  ONE,
144
144
  PERCENTAGE_PRECISION,
145
145
  PRICE_PRECISION,
@@ -147,7 +147,11 @@ import {
147
147
  QUOTE_SPOT_MARKET_INDEX,
148
148
  ZERO,
149
149
  } from './constants/numericConstants';
150
- import { findDirectionToClose, positionIsAvailable } from './math/position';
150
+ import {
151
+ calculateClaimablePnl,
152
+ findDirectionToClose,
153
+ positionIsAvailable,
154
+ } from './math/position';
151
155
  import { getSignedTokenAmount, getTokenAmount } from './math/spotBalance';
152
156
  import { decodeName, DEFAULT_USER_NAME, encodeName } from './userName';
153
157
  import { MMOraclePriceData, OraclePriceData } from './oracles/types';
@@ -210,6 +214,8 @@ import nacl from 'tweetnacl';
210
214
  import { Slothash } from './slot/SlothashSubscriber';
211
215
  import { getOracleId } from './oracles/oracleId';
212
216
  import { SignedMsgOrderParams } from './types';
217
+ import { TakerInfo } from './types';
218
+ // BN is already imported globally in this file via other imports
213
219
  import { sha256 } from '@noble/hashes/sha256';
214
220
  import { getOracleConfidenceFromMMOracleData } from './oracles/utils';
215
221
  import { ConstituentMap } from './constituentMap/constituentMap';
@@ -295,6 +301,46 @@ export class DriftClient {
295
301
  return this._isSubscribed && this.accountSubscriber.isSubscribed;
296
302
  }
297
303
 
304
+ private async getPrePlaceOrderIxs(
305
+ orderParams: OptionalOrderParams,
306
+ userAccount: UserAccount,
307
+ options?: { positionMaxLev?: number; isolatedPositionDepositAmount?: BN }
308
+ ): Promise<TransactionInstruction[]> {
309
+ const preIxs: TransactionInstruction[] = [];
310
+
311
+ if (isVariant(orderParams.marketType, 'perp')) {
312
+ const { positionMaxLev, isolatedPositionDepositAmount } = options ?? {};
313
+
314
+ if (
315
+ isolatedPositionDepositAmount?.gt?.(ZERO) &&
316
+ this.isOrderIncreasingPosition(orderParams, userAccount.subAccountId)
317
+ ) {
318
+ preIxs.push(
319
+ await this.getTransferIsolatedPerpPositionDepositIx(
320
+ isolatedPositionDepositAmount as BN,
321
+ orderParams.marketIndex,
322
+ userAccount.subAccountId
323
+ )
324
+ );
325
+ }
326
+
327
+ if (positionMaxLev) {
328
+ const marginRatio = Math.floor(
329
+ (1 / positionMaxLev) * MARGIN_PRECISION.toNumber()
330
+ );
331
+ preIxs.push(
332
+ await this.getUpdateUserPerpPositionCustomMarginRatioIx(
333
+ orderParams.marketIndex,
334
+ marginRatio,
335
+ userAccount.subAccountId
336
+ )
337
+ );
338
+ }
339
+ }
340
+
341
+ return preIxs;
342
+ }
343
+
298
344
  public set isSubscribed(val: boolean) {
299
345
  this._isSubscribed = val;
300
346
  }
@@ -768,7 +814,6 @@ export class DriftClient {
768
814
 
769
815
  return lookupTableAccount;
770
816
  }
771
-
772
817
  public async fetchAllLookupTableAccounts(): Promise<
773
818
  AddressLookupTableAccount[]
774
819
  > {
@@ -1746,7 +1791,6 @@ export class DriftClient {
1746
1791
  const { txSig } = await this.sendTransaction(tx, [], this.opts);
1747
1792
  return txSig;
1748
1793
  }
1749
-
1750
1794
  public async getUpdateUserCustomMarginRatioIx(
1751
1795
  marginRatio: number,
1752
1796
  subAccountId = 0
@@ -2469,6 +2513,15 @@ export class DriftClient {
2469
2513
  return this.getTokenAmount(QUOTE_SPOT_MARKET_INDEX);
2470
2514
  }
2471
2515
 
2516
+ public getIsolatedPerpPositionTokenAmount(
2517
+ perpMarketIndex: number,
2518
+ subAccountId?: number
2519
+ ): BN {
2520
+ return this.getUser(subAccountId).getIsolatePerpPositionTokenAmount(
2521
+ perpMarketIndex
2522
+ );
2523
+ }
2524
+
2472
2525
  /**
2473
2526
  * Returns the token amount for a given market. The spot market precision is based on the token mint decimals.
2474
2527
  * Positive if it is a deposit, negative if it is a borrow.
@@ -2546,7 +2599,6 @@ export class DriftClient {
2546
2599
  this.mustIncludeSpotMarketIndexes.add(spotMarketIndex);
2547
2600
  });
2548
2601
  }
2549
-
2550
2602
  getRemainingAccounts(params: RemainingAccountParams): AccountMeta[] {
2551
2603
  const { oracleAccountMap, spotMarketAccountMap, perpMarketAccountMap } =
2552
2604
  this.getRemainingAccountMapsForUsers(params.userAccounts);
@@ -3434,7 +3486,6 @@ export class DriftClient {
3434
3486
  userAccountPublicKey,
3435
3487
  };
3436
3488
  }
3437
-
3438
3489
  public async createInitializeUserAccountAndDepositCollateral(
3439
3490
  amount: BN,
3440
3491
  userTokenAccount: PublicKey,
@@ -4082,6 +4133,281 @@ export class DriftClient {
4082
4133
  );
4083
4134
  }
4084
4135
 
4136
+ async depositIntoIsolatedPerpPosition(
4137
+ amount: BN,
4138
+ perpMarketIndex: number,
4139
+ userTokenAccount: PublicKey,
4140
+ subAccountId?: number,
4141
+ txParams?: TxParams
4142
+ ): Promise<TransactionSignature> {
4143
+ const { txSig } = await this.sendTransaction(
4144
+ await this.buildTransaction(
4145
+ await this.getDepositIntoIsolatedPerpPositionIx(
4146
+ amount,
4147
+ perpMarketIndex,
4148
+ userTokenAccount,
4149
+ subAccountId
4150
+ ),
4151
+ txParams
4152
+ ),
4153
+ [],
4154
+ this.opts
4155
+ );
4156
+ return txSig;
4157
+ }
4158
+
4159
+ async getDepositIntoIsolatedPerpPositionIx(
4160
+ amount: BN,
4161
+ perpMarketIndex: number,
4162
+ userTokenAccount: PublicKey,
4163
+ subAccountId?: number
4164
+ ): Promise<TransactionInstruction> {
4165
+ const userAccountPublicKey = await getUserAccountPublicKey(
4166
+ this.program.programId,
4167
+ this.authority,
4168
+ subAccountId ?? this.activeSubAccountId
4169
+ );
4170
+
4171
+ const perpMarketAccount = this.getPerpMarketAccount(perpMarketIndex);
4172
+ const spotMarketIndex = perpMarketAccount.quoteSpotMarketIndex;
4173
+ const spotMarketAccount = this.getSpotMarketAccount(spotMarketIndex);
4174
+
4175
+ const remainingAccounts = this.getRemainingAccounts({
4176
+ userAccounts: [],
4177
+ writableSpotMarketIndexes: [spotMarketIndex],
4178
+ readablePerpMarketIndex: [perpMarketIndex],
4179
+ });
4180
+
4181
+ const tokenProgram = this.getTokenProgramForSpotMarket(spotMarketAccount);
4182
+ return await this.program.instruction.depositIntoIsolatedPerpPosition(
4183
+ spotMarketIndex,
4184
+ perpMarketIndex,
4185
+ amount,
4186
+ {
4187
+ accounts: {
4188
+ state: await this.getStatePublicKey(),
4189
+ spotMarketVault: spotMarketAccount.vault,
4190
+ user: userAccountPublicKey,
4191
+ userStats: this.getUserStatsAccountPublicKey(),
4192
+ userTokenAccount: userTokenAccount,
4193
+ authority: this.wallet.publicKey,
4194
+ tokenProgram,
4195
+ },
4196
+ remainingAccounts,
4197
+ }
4198
+ );
4199
+ }
4200
+
4201
+ public async transferIsolatedPerpPositionDeposit(
4202
+ amount: BN,
4203
+ perpMarketIndex: number,
4204
+ subAccountId?: number,
4205
+ txParams?: TxParams,
4206
+ trySettle?: boolean
4207
+ ): Promise<TransactionSignature> {
4208
+ const ixs = [];
4209
+ const tokenAmountDeposited =
4210
+ this.getIsolatedPerpPositionTokenAmount(perpMarketIndex);
4211
+ const transferIx = await this.getTransferIsolatedPerpPositionDepositIx(
4212
+ amount,
4213
+ perpMarketIndex,
4214
+ subAccountId
4215
+ );
4216
+
4217
+ const needsToSettle =
4218
+ amount.gt(tokenAmountDeposited) || amount.eq(MIN_I64) || trySettle;
4219
+ if (needsToSettle) {
4220
+ const settleIx = await this.settleMultiplePNLsIx(
4221
+ await getUserAccountPublicKey(
4222
+ this.program.programId,
4223
+ this.authority,
4224
+ subAccountId ?? this.activeSubAccountId
4225
+ ),
4226
+ this.getUserAccount(subAccountId),
4227
+ [perpMarketIndex],
4228
+ SettlePnlMode.TRY_SETTLE
4229
+ );
4230
+ ixs.push(settleIx);
4231
+ }
4232
+
4233
+ ixs.push(transferIx);
4234
+
4235
+ const tx = await this.buildTransaction(ixs, txParams);
4236
+ const { txSig } = await this.sendTransaction(tx, [], {
4237
+ ...this.opts,
4238
+ skipPreflight: true,
4239
+ });
4240
+ return txSig;
4241
+ }
4242
+
4243
+ public async getTransferIsolatedPerpPositionDepositIx(
4244
+ amount: BN,
4245
+ perpMarketIndex: number,
4246
+ subAccountId?: number,
4247
+ noAmountBuffer?: boolean
4248
+ ): Promise<TransactionInstruction> {
4249
+ const userAccountPublicKey = await getUserAccountPublicKey(
4250
+ this.program.programId,
4251
+ this.authority,
4252
+ subAccountId ?? this.activeSubAccountId
4253
+ );
4254
+
4255
+ const perpMarketAccount = this.getPerpMarketAccount(perpMarketIndex);
4256
+ const spotMarketIndex = perpMarketAccount.quoteSpotMarketIndex;
4257
+ const spotMarketAccount = this.getSpotMarketAccount(spotMarketIndex);
4258
+ const user = await this.getUserAccount(subAccountId);
4259
+ const remainingAccounts = this.getRemainingAccounts({
4260
+ userAccounts: [user],
4261
+ writableSpotMarketIndexes: [spotMarketIndex],
4262
+ readablePerpMarketIndex: [perpMarketIndex],
4263
+ });
4264
+
4265
+ const amountWithBuffer =
4266
+ noAmountBuffer || amount.eq(MIN_I64)
4267
+ ? amount
4268
+ : amount.add(amount.div(new BN(200))); // .5% buffer
4269
+
4270
+ return await this.program.instruction.transferIsolatedPerpPositionDeposit(
4271
+ spotMarketIndex,
4272
+ perpMarketIndex,
4273
+ amountWithBuffer,
4274
+ {
4275
+ accounts: {
4276
+ state: await this.getStatePublicKey(),
4277
+ spotMarketVault: spotMarketAccount.vault,
4278
+ user: userAccountPublicKey,
4279
+ userStats: this.getUserStatsAccountPublicKey(),
4280
+ authority: this.wallet.publicKey,
4281
+ },
4282
+ remainingAccounts,
4283
+ }
4284
+ );
4285
+ }
4286
+
4287
+ public async withdrawFromIsolatedPerpPosition(
4288
+ amount: BN,
4289
+ perpMarketIndex: number,
4290
+ userTokenAccount: PublicKey,
4291
+ subAccountId?: number,
4292
+ txParams?: TxParams
4293
+ ): Promise<TransactionSignature> {
4294
+ const instructions =
4295
+ await this.getWithdrawFromIsolatedPerpPositionIxsBundle(
4296
+ amount,
4297
+ perpMarketIndex,
4298
+ subAccountId,
4299
+ userTokenAccount
4300
+ );
4301
+ const { txSig } = await this.sendTransaction(
4302
+ await this.buildTransaction(instructions, txParams)
4303
+ );
4304
+ return txSig;
4305
+ }
4306
+
4307
+ public async getWithdrawFromIsolatedPerpPositionIxsBundle(
4308
+ amount: BN,
4309
+ perpMarketIndex: number,
4310
+ subAccountId?: number,
4311
+ userTokenAccount?: PublicKey
4312
+ ): Promise<TransactionInstruction[]> {
4313
+ const userAccountPublicKey = await getUserAccountPublicKey(
4314
+ this.program.programId,
4315
+ this.authority,
4316
+ subAccountId ?? this.activeSubAccountId
4317
+ );
4318
+ const userAccount = this.getUserAccount(subAccountId);
4319
+
4320
+ const tokenAmountDeposited =
4321
+ this.getIsolatedPerpPositionTokenAmount(perpMarketIndex);
4322
+ const isolatedPositionUnrealizedPnl = calculateClaimablePnl(
4323
+ this.getPerpMarketAccount(perpMarketIndex),
4324
+ this.getSpotMarketAccount(
4325
+ this.getPerpMarketAccount(perpMarketIndex).quoteSpotMarketIndex
4326
+ ),
4327
+ userAccount.perpPositions.find((p) => p.marketIndex === perpMarketIndex),
4328
+ this.getOracleDataForSpotMarket(
4329
+ this.getPerpMarketAccount(perpMarketIndex).quoteSpotMarketIndex
4330
+ )
4331
+ );
4332
+
4333
+ const depositAmountPlusUnrealizedPnl = tokenAmountDeposited.add(
4334
+ isolatedPositionUnrealizedPnl
4335
+ );
4336
+
4337
+ const amountToWithdraw = amount.gt(depositAmountPlusUnrealizedPnl)
4338
+ ? MIN_I64 // min i64
4339
+ : amount;
4340
+ let associatedTokenAccount = userTokenAccount;
4341
+ if (!associatedTokenAccount) {
4342
+ const perpMarketAccount = this.getPerpMarketAccount(perpMarketIndex);
4343
+ const quoteSpotMarketIndex = perpMarketAccount.quoteSpotMarketIndex;
4344
+ associatedTokenAccount = await this.getAssociatedTokenAccount(
4345
+ quoteSpotMarketIndex
4346
+ );
4347
+ }
4348
+
4349
+ const withdrawIx = await this.getWithdrawFromIsolatedPerpPositionIx(
4350
+ amountToWithdraw,
4351
+ perpMarketIndex,
4352
+ associatedTokenAccount,
4353
+ subAccountId
4354
+ );
4355
+ const ixs = [withdrawIx];
4356
+
4357
+ const needsToSettle =
4358
+ amount.gt(tokenAmountDeposited) && isolatedPositionUnrealizedPnl.gt(ZERO);
4359
+ if (needsToSettle) {
4360
+ const settleIx = await this.settleMultiplePNLsIx(
4361
+ userAccountPublicKey,
4362
+ userAccount,
4363
+ [perpMarketIndex],
4364
+ SettlePnlMode.TRY_SETTLE
4365
+ );
4366
+ ixs.push(settleIx);
4367
+ }
4368
+ return ixs;
4369
+ }
4370
+
4371
+ public async getWithdrawFromIsolatedPerpPositionIx(
4372
+ amount: BN,
4373
+ perpMarketIndex: number,
4374
+ userTokenAccount: PublicKey,
4375
+ subAccountId?: number
4376
+ ): Promise<TransactionInstruction> {
4377
+ const userAccountPublicKey = await getUserAccountPublicKey(
4378
+ this.program.programId,
4379
+ this.authority,
4380
+ subAccountId ?? this.activeSubAccountId
4381
+ );
4382
+ const perpMarketAccount = this.getPerpMarketAccount(perpMarketIndex);
4383
+ const spotMarketIndex = perpMarketAccount.quoteSpotMarketIndex;
4384
+ const spotMarketAccount = this.getSpotMarketAccount(spotMarketIndex);
4385
+ const remainingAccounts = this.getRemainingAccounts({
4386
+ userAccounts: [this.getUserAccount(subAccountId)],
4387
+ writableSpotMarketIndexes: [spotMarketIndex],
4388
+ readablePerpMarketIndex: [perpMarketIndex],
4389
+ });
4390
+
4391
+ return await this.program.instruction.withdrawFromIsolatedPerpPosition(
4392
+ spotMarketIndex,
4393
+ perpMarketIndex,
4394
+ amount,
4395
+ {
4396
+ accounts: {
4397
+ state: await this.getStatePublicKey(),
4398
+ spotMarketVault: spotMarketAccount.vault,
4399
+ user: userAccountPublicKey,
4400
+ userStats: this.getUserStatsAccountPublicKey(),
4401
+ authority: this.wallet.publicKey,
4402
+ userTokenAccount: userTokenAccount,
4403
+ tokenProgram: this.getTokenProgramForSpotMarket(spotMarketAccount),
4404
+ driftSigner: this.getSignerPublicKey(),
4405
+ },
4406
+ remainingAccounts,
4407
+ }
4408
+ );
4409
+ }
4410
+
4085
4411
  public async updateSpotMarketCumulativeInterest(
4086
4412
  marketIndex: number,
4087
4413
  txParams?: TxParams
@@ -4225,7 +4551,6 @@ export class DriftClient {
4225
4551
  }
4226
4552
  );
4227
4553
  }
4228
-
4229
4554
  public async getRemovePerpLpSharesIx(
4230
4555
  marketIndex: number,
4231
4556
  sharesToBurn?: BN,
@@ -4382,7 +4707,8 @@ export class DriftClient {
4382
4707
  referrerInfo?: ReferrerInfo,
4383
4708
  cancelExistingOrders?: boolean,
4384
4709
  settlePnl?: boolean,
4385
- positionMaxLev?: number
4710
+ positionMaxLev?: number,
4711
+ isolatedPositionDepositAmount?: BN
4386
4712
  ): Promise<{
4387
4713
  cancelExistingOrdersTx?: Transaction | VersionedTransaction;
4388
4714
  settlePnlTx?: Transaction | VersionedTransaction;
@@ -4410,18 +4736,25 @@ export class DriftClient {
4410
4736
 
4411
4737
  const txKeys = Object.keys(ixPromisesForTxs);
4412
4738
 
4413
- const marketOrderTxIxs = positionMaxLev
4414
- ? this.getPlaceOrdersAndSetPositionMaxLevIx(
4415
- [orderParams, ...bracketOrdersParams],
4416
- positionMaxLev,
4417
- userAccount.subAccountId
4418
- )
4419
- : this.getPlaceOrdersIx(
4420
- [orderParams, ...bracketOrdersParams],
4421
- userAccount.subAccountId
4422
- );
4739
+ const preIxs: TransactionInstruction[] = await this.getPrePlaceOrderIxs(
4740
+ orderParams,
4741
+ userAccount,
4742
+ {
4743
+ positionMaxLev,
4744
+ isolatedPositionDepositAmount,
4745
+ }
4746
+ );
4423
4747
 
4424
- ixPromisesForTxs.marketOrderTx = marketOrderTxIxs;
4748
+ ixPromisesForTxs.marketOrderTx = (async () => {
4749
+ const placeOrdersIx = await this.getPlaceOrdersIx(
4750
+ [orderParams, ...bracketOrdersParams],
4751
+ userAccount.subAccountId
4752
+ );
4753
+ if (preIxs.length) {
4754
+ return [...preIxs, placeOrdersIx] as unknown as TransactionInstruction;
4755
+ }
4756
+ return placeOrdersIx;
4757
+ })();
4425
4758
 
4426
4759
  /* Cancel open orders in market if requested */
4427
4760
  if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) {
@@ -4539,12 +4872,32 @@ export class DriftClient {
4539
4872
  public async placePerpOrder(
4540
4873
  orderParams: OptionalOrderParams,
4541
4874
  txParams?: TxParams,
4542
- subAccountId?: number
4875
+ subAccountId?: number,
4876
+ isolatedPositionDepositAmount?: BN
4543
4877
  ): Promise<TransactionSignature> {
4878
+ const preIxs: TransactionInstruction[] = [];
4879
+ if (
4880
+ isolatedPositionDepositAmount?.gt?.(ZERO) &&
4881
+ this.isOrderIncreasingPosition(orderParams, subAccountId)
4882
+ ) {
4883
+ preIxs.push(
4884
+ await this.getTransferIsolatedPerpPositionDepositIx(
4885
+ isolatedPositionDepositAmount as BN,
4886
+ orderParams.marketIndex,
4887
+ subAccountId
4888
+ )
4889
+ );
4890
+ }
4891
+
4544
4892
  const { txSig, slot } = await this.sendTransaction(
4545
4893
  await this.buildTransaction(
4546
4894
  await this.getPlacePerpOrderIx(orderParams, subAccountId),
4547
- txParams
4895
+ txParams,
4896
+ undefined,
4897
+ undefined,
4898
+ undefined,
4899
+ undefined,
4900
+ preIxs
4548
4901
  ),
4549
4902
  [],
4550
4903
  this.opts
@@ -4732,13 +5085,31 @@ export class DriftClient {
4732
5085
  public async cancelOrder(
4733
5086
  orderId?: number,
4734
5087
  txParams?: TxParams,
4735
- subAccountId?: number
5088
+ subAccountId?: number,
5089
+ overrides?: { withdrawIsolatedDepositAmount?: BN }
4736
5090
  ): Promise<TransactionSignature> {
5091
+ const cancelIx = await this.getCancelOrderIx(orderId, subAccountId);
5092
+
5093
+ const instructions: TransactionInstruction[] = [cancelIx];
5094
+
5095
+ if (overrides?.withdrawIsolatedDepositAmount !== undefined) {
5096
+ const order = this.getOrder(orderId, subAccountId);
5097
+ const perpMarketIndex = order?.marketIndex;
5098
+ const withdrawAmount = overrides.withdrawIsolatedDepositAmount;
5099
+
5100
+ if (withdrawAmount.gt(ZERO)) {
5101
+ const withdrawIxs =
5102
+ await this.getWithdrawFromIsolatedPerpPositionIxsBundle(
5103
+ withdrawAmount,
5104
+ perpMarketIndex,
5105
+ subAccountId
5106
+ );
5107
+ instructions.push(...withdrawIxs);
5108
+ }
5109
+ }
5110
+
4737
5111
  const { txSig } = await this.sendTransaction(
4738
- await this.buildTransaction(
4739
- await this.getCancelOrderIx(orderId, subAccountId),
4740
- txParams
4741
- ),
5112
+ await this.buildTransaction(instructions, txParams),
4742
5113
  [],
4743
5114
  this.opts
4744
5115
  );
@@ -4972,7 +5343,8 @@ export class DriftClient {
4972
5343
  params: OrderParams[],
4973
5344
  txParams?: TxParams,
4974
5345
  subAccountId?: number,
4975
- optionalIxs?: TransactionInstruction[]
5346
+ optionalIxs?: TransactionInstruction[],
5347
+ isolatedPositionDepositAmount?: BN
4976
5348
  ): Promise<TransactionSignature> {
4977
5349
  const { txSig } = await this.sendTransaction(
4978
5350
  (
@@ -4980,7 +5352,8 @@ export class DriftClient {
4980
5352
  params,
4981
5353
  txParams,
4982
5354
  subAccountId,
4983
- optionalIxs
5355
+ optionalIxs,
5356
+ isolatedPositionDepositAmount
4984
5357
  )
4985
5358
  ).placeOrdersTx,
4986
5359
  [],
@@ -4994,10 +5367,29 @@ export class DriftClient {
4994
5367
  params: OrderParams[],
4995
5368
  txParams?: TxParams,
4996
5369
  subAccountId?: number,
4997
- optionalIxs?: TransactionInstruction[]
5370
+ optionalIxs?: TransactionInstruction[],
5371
+ isolatedPositionDepositAmount?: BN
4998
5372
  ) {
4999
5373
  const lookupTableAccounts = await this.fetchAllLookupTableAccounts();
5000
5374
 
5375
+ const preIxs: TransactionInstruction[] = [];
5376
+ if (params?.length === 1) {
5377
+ const p = params[0];
5378
+ if (
5379
+ isVariant(p.marketType, 'perp') &&
5380
+ isolatedPositionDepositAmount?.gt?.(ZERO) &&
5381
+ this.isOrderIncreasingPosition(p, subAccountId)
5382
+ ) {
5383
+ preIxs.push(
5384
+ await this.getTransferIsolatedPerpPositionDepositIx(
5385
+ isolatedPositionDepositAmount as BN,
5386
+ p.marketIndex,
5387
+ subAccountId
5388
+ )
5389
+ );
5390
+ }
5391
+ }
5392
+
5001
5393
  const tx = await this.buildTransaction(
5002
5394
  await this.getPlaceOrdersIx(params, subAccountId),
5003
5395
  txParams,
@@ -5005,14 +5397,13 @@ export class DriftClient {
5005
5397
  lookupTableAccounts,
5006
5398
  undefined,
5007
5399
  undefined,
5008
- optionalIxs
5400
+ [...preIxs, ...(optionalIxs ?? [])]
5009
5401
  );
5010
5402
 
5011
5403
  return {
5012
5404
  placeOrdersTx: tx,
5013
5405
  };
5014
5406
  }
5015
-
5016
5407
  public async getPlaceOrdersIx(
5017
5408
  params: OptionalOrderParams[],
5018
5409
  subAccountId?: number,
@@ -5121,8 +5512,7 @@ export class DriftClient {
5121
5512
  const marginRatio = Math.floor(
5122
5513
  (1 / positionMaxLev) * MARGIN_PRECISION.toNumber()
5123
5514
  );
5124
-
5125
- // TODO: Handle multiple markets?
5515
+ // Keep existing behavior but note: prefer using getPostPlaceOrderIxs path
5126
5516
  const setPositionMaxLevIxs =
5127
5517
  await this.getUpdateUserPerpPositionCustomMarginRatioIx(
5128
5518
  readablePerpMarketIndex[0],
@@ -6840,7 +7230,6 @@ export class DriftClient {
6840
7230
  this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot);
6841
7231
  return txSig;
6842
7232
  }
6843
-
6844
7233
  public async preparePlaceAndTakePerpOrderWithAdditionalOrders(
6845
7234
  orderParams: OptionalOrderParams,
6846
7235
  makerInfo?: MakerInfo | MakerInfo[],
@@ -6852,7 +7241,8 @@ export class DriftClient {
6852
7241
  settlePnl?: boolean,
6853
7242
  exitEarlyIfSimFails?: boolean,
6854
7243
  auctionDurationPercentage?: number,
6855
- optionalIxs?: TransactionInstruction[]
7244
+ optionalIxs?: TransactionInstruction[],
7245
+ isolatedPositionDepositAmount?: BN
6856
7246
  ): Promise<{
6857
7247
  placeAndTakeTx: Transaction | VersionedTransaction;
6858
7248
  cancelExistingOrdersTx: Transaction | VersionedTransaction;
@@ -6886,6 +7276,20 @@ export class DriftClient {
6886
7276
  subAccountId
6887
7277
  );
6888
7278
 
7279
+ if (
7280
+ isVariant(orderParams.marketType, 'perp') &&
7281
+ isolatedPositionDepositAmount?.gt?.(ZERO) &&
7282
+ this.isOrderIncreasingPosition(orderParams, subAccountId)
7283
+ ) {
7284
+ placeAndTakeIxs.push(
7285
+ await this.getTransferIsolatedPerpPositionDepositIx(
7286
+ isolatedPositionDepositAmount as BN,
7287
+ orderParams.marketIndex,
7288
+ subAccountId
7289
+ )
7290
+ );
7291
+ }
7292
+
6889
7293
  placeAndTakeIxs.push(placeAndTakeIx);
6890
7294
 
6891
7295
  if (bracketOrdersParams.length > 0) {
@@ -6896,6 +7300,11 @@ export class DriftClient {
6896
7300
  placeAndTakeIxs.push(bracketOrdersIx);
6897
7301
  }
6898
7302
 
7303
+ // Optional extra ixs can be appended at the front
7304
+ if (optionalIxs?.length) {
7305
+ placeAndTakeIxs.unshift(...optionalIxs);
7306
+ }
7307
+
6899
7308
  const shouldUseSimulationComputeUnits =
6900
7309
  txParams?.useSimulatedComputeUnits;
6901
7310
  const shouldExitIfSimulationFails = exitEarlyIfSimFails;
@@ -7703,7 +8112,6 @@ export class DriftClient {
7703
8112
  this.spotMarketLastSlotCache.set(QUOTE_SPOT_MARKET_INDEX, slot);
7704
8113
  return txSig;
7705
8114
  }
7706
-
7707
8115
  public async getPlaceAndTakeSpotOrderIx(
7708
8116
  orderParams: OptionalOrderParams,
7709
8117
  fulfillmentConfig?: SerumV3FulfillmentConfigAccount,
@@ -8166,7 +8574,6 @@ export class DriftClient {
8166
8574
  bitFlags?: number;
8167
8575
  policy?: ModifyOrderPolicy;
8168
8576
  maxTs?: BN;
8169
- txParams?: TxParams;
8170
8577
  },
8171
8578
  subAccountId?: number
8172
8579
  ): Promise<TransactionInstruction> {
@@ -8692,7 +9099,6 @@ export class DriftClient {
8692
9099
  this.perpMarketLastSlotCache.set(marketIndex, slot);
8693
9100
  return txSig;
8694
9101
  }
8695
-
8696
9102
  public async getLiquidatePerpIx(
8697
9103
  userAccountPublicKey: PublicKey,
8698
9104
  userAccount: UserAccount,
@@ -9483,7 +9889,6 @@ export class DriftClient {
9483
9889
  }
9484
9890
  );
9485
9891
  }
9486
-
9487
9892
  public async resolveSpotBankruptcy(
9488
9893
  userAccountPublicKey: PublicKey,
9489
9894
  userAccount: UserAccount,
@@ -10320,7 +10725,6 @@ export class DriftClient {
10320
10725
  const { txSig } = await this.sendTransaction(tx, [], this.opts);
10321
10726
  return txSig;
10322
10727
  }
10323
-
10324
10728
  public async getSettleRevenueToInsuranceFundIx(
10325
10729
  spotMarketIndex: number
10326
10730
  ): Promise<TransactionInstruction> {
@@ -11124,7 +11528,6 @@ export class DriftClient {
11124
11528
  );
11125
11529
  return config as ProtectedMakerModeConfig;
11126
11530
  }
11127
-
11128
11531
  public async updateUserProtectedMakerOrders(
11129
11532
  subAccountId: number,
11130
11533
  protectedOrders: boolean,
@@ -12518,4 +12921,24 @@ export class DriftClient {
12518
12921
  forceVersionedTransaction,
12519
12922
  });
12520
12923
  }
12924
+
12925
+ isOrderIncreasingPosition(
12926
+ orderParams: OptionalOrderParams,
12927
+ subAccountId: number
12928
+ ): boolean {
12929
+ const userAccount = this.getUserAccount(subAccountId);
12930
+ const perpPosition = userAccount.perpPositions.find(
12931
+ (p) => p.marketIndex === orderParams.marketIndex
12932
+ );
12933
+ if (!perpPosition) return true;
12934
+
12935
+ const currentBase = perpPosition.baseAssetAmount;
12936
+ if (currentBase.eq(ZERO)) return true;
12937
+
12938
+ const orderBaseAmount = isVariant(orderParams.direction, 'long')
12939
+ ? orderParams.baseAssetAmount
12940
+ : orderParams.baseAssetAmount.neg();
12941
+
12942
+ return currentBase.add(orderBaseAmount).abs().gt(currentBase.abs());
12943
+ }
12521
12944
  }