From 25c1d2bd901d4daf100ab66fbe81400b1d997d0e Mon Sep 17 00:00:00 2001 From: Lukas deConantsesznak Date: Tue, 9 Sep 2025 12:10:48 -0600 Subject: [PATCH 1/9] feat: add margin ratio ix to open orders + swift prop --- sdk/src/driftClient.ts | 74 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index dd8e680d0b..1963506dd8 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -4018,7 +4018,8 @@ export class DriftClient { bracketOrdersParams = new Array(), referrerInfo?: ReferrerInfo, cancelExistingOrders?: boolean, - settlePnl?: boolean + settlePnl?: boolean, + positionMaxLev?: number ): Promise<{ cancelExistingOrdersTx?: Transaction | VersionedTransaction; settlePnlTx?: Transaction | VersionedTransaction; @@ -4034,7 +4035,7 @@ export class DriftClient { const marketIndex = orderParams.marketIndex; const orderId = userAccount.nextOrderId; - const ixPromisesForTxs: Record> = { + const ixPromisesForTxs: Record> = { cancelExistingOrdersTx: undefined, settlePnlTx: undefined, fillTx: undefined, @@ -4043,11 +4044,17 @@ export class DriftClient { const txKeys = Object.keys(ixPromisesForTxs); - ixPromisesForTxs.marketOrderTx = this.getPlaceOrdersIx( + const marketOrderTxIxs = positionMaxLev ? this.getPlaceOrdersAndSetPositionMaxLevIx( + [orderParams, ...bracketOrdersParams], + positionMaxLev, + userAccount.subAccountId + ) : this.getPlaceOrdersIx( [orderParams, ...bracketOrdersParams], userAccount.subAccountId ); + ixPromisesForTxs.marketOrderTx = marketOrderTxIxs; + /* Cancel open orders in market if requested */ if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { ixPromisesForTxs.cancelExistingOrdersTx = this.getCancelOrdersIx( @@ -4087,7 +4094,7 @@ export class DriftClient { const ixsMap = ixs.reduce((acc, ix, i) => { acc[txKeys[i]] = ix; return acc; - }, {}) as MappedRecord; + }, {}) as MappedRecord; const txsMap = (await this.buildTransactionsMap( ixsMap, @@ -4671,6 +4678,65 @@ export class DriftClient { }); } + public async getPlaceOrdersAndSetPositionMaxLevIx( + params: OptionalOrderParams[], + positionMaxLev: number, + subAccountId?: number + ): Promise { + const user = await this.getUserAccountPublicKey(subAccountId); + + const readablePerpMarketIndex: number[] = []; + const readableSpotMarketIndexes: number[] = []; + for (const param of params) { + if (!param.marketType) { + throw new Error('must set param.marketType'); + } + if (isVariant(param.marketType, 'perp')) { + readablePerpMarketIndex.push(param.marketIndex); + } else { + readableSpotMarketIndexes.push(param.marketIndex); + } + } + + const remainingAccounts = this.getRemainingAccounts({ + userAccounts: [this.getUserAccount(subAccountId)], + readablePerpMarketIndex, + readableSpotMarketIndexes, + useMarketLastSlotCache: true, + }); + + for (const param of params) { + if (isUpdateHighLeverageMode(param.bitFlags)) { + remainingAccounts.push({ + pubkey: getHighLeverageModeConfigPublicKey(this.program.programId), + isWritable: true, + isSigner: false, + }); + } + } + + const formattedParams = params.map((item) => getOrderParams(item)); + + const placeOrdersIxs = await this.program.instruction.placeOrders(formattedParams, { + accounts: { + state: await this.getStatePublicKey(), + user, + userStats: this.getUserStatsAccountPublicKey(), + authority: this.wallet.publicKey, + }, + remainingAccounts, + }); + + // TODO: Handle multiple markets? + const setPositionMaxLevIxs = await this.getUpdateUserPerpPositionCustomMarginRatioIx( + readablePerpMarketIndex[0], + 1 / positionMaxLev, + subAccountId + ); + + return [placeOrdersIxs, setPositionMaxLevIxs]; + } + public async fillPerpOrder( userAccountPublicKey: PublicKey, user: UserAccount, From eee9676998360f0737d855ebc15d8101f25b0019 Mon Sep 17 00:00:00 2001 From: Lukas deConantsesznak Date: Tue, 9 Sep 2025 21:14:52 -0600 Subject: [PATCH 2/9] fix: bug with max lev available calculation --- sdk/src/user.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 08494f71f8..274fcf5dd2 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -484,15 +484,11 @@ export class User { enterHighLeverageMode = undefined, perpPosition?: PerpPosition ): BN { - const userCustomMargin = Math.max( - perpPosition?.maxMarginRatio ?? 0, - this.getUserAccount().maxMarginRatio - ); const marginRatio = calculateMarketMarginRatio( this.driftClient.getPerpMarketAccount(marketIndex), baseAssetAmount, 'Initial', - userCustomMargin, + this.getUserAccount().maxMarginRatio, enterHighLeverageMode || this.isHighLeverageMode('Initial') ); @@ -2169,10 +2165,7 @@ export class User { ); } - const userCustomMargin = Math.max( - perpPosition.maxMarginRatio, - this.getUserAccount().maxMarginRatio - ); + const userCustomMargin = this.getUserAccount().maxMarginRatio; const marginRatio = calculateMarketMarginRatio( market, baseAssetAmount.abs(), From 3999959d3e163a6143b1bb4f7efc45a52810b9f2 Mon Sep 17 00:00:00 2001 From: Lukas deConantsesznak Date: Thu, 11 Sep 2025 14:16:45 -0600 Subject: [PATCH 3/9] fix: bug with swift msg encoding + margin ratio --- sdk/src/driftClient.ts | 68 ++++++++++++++++++++++++++---------------- sdk/src/user.ts | 45 +++++++++++++++++++++------- 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 1963506dd8..0048552d51 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -4035,7 +4035,10 @@ export class DriftClient { const marketIndex = orderParams.marketIndex; const orderId = userAccount.nextOrderId; - const ixPromisesForTxs: Record> = { + const ixPromisesForTxs: Record< + TxKeys, + Promise + > = { cancelExistingOrdersTx: undefined, settlePnlTx: undefined, fillTx: undefined, @@ -4044,14 +4047,16 @@ export class DriftClient { const txKeys = Object.keys(ixPromisesForTxs); - const marketOrderTxIxs = positionMaxLev ? this.getPlaceOrdersAndSetPositionMaxLevIx( - [orderParams, ...bracketOrdersParams], - positionMaxLev, - userAccount.subAccountId - ) : this.getPlaceOrdersIx( - [orderParams, ...bracketOrdersParams], - userAccount.subAccountId - ); + const marketOrderTxIxs = positionMaxLev + ? this.getPlaceOrdersAndSetPositionMaxLevIx( + [orderParams, ...bracketOrdersParams], + positionMaxLev, + userAccount.subAccountId + ) + : this.getPlaceOrdersIx( + [orderParams, ...bracketOrdersParams], + userAccount.subAccountId + ); ixPromisesForTxs.marketOrderTx = marketOrderTxIxs; @@ -4094,7 +4099,10 @@ export class DriftClient { const ixsMap = ixs.reduce((acc, ix, i) => { acc[txKeys[i]] = ix; return acc; - }, {}) as MappedRecord; + }, {}) as MappedRecord< + typeof ixPromisesForTxs, + TransactionInstruction | TransactionInstruction[] + >; const txsMap = (await this.buildTransactionsMap( ixsMap, @@ -4717,22 +4725,26 @@ export class DriftClient { const formattedParams = params.map((item) => getOrderParams(item)); - const placeOrdersIxs = await this.program.instruction.placeOrders(formattedParams, { - accounts: { - state: await this.getStatePublicKey(), - user, - userStats: this.getUserStatsAccountPublicKey(), - authority: this.wallet.publicKey, - }, - remainingAccounts, - }); + const placeOrdersIxs = await this.program.instruction.placeOrders( + formattedParams, + { + accounts: { + state: await this.getStatePublicKey(), + user, + userStats: this.getUserStatsAccountPublicKey(), + authority: this.wallet.publicKey, + }, + remainingAccounts, + } + ); // TODO: Handle multiple markets? - const setPositionMaxLevIxs = await this.getUpdateUserPerpPositionCustomMarginRatioIx( - readablePerpMarketIndex[0], - 1 / positionMaxLev, - subAccountId - ); + const setPositionMaxLevIxs = + await this.getUpdateUserPerpPositionCustomMarginRatioIx( + readablePerpMarketIndex[0], + 1 / positionMaxLev, + subAccountId + ); return [placeOrdersIxs, setPositionMaxLevIxs]; } @@ -4893,8 +4905,9 @@ export class DriftClient { subAccountId?: number ): Promise { const { txSig, slot } = await this.sendTransaction( - (await this.preparePlaceSpotOrderTx(orderParams, txParams, subAccountId)) - .placeSpotOrderTx, + ( + await this.preparePlaceSpotOrderTx(orderParams, txParams, subAccountId) + ).placeSpotOrderTx, [], this.opts, false @@ -6554,6 +6567,9 @@ export class DriftClient { | SignedMsgOrderParamsDelegateMessage, delegateSigner?: boolean ): Buffer { + if (orderParamsMessage.maxMarginRatio === undefined) { + orderParamsMessage.maxMarginRatio = null; + } const anchorIxName = delegateSigner ? 'global' + ':' + 'SignedMsgOrderParamsDelegateMessage' : 'global' + ':' + 'SignedMsgOrderParamsMessage'; diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 274fcf5dd2..9e314d9716 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -473,7 +473,7 @@ export class User { freeCollateral, worstCaseBaseAssetAmount, enterHighLeverageMode, - perpPosition + perpPosition.maxMarginRatio ); } @@ -482,13 +482,17 @@ export class User { freeCollateral: BN, baseAssetAmount: BN, enterHighLeverageMode = undefined, - perpPosition?: PerpPosition + perpMarketMaxMarginRatio = undefined ): BN { + const maxMarginRatio = Math.max( + perpMarketMaxMarginRatio, + this.getUserAccount().maxMarginRatio + ); const marginRatio = calculateMarketMarginRatio( this.driftClient.getPerpMarketAccount(marketIndex), baseAssetAmount, 'Initial', - this.getUserAccount().maxMarginRatio, + maxMarginRatio, enterHighLeverageMode || this.isHighLeverageMode('Initial') ); @@ -1023,6 +1027,7 @@ export class User { if (marketIndex === undefined || marketIndex === QUOTE_SPOT_MARKET_INDEX) { if (netQuoteValue.gt(ZERO)) { + totalAssetValue = totalAssetValue.add(netQuoteValue); } else { totalLiabilityValue = totalLiabilityValue.add(netQuoteValue.abs()); @@ -1243,7 +1248,10 @@ export class User { } if (marginCategory) { - const userCustomMargin = this.getUserAccount().maxMarginRatio; + const userCustomMargin = Math.max( + perpPosition.maxMarginRatio, + this.getUserAccount().maxMarginRatio + ); let marginRatio = new BN( calculateMarketMarginRatio( market, @@ -2165,7 +2173,10 @@ export class User { ); } - const userCustomMargin = this.getUserAccount().maxMarginRatio; + const userCustomMargin = Math.max( + perpPosition.maxMarginRatio, + this.getUserAccount().maxMarginRatio + ); const marginRatio = calculateMarketMarginRatio( market, baseAssetAmount.abs(), @@ -2338,13 +2349,18 @@ export class User { public getMarginUSDCRequiredForTrade( targetMarketIndex: number, baseSize: BN, - estEntryPrice?: BN + estEntryPrice?: BN, + perpMarketMaxMarginRatio?: number ): BN { + const maxMarginRatio = Math.max( + perpMarketMaxMarginRatio, + this.getUserAccount().maxMarginRatio + ); return calculateMarginUSDCRequiredForTrade( this.driftClient, targetMarketIndex, baseSize, - this.getUserAccount().maxMarginRatio, + maxMarginRatio, undefined, estEntryPrice ); @@ -2353,14 +2369,19 @@ export class User { public getCollateralDepositRequiredForTrade( targetMarketIndex: number, baseSize: BN, - collateralIndex: number + collateralIndex: number, + perpMarketMaxMarginRatio?: number ): BN { + const maxMarginRatio = Math.max( + perpMarketMaxMarginRatio, + this.getUserAccount().maxMarginRatio + ); return calculateCollateralDepositRequiredForTrade( this.driftClient, targetMarketIndex, baseSize, collateralIndex, - this.getUserAccount().maxMarginRatio, + maxMarginRatio, false // assume user cant be high leverage if they havent created user account ? ); } @@ -2444,8 +2465,9 @@ export class User { const marginRequirement = this.getInitialMarginRequirement( enterHighLeverageMode ); + const marginRatio = Math.max(currentPosition.maxMarginRatio, this.getUserAccount().maxMarginRatio); const marginFreedByClosing = perpLiabilityValue - .mul(new BN(market.marginRatioInitial)) + .mul(new BN(marginRatio)) .div(MARGIN_PRECISION); const marginRequirementAfterClosing = marginRequirement.sub(marginFreedByClosing); @@ -2461,7 +2483,8 @@ export class User { this.getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount( targetMarketIndex, freeCollateralAfterClose, - ZERO + ZERO, + currentPosition.maxMarginRatio ); oppositeSideTradeSize = perpLiabilityValue; tradeSize = buyingPowerAfterClose; From ef5ed00f77b920f5bb2bfc3ac2c49e92b435b586 Mon Sep 17 00:00:00 2001 From: Lukas deConantsesznak Date: Thu, 11 Sep 2025 16:24:04 -0600 Subject: [PATCH 4/9] feat: re-add types for swift non-optional --- sdk/src/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/src/types.ts b/sdk/src/types.ts index 369520dabf..bb13ebd800 100644 --- a/sdk/src/types.ts +++ b/sdk/src/types.ts @@ -1247,6 +1247,7 @@ export type SignedMsgOrderParamsMessage = { uuid: Uint8Array; takeProfitOrderParams: SignedMsgTriggerOrderParams | null; stopLossOrderParams: SignedMsgTriggerOrderParams | null; + maxMarginRatio: number | null; }; export type SignedMsgOrderParamsDelegateMessage = { @@ -1256,6 +1257,7 @@ export type SignedMsgOrderParamsDelegateMessage = { takerPubkey: PublicKey; takeProfitOrderParams: SignedMsgTriggerOrderParams | null; stopLossOrderParams: SignedMsgTriggerOrderParams | null; + maxMarginRatio: number | null; }; export type SignedMsgTriggerOrderParams = { From 54af3ae8e78feb1b888e2eb1ea02908aec4aaaba Mon Sep 17 00:00:00 2001 From: Lukas deConantsesznak Date: Thu, 11 Sep 2025 16:28:28 -0600 Subject: [PATCH 5/9] rm: unneeded undefined check on swift maxMarginRation --- sdk/src/driftClient.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 0048552d51..c654adef31 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -6567,9 +6567,6 @@ export class DriftClient { | SignedMsgOrderParamsDelegateMessage, delegateSigner?: boolean ): Buffer { - if (orderParamsMessage.maxMarginRatio === undefined) { - orderParamsMessage.maxMarginRatio = null; - } const anchorIxName = delegateSigner ? 'global' + ':' + 'SignedMsgOrderParamsDelegateMessage' : 'global' + ':' + 'SignedMsgOrderParamsMessage'; From 27588b2dfe8b5da8886dbd001ff36a14d7171692 Mon Sep 17 00:00:00 2001 From: Nick Caradonna Date: Fri, 19 Sep 2025 13:23:04 -0400 Subject: [PATCH 6/9] allow enter HLM on position margin ratio update --- sdk/src/driftClient.ts | 18 ++++++++++++------ sdk/src/user.ts | 6 ++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 30c39366e0..0036c46510 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -1570,14 +1570,21 @@ export class DriftClient { perpMarketIndex: number, marginRatio: number, subAccountId = 0, - txParams?: TxParams + txParams?: TxParams, + enterHighLeverageMode?: boolean ): Promise { - const ix = await this.getUpdateUserPerpPositionCustomMarginRatioIx( + const ixs = []; + if (enterHighLeverageMode) { + const enableIx = await this.getEnableHighLeverageModeIx(subAccountId); + ixs.push(enableIx); + } + const updateIx = await this.getUpdateUserPerpPositionCustomMarginRatioIx( perpMarketIndex, marginRatio, subAccountId ); - const tx = await this.buildTransaction(ix, txParams ?? this.txParams); + ixs.push(updateIx); + const tx = await this.buildTransaction(ixs, txParams ?? this.txParams); const { txSig } = await this.sendTransaction(tx, [], this.opts); return txSig; } @@ -4958,9 +4965,8 @@ export class DriftClient { subAccountId?: number ): Promise { const { txSig, slot } = await this.sendTransaction( - ( - await this.preparePlaceSpotOrderTx(orderParams, txParams, subAccountId) - ).placeSpotOrderTx, + (await this.preparePlaceSpotOrderTx(orderParams, txParams, subAccountId)) + .placeSpotOrderTx, [], this.opts, false diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 9e314d9716..c70e23013f 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -1027,7 +1027,6 @@ export class User { if (marketIndex === undefined || marketIndex === QUOTE_SPOT_MARKET_INDEX) { if (netQuoteValue.gt(ZERO)) { - totalAssetValue = totalAssetValue.add(netQuoteValue); } else { totalLiabilityValue = totalLiabilityValue.add(netQuoteValue.abs()); @@ -2465,7 +2464,10 @@ export class User { const marginRequirement = this.getInitialMarginRequirement( enterHighLeverageMode ); - const marginRatio = Math.max(currentPosition.maxMarginRatio, this.getUserAccount().maxMarginRatio); + const marginRatio = Math.max( + currentPosition.maxMarginRatio, + this.getUserAccount().maxMarginRatio + ); const marginFreedByClosing = perpLiabilityValue .mul(new BN(marginRatio)) .div(MARGIN_PRECISION); From 68ee19398a110935c1ffa83b1616f38ff7be61a3 Mon Sep 17 00:00:00 2001 From: Nick Caradonna Date: Mon, 22 Sep 2025 22:07:08 -0400 Subject: [PATCH 7/9] fix margin ratio calc --- sdk/src/driftClient.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 134d845b5c..721bca4bca 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -124,6 +124,7 @@ import { TxSender, TxSigAndSlot } from './tx/types'; import { BASE_PRECISION, GOV_SPOT_MARKET_INDEX, + MARGIN_PRECISION, ONE, PERCENTAGE_PRECISION, PRICE_PRECISION, @@ -4798,11 +4799,15 @@ export class DriftClient { } ); + const marginRatio = Math.floor( + (1 / positionMaxLev) * MARGIN_PRECISION.toNumber() + ); + // TODO: Handle multiple markets? const setPositionMaxLevIxs = await this.getUpdateUserPerpPositionCustomMarginRatioIx( readablePerpMarketIndex[0], - 1 / positionMaxLev, + marginRatio, subAccountId ); From 535ba240591b929120cf35c0566f0363accb4484 Mon Sep 17 00:00:00 2001 From: Nick Caradonna Date: Thu, 25 Sep 2025 17:54:49 -0400 Subject: [PATCH 8/9] updates --- sdk/src/driftClient.ts | 9 +++++++-- sdk/src/user.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 721bca4bca..b1d139e6c6 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -1546,11 +1546,16 @@ export class DriftClient { ): Promise { const userAccountPublicKey = getUserAccountPublicKeySync( this.program.programId, - this.wallet.publicKey, + this.authority, subAccountId ); - await this.addUser(subAccountId, this.wallet.publicKey); + await this.addUser(subAccountId, this.authority); + + console.log('userAccountPublicKey', userAccountPublicKey.toString()); + console.log('authority', this.authority.toString()); + console.log('subAccountId', subAccountId); + console.log('wallet.publicKey', this.wallet.publicKey.toString()); const ix = this.program.instruction.updateUserPerpPositionCustomMarginRatio( subAccountId, diff --git a/sdk/src/user.ts b/sdk/src/user.ts index c70e23013f..a36820306d 100644 --- a/sdk/src/user.ts +++ b/sdk/src/user.ts @@ -449,7 +449,8 @@ export class User { public getPerpBuyingPower( marketIndex: number, collateralBuffer = ZERO, - enterHighLeverageMode = undefined + enterHighLeverageMode = undefined, + maxMarginRatio = undefined ): BN { const perpPosition = this.getPerpPositionOrEmpty(marketIndex); @@ -473,7 +474,7 @@ export class User { freeCollateral, worstCaseBaseAssetAmount, enterHighLeverageMode, - perpPosition.maxMarginRatio + maxMarginRatio || perpPosition.maxMarginRatio ); } @@ -2398,7 +2399,8 @@ export class User { targetMarketIndex: number, tradeSide: PositionDirection, isLp = false, - enterHighLeverageMode = undefined + enterHighLeverageMode = undefined, + maxMarginRatio = undefined ): { tradeSize: BN; oppositeSideTradeSize: BN } { let tradeSize = ZERO; let oppositeSideTradeSize = ZERO; @@ -2437,7 +2439,8 @@ export class User { const maxPositionSize = this.getPerpBuyingPower( targetMarketIndex, lpBuffer, - enterHighLeverageMode + enterHighLeverageMode, + maxMarginRatio ); if (maxPositionSize.gte(ZERO)) { From 488e491139b03354719484f53f34dbc02af50a49 Mon Sep 17 00:00:00 2001 From: Nick Caradonna Date: Mon, 29 Sep 2025 15:37:39 -0400 Subject: [PATCH 9/9] rm logs --- sdk/src/driftClient.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index b1d139e6c6..e5fc4ae0ba 100644 --- a/sdk/src/driftClient.ts +++ b/sdk/src/driftClient.ts @@ -1552,11 +1552,6 @@ export class DriftClient { await this.addUser(subAccountId, this.authority); - console.log('userAccountPublicKey', userAccountPublicKey.toString()); - console.log('authority', this.authority.toString()); - console.log('subAccountId', subAccountId); - console.log('wallet.publicKey', this.wallet.publicKey.toString()); - const ix = this.program.instruction.updateUserPerpPositionCustomMarginRatio( subAccountId, perpMarketIndex,