@drift-labs/sdk 2.96.0-beta.8 → 2.96.0-beta.9

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.96.0-beta.8
1
+ 2.96.0-beta.9
@@ -239,6 +239,7 @@ export declare class DriftClient {
239
239
  */
240
240
  getAssociatedTokenAccount(marketIndex: number, useNative?: boolean, tokenProgram?: anchor.web3.PublicKey): Promise<PublicKey>;
241
241
  createAssociatedTokenAccountIdempotentInstruction(account: PublicKey, payer: PublicKey, owner: PublicKey, mint: PublicKey, tokenProgram?: anchor.web3.PublicKey): TransactionInstruction;
242
+ getDepositTxnIx(amount: BN, marketIndex: number, associatedTokenAccount: PublicKey, subAccountId?: number, reduceOnly?: boolean): Promise<TransactionInstruction[]>;
242
243
  createDepositTxn(amount: BN, marketIndex: number, associatedTokenAccount: PublicKey, subAccountId?: number, reduceOnly?: boolean, txParams?: TxParams): Promise<VersionedTransaction | Transaction>;
243
244
  /**
244
245
  * Deposit funds into the given spot market
@@ -261,6 +262,10 @@ export declare class DriftClient {
261
262
  getTokenProgramForSpotMarket(spotMarketAccount: SpotMarketAccount): PublicKey;
262
263
  addTokenMintToRemainingAccounts(spotMarketAccount: SpotMarketAccount, remainingAccounts: AccountMeta[]): void;
263
264
  getAssociatedTokenAccountCreationIx(tokenMintAddress: PublicKey, associatedTokenAddress: PublicKey, tokenProgram: PublicKey): anchor.web3.TransactionInstruction;
265
+ createInitializeUserAccountAndDepositCollateralIxs(amount: BN, userTokenAccount: PublicKey, marketIndex?: number, subAccountId?: number, name?: string, fromSubAccountId?: number, referrerInfo?: ReferrerInfo, donateAmount?: BN, customMaxMarginRatio?: number): Promise<{
266
+ ixs: TransactionInstruction[];
267
+ userAccountPublicKey: PublicKey;
268
+ }>;
264
269
  createInitializeUserAccountAndDepositCollateral(amount: BN, userTokenAccount: PublicKey, marketIndex?: number, subAccountId?: number, name?: string, fromSubAccountId?: number, referrerInfo?: ReferrerInfo, donateAmount?: BN, txParams?: TxParams, customMaxMarginRatio?: number): Promise<[Transaction | VersionedTransaction, PublicKey]>;
265
270
  /**
266
271
  * Creates the User account for a user, and deposits some initial collateral
@@ -340,7 +345,10 @@ export declare class DriftClient {
340
345
  signedSettlePnlTx?: Transaction;
341
346
  }>;
342
347
  placePerpOrder(orderParams: OptionalOrderParams, txParams?: TxParams, subAccountId?: number): Promise<TransactionSignature>;
343
- getPlacePerpOrderIx(orderParams: OptionalOrderParams, subAccountId?: number): Promise<TransactionInstruction>;
348
+ getPlacePerpOrderIx(orderParams: OptionalOrderParams, subAccountId?: number, depositToTradeArgs?: {
349
+ isMakingNewAccount: boolean;
350
+ depositMarketIndex: number;
351
+ }): Promise<TransactionInstruction>;
344
352
  updateAMMs(marketIndexes: number[], txParams?: TxParams): Promise<TransactionSignature>;
345
353
  getUpdateAMMsIx(marketIndexes: number[]): Promise<TransactionInstruction>;
346
354
  settleExpiredMarket(marketIndex: number, txParams?: TxParams): Promise<TransactionSignature>;
@@ -1118,7 +1118,7 @@ class DriftClient {
1118
1118
  data: Buffer.from([0x1]),
1119
1119
  });
1120
1120
  }
1121
- async createDepositTxn(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false, txParams) {
1121
+ async getDepositTxnIx(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false) {
1122
1122
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
1123
1123
  const isSolMarket = spotMarketAccount.mint.equals(spotMarkets_1.WRAPPED_SOL_MINT);
1124
1124
  const signerAuthority = this.wallet.publicKey;
@@ -1135,6 +1135,10 @@ class DriftClient {
1135
1135
  if (createWSOLTokenAccount) {
1136
1136
  instructions.push((0, spl_token_1.createCloseAccountInstruction)(associatedTokenAccount, signerAuthority, signerAuthority, []));
1137
1137
  }
1138
+ return instructions;
1139
+ }
1140
+ async createDepositTxn(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly = false, txParams) {
1141
+ const instructions = await this.getDepositTxnIx(amount, marketIndex, associatedTokenAccount, subAccountId, reduceOnly);
1138
1142
  txParams = { ...(txParams !== null && txParams !== void 0 ? txParams : this.txParams), computeUnits: 600000 };
1139
1143
  const tx = await this.buildTransaction(instructions, txParams);
1140
1144
  return tx;
@@ -1242,7 +1246,7 @@ class DriftClient {
1242
1246
  getAssociatedTokenAccountCreationIx(tokenMintAddress, associatedTokenAddress, tokenProgram) {
1243
1247
  return (0, spl_token_1.createAssociatedTokenAccountInstruction)(this.wallet.publicKey, associatedTokenAddress, this.wallet.publicKey, tokenMintAddress, tokenProgram);
1244
1248
  }
1245
- async createInitializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio) {
1249
+ async createInitializeUserAccountAndDepositCollateralIxs(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, customMaxMarginRatio) {
1246
1250
  const ixs = [];
1247
1251
  const [userAccountPublicKey, initializeUserAccountIx] = await this.getInitializeUserInstructions(subAccountId, name, referrerInfo);
1248
1252
  const spotMarket = this.getSpotMarketAccount(marketIndex);
@@ -1288,6 +1292,13 @@ class DriftClient {
1288
1292
  if (createWSOLTokenAccount) {
1289
1293
  ixs.push((0, spl_token_1.createCloseAccountInstruction)(wsolTokenAccount, authority, authority, []));
1290
1294
  }
1295
+ return {
1296
+ ixs,
1297
+ userAccountPublicKey,
1298
+ };
1299
+ }
1300
+ async createInitializeUserAccountAndDepositCollateral(amount, userTokenAccount, marketIndex = 0, subAccountId = 0, name, fromSubAccountId, referrerInfo, donateAmount, txParams, customMaxMarginRatio) {
1301
+ const { ixs, userAccountPublicKey } = await this.createInitializeUserAccountAndDepositCollateralIxs(amount, userTokenAccount, marketIndex, subAccountId, name, fromSubAccountId, referrerInfo, donateAmount, customMaxMarginRatio);
1291
1302
  const tx = await this.buildTransaction(ixs, txParams);
1292
1303
  return [tx, userAccountPublicKey];
1293
1304
  }
@@ -1663,13 +1674,21 @@ class DriftClient {
1663
1674
  this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot);
1664
1675
  return txSig;
1665
1676
  }
1666
- async getPlacePerpOrderIx(orderParams, subAccountId) {
1677
+ async getPlacePerpOrderIx(orderParams, subAccountId, depositToTradeArgs) {
1667
1678
  orderParams = (0, orderParams_1.getOrderParams)(orderParams, { marketType: types_1.MarketType.PERP });
1668
- const user = await this.getUserAccountPublicKey(subAccountId);
1679
+ const isDepositToTradeTx = depositToTradeArgs !== undefined;
1680
+ const user = isDepositToTradeTx
1681
+ ? (0, pda_1.getUserAccountPublicKeySync)(this.program.programId, this.authority, subAccountId)
1682
+ : await this.getUserAccountPublicKey(subAccountId);
1669
1683
  const remainingAccounts = this.getRemainingAccounts({
1670
- userAccounts: [this.getUserAccount(subAccountId)],
1671
- useMarketLastSlotCache: true,
1684
+ userAccounts: (depositToTradeArgs === null || depositToTradeArgs === void 0 ? void 0 : depositToTradeArgs.isMakingNewAccount)
1685
+ ? []
1686
+ : [this.getUserAccount(subAccountId)],
1687
+ useMarketLastSlotCache: false,
1672
1688
  readablePerpMarketIndex: orderParams.marketIndex,
1689
+ readableSpotMarketIndexes: isDepositToTradeTx
1690
+ ? [depositToTradeArgs === null || depositToTradeArgs === void 0 ? void 0 : depositToTradeArgs.depositMarketIndex]
1691
+ : undefined,
1673
1692
  });
1674
1693
  return await this.program.instruction.placePerpOrder(orderParams, {
1675
1694
  accounts: {
@@ -1,7 +1,7 @@
1
1
  /// <reference types="bn.js" />
2
2
  import { BN } from '@coral-xyz/anchor';
3
3
  import { OraclePriceData } from '../oracles/types';
4
- import { PerpMarketAccount, PerpPosition } from '..';
4
+ import { DriftClient, PerpMarketAccount, PerpPosition } from '..';
5
5
  export declare function calculateSizePremiumLiabilityWeight(size: BN, // AMM_RESERVE_PRECISION
6
6
  imfFactor: BN, liabilityWeight: BN, precision: BN): BN;
7
7
  export declare function calculateSizeDiscountAssetWeight(size: BN, // AMM_RESERVE_PRECISION
@@ -22,3 +22,18 @@ export declare function calculateWorstCasePerpLiabilityValue(perpPosition: PerpP
22
22
  worstCaseLiabilityValue: BN;
23
23
  };
24
24
  export declare function calculatePerpLiabilityValue(baseAssetAmount: BN, oraclePrice: BN, isPredictionMarket: boolean): BN;
25
+ /**
26
+ * Calculates the margin required to open a trade, in quote amount. Only accounts for the trade size as a scalar value, does not account for the trade direction or current open positions and whether the trade would _actually_ be risk-increasing and use any extra collateral.
27
+ * @param targetMarketIndex
28
+ * @param baseSize
29
+ * @returns
30
+ */
31
+ export declare function calculateMarginUSDCRequiredForTrade(driftClient: DriftClient, targetMarketIndex: number, baseSize: BN, userMaxMarginRatio?: number): BN;
32
+ /**
33
+ * Similar to calculatetMarginUSDCRequiredForTrade, but calculates how much of a given collateral is required to cover the margin requirements for a given trade. Basically does the same thing as getMarginUSDCRequiredForTrade but also accounts for asset weight of the selected collateral.
34
+ *
35
+ * Returns collateral required in the precision of the target collateral market.
36
+ */
37
+ export declare function calculateCollateralDepositRequiredForTrade(driftClient: DriftClient, targetMarketIndex: number, baseSize: BN, collateralIndex: number, userMaxMarginRatio?: number): BN;
38
+ export declare function calculateCollateralValueOfDeposit(driftClient: DriftClient, collateralIndex: number, baseSize: BN): BN;
39
+ export declare function calculateLiquidationPrice(freeCollateral: BN, freeCollateralDelta: BN, oraclePrice: BN): BN;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculatePerpLiabilityValue = exports.calculateWorstCasePerpLiabilityValue = exports.calculateWorstCaseBaseAssetAmount = exports.calculateBaseAssetValueWithOracle = exports.calculateOraclePriceForPerpMargin = exports.calculateSizeDiscountAssetWeight = exports.calculateSizePremiumLiabilityWeight = void 0;
3
+ exports.calculateLiquidationPrice = exports.calculateCollateralValueOfDeposit = exports.calculateCollateralDepositRequiredForTrade = exports.calculateMarginUSDCRequiredForTrade = exports.calculatePerpLiabilityValue = exports.calculateWorstCasePerpLiabilityValue = exports.calculateWorstCaseBaseAssetAmount = exports.calculateBaseAssetValueWithOracle = exports.calculateOraclePriceForPerpMargin = exports.calculateSizeDiscountAssetWeight = exports.calculateSizePremiumLiabilityWeight = void 0;
4
4
  const utils_1 = require("./utils");
5
5
  const numericConstants_1 = require("../constants/numericConstants");
6
6
  const anchor_1 = require("@coral-xyz/anchor");
7
+ const __1 = require("..");
7
8
  const types_1 = require("../types");
8
9
  const assert_1 = require("../assert/assert");
9
10
  function calculateSizePremiumLiabilityWeight(size, // AMM_RESERVE_PRECISION
@@ -116,3 +117,68 @@ function calculatePerpLiabilityValue(baseAssetAmount, oraclePrice, isPredictionM
116
117
  }
117
118
  }
118
119
  exports.calculatePerpLiabilityValue = calculatePerpLiabilityValue;
120
+ /**
121
+ * Calculates the margin required to open a trade, in quote amount. Only accounts for the trade size as a scalar value, does not account for the trade direction or current open positions and whether the trade would _actually_ be risk-increasing and use any extra collateral.
122
+ * @param targetMarketIndex
123
+ * @param baseSize
124
+ * @returns
125
+ */
126
+ function calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio) {
127
+ const targetMarket = driftClient.getPerpMarketAccount(targetMarketIndex);
128
+ const oracleData = driftClient.getOracleDataForPerpMarket(targetMarket.marketIndex);
129
+ const perpLiabilityValue = calculatePerpLiabilityValue(baseSize, oracleData.price, (0, types_1.isVariant)(targetMarket.contractType, 'prediction'));
130
+ const marginRequired = new anchor_1.BN((0, __1.calculateMarketMarginRatio)(targetMarket, baseSize.abs(), 'Initial', userMaxMarginRatio))
131
+ .mul(perpLiabilityValue)
132
+ .div(numericConstants_1.MARGIN_PRECISION);
133
+ return marginRequired;
134
+ }
135
+ exports.calculateMarginUSDCRequiredForTrade = calculateMarginUSDCRequiredForTrade;
136
+ /**
137
+ * Similar to calculatetMarginUSDCRequiredForTrade, but calculates how much of a given collateral is required to cover the margin requirements for a given trade. Basically does the same thing as getMarginUSDCRequiredForTrade but also accounts for asset weight of the selected collateral.
138
+ *
139
+ * Returns collateral required in the precision of the target collateral market.
140
+ */
141
+ function calculateCollateralDepositRequiredForTrade(driftClient, targetMarketIndex, baseSize, collateralIndex, userMaxMarginRatio) {
142
+ const marginRequiredUsdc = calculateMarginUSDCRequiredForTrade(driftClient, targetMarketIndex, baseSize, userMaxMarginRatio);
143
+ const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
144
+ const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex);
145
+ const scaledAssetWeight = (0, __1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price);
146
+ // Base amount required to deposit = (marginRequiredUsdc / priceOfAsset) / assetWeight .. (E.g. $100 required / $10000 price / 0.5 weight)
147
+ const baseAmountRequired = driftClient
148
+ .convertToSpotPrecision(collateralIndex, marginRequiredUsdc)
149
+ .mul(numericConstants_1.PRICE_PRECISION) // adjust for division by oracle price
150
+ .mul(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION) // adjust for division by scaled asset weight
151
+ .div(collateralOracleData.price)
152
+ .div(scaledAssetWeight)
153
+ .div(numericConstants_1.QUOTE_PRECISION); // adjust for marginRequiredUsdc value's QUOTE_PRECISION
154
+ // TODO : Round by step size?
155
+ return baseAmountRequired;
156
+ }
157
+ exports.calculateCollateralDepositRequiredForTrade = calculateCollateralDepositRequiredForTrade;
158
+ function calculateCollateralValueOfDeposit(driftClient, collateralIndex, baseSize) {
159
+ const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
160
+ const collateralOracleData = driftClient.getOracleDataForSpotMarket(collateralIndex);
161
+ const scaledAssetWeight = (0, __1.calculateScaledInitialAssetWeight)(collateralMarket, collateralOracleData.price);
162
+ // CollateralBaseValue = oracle price * collateral base amount (and shift to QUOTE_PRECISION)
163
+ const collateralBaseValue = collateralOracleData.price
164
+ .mul(baseSize)
165
+ .mul(numericConstants_1.QUOTE_PRECISION)
166
+ .div(numericConstants_1.PRICE_PRECISION)
167
+ .div(new anchor_1.BN(10).pow(new anchor_1.BN(collateralMarket.decimals)));
168
+ const depositCollateralValue = collateralBaseValue
169
+ .mul(scaledAssetWeight)
170
+ .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
171
+ return depositCollateralValue;
172
+ }
173
+ exports.calculateCollateralValueOfDeposit = calculateCollateralValueOfDeposit;
174
+ function calculateLiquidationPrice(freeCollateral, freeCollateralDelta, oraclePrice) {
175
+ const liqPriceDelta = freeCollateral
176
+ .mul(numericConstants_1.QUOTE_PRECISION)
177
+ .div(freeCollateralDelta);
178
+ const liqPrice = oraclePrice.sub(liqPriceDelta);
179
+ if (liqPrice.lt(numericConstants_1.ZERO)) {
180
+ return new anchor_1.BN(-1);
181
+ }
182
+ return liqPrice;
183
+ }
184
+ exports.calculateLiquidationPrice = calculateLiquidationPrice;
package/lib/user.d.ts CHANGED
@@ -265,9 +265,10 @@ export declare class User {
265
265
  * @param estimatedEntryPrice
266
266
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
267
267
  * @param includeOpenOrders
268
+ * @param offsetCollateral // allows calculating the liquidation price after this offset collateral is added to the user's account (e.g. : what will the liquidation price be for this position AFTER I deposit $x worth of collateral)
268
269
  * @returns Precision : PRICE_PRECISION
269
270
  */
270
- liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN;
271
+ liquidationPrice(marketIndex: number, positionBaseSizeChange?: BN, estimatedEntryPrice?: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean, offsetCollateral?: BN): BN;
271
272
  calculateEntriesEffectOnFreeCollateral(market: PerpMarketAccount, oraclePrice: BN, perpPosition: PerpPosition, positionBaseSizeChange: BN, estimatedEntryPrice: BN, includeOpenOrders: boolean): BN;
272
273
  calculateFreeCollateralDeltaForPerp(market: PerpMarketAccount, perpPosition: PerpPosition, positionBaseSizeChange: BN, oraclePrice: BN, marginCategory?: MarginCategory, includeOpenOrders?: boolean): BN | undefined;
273
274
  calculateFreeCollateralDeltaForSpot(market: SpotMarketAccount, signedTokenAmount: BN, marginCategory?: MarginCategory): BN;
@@ -278,6 +279,8 @@ export declare class User {
278
279
  * @returns : Precision PRICE_PRECISION
279
280
  */
280
281
  liquidationPriceAfterClose(positionMarketIndex: number, closeQuoteAmount: BN, estimatedEntryPrice?: BN): BN;
282
+ getMarginUSDCRequiredForTrade(targetMarketIndex: number, baseSize: BN): BN;
283
+ getCollateralDepositRequiredForTrade(targetMarketIndex: number, baseSize: BN, collateralIndex: number): BN;
281
284
  /**
282
285
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
283
286
  *
package/lib/user.js CHANGED
@@ -1202,12 +1202,13 @@ class User {
1202
1202
  * @param estimatedEntryPrice
1203
1203
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
1204
1204
  * @param includeOpenOrders
1205
+ * @param offsetCollateral // allows calculating the liquidation price after this offset collateral is added to the user's account (e.g. : what will the liquidation price be for this position AFTER I deposit $x worth of collateral)
1205
1206
  * @returns Precision : PRICE_PRECISION
1206
1207
  */
1207
- liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false) {
1208
+ liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false, offsetCollateral = numericConstants_1.ZERO) {
1208
1209
  const totalCollateral = this.getTotalCollateral(marginCategory);
1209
1210
  const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders);
1210
- let freeCollateral = _1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement));
1211
+ let freeCollateral = _1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1211
1212
  const oracle = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
1212
1213
  const oraclePrice = this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
1213
1214
  const market = this.driftClient.getPerpMarketAccount(marketIndex);
@@ -1367,6 +1368,12 @@ class User {
1367
1368
  .neg();
1368
1369
  return this.liquidationPrice(positionMarketIndex, closeBaseAmount, estimatedEntryPrice);
1369
1370
  }
1371
+ getMarginUSDCRequiredForTrade(targetMarketIndex, baseSize) {
1372
+ return (0, margin_1.calculateMarginUSDCRequiredForTrade)(this.driftClient, targetMarketIndex, baseSize, this.getUserAccount().maxMarginRatio);
1373
+ }
1374
+ getCollateralDepositRequiredForTrade(targetMarketIndex, baseSize, collateralIndex) {
1375
+ return (0, margin_1.calculateCollateralDepositRequiredForTrade)(this.driftClient, targetMarketIndex, baseSize, collateralIndex, this.getUserAccount().maxMarginRatio);
1376
+ }
1370
1377
  /**
1371
1378
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
1372
1379
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.96.0-beta.8",
3
+ "version": "2.96.0-beta.9",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -1906,14 +1906,13 @@ export class DriftClient {
1906
1906
  });
1907
1907
  }
1908
1908
 
1909
- public async createDepositTxn(
1909
+ public async getDepositTxnIx(
1910
1910
  amount: BN,
1911
1911
  marketIndex: number,
1912
1912
  associatedTokenAccount: PublicKey,
1913
1913
  subAccountId?: number,
1914
- reduceOnly = false,
1915
- txParams?: TxParams
1916
- ): Promise<VersionedTransaction | Transaction> {
1914
+ reduceOnly = false
1915
+ ): Promise<TransactionInstruction[]> {
1917
1916
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
1918
1917
 
1919
1918
  const isSolMarket = spotMarketAccount.mint.equals(WRAPPED_SOL_MINT);
@@ -1959,6 +1958,25 @@ export class DriftClient {
1959
1958
  );
1960
1959
  }
1961
1960
 
1961
+ return instructions;
1962
+ }
1963
+
1964
+ public async createDepositTxn(
1965
+ amount: BN,
1966
+ marketIndex: number,
1967
+ associatedTokenAccount: PublicKey,
1968
+ subAccountId?: number,
1969
+ reduceOnly = false,
1970
+ txParams?: TxParams
1971
+ ): Promise<VersionedTransaction | Transaction> {
1972
+ const instructions = await this.getDepositTxnIx(
1973
+ amount,
1974
+ marketIndex,
1975
+ associatedTokenAccount,
1976
+ subAccountId,
1977
+ reduceOnly
1978
+ );
1979
+
1962
1980
  txParams = { ...(txParams ?? this.txParams), computeUnits: 600_000 };
1963
1981
 
1964
1982
  const tx = await this.buildTransaction(instructions, txParams);
@@ -2151,7 +2169,7 @@ export class DriftClient {
2151
2169
  );
2152
2170
  }
2153
2171
 
2154
- public async createInitializeUserAccountAndDepositCollateral(
2172
+ public async createInitializeUserAccountAndDepositCollateralIxs(
2155
2173
  amount: BN,
2156
2174
  userTokenAccount: PublicKey,
2157
2175
  marketIndex = 0,
@@ -2160,9 +2178,11 @@ export class DriftClient {
2160
2178
  fromSubAccountId?: number,
2161
2179
  referrerInfo?: ReferrerInfo,
2162
2180
  donateAmount?: BN,
2163
- txParams?: TxParams,
2164
2181
  customMaxMarginRatio?: number
2165
- ): Promise<[Transaction | VersionedTransaction, PublicKey]> {
2182
+ ): Promise<{
2183
+ ixs: TransactionInstruction[];
2184
+ userAccountPublicKey: PublicKey;
2185
+ }> {
2166
2186
  const ixs = [];
2167
2187
 
2168
2188
  const [userAccountPublicKey, initializeUserAccountIx] =
@@ -2263,6 +2283,37 @@ export class DriftClient {
2263
2283
  );
2264
2284
  }
2265
2285
 
2286
+ return {
2287
+ ixs,
2288
+ userAccountPublicKey,
2289
+ };
2290
+ }
2291
+
2292
+ public async createInitializeUserAccountAndDepositCollateral(
2293
+ amount: BN,
2294
+ userTokenAccount: PublicKey,
2295
+ marketIndex = 0,
2296
+ subAccountId = 0,
2297
+ name?: string,
2298
+ fromSubAccountId?: number,
2299
+ referrerInfo?: ReferrerInfo,
2300
+ donateAmount?: BN,
2301
+ txParams?: TxParams,
2302
+ customMaxMarginRatio?: number
2303
+ ): Promise<[Transaction | VersionedTransaction, PublicKey]> {
2304
+ const { ixs, userAccountPublicKey } =
2305
+ await this.createInitializeUserAccountAndDepositCollateralIxs(
2306
+ amount,
2307
+ userTokenAccount,
2308
+ marketIndex,
2309
+ subAccountId,
2310
+ name,
2311
+ fromSubAccountId,
2312
+ referrerInfo,
2313
+ donateAmount,
2314
+ customMaxMarginRatio
2315
+ );
2316
+
2266
2317
  const tx = await this.buildTransaction(ixs, txParams);
2267
2318
 
2268
2319
  return [tx, userAccountPublicKey];
@@ -3129,15 +3180,33 @@ export class DriftClient {
3129
3180
 
3130
3181
  public async getPlacePerpOrderIx(
3131
3182
  orderParams: OptionalOrderParams,
3132
- subAccountId?: number
3183
+ subAccountId?: number,
3184
+ depositToTradeArgs?: {
3185
+ isMakingNewAccount: boolean;
3186
+ depositMarketIndex: number;
3187
+ }
3133
3188
  ): Promise<TransactionInstruction> {
3134
3189
  orderParams = getOrderParams(orderParams, { marketType: MarketType.PERP });
3135
- const user = await this.getUserAccountPublicKey(subAccountId);
3190
+
3191
+ const isDepositToTradeTx = depositToTradeArgs !== undefined;
3192
+
3193
+ const user = isDepositToTradeTx
3194
+ ? getUserAccountPublicKeySync(
3195
+ this.program.programId,
3196
+ this.authority,
3197
+ subAccountId
3198
+ )
3199
+ : await this.getUserAccountPublicKey(subAccountId);
3136
3200
 
3137
3201
  const remainingAccounts = this.getRemainingAccounts({
3138
- userAccounts: [this.getUserAccount(subAccountId)],
3139
- useMarketLastSlotCache: true,
3202
+ userAccounts: depositToTradeArgs?.isMakingNewAccount
3203
+ ? []
3204
+ : [this.getUserAccount(subAccountId)],
3205
+ useMarketLastSlotCache: false,
3140
3206
  readablePerpMarketIndex: orderParams.marketIndex,
3207
+ readableSpotMarketIndexes: isDepositToTradeTx
3208
+ ? [depositToTradeArgs?.depositMarketIndex]
3209
+ : undefined,
3141
3210
  });
3142
3211
 
3143
3212
  return await this.program.instruction.placePerpOrder(orderParams, {
@@ -7,10 +7,19 @@ import {
7
7
  AMM_RESERVE_PRECISION,
8
8
  MAX_PREDICTION_PRICE,
9
9
  BASE_PRECISION,
10
+ MARGIN_PRECISION,
11
+ PRICE_PRECISION,
12
+ QUOTE_PRECISION,
10
13
  } from '../constants/numericConstants';
11
14
  import { BN } from '@coral-xyz/anchor';
12
15
  import { OraclePriceData } from '../oracles/types';
13
- import { PerpMarketAccount, PerpPosition } from '..';
16
+ import {
17
+ calculateMarketMarginRatio,
18
+ calculateScaledInitialAssetWeight,
19
+ DriftClient,
20
+ PerpMarketAccount,
21
+ PerpPosition,
22
+ } from '..';
14
23
  import { isVariant } from '../types';
15
24
  import { assert } from '../assert/assert';
16
25
 
@@ -194,3 +203,130 @@ export function calculatePerpLiabilityValue(
194
203
  return baseAssetAmount.abs().mul(oraclePrice).div(BASE_PRECISION);
195
204
  }
196
205
  }
206
+
207
+ /**
208
+ * Calculates the margin required to open a trade, in quote amount. Only accounts for the trade size as a scalar value, does not account for the trade direction or current open positions and whether the trade would _actually_ be risk-increasing and use any extra collateral.
209
+ * @param targetMarketIndex
210
+ * @param baseSize
211
+ * @returns
212
+ */
213
+ export function calculateMarginUSDCRequiredForTrade(
214
+ driftClient: DriftClient,
215
+ targetMarketIndex: number,
216
+ baseSize: BN,
217
+ userMaxMarginRatio?: number
218
+ ): BN {
219
+ const targetMarket = driftClient.getPerpMarketAccount(targetMarketIndex);
220
+ const oracleData = driftClient.getOracleDataForPerpMarket(
221
+ targetMarket.marketIndex
222
+ );
223
+
224
+ const perpLiabilityValue = calculatePerpLiabilityValue(
225
+ baseSize,
226
+ oracleData.price,
227
+ isVariant(targetMarket.contractType, 'prediction')
228
+ );
229
+
230
+ const marginRequired = new BN(
231
+ calculateMarketMarginRatio(
232
+ targetMarket,
233
+ baseSize.abs(),
234
+ 'Initial',
235
+ userMaxMarginRatio
236
+ )
237
+ )
238
+ .mul(perpLiabilityValue)
239
+ .div(MARGIN_PRECISION);
240
+
241
+ return marginRequired;
242
+ }
243
+
244
+ /**
245
+ * Similar to calculatetMarginUSDCRequiredForTrade, but calculates how much of a given collateral is required to cover the margin requirements for a given trade. Basically does the same thing as getMarginUSDCRequiredForTrade but also accounts for asset weight of the selected collateral.
246
+ *
247
+ * Returns collateral required in the precision of the target collateral market.
248
+ */
249
+ export function calculateCollateralDepositRequiredForTrade(
250
+ driftClient: DriftClient,
251
+ targetMarketIndex: number,
252
+ baseSize: BN,
253
+ collateralIndex: number,
254
+ userMaxMarginRatio?: number
255
+ ): BN {
256
+ const marginRequiredUsdc = calculateMarginUSDCRequiredForTrade(
257
+ driftClient,
258
+ targetMarketIndex,
259
+ baseSize,
260
+ userMaxMarginRatio
261
+ );
262
+
263
+ const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
264
+
265
+ const collateralOracleData =
266
+ driftClient.getOracleDataForSpotMarket(collateralIndex);
267
+
268
+ const scaledAssetWeight = calculateScaledInitialAssetWeight(
269
+ collateralMarket,
270
+ collateralOracleData.price
271
+ );
272
+
273
+ // Base amount required to deposit = (marginRequiredUsdc / priceOfAsset) / assetWeight .. (E.g. $100 required / $10000 price / 0.5 weight)
274
+ const baseAmountRequired = driftClient
275
+ .convertToSpotPrecision(collateralIndex, marginRequiredUsdc)
276
+ .mul(PRICE_PRECISION) // adjust for division by oracle price
277
+ .mul(SPOT_MARKET_WEIGHT_PRECISION) // adjust for division by scaled asset weight
278
+ .div(collateralOracleData.price)
279
+ .div(scaledAssetWeight)
280
+ .div(QUOTE_PRECISION); // adjust for marginRequiredUsdc value's QUOTE_PRECISION
281
+
282
+ // TODO : Round by step size?
283
+
284
+ return baseAmountRequired;
285
+ }
286
+
287
+ export function calculateCollateralValueOfDeposit(
288
+ driftClient: DriftClient,
289
+ collateralIndex: number,
290
+ baseSize: BN
291
+ ): BN {
292
+ const collateralMarket = driftClient.getSpotMarketAccount(collateralIndex);
293
+
294
+ const collateralOracleData =
295
+ driftClient.getOracleDataForSpotMarket(collateralIndex);
296
+
297
+ const scaledAssetWeight = calculateScaledInitialAssetWeight(
298
+ collateralMarket,
299
+ collateralOracleData.price
300
+ );
301
+
302
+ // CollateralBaseValue = oracle price * collateral base amount (and shift to QUOTE_PRECISION)
303
+ const collateralBaseValue = collateralOracleData.price
304
+ .mul(baseSize)
305
+ .mul(QUOTE_PRECISION)
306
+ .div(PRICE_PRECISION)
307
+ .div(new BN(10).pow(new BN(collateralMarket.decimals)));
308
+
309
+ const depositCollateralValue = collateralBaseValue
310
+ .mul(scaledAssetWeight)
311
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
312
+
313
+ return depositCollateralValue;
314
+ }
315
+
316
+ export function calculateLiquidationPrice(
317
+ freeCollateral: BN,
318
+ freeCollateralDelta: BN,
319
+ oraclePrice: BN
320
+ ): BN {
321
+ const liqPriceDelta = freeCollateral
322
+ .mul(QUOTE_PRECISION)
323
+ .div(freeCollateralDelta);
324
+
325
+ const liqPrice = oraclePrice.sub(liqPriceDelta);
326
+
327
+ if (liqPrice.lt(ZERO)) {
328
+ return new BN(-1);
329
+ }
330
+
331
+ return liqPrice;
332
+ }
package/src/user.ts CHANGED
@@ -78,6 +78,8 @@ import {
78
78
  import { calculateMarketOpenBidAsk } from './math/amm';
79
79
  import {
80
80
  calculateBaseAssetValueWithOracle,
81
+ calculateCollateralDepositRequiredForTrade,
82
+ calculateMarginUSDCRequiredForTrade,
81
83
  calculateWorstCaseBaseAssetAmount,
82
84
  } from './math/margin';
83
85
  import { OraclePriceData } from './oracles/types';
@@ -2274,6 +2276,7 @@ export class User {
2274
2276
  * @param estimatedEntryPrice
2275
2277
  * @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
2276
2278
  * @param includeOpenOrders
2279
+ * @param offsetCollateral // allows calculating the liquidation price after this offset collateral is added to the user's account (e.g. : what will the liquidation price be for this position AFTER I deposit $x worth of collateral)
2277
2280
  * @returns Precision : PRICE_PRECISION
2278
2281
  */
2279
2282
  public liquidationPrice(
@@ -2281,7 +2284,8 @@ export class User {
2281
2284
  positionBaseSizeChange: BN = ZERO,
2282
2285
  estimatedEntryPrice: BN = ZERO,
2283
2286
  marginCategory: MarginCategory = 'Maintenance',
2284
- includeOpenOrders = false
2287
+ includeOpenOrders = false,
2288
+ offsetCollateral = ZERO
2285
2289
  ): BN {
2286
2290
  const totalCollateral = this.getTotalCollateral(marginCategory);
2287
2291
  const marginRequirement = this.getMarginRequirement(
@@ -2290,7 +2294,10 @@ export class User {
2290
2294
  false,
2291
2295
  includeOpenOrders
2292
2296
  );
2293
- let freeCollateral = BN.max(ZERO, totalCollateral.sub(marginRequirement));
2297
+ let freeCollateral = BN.max(
2298
+ ZERO,
2299
+ totalCollateral.sub(marginRequirement)
2300
+ ).add(offsetCollateral);
2294
2301
 
2295
2302
  const oracle =
2296
2303
  this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
@@ -2593,6 +2600,32 @@ export class User {
2593
2600
  );
2594
2601
  }
2595
2602
 
2603
+ public getMarginUSDCRequiredForTrade(
2604
+ targetMarketIndex: number,
2605
+ baseSize: BN
2606
+ ): BN {
2607
+ return calculateMarginUSDCRequiredForTrade(
2608
+ this.driftClient,
2609
+ targetMarketIndex,
2610
+ baseSize,
2611
+ this.getUserAccount().maxMarginRatio
2612
+ );
2613
+ }
2614
+
2615
+ public getCollateralDepositRequiredForTrade(
2616
+ targetMarketIndex: number,
2617
+ baseSize: BN,
2618
+ collateralIndex: number
2619
+ ): BN {
2620
+ return calculateCollateralDepositRequiredForTrade(
2621
+ this.driftClient,
2622
+ targetMarketIndex,
2623
+ baseSize,
2624
+ collateralIndex,
2625
+ this.getUserAccount().maxMarginRatio
2626
+ );
2627
+ }
2628
+
2596
2629
  /**
2597
2630
  * Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
2598
2631
  *
package/tests/ci/idl.ts CHANGED
@@ -51,7 +51,9 @@ describe('Verify IDL', function () {
51
51
  );
52
52
 
53
53
  if (onChainIdl === null) {
54
- throw new Error(`onChainIdl for ${mainnetDriftClient.program.programId.toBase58()} null`);
54
+ throw new Error(
55
+ `onChainIdl for ${mainnetDriftClient.program.programId.toBase58()} null`
56
+ );
55
57
  }
56
58
 
57
59
  // anchor idl init seems to strip the metadata
@@ -65,13 +67,20 @@ describe('Verify IDL', function () {
65
67
  const encodedSdkIdl = JSON.stringify(sdkIdl);
66
68
 
67
69
  try {
68
- assert(encodedSdkIdl === encodedMainnetIdl, 'on-chain IDL does not match SDK IDL');
70
+ assert(
71
+ encodedSdkIdl === encodedMainnetIdl,
72
+ 'on-chain IDL does not match SDK IDL'
73
+ );
69
74
  } catch (error) {
70
75
  const diff = {};
71
76
  for (const key of IDL_KEYS_TO_CHECK) {
72
77
  const onChainItems = onChainIdl[key];
73
78
  const sdkItems = sdkIdl[key];
74
- for (let i = 0; i < Math.max(onChainItems.length, sdkItems.length); i++) {
79
+ for (
80
+ let i = 0;
81
+ i < Math.max(onChainItems.length, sdkItems.length);
82
+ i++
83
+ ) {
75
84
  let onChainItem = null;
76
85
  let sdkItem = null;
77
86
  if (i < onChainItems.length) {