@drift-labs/sdk 2.149.0-beta.0 → 2.149.0-beta.1

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.
@@ -26,6 +26,7 @@ const tiers_1 = require("./math/tiers");
26
26
  const strictOraclePrice_1 = require("./oracles/strictOraclePrice");
27
27
  const fuel_1 = require("./math/fuel");
28
28
  const grpcUserAccountSubscriber_1 = require("./accounts/grpcUserAccountSubscriber");
29
+ const marginCalculation_1 = require("./marginCalculation");
29
30
  class User {
30
31
  get isSubscribed() {
31
32
  return this._isSubscribed && this.accountSubscriber.isSubscribed;
@@ -190,6 +191,19 @@ class User {
190
191
  positionFlag: 0,
191
192
  };
192
193
  }
194
+ getIsolatePerpPositionTokenAmount(perpMarketIndex) {
195
+ var _a;
196
+ const perpPosition = this.getPerpPosition(perpMarketIndex);
197
+ if (!perpPosition)
198
+ return numericConstants_1.ZERO;
199
+ const perpMarket = this.driftClient.getPerpMarketAccount(perpMarketIndex);
200
+ const spotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
201
+ if (perpPosition === undefined) {
202
+ return numericConstants_1.ZERO;
203
+ }
204
+ return (0, spotBalance_2.getTokenAmount)((_a = perpPosition.isolatedPositionScaledBalance) !== null && _a !== void 0 ? _a : numericConstants_1.ZERO, //TODO remove ? later
205
+ spotMarket, types_2.SpotBalanceType.DEPOSIT);
206
+ }
193
207
  getClonedPosition(position) {
194
208
  const clonedPosition = Object.assign({}, position);
195
209
  return clonedPosition;
@@ -288,36 +302,73 @@ class User {
288
302
  * calculates Free Collateral = Total collateral - margin requirement
289
303
  * @returns : Precision QUOTE_PRECISION
290
304
  */
291
- getFreeCollateral(marginCategory = 'Initial', enterHighLeverageMode = undefined) {
292
- const totalCollateral = this.getTotalCollateral(marginCategory, true);
293
- const marginRequirement = marginCategory === 'Initial'
294
- ? this.getInitialMarginRequirement(enterHighLeverageMode)
295
- : this.getMaintenanceMarginRequirement();
296
- const freeCollateral = totalCollateral.sub(marginRequirement);
297
- return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
305
+ getFreeCollateral(marginCategory = 'Initial', enterHighLeverageMode = false, perpMarketIndex) {
306
+ const { totalCollateral, marginRequirement, getIsolatedFreeCollateral } = this.getMarginCalculation(marginCategory, {
307
+ enteringHighLeverage: enterHighLeverageMode,
308
+ strict: marginCategory === 'Initial',
309
+ });
310
+ if (perpMarketIndex !== undefined) {
311
+ return getIsolatedFreeCollateral(perpMarketIndex);
312
+ }
313
+ else {
314
+ const freeCollateral = totalCollateral.sub(marginRequirement);
315
+ return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
316
+ }
298
317
  }
299
- /**
300
- * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
301
- */
302
- getMarginRequirement(marginCategory, liquidationBuffer, strict = false, includeOpenOrders = true, enteringHighLeverage = undefined) {
303
- return this.getTotalPerpPositionLiability(marginCategory, liquidationBuffer, includeOpenOrders, strict, enteringHighLeverage).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, includeOpenOrders, strict));
318
+ getMarginRequirement(marginCategory, liquidationBuffer, strict, includeOpenOrders, enteringHighLeverage, perpMarketIndex) {
319
+ const liquidationBufferMap = new Map();
320
+ if (liquidationBuffer && perpMarketIndex !== undefined) {
321
+ liquidationBufferMap.set(perpMarketIndex, liquidationBuffer);
322
+ }
323
+ else if (liquidationBuffer) {
324
+ liquidationBufferMap.set('cross', liquidationBuffer);
325
+ }
326
+ const marginCalc = this.getMarginCalculation(marginCategory, {
327
+ strict,
328
+ includeOpenOrders,
329
+ enteringHighLeverage,
330
+ liquidationBufferMap,
331
+ });
332
+ // If perpMarketIndex is provided, compute only for that market index
333
+ if (perpMarketIndex !== undefined) {
334
+ const isolatedMarginCalculation = marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
335
+ if (!isolatedMarginCalculation)
336
+ return numericConstants_1.ZERO;
337
+ const { marginRequirement, marginRequirementPlusBuffer } = isolatedMarginCalculation;
338
+ if (liquidationBuffer === null || liquidationBuffer === void 0 ? void 0 : liquidationBuffer.gt(numericConstants_1.ZERO)) {
339
+ return marginRequirementPlusBuffer;
340
+ }
341
+ return marginRequirement;
342
+ }
343
+ // Default: Cross margin requirement
344
+ if (liquidationBuffer === null || liquidationBuffer === void 0 ? void 0 : liquidationBuffer.gt(numericConstants_1.ZERO)) {
345
+ return marginCalc.marginRequirementPlusBuffer;
346
+ }
347
+ return marginCalc.marginRequirement;
304
348
  }
305
349
  /**
306
350
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
307
351
  */
308
- getInitialMarginRequirement(enterHighLeverageMode = undefined) {
309
- return this.getMarginRequirement('Initial', undefined, true, undefined, enterHighLeverageMode);
352
+ getInitialMarginRequirement(enterHighLeverageMode = false, perpMarketIndex) {
353
+ return this.getMarginRequirement('Initial', undefined, true, undefined, enterHighLeverageMode, perpMarketIndex);
310
354
  }
311
355
  /**
312
356
  * @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
313
357
  */
314
- getMaintenanceMarginRequirement(liquidationBuffer) {
315
- return this.getMarginRequirement('Maintenance', liquidationBuffer);
358
+ getMaintenanceMarginRequirement(liquidationBuffer, perpMarketIndex) {
359
+ return this.getMarginRequirement('Maintenance', liquidationBuffer, false, // strict default
360
+ true, // includeOpenOrders default
361
+ false, // enteringHighLeverage default
362
+ perpMarketIndex);
316
363
  }
317
364
  getActivePerpPositionsForUserAccount(userAccount) {
318
- return userAccount.perpPositions.filter((pos) => !pos.baseAssetAmount.eq(numericConstants_1.ZERO) ||
319
- !pos.quoteAssetAmount.eq(numericConstants_1.ZERO) ||
320
- !(pos.openOrders == 0));
365
+ return userAccount.perpPositions.filter((pos) => {
366
+ var _a;
367
+ return !pos.baseAssetAmount.eq(numericConstants_1.ZERO) ||
368
+ !pos.quoteAssetAmount.eq(numericConstants_1.ZERO) ||
369
+ !(pos.openOrders == 0) ||
370
+ ((_a = pos.isolatedPositionScaledBalance) === null || _a === void 0 ? void 0 : _a.gt(numericConstants_1.ZERO));
371
+ });
321
372
  }
322
373
  getActivePerpPositions() {
323
374
  const userAccount = this.getUserAccount();
@@ -555,27 +606,72 @@ class User {
555
606
  * calculates TotalCollateral: collateral + unrealized pnl
556
607
  * @returns : Precision QUOTE_PRECISION
557
608
  */
558
- getTotalCollateral(marginCategory = 'Initial', strict = false, includeOpenOrders = true, liquidationBuffer) {
559
- return this.getSpotMarketAssetValue(undefined, marginCategory, includeOpenOrders, strict).add(this.getUnrealizedPNL(true, undefined, marginCategory, strict, liquidationBuffer));
609
+ getTotalCollateral(marginCategory = 'Initial', strict = false, includeOpenOrders = true, liquidationBuffer, perpMarketIndex) {
610
+ const liquidationBufferMap = (() => {
611
+ if (liquidationBuffer && perpMarketIndex !== undefined) {
612
+ return new Map([[perpMarketIndex, liquidationBuffer]]);
613
+ }
614
+ else if (liquidationBuffer) {
615
+ return new Map([['cross', liquidationBuffer]]);
616
+ }
617
+ return new Map();
618
+ })();
619
+ const marginCalc = this.getMarginCalculation(marginCategory, {
620
+ strict,
621
+ includeOpenOrders,
622
+ liquidationBufferMap,
623
+ });
624
+ if (perpMarketIndex !== undefined) {
625
+ const { totalCollateral, totalCollateralBuffer } = marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
626
+ if (liquidationBuffer === null || liquidationBuffer === void 0 ? void 0 : liquidationBuffer.gt(numericConstants_1.ZERO)) {
627
+ return totalCollateralBuffer;
628
+ }
629
+ return totalCollateral;
630
+ }
631
+ if (liquidationBuffer === null || liquidationBuffer === void 0 ? void 0 : liquidationBuffer.gt(numericConstants_1.ZERO)) {
632
+ return marginCalc.totalCollateralBuffer;
633
+ }
634
+ return marginCalc.totalCollateral;
560
635
  }
561
636
  getLiquidationBuffer() {
562
- // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
563
- let liquidationBuffer = undefined;
637
+ const liquidationBufferMap = new Map();
564
638
  if (this.isBeingLiquidated()) {
565
- liquidationBuffer = new anchor_1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio);
639
+ liquidationBufferMap.set('cross', new anchor_1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio));
566
640
  }
567
- return liquidationBuffer;
641
+ for (const position of this.getActivePerpPositions()) {
642
+ if (position.positionFlag &
643
+ (types_2.PositionFlag.BeingLiquidated | types_2.PositionFlag.Bankruptcy)) {
644
+ liquidationBufferMap.set(position.marketIndex, new anchor_1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio));
645
+ }
646
+ }
647
+ return liquidationBufferMap;
568
648
  }
569
649
  /**
570
650
  * calculates User Health by comparing total collateral and maint. margin requirement
571
651
  * @returns : number (value from [0, 100])
572
652
  */
573
- getHealth() {
574
- if (this.isBeingLiquidated()) {
653
+ getHealth(perpMarketIndex) {
654
+ if (this.isCrossMarginBeingLiquidated() && !perpMarketIndex) {
575
655
  return 0;
576
656
  }
577
- const totalCollateral = this.getTotalCollateral('Maintenance');
578
- const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
657
+ if (perpMarketIndex &&
658
+ this.isIsolatedPositionBeingLiquidated(perpMarketIndex)) {
659
+ return 0;
660
+ }
661
+ const marginCalc = this.getMarginCalculation('Maintenance');
662
+ let totalCollateral;
663
+ let maintenanceMarginReq;
664
+ if (perpMarketIndex) {
665
+ const isolatedMarginCalc = marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
666
+ if (isolatedMarginCalc) {
667
+ totalCollateral = isolatedMarginCalc.totalCollateral;
668
+ maintenanceMarginReq = isolatedMarginCalc.marginRequirement;
669
+ }
670
+ }
671
+ else {
672
+ totalCollateral = marginCalc.totalCollateral;
673
+ maintenanceMarginReq = marginCalc.marginRequirement;
674
+ }
579
675
  let health;
580
676
  if (maintenanceMarginReq.eq(numericConstants_1.ZERO) && totalCollateral.gte(numericConstants_1.ZERO)) {
581
677
  health = 100;
@@ -734,8 +830,8 @@ class User {
734
830
  * calculates current user leverage which is (total liability size) / (net asset value)
735
831
  * @returns : Precision TEN_THOUSAND
736
832
  */
737
- getLeverage(includeOpenOrders = true) {
738
- return this.calculateLeverageFromComponents(this.getLeverageComponents(includeOpenOrders));
833
+ getLeverage(includeOpenOrders = true, perpMarketIndex) {
834
+ return this.calculateLeverageFromComponents(this.getLeverageComponents(includeOpenOrders, undefined, perpMarketIndex));
739
835
  }
740
836
  calculateLeverageFromComponents({ perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue, }) {
741
837
  const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
@@ -746,7 +842,26 @@ class User {
746
842
  }
747
843
  return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
748
844
  }
749
- getLeverageComponents(includeOpenOrders = true, marginCategory = undefined) {
845
+ getLeverageComponents(includeOpenOrders = true, marginCategory = undefined, perpMarketIndex) {
846
+ var _a;
847
+ if (perpMarketIndex) {
848
+ const perpPosition = this.getPerpPositionOrEmpty(perpMarketIndex);
849
+ const perpLiability = this.calculateWeightedPerpPositionLiability(perpPosition, marginCategory, undefined, includeOpenOrders);
850
+ const perpMarket = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
851
+ const oraclePriceData = this.getOracleDataForPerpMarket(perpPosition.marketIndex);
852
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
853
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(perpMarket.quoteSpotMarketIndex);
854
+ const strictOracle = new strictOraclePrice_1.StrictOraclePrice(quoteOraclePriceData.price, quoteOraclePriceData.twap);
855
+ const positionUnrealizedPnl = (0, position_2.calculatePositionPNL)(perpMarket, perpPosition, true, oraclePriceData);
856
+ const tokenAmount = (0, spotBalance_2.getTokenAmount)((_a = perpPosition.isolatedPositionScaledBalance) !== null && _a !== void 0 ? _a : numericConstants_1.ZERO, quoteSpotMarket, types_2.SpotBalanceType.DEPOSIT);
857
+ const spotAssetValue = (0, spotBalance_1.getStrictTokenValue)(tokenAmount, quoteSpotMarket.decimals, strictOracle);
858
+ return {
859
+ perpLiabilityValue: perpLiability,
860
+ perpPnl: positionUnrealizedPnl,
861
+ spotAssetValue,
862
+ spotLiabilityValue: numericConstants_1.ZERO,
863
+ };
864
+ }
750
865
  const perpLiability = this.getTotalPerpPositionLiability(marginCategory, undefined, includeOpenOrders);
751
866
  const perpPnl = this.getUnrealizedPNL(true, undefined, marginCategory);
752
867
  const { totalAssetValue: spotAssetValue, totalLiabilityValue: spotLiabilityValue, } = this.getSpotMarketAssetAndLiabilityValue(undefined, marginCategory, undefined, includeOpenOrders);
@@ -895,21 +1010,96 @@ class User {
895
1010
  return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
896
1011
  }
897
1012
  canBeLiquidated() {
898
- const liquidationBuffer = this.getLiquidationBuffer();
899
- const totalCollateral = this.getTotalCollateral('Maintenance', undefined, undefined, liquidationBuffer);
900
- const marginRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
901
- const canBeLiquidated = totalCollateral.lt(marginRequirement);
902
- return {
903
- canBeLiquidated,
904
- marginRequirement,
905
- totalCollateral,
906
- };
1013
+ // Deprecated signature retained for backward compatibility in type only
1014
+ // but implementation now delegates to the new Map-based API and returns cross margin status.
1015
+ const map = this.getLiquidationStatuses();
1016
+ const cross = map.get('cross');
1017
+ const isolatedPositions = new Map(Array.from(map.entries())
1018
+ .filter((e) => e[0] !== 'cross')
1019
+ .map(([key, value]) => [key, value]));
1020
+ return cross
1021
+ ? { ...cross, isolatedPositions }
1022
+ : {
1023
+ canBeLiquidated: false,
1024
+ marginRequirement: numericConstants_1.ZERO,
1025
+ totalCollateral: numericConstants_1.ZERO,
1026
+ isolatedPositions,
1027
+ };
1028
+ }
1029
+ /**
1030
+ * New API: Returns liquidation status for cross and each isolated perp position.
1031
+ * Map keys:
1032
+ * - 'cross' for cross margin
1033
+ * - marketIndex (number) for each isolated perp position
1034
+ */
1035
+ getLiquidationStatuses(marginCalc) {
1036
+ // If not provided, use buffer-aware calc for canBeLiquidated checks
1037
+ if (!marginCalc) {
1038
+ const liquidationBufferMap = this.getLiquidationBuffer();
1039
+ marginCalc = this.getMarginCalculation('Maintenance', {
1040
+ liquidationBufferMap,
1041
+ });
1042
+ }
1043
+ const result = new Map();
1044
+ // Cross margin status
1045
+ const crossTotalCollateral = marginCalc.totalCollateral;
1046
+ const crossMarginRequirement = marginCalc.marginRequirement;
1047
+ result.set('cross', {
1048
+ canBeLiquidated: crossTotalCollateral.lt(crossMarginRequirement),
1049
+ marginRequirement: crossMarginRequirement,
1050
+ totalCollateral: crossTotalCollateral,
1051
+ });
1052
+ // Isolated positions status
1053
+ for (const [marketIndex, isoCalc,] of marginCalc.isolatedMarginCalculations) {
1054
+ const isoTotalCollateral = isoCalc.totalCollateral;
1055
+ const isoMarginRequirement = isoCalc.marginRequirement;
1056
+ result.set(marketIndex, {
1057
+ canBeLiquidated: isoTotalCollateral.lt(isoMarginRequirement),
1058
+ marginRequirement: isoMarginRequirement,
1059
+ totalCollateral: isoTotalCollateral,
1060
+ });
1061
+ }
1062
+ return result;
907
1063
  }
908
1064
  isBeingLiquidated() {
1065
+ return (this.isCrossMarginBeingLiquidated() ||
1066
+ this.hasIsolatedPositionBeingLiquidated());
1067
+ }
1068
+ isCrossMarginBeingLiquidated() {
909
1069
  return ((this.getUserAccount().status &
910
1070
  (types_1.UserStatus.BEING_LIQUIDATED | types_1.UserStatus.BANKRUPT)) >
911
1071
  0);
912
1072
  }
1073
+ /** Returns true if cross margin is currently below maintenance requirement (no buffer). */
1074
+ canCrossMarginBeLiquidated(marginCalc) {
1075
+ const calc = marginCalc !== null && marginCalc !== void 0 ? marginCalc : this.getMarginCalculation('Maintenance');
1076
+ return calc.totalCollateral.lt(calc.marginRequirement);
1077
+ }
1078
+ hasIsolatedPositionBeingLiquidated() {
1079
+ return this.getActivePerpPositions().some((position) => (position.positionFlag &
1080
+ (types_2.PositionFlag.BeingLiquidated | types_2.PositionFlag.Bankruptcy)) >
1081
+ 0);
1082
+ }
1083
+ isIsolatedPositionBeingLiquidated(perpMarketIndex) {
1084
+ const position = this.getActivePerpPositions().find((position) => position.marketIndex === perpMarketIndex);
1085
+ return (((position === null || position === void 0 ? void 0 : position.positionFlag) &
1086
+ (types_2.PositionFlag.BeingLiquidated | types_2.PositionFlag.Bankruptcy)) >
1087
+ 0);
1088
+ }
1089
+ /** Returns true if any isolated perp position is currently below its maintenance requirement (no buffer). */
1090
+ getLiquidatableIsolatedPositions(marginCalc) {
1091
+ const liquidatableIsolatedPositions = [];
1092
+ const calc = marginCalc !== null && marginCalc !== void 0 ? marginCalc : this.getMarginCalculation('Maintenance');
1093
+ for (const [marketIndex, isoCalc] of calc.isolatedMarginCalculations) {
1094
+ if (this.canIsolatedPositionMarginBeLiquidated(isoCalc)) {
1095
+ liquidatableIsolatedPositions.push(marketIndex);
1096
+ }
1097
+ }
1098
+ return liquidatableIsolatedPositions;
1099
+ }
1100
+ canIsolatedPositionMarginBeLiquidated(isolatedMarginCalculation) {
1101
+ return isolatedMarginCalculation.totalCollateral.lt(isolatedMarginCalculation.marginRequirement);
1102
+ }
913
1103
  hasStatus(status) {
914
1104
  return (this.getUserAccount().status & status) > 0;
915
1105
  }
@@ -999,14 +1189,38 @@ class User {
999
1189
  * @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)
1000
1190
  * @returns Precision : PRICE_PRECISION
1001
1191
  */
1002
- liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false, offsetCollateral = numericConstants_1.ZERO, enteringHighLeverage = undefined) {
1003
- const totalCollateral = this.getTotalCollateral(marginCategory, false, includeOpenOrders);
1004
- const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders, enteringHighLeverage);
1005
- let freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1192
+ liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false, offsetCollateral = numericConstants_1.ZERO, enteringHighLeverage = false, marginType) {
1193
+ const market = this.driftClient.getPerpMarketAccount(marketIndex);
1006
1194
  const oracle = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
1007
1195
  const oraclePrice = this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
1008
- const market = this.driftClient.getPerpMarketAccount(marketIndex);
1009
1196
  const currentPerpPosition = this.getPerpPositionOrEmpty(marketIndex);
1197
+ if (marginType === 'Isolated') {
1198
+ const marginCalculation = this.getMarginCalculation(marginCategory, {
1199
+ strict: false,
1200
+ includeOpenOrders,
1201
+ enteringHighLeverage,
1202
+ });
1203
+ const isolatedMarginCalculation = marginCalculation.isolatedMarginCalculations.get(marketIndex);
1204
+ if (!isolatedMarginCalculation)
1205
+ return new anchor_1.BN(-1);
1206
+ const { totalCollateral, marginRequirement } = isolatedMarginCalculation;
1207
+ const freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1208
+ const freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, oraclePrice, marginCategory, includeOpenOrders, enteringHighLeverage);
1209
+ if (freeCollateralDelta.eq(numericConstants_1.ZERO)) {
1210
+ return new anchor_1.BN(-1);
1211
+ }
1212
+ const liqPriceDelta = freeCollateral
1213
+ .mul(numericConstants_1.QUOTE_PRECISION)
1214
+ .div(freeCollateralDelta);
1215
+ const liqPrice = oraclePrice.sub(liqPriceDelta);
1216
+ if (liqPrice.lt(numericConstants_1.ZERO)) {
1217
+ return new anchor_1.BN(-1);
1218
+ }
1219
+ return liqPrice;
1220
+ }
1221
+ const totalCollateral = this.getTotalCollateral(marginCategory, false, includeOpenOrders);
1222
+ const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders, enteringHighLeverage);
1223
+ let freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1010
1224
  positionBaseSizeChange = (0, orders_1.standardizeBaseAssetAmount)(positionBaseSizeChange, market.amm.orderStepSize);
1011
1225
  const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders, enteringHighLeverage);
1012
1226
  freeCollateral = freeCollateral.add(freeCollateralChangeFromNewPosition);
@@ -2004,5 +2218,159 @@ class User {
2004
2218
  activeSpotPositions: activeSpotMarkets,
2005
2219
  };
2006
2220
  }
2221
+ /**
2222
+ * Compute a consolidated margin snapshot once, without caching.
2223
+ * Consumers can use this to avoid duplicating work across separate calls.
2224
+ */
2225
+ getMarginCalculation(marginCategory = 'Initial', opts) {
2226
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
2227
+ const strict = (_a = opts === null || opts === void 0 ? void 0 : opts.strict) !== null && _a !== void 0 ? _a : false;
2228
+ const enteringHighLeverage = (_b = opts === null || opts === void 0 ? void 0 : opts.enteringHighLeverage) !== null && _b !== void 0 ? _b : false;
2229
+ const liquidationBufferMap = (_c = opts === null || opts === void 0 ? void 0 : opts.liquidationBufferMap) !== null && _c !== void 0 ? _c : new Map();
2230
+ const includeOpenOrders = (_d = opts === null || opts === void 0 ? void 0 : opts.includeOpenOrders) !== null && _d !== void 0 ? _d : true;
2231
+ // Equivalent to on-chain user_custom_margin_ratio
2232
+ const userCustomMarginRatio = marginCategory === 'Initial' ? this.getUserAccount().maxMarginRatio : 0;
2233
+ // Initialize calc via JS mirror of Rust/on-chain MarginCalculation
2234
+ const isolatedMarginBuffers = new Map();
2235
+ for (const [marketIndex, isolatedMarginBuffer,] of (_e = opts === null || opts === void 0 ? void 0 : opts.liquidationBufferMap) !== null && _e !== void 0 ? _e : new Map()) {
2236
+ if (marketIndex !== 'cross') {
2237
+ isolatedMarginBuffers.set(marketIndex, isolatedMarginBuffer);
2238
+ }
2239
+ }
2240
+ const ctx = marginCalculation_1.MarginContext.standard(marginCategory)
2241
+ .strictMode(strict)
2242
+ .setCrossMarginBuffer((_g = (_f = opts === null || opts === void 0 ? void 0 : opts.liquidationBufferMap) === null || _f === void 0 ? void 0 : _f.get('cross')) !== null && _g !== void 0 ? _g : numericConstants_1.ZERO)
2243
+ .setIsolatedMarginBuffers(isolatedMarginBuffers);
2244
+ const calc = new marginCalculation_1.MarginCalculation(ctx);
2245
+ // SPOT POSITIONS
2246
+ for (const spotPosition of this.getUserAccount().spotPositions) {
2247
+ if ((0, spotPosition_1.isSpotPositionAvailable)(spotPosition))
2248
+ continue;
2249
+ const isQuote = spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX;
2250
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
2251
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
2252
+ const twap5 = strict
2253
+ ? (0, oracles_1.calculateLiveOracleTwap)(spotMarket.historicalOracleData, oraclePriceData, new anchor_1.BN(Math.floor(Date.now() / 1000)), numericConstants_1.FIVE_MINUTE)
2254
+ : undefined;
2255
+ const strictOracle = new strictOraclePrice_1.StrictOraclePrice(oraclePriceData.price, twap5);
2256
+ if (isQuote) {
2257
+ const tokenAmount = (0, spotBalance_1.getSignedTokenAmount)((0, spotBalance_2.getTokenAmount)(spotPosition.scaledBalance, spotMarket, spotPosition.balanceType), spotPosition.balanceType);
2258
+ if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
2259
+ // add deposit value to total collateral
2260
+ const weightedTokenValue = this.getSpotAssetValue(tokenAmount, strictOracle, spotMarket, marginCategory);
2261
+ calc.addCrossMarginTotalCollateral(weightedTokenValue);
2262
+ }
2263
+ else {
2264
+ // borrow on quote contributes to margin requirement
2265
+ const tokenValueAbs = this.getSpotLiabilityValue(tokenAmount, strictOracle, spotMarket, marginCategory, (_h = liquidationBufferMap.get('cross')) !== null && _h !== void 0 ? _h : new anchor_1.BN(0)).abs();
2266
+ calc.addCrossMarginRequirement(tokenValueAbs, tokenValueAbs);
2267
+ }
2268
+ continue;
2269
+ }
2270
+ // Non-quote spot: worst-case simulation
2271
+ const { tokenAmount: worstCaseTokenAmount, ordersValue: worstCaseOrdersValue, } = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarket, strictOracle, marginCategory, userCustomMarginRatio, includeOpenOrders
2272
+ // false
2273
+ );
2274
+ if (includeOpenOrders) {
2275
+ // open order IM
2276
+ calc.addCrossMarginRequirement(new anchor_1.BN(spotPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT), numericConstants_1.ZERO);
2277
+ }
2278
+ if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
2279
+ const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, strictOracle, spotMarket, marginCategory);
2280
+ // asset side increases total collateral (weighted)
2281
+ calc.addCrossMarginTotalCollateral(baseAssetValue);
2282
+ }
2283
+ else if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
2284
+ // liability side increases margin requirement (weighted >= abs(token_value))
2285
+ const getSpotLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, strictOracle, spotMarket, marginCategory, liquidationBufferMap.get('cross'));
2286
+ calc.addCrossMarginRequirement(getSpotLiabilityValue.abs(), getSpotLiabilityValue.abs());
2287
+ }
2288
+ // orders value contributes to collateral or requirement
2289
+ if (worstCaseOrdersValue.gt(numericConstants_1.ZERO)) {
2290
+ calc.addCrossMarginTotalCollateral(worstCaseOrdersValue);
2291
+ }
2292
+ else if (worstCaseOrdersValue.lt(numericConstants_1.ZERO)) {
2293
+ const absVal = worstCaseOrdersValue.abs();
2294
+ calc.addCrossMarginRequirement(absVal, absVal);
2295
+ }
2296
+ }
2297
+ // PERP POSITIONS
2298
+ for (const marketPosition of this.getActivePerpPositions()) {
2299
+ const market = this.driftClient.getPerpMarketAccount(marketPosition.marketIndex);
2300
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
2301
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
2302
+ const oraclePriceData = this.getMMOracleDataForPerpMarket(market.marketIndex);
2303
+ const nonMmmOraclePriceData = this.getOracleDataForPerpMarket(market.marketIndex);
2304
+ // Worst-case perp liability and weighted pnl
2305
+ const { worstCaseBaseAssetAmount, worstCaseLiabilityValue } = (0, margin_1.calculateWorstCasePerpLiabilityValue)(marketPosition, market, nonMmmOraclePriceData.price, includeOpenOrders);
2306
+ // margin ratio for this perp
2307
+ const customMarginRatio = Math.max(userCustomMarginRatio, marketPosition.maxMarginRatio);
2308
+ let marginRatio = new anchor_1.BN((0, market_1.calculateMarketMarginRatio)(market, worstCaseBaseAssetAmount.abs(), marginCategory, customMarginRatio, this.isHighLeverageMode(marginCategory) || enteringHighLeverage));
2309
+ if ((0, types_1.isVariant)(market.status, 'settlement')) {
2310
+ marginRatio = numericConstants_1.ZERO;
2311
+ }
2312
+ // convert liability to quote value and apply margin ratio
2313
+ const quotePrice = strict
2314
+ ? anchor_1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min)
2315
+ : quoteOraclePriceData.price;
2316
+ let perpMarginRequirement = worstCaseLiabilityValue
2317
+ .mul(quotePrice)
2318
+ .div(numericConstants_1.PRICE_PRECISION)
2319
+ .mul(marginRatio)
2320
+ .div(numericConstants_1.MARGIN_PRECISION);
2321
+ // add open orders IM
2322
+ if (includeOpenOrders) {
2323
+ perpMarginRequirement = perpMarginRequirement.add(new anchor_1.BN(marketPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
2324
+ }
2325
+ // weighted unrealized pnl
2326
+ let positionUnrealizedPnl = (0, position_2.calculatePositionPNL)(market, marketPosition, true, oraclePriceData);
2327
+ let pnlQuotePrice;
2328
+ if (strict && positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
2329
+ pnlQuotePrice = anchor_1.BN.min(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
2330
+ }
2331
+ else if (strict && positionUnrealizedPnl.lt(numericConstants_1.ZERO)) {
2332
+ pnlQuotePrice = anchor_1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
2333
+ }
2334
+ else {
2335
+ pnlQuotePrice = quoteOraclePriceData.price;
2336
+ }
2337
+ positionUnrealizedPnl = positionUnrealizedPnl
2338
+ .mul(pnlQuotePrice)
2339
+ .div(numericConstants_1.PRICE_PRECISION);
2340
+ if (marginCategory !== undefined) {
2341
+ if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
2342
+ positionUnrealizedPnl = positionUnrealizedPnl
2343
+ .mul((0, market_1.calculateUnrealizedAssetWeight)(market, quoteSpotMarket, positionUnrealizedPnl, marginCategory, oraclePriceData))
2344
+ .div(new anchor_1.BN(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION));
2345
+ }
2346
+ }
2347
+ // Add perp contribution: isolated vs cross
2348
+ const isIsolated = this.isPerpPositionIsolated(marketPosition);
2349
+ if (isIsolated) {
2350
+ // derive isolated quote deposit value, mirroring on-chain logic
2351
+ let depositValue = numericConstants_1.ZERO;
2352
+ if ((_j = marketPosition.isolatedPositionScaledBalance) === null || _j === void 0 ? void 0 : _j.gt(numericConstants_1.ZERO)) {
2353
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
2354
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
2355
+ const strictQuote = new strictOraclePrice_1.StrictOraclePrice(quoteOraclePriceData.price, strict
2356
+ ? quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
2357
+ : undefined);
2358
+ const quoteTokenAmount = (0, spotBalance_2.getTokenAmount)((_k = marketPosition.isolatedPositionScaledBalance) !== null && _k !== void 0 ? _k : numericConstants_1.ZERO, quoteSpotMarket, types_2.SpotBalanceType.DEPOSIT);
2359
+ depositValue = (0, spotBalance_1.getStrictTokenValue)(quoteTokenAmount, quoteSpotMarket.decimals, strictQuote);
2360
+ }
2361
+ calc.addIsolatedMarginCalculation(market.marketIndex, depositValue, positionUnrealizedPnl, worstCaseLiabilityValue, perpMarginRequirement);
2362
+ calc.addPerpLiabilityValue(worstCaseLiabilityValue);
2363
+ }
2364
+ else {
2365
+ // cross: add to global requirement and collateral
2366
+ calc.addCrossMarginRequirement(perpMarginRequirement, worstCaseLiabilityValue);
2367
+ calc.addCrossMarginTotalCollateral(positionUnrealizedPnl);
2368
+ }
2369
+ }
2370
+ return calc;
2371
+ }
2372
+ isPerpPositionIsolated(perpPosition) {
2373
+ return (perpPosition.positionFlag & types_2.PositionFlag.IsolatedPosition) !== 0;
2374
+ }
2007
2375
  }
2008
2376
  exports.User = User;
@@ -0,0 +1,80 @@
1
+ /// <reference types="bn.js" />
2
+ import { BN } from '@coral-xyz/anchor';
3
+ import { MarketType } from './types';
4
+ export type MarginCategory = 'Initial' | 'Maintenance' | 'Fill';
5
+ export type MarginCalculationMode = {
6
+ type: 'Standard';
7
+ } | {
8
+ type: 'Liquidation';
9
+ };
10
+ export declare class MarketIdentifier {
11
+ marketType: MarketType;
12
+ marketIndex: number;
13
+ private constructor();
14
+ static spot(marketIndex: number): MarketIdentifier;
15
+ static perp(marketIndex: number): MarketIdentifier;
16
+ equals(other: MarketIdentifier | undefined): boolean;
17
+ }
18
+ export declare class MarginContext {
19
+ marginType: MarginCategory;
20
+ mode: MarginCalculationMode;
21
+ strict: boolean;
22
+ ignoreInvalidDepositOracles: boolean;
23
+ isolatedMarginBuffers: Map<number, BN>;
24
+ crossMarginBuffer: BN;
25
+ private constructor();
26
+ static standard(marginType: MarginCategory): MarginContext;
27
+ static liquidation(crossMarginBuffer: BN, isolatedMarginBuffers: Map<number, BN>): MarginContext;
28
+ strictMode(strict: boolean): this;
29
+ ignoreInvalidDeposits(ignore: boolean): this;
30
+ setCrossMarginBuffer(crossMarginBuffer: BN): this;
31
+ setIsolatedMarginBuffers(isolatedMarginBuffers: Map<number, BN>): this;
32
+ setIsolatedMarginBuffer(marketIndex: number, isolatedMarginBuffer: BN): this;
33
+ }
34
+ export declare class IsolatedMarginCalculation {
35
+ marginRequirement: BN;
36
+ totalCollateral: BN;
37
+ totalCollateralBuffer: BN;
38
+ marginRequirementPlusBuffer: BN;
39
+ constructor();
40
+ getTotalCollateralPlusBuffer(): BN;
41
+ meetsMarginRequirement(): boolean;
42
+ meetsMarginRequirementWithBuffer(): boolean;
43
+ marginShortage(): BN;
44
+ }
45
+ export declare class MarginCalculation {
46
+ context: MarginContext;
47
+ totalCollateral: BN;
48
+ totalCollateralBuffer: BN;
49
+ marginRequirement: BN;
50
+ marginRequirementPlusBuffer: BN;
51
+ isolatedMarginCalculations: Map<number, IsolatedMarginCalculation>;
52
+ allDepositOraclesValid: boolean;
53
+ allLiabilityOraclesValid: boolean;
54
+ withPerpIsolatedLiability: boolean;
55
+ withSpotIsolatedLiability: boolean;
56
+ totalPerpLiabilityValue: BN;
57
+ trackedMarketMarginRequirement: BN;
58
+ fuelDeposits: number;
59
+ fuelBorrows: number;
60
+ fuelPositions: number;
61
+ constructor(context: MarginContext);
62
+ addCrossMarginTotalCollateral(delta: BN): void;
63
+ addCrossMarginRequirement(marginRequirement: BN, liabilityValue: BN): void;
64
+ addIsolatedMarginCalculation(marketIndex: number, depositValue: BN, pnl: BN, liabilityValue: BN, marginRequirement: BN): void;
65
+ addPerpLiabilityValue(perpLiabilityValue: BN): void;
66
+ updateAllDepositOraclesValid(valid: boolean): void;
67
+ updateAllLiabilityOraclesValid(valid: boolean): void;
68
+ updateWithSpotIsolatedLiability(isolated: boolean): void;
69
+ updateWithPerpIsolatedLiability(isolated: boolean): void;
70
+ getCrossTotalCollateralPlusBuffer(): BN;
71
+ meetsCrossMarginRequirement(): boolean;
72
+ meetsCrossMarginRequirementWithBuffer(): boolean;
73
+ meetsMarginRequirement(): boolean;
74
+ meetsMarginRequirementWithBuffer(): boolean;
75
+ getCrossFreeCollateral(): BN;
76
+ getIsolatedFreeCollateral(marketIndex: number): BN;
77
+ getIsolatedMarginCalculation(marketIndex: number): IsolatedMarginCalculation | undefined;
78
+ hasIsolatedMarginCalculation(marketIndex: number): boolean;
79
+ }
80
+ //# sourceMappingURL=marginCalculation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marginCalculation.d.ts","sourceRoot":"","sources":["../../src/marginCalculation.ts"],"names":[],"mappings":"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"}