diff --git a/sdk/src/driftClient.ts b/sdk/src/driftClient.ts index 4d39ae6d0f..e5fc4ae0ba 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, @@ -1545,11 +1546,11 @@ 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); const ix = this.program.instruction.updateUserPerpPositionCustomMarginRatio( subAccountId, @@ -1570,14 +1571,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; } @@ -4071,7 +4079,8 @@ export class DriftClient { bracketOrdersParams = new Array(), referrerInfo?: ReferrerInfo, cancelExistingOrders?: boolean, - settlePnl?: boolean + settlePnl?: boolean, + positionMaxLev?: number ): Promise<{ cancelExistingOrdersTx?: Transaction | VersionedTransaction; settlePnlTx?: Transaction | VersionedTransaction; @@ -4087,7 +4096,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, @@ -4096,10 +4108,18 @@ export class DriftClient { const txKeys = Object.keys(ixPromisesForTxs); - ixPromisesForTxs.marketOrderTx = 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; /* Cancel open orders in market if requested */ if (cancelExistingOrders && isVariant(orderParams.marketType, 'perp')) { @@ -4140,7 +4160,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, @@ -4724,6 +4747,73 @@ 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, + } + ); + + const marginRatio = Math.floor( + (1 / positionMaxLev) * MARGIN_PRECISION.toNumber() + ); + + // TODO: Handle multiple markets? + const setPositionMaxLevIxs = + await this.getUpdateUserPerpPositionCustomMarginRatioIx( + readablePerpMarketIndex[0], + marginRatio, + subAccountId + ); + + return [placeOrdersIxs, setPositionMaxLevIxs]; + } + public async fillPerpOrder( userAccountPublicKey: PublicKey, user: UserAccount, diff --git a/sdk/src/user.ts b/sdk/src/user.ts index 08494f71f8..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 || perpPosition.maxMarginRatio ); } @@ -482,17 +483,17 @@ export class User { freeCollateral: BN, baseAssetAmount: BN, enterHighLeverageMode = undefined, - perpPosition?: PerpPosition + perpMarketMaxMarginRatio = undefined ): BN { - const userCustomMargin = Math.max( - perpPosition?.maxMarginRatio ?? 0, + const maxMarginRatio = Math.max( + perpMarketMaxMarginRatio, this.getUserAccount().maxMarginRatio ); const marginRatio = calculateMarketMarginRatio( this.driftClient.getPerpMarketAccount(marketIndex), baseAssetAmount, 'Initial', - userCustomMargin, + maxMarginRatio, enterHighLeverageMode || this.isHighLeverageMode('Initial') ); @@ -1247,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, @@ -2345,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 ); @@ -2360,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 ? ); } @@ -2385,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; @@ -2424,7 +2439,8 @@ export class User { const maxPositionSize = this.getPerpBuyingPower( targetMarketIndex, lpBuffer, - enterHighLeverageMode + enterHighLeverageMode, + maxMarginRatio ); if (maxPositionSize.gte(ZERO)) { @@ -2451,8 +2467,12 @@ 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); @@ -2468,7 +2488,8 @@ export class User { this.getPerpBuyingPowerFromFreeCollateralAndBaseAssetAmount( targetMarketIndex, freeCollateralAfterClose, - ZERO + ZERO, + currentPosition.maxMarginRatio ); oppositeSideTradeSize = perpLiabilityValue; tradeSize = buyingPowerAfterClose; diff --git a/sdk/tests/dlob/helpers.ts b/sdk/tests/dlob/helpers.ts index d1b68abe8c..d682f5e757 100644 --- a/sdk/tests/dlob/helpers.ts +++ b/sdk/tests/dlob/helpers.ts @@ -44,6 +44,7 @@ export const mockPerpPosition: PerpPosition = { lastBaseAssetAmountPerLp: new BN(0), lastQuoteAssetAmountPerLp: new BN(0), perLpBase: 0, + maxMarginRatio: 1, }; export const mockAMM: AMM = {