diff --git a/common-ts/src/common-ui-utils/user.ts b/common-ts/src/common-ui-utils/user.ts index 89ed6143..d9752303 100644 --- a/common-ts/src/common-ui-utils/user.ts +++ b/common-ts/src/common-ui-utils/user.ts @@ -154,7 +154,18 @@ const getOpenPositionData = ( markPrice, entryPrice, exitPrice: estExitPrice, - liqPrice: user.liquidationPrice(position.marketIndex, ZERO), + liqPrice: user.isPerpPositionIsolated(position) + ? user.liquidationPrice( + position.marketIndex, + ZERO, + undefined, + undefined, + undefined, + undefined, + undefined, + 'Isolated' + ) + : user.liquidationPrice(position.marketIndex, ZERO), quoteAssetNotionalAmount: position.quoteAssetAmount, quoteEntryAmount: position.quoteEntryAmount, quoteBreakEvenAmount: position.quoteBreakEvenAmount, diff --git a/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/index.ts b/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/index.ts index 0eb7521f..eca77548 100644 --- a/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/index.ts +++ b/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/index.ts @@ -467,6 +467,7 @@ export class DriftOperations { params.orderConfig.optionalAuctionParamsInputs, builderParams: params.builderParams, positionMaxLeverage: params.positionMaxLeverage, + isolatedPositionDeposit: params.isolatedPositionDeposit, }); return swiftOrderResult; @@ -484,6 +485,7 @@ export class DriftOperations { dlobServerHttpUrl: this.dlobServerHttpUrl, useSwift: false, positionMaxLeverage: params.positionMaxLeverage, + isolatedPositionDeposit: params.isolatedPositionDeposit, }); const { txSig } = await this.driftClient.sendTransaction(result); @@ -527,6 +529,7 @@ export class DriftOperations { }, builderParams: params.builderParams, positionMaxLeverage: params.positionMaxLeverage, + isolatedPositionDeposit: params.isolatedPositionDeposit, }); return swiftOrderResult; @@ -548,6 +551,7 @@ export class DriftOperations { useSwift: false, txParams: this.getTxParams(), positionMaxLeverage: params.positionMaxLeverage, + isolatedPositionDeposit: params.isolatedPositionDeposit, }); const { txSig } = await this.driftClient.sendTransaction(txn); diff --git a/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/types.ts b/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/types.ts index 62c0a472..b780d662 100644 --- a/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/types.ts +++ b/common-ts/src/drift/Drift/clients/AuthorityDrift/DriftOperations/types.ts @@ -1,5 +1,6 @@ import { BigNum, + BN, PositionDirection, PostOnlyParams, PublicKey, @@ -60,6 +61,7 @@ export type PerpOrderParams = { assetType: 'base' | 'quote'; size: BigNum; positionMaxLeverage: number; + isolatedPositionDeposit?: BN; reduceOnly?: boolean; postOnly?: PostOnlyParams; isMaxLeverage?: boolean; diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/index.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/index.ts index 8c67248f..7eb95a29 100644 --- a/common-ts/src/drift/base/actions/trade/openPerpOrder/index.ts +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/index.ts @@ -4,3 +4,4 @@ export * from './openSwiftOrder'; export * from './dlobServer'; export * from './types'; export * from './positionMaxLeverage'; +export * from './isolatedPositionDeposit'; diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/isolatedPositionDeposit.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/isolatedPositionDeposit.ts new file mode 100644 index 00000000..404ce8f2 --- /dev/null +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/isolatedPositionDeposit.ts @@ -0,0 +1,115 @@ +import { + BN, + DriftClient, + User, + calculateMarginUSDCRequiredForTrade, + OptionalOrderParams, + PositionDirection, + MarketType, + OrderType, +} from '@drift-labs/sdk'; +import { PublicKey, TransactionInstruction } from '@solana/web3.js'; + +export const ISOLATED_POSITION_DEPOSIT_BUFFER_BPS = 300; + +export interface ComputeIsolatedPositionDepositParams { + driftClient: DriftClient; + user: User; + marketIndex: number; + baseAssetAmount: BN; + /** + * Optional direction of the order. + * If provided, we will check if the order will increase the position. + * If the order will not increase the position, we will return 0. + */ + direction?: PositionDirection; + /** + * Margin ratio to use for the position (e.g. 2000 for 5x leverage). + */ + marginRatio: number; + /** + * Optional estimated entry price to use for the margin calculation. + */ + entryPrice?: BN; + /** + * Number of open high leverage spots available to the user (if any). + * If greater than 0, we will consider the trade as entering high leverage mode. + */ + numOfOpenHighLeverageSpots?: number; +} + +/** + * Computes the isolated position deposit required for opening an isolated perp position. + * Returns a BN in QUOTE_PRECISION (USDC). + */ +export function computeIsolatedPositionDepositForTrade({ + driftClient, + user, + marketIndex, + baseAssetAmount, + direction, + marginRatio, + entryPrice, + numOfOpenHighLeverageSpots, +}: ComputeIsolatedPositionDepositParams): BN | null { + // Only require isolated deposit if the order will increase the position (when direction is provided) + if (direction !== undefined) { + const maybeOrderParams: OptionalOrderParams = { + marketIndex, + marketType: MarketType.PERP, + orderType: OrderType.MARKET, + direction, + baseAssetAmount, + }; + const subAccountId = user.getUserAccount().subAccountId; + const isIncreasing = driftClient.isOrderIncreasingPosition( + maybeOrderParams, + subAccountId + ); + if (!isIncreasing) { + return null; + } + } + + const userIsInHighLeverageMode = user.isHighLeverageMode('Initial') ?? false; + const hasOpenHighLeverageSpots = + numOfOpenHighLeverageSpots !== undefined && numOfOpenHighLeverageSpots > 0; + const enteringHighLeverageMode = + userIsInHighLeverageMode || hasOpenHighLeverageSpots; + + const marginRequired = calculateMarginUSDCRequiredForTrade( + driftClient, + marketIndex, + baseAssetAmount, + marginRatio, + enteringHighLeverageMode, + entryPrice + ); + + return marginRequired.add( + marginRequired.div(new BN(ISOLATED_POSITION_DEPOSIT_BUFFER_BPS)) + ); // buffer in basis points +} + +export async function getIsolatedPositionDepositIxIfNeeded( + driftClient: DriftClient, + user: User, + marketIndex: number, + isolatedPositionDeposit?: BN, + signingAuthority?: PublicKey +): Promise { + if (!isolatedPositionDeposit) { + return undefined; + } + if (isolatedPositionDeposit.isZero()) { + return undefined; + } + + return driftClient.getTransferIsolatedPerpPositionDepositIx( + isolatedPositionDeposit, + marketIndex, + user.getUserAccount().subAccountId, + undefined, // noAmountBuffer + signingAuthority + ); +} diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.ts index 13aa607f..9534ef17 100644 --- a/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.ts +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpMarketOrder/index.ts @@ -36,6 +36,7 @@ import { NoTopMakersError } from '../../../../../Drift/constants/errors'; import { PlaceAndTakeParams, OptionalTriggerOrderParams } from '../types'; import { getPositionMaxLeverageIxIfNeeded } from '../positionMaxLeverage'; import { AuctionParamsFetchedCallback } from '../../../../../utils/auctionParamsResponseMapper'; +import { getIsolatedPositionDepositIxIfNeeded } from '../isolatedPositionDeposit'; export interface OpenPerpMarketOrderBaseParams { driftClient: DriftClient; @@ -61,6 +62,12 @@ export interface OpenPerpMarketOrderBaseParams { * Example: 5 for 5x leverage, 10 for 10x leverage */ positionMaxLeverage: number; + /** + * Optional isolated position deposit amount. + * If provided, it will be added to the order params as isolatedPositionDeposit. + * This field is used for opening isolated positions. + */ + isolatedPositionDeposit?: BN; /** * If provided, will override the main signer for the order. Otherwise, the main signer will be the user's authority. * This is only applicable for non-SWIFT orders. @@ -139,6 +146,7 @@ export async function createSwiftMarketOrder({ swiftOptions, userOrderId = 0, positionMaxLeverage, + isolatedPositionDeposit, builderParams, highLeverageOptions, callbacks, @@ -190,6 +198,7 @@ export async function createSwiftMarketOrder({ takeProfit: bracketOrders?.takeProfit, stopLoss: bracketOrders?.stopLoss, positionMaxLeverage, + isolatedPositionDeposit, }, builderParams, }); @@ -337,6 +346,7 @@ export const createOpenPerpMarketOrderIxs = async ({ positionMaxLeverage, mainSignerOverride, highLeverageOptions, + isolatedPositionDeposit, callbacks, }: OpenPerpMarketOrderBaseParams): Promise => { if (!amount || amount.isZero()) { @@ -357,6 +367,18 @@ export const createOpenPerpMarketOrderIxs = async ({ allIxs.push(leverageIx); } + const isolatedPositionDepositIx = await getIsolatedPositionDepositIxIfNeeded( + driftClient, + user, + marketIndex, + isolatedPositionDeposit, + mainSignerOverride + ); + + if (isolatedPositionDepositIx) { + allIxs.push(isolatedPositionDepositIx); + } + if (placeAndTake?.enable) { try { const placeAndTakeIx = await createPlaceAndTakePerpMarketOrderIx({ diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.ts index 4f3e7b63..bb5f6845 100644 --- a/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.ts +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/openPerpNonMarketOrder/index.ts @@ -38,6 +38,7 @@ import { import { WithTxnParams } from '../../../../types'; import { getPositionMaxLeverageIxIfNeeded } from '../positionMaxLeverage'; import { getLimitAuctionOrderParams } from '../auction'; +import { getIsolatedPositionDepositIxIfNeeded } from '../isolatedPositionDeposit'; export interface OpenPerpNonMarketOrderBaseParams extends Omit { @@ -141,6 +142,7 @@ export const createOpenPerpNonMarketOrderIxs = async ( orderConfig, userOrderId = 0, positionMaxLeverage, + isolatedPositionDeposit, mainSignerOverride, highLeverageOptions, } = params; @@ -174,6 +176,18 @@ export const createOpenPerpNonMarketOrderIxs = async ( allIxs.push(leverageIx); } + const isolatedPositionDepositIx: TransactionInstruction | undefined = + await getIsolatedPositionDepositIxIfNeeded( + driftClient, + user, + marketIndex, + isolatedPositionDeposit, + mainSignerOverride + ); + if (isolatedPositionDepositIx) { + allIxs.push(isolatedPositionDepositIx); + } + // handle limit auction if ( orderConfig.orderType === 'limit' && @@ -359,7 +373,7 @@ export const createSwiftLimitOrder = async ( reduceOnly: params.reduceOnly, postOnly: params.postOnly, userOrderId: params.userOrderId, - positionMaxLeverage: params.positionMaxLeverage, + positionMaxLeverage: params.positionMaxLeverage, // TODO: this isn't referenced in the function... do we need it? }); const userAccount = user.getUserAccount(); @@ -376,6 +390,7 @@ export const createSwiftLimitOrder = async ( takeProfit: orderConfig.bracketOrders?.takeProfit, stopLoss: orderConfig.bracketOrders?.stopLoss, positionMaxLeverage: params.positionMaxLeverage, + isolatedPositionDeposit: params.isolatedPositionDeposit, }, builderParams: params.builderParams, }); diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.ts index 391f8d70..3b8e462f 100644 --- a/common-ts/src/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.ts +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/openSwiftOrder/index.ts @@ -128,6 +128,8 @@ interface PrepSwiftOrderParams { takeProfit?: OptionalTriggerOrderParams; /** Optional max leverage for the position */ positionMaxLeverage?: number; + /** Optional isolated position deposit amount */ + isolatedPositionDeposit?: BN; }; /** * Buffer slots to account for the user to sign the message. Affects the auction start slot. @@ -222,6 +224,11 @@ export const prepSwiftOrder = ({ const signedMsgOrderUuid = generateSignedMsgUuid(); + console.log( + 'DEBUG swift prep orderParams.isolatedPositionDeposit', + orderParams.isolatedPositionDeposit?.toString() + ); + const baseSignedMsgOrderParamsMessage = { signedMsgOrderParams: mainOrderParams, uuid: signedMsgOrderUuid, @@ -243,6 +250,7 @@ export const prepSwiftOrder = ({ orderParams.positionMaxLeverage ) : null, + isolatedPositionDeposit: orderParams.isolatedPositionDeposit ?? null, // Include builder params if provided builderIdx: builderParams?.builderIdx ?? null, builderFeeTenthBps: builderParams?.builderFeeTenthBps ?? null, @@ -461,6 +469,10 @@ type PrepSignAndSendSwiftOrderParams = { * Adjusts the max leverage of a position. */ positionMaxLeverage?: number; + /** + * Optional isolated position deposit amount for isolated positions. + */ + isolatedPositionDeposit?: BN; }; /** * Optional builder code parameters for revenue sharing. diff --git a/common-ts/src/drift/base/actions/trade/openPerpOrder/types.ts b/common-ts/src/drift/base/actions/trade/openPerpOrder/types.ts index e2137ba9..213b7797 100644 --- a/common-ts/src/drift/base/actions/trade/openPerpOrder/types.ts +++ b/common-ts/src/drift/base/actions/trade/openPerpOrder/types.ts @@ -80,6 +80,7 @@ export interface NonMarketOrderParamsConfig { * Example: 5 for 5x leverage, 10 for 10x leverage */ positionMaxLeverage: number; + isolatedPositionDeposit?: BN; orderConfig: | LimitOrderParamsOrderConfig | { diff --git a/common-ts/tsconfig.json b/common-ts/tsconfig.json index 7de5cea5..06fbc0a5 100644 --- a/common-ts/tsconfig.json +++ b/common-ts/tsconfig.json @@ -18,6 +18,7 @@ "@drift-labs/sdk": ["../protocol/sdk"] }, "outDir": "./lib" + // "strictNullChecks": true // TODO: we should enable this }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/protocol b/protocol index 0ad0f422..fb4b680c 160000 --- a/protocol +++ b/protocol @@ -1 +1 @@ -Subproject commit 0ad0f422a1dc5a1dc94476d0bde3a796900d029f +Subproject commit fb4b680cd4508c4a5e07d608c54d270687baab3f diff --git a/react/src/hooks/useSyncLocalStorage.ts b/react/src/hooks/useSyncLocalStorage.ts index 6149a21a..f305ecb9 100644 --- a/react/src/hooks/useSyncLocalStorage.ts +++ b/react/src/hooks/useSyncLocalStorage.ts @@ -46,7 +46,7 @@ export function useSyncLocalStorage( return initialValue; } catch (error) { - console.log(error); + console.log('localStorage sync error on key:', key, error); return initialValue; } }); @@ -64,6 +64,7 @@ export function useSyncLocalStorage( // Store the value in `localStorage` if it's not nullish. Else, remove it. if (valueToStore != null) { + console.log('localStorage setValue setting key:', key, valueToStore); storage.setItem(key, getValueToStore(valueToStore)); } else { storage.removeItem(key); @@ -71,7 +72,7 @@ export function useSyncLocalStorage( window.dispatchEvent(new Event(LOCAL_STORAGE_EVENT_TYPE)); } catch (error) { - console.log(error); + console.log('localStorage setValue error on key:', key, error); } }, [storedValue, storage] @@ -93,7 +94,11 @@ export function useSyncLocalStorage( setStoredValue(parseValueFromStorage(newValue)); } catch (error) { - console.log(error); + console.log( + 'localStorage handleStorageChange error on key:', + key, + error + ); } };