@drift-labs/sdk 2.145.0 → 2.146.0-alpha.13

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.
Files changed (99) hide show
  1. package/.env +4 -0
  2. package/VERSION +1 -1
  3. package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
  4. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
  5. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
  6. package/lib/browser/adminClient.d.ts +5 -1
  7. package/lib/browser/adminClient.js +57 -23
  8. package/lib/browser/constants/numericConstants.d.ts +2 -0
  9. package/lib/browser/constants/numericConstants.js +5 -1
  10. package/lib/browser/constants/perpMarkets.js +0 -2
  11. package/lib/browser/decode/user.js +4 -0
  12. package/lib/browser/driftClient.d.ts +25 -10
  13. package/lib/browser/driftClient.js +238 -41
  14. package/lib/browser/driftClientConfig.d.ts +7 -2
  15. package/lib/browser/idl/drift.json +245 -22
  16. package/lib/browser/index.d.ts +4 -0
  17. package/lib/browser/index.js +9 -1
  18. package/lib/browser/marginCalculation.d.ts +86 -0
  19. package/lib/browser/marginCalculation.js +209 -0
  20. package/lib/browser/math/margin.d.ts +1 -1
  21. package/lib/browser/math/margin.js +8 -1
  22. package/lib/browser/math/position.d.ts +1 -0
  23. package/lib/browser/math/position.js +10 -2
  24. package/lib/browser/math/spotPosition.d.ts +1 -1
  25. package/lib/browser/math/spotPosition.js +3 -2
  26. package/lib/browser/math/superStake.d.ts +3 -2
  27. package/lib/browser/types.d.ts +13 -0
  28. package/lib/browser/types.js +12 -1
  29. package/lib/browser/user.d.ts +59 -11
  30. package/lib/browser/user.js +348 -43
  31. package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
  32. package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
  33. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
  34. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
  35. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
  36. package/lib/node/adminClient.d.ts +5 -1
  37. package/lib/node/adminClient.d.ts.map +1 -1
  38. package/lib/node/adminClient.js +57 -23
  39. package/lib/node/constants/numericConstants.d.ts +2 -0
  40. package/lib/node/constants/numericConstants.d.ts.map +1 -1
  41. package/lib/node/constants/numericConstants.js +5 -1
  42. package/lib/node/constants/perpMarkets.d.ts.map +1 -1
  43. package/lib/node/constants/perpMarkets.js +0 -2
  44. package/lib/node/decode/user.d.ts.map +1 -1
  45. package/lib/node/decode/user.js +4 -0
  46. package/lib/node/driftClient.d.ts +25 -10
  47. package/lib/node/driftClient.d.ts.map +1 -1
  48. package/lib/node/driftClient.js +238 -41
  49. package/lib/node/driftClientConfig.d.ts +7 -2
  50. package/lib/node/driftClientConfig.d.ts.map +1 -1
  51. package/lib/node/idl/drift.json +245 -22
  52. package/lib/node/index.d.ts +4 -0
  53. package/lib/node/index.d.ts.map +1 -1
  54. package/lib/node/index.js +9 -1
  55. package/lib/node/marginCalculation.d.ts +87 -0
  56. package/lib/node/marginCalculation.d.ts.map +1 -0
  57. package/lib/node/marginCalculation.js +209 -0
  58. package/lib/node/math/margin.d.ts +1 -1
  59. package/lib/node/math/margin.d.ts.map +1 -1
  60. package/lib/node/math/margin.js +8 -1
  61. package/lib/node/math/position.d.ts +1 -0
  62. package/lib/node/math/position.d.ts.map +1 -1
  63. package/lib/node/math/position.js +10 -2
  64. package/lib/node/math/spotPosition.d.ts +1 -1
  65. package/lib/node/math/spotPosition.d.ts.map +1 -1
  66. package/lib/node/math/spotPosition.js +3 -2
  67. package/lib/node/math/superStake.d.ts +3 -2
  68. package/lib/node/math/superStake.d.ts.map +1 -1
  69. package/lib/node/types.d.ts +13 -0
  70. package/lib/node/types.d.ts.map +1 -1
  71. package/lib/node/types.js +12 -1
  72. package/lib/node/user.d.ts +59 -11
  73. package/lib/node/user.d.ts.map +1 -1
  74. package/lib/node/user.js +348 -43
  75. package/package.json +1 -1
  76. package/scripts/deposit-isolated-positions.ts +110 -0
  77. package/scripts/single-grpc-client-test.ts +71 -21
  78. package/scripts/withdraw-isolated-positions.ts +174 -0
  79. package/src/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
  80. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
  81. package/src/adminClient.ts +74 -25
  82. package/src/constants/numericConstants.ts +5 -0
  83. package/src/constants/perpMarkets.ts +0 -3
  84. package/src/decode/user.ts +7 -1
  85. package/src/driftClient.ts +465 -52
  86. package/src/driftClientConfig.ts +15 -8
  87. package/src/idl/drift.json +246 -23
  88. package/src/index.ts +4 -0
  89. package/src/margin/README.md +143 -0
  90. package/src/marginCalculation.ts +306 -0
  91. package/src/math/margin.ts +13 -1
  92. package/src/math/position.ts +12 -2
  93. package/src/math/spotPosition.ts +6 -2
  94. package/src/types.ts +16 -0
  95. package/src/user.ts +623 -81
  96. package/tests/amm/test.ts +1 -1
  97. package/tests/dlob/helpers.ts +6 -3
  98. package/tests/user/getMarginCalculation.ts +405 -0
  99. package/tests/user/test.ts +0 -7
@@ -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;
@@ -186,8 +187,19 @@ class User {
186
187
  lastQuoteAssetAmountPerLp: numericConstants_1.ZERO,
187
188
  perLpBase: 0,
188
189
  maxMarginRatio: 0,
190
+ positionFlag: 0,
191
+ isolatedPositionScaledBalance: numericConstants_1.ZERO,
189
192
  };
190
193
  }
194
+ getIsolatePerpPositionTokenAmount(perpMarketIndex) {
195
+ const perpPosition = this.getPerpPosition(perpMarketIndex);
196
+ const perpMarket = this.driftClient.getPerpMarketAccount(perpMarketIndex);
197
+ const spotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
198
+ if (perpPosition === undefined) {
199
+ return numericConstants_1.ZERO;
200
+ }
201
+ return (0, spotBalance_2.getTokenAmount)(perpPosition.isolatedPositionScaledBalance, spotMarket, types_2.SpotBalanceType.DEPOSIT);
202
+ }
191
203
  getClonedPosition(position) {
192
204
  const clonedPosition = Object.assign({}, position);
193
205
  return clonedPosition;
@@ -286,36 +298,54 @@ class User {
286
298
  * calculates Free Collateral = Total collateral - margin requirement
287
299
  * @returns : Precision QUOTE_PRECISION
288
300
  */
289
- getFreeCollateral(marginCategory = 'Initial', enterHighLeverageMode = undefined) {
290
- const totalCollateral = this.getTotalCollateral(marginCategory, true);
291
- const marginRequirement = marginCategory === 'Initial'
292
- ? this.getInitialMarginRequirement(enterHighLeverageMode)
293
- : this.getMaintenanceMarginRequirement();
294
- const freeCollateral = totalCollateral.sub(marginRequirement);
295
- return freeCollateral.gte(numericConstants_1.ZERO) ? freeCollateral : numericConstants_1.ZERO;
301
+ getFreeCollateral(marginCategory = 'Initial', enterHighLeverageMode = false, perpMarketIndex) {
302
+ const marginCalc = this.getMarginCalculation(marginCategory, {
303
+ enteringHighLeverage: enterHighLeverageMode,
304
+ });
305
+ if (perpMarketIndex !== undefined) {
306
+ return marginCalc.getIsolatedFreeCollateral(perpMarketIndex);
307
+ }
308
+ else {
309
+ return marginCalc.getCrossFreeCollateral();
310
+ }
296
311
  }
297
- /**
298
- * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
299
- */
300
- getMarginRequirement(marginCategory, liquidationBuffer, strict = false, includeOpenOrders = true, enteringHighLeverage = undefined) {
301
- return this.getTotalPerpPositionLiability(marginCategory, liquidationBuffer, includeOpenOrders, strict, enteringHighLeverage).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, includeOpenOrders, strict));
312
+ getMarginRequirement(marginCategory, liquidationBuffer, strict, includeOpenOrders, enteringHighLeverage, perpMarketIndex) {
313
+ const marginCalc = this.getMarginCalculation(marginCategory, {
314
+ strict,
315
+ includeOpenOrders,
316
+ enteringHighLeverage,
317
+ liquidationBuffer,
318
+ });
319
+ // If perpMarketIndex is provided, compute only for that market index
320
+ if (perpMarketIndex !== undefined) {
321
+ const isolatedMarginCalculation = marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
322
+ const { marginRequirement } = isolatedMarginCalculation;
323
+ return marginRequirement;
324
+ }
325
+ // Default: Cross margin requirement
326
+ // TODO: should we be using plus buffer sometimes?
327
+ return marginCalc.marginRequirement;
302
328
  }
303
329
  /**
304
330
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
305
331
  */
306
- getInitialMarginRequirement(enterHighLeverageMode = undefined) {
307
- return this.getMarginRequirement('Initial', undefined, true, undefined, enterHighLeverageMode);
332
+ getInitialMarginRequirement(enterHighLeverageMode = false, perpMarketIndex) {
333
+ return this.getMarginRequirement('Initial', undefined, false, undefined, enterHighLeverageMode, perpMarketIndex);
308
334
  }
309
335
  /**
310
336
  * @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
311
337
  */
312
- getMaintenanceMarginRequirement(liquidationBuffer) {
313
- return this.getMarginRequirement('Maintenance', liquidationBuffer);
338
+ getMaintenanceMarginRequirement(liquidationBuffer, perpMarketIndex) {
339
+ return this.getMarginRequirement('Maintenance', liquidationBuffer, true, // strict default
340
+ true, // includeOpenOrders default
341
+ false, // enteringHighLeverage default
342
+ perpMarketIndex);
314
343
  }
315
344
  getActivePerpPositionsForUserAccount(userAccount) {
316
345
  return userAccount.perpPositions.filter((pos) => !pos.baseAssetAmount.eq(numericConstants_1.ZERO) ||
317
346
  !pos.quoteAssetAmount.eq(numericConstants_1.ZERO) ||
318
- !(pos.openOrders == 0));
347
+ !(pos.openOrders == 0) ||
348
+ pos.isolatedPositionScaledBalance.gt(numericConstants_1.ZERO));
319
349
  }
320
350
  getActivePerpPositions() {
321
351
  const userAccount = this.getUserAccount();
@@ -353,6 +383,8 @@ class User {
353
383
  .filter((pos) => marketIndex !== undefined ? pos.marketIndex === marketIndex : true)
354
384
  .reduce((unrealizedPnl, perpPosition) => {
355
385
  const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
386
+ if (!market)
387
+ return unrealizedPnl;
356
388
  const oraclePriceData = this.getMMOracleDataForPerpMarket(market.marketIndex);
357
389
  const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
358
390
  const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
@@ -553,8 +585,17 @@ class User {
553
585
  * calculates TotalCollateral: collateral + unrealized pnl
554
586
  * @returns : Precision QUOTE_PRECISION
555
587
  */
556
- getTotalCollateral(marginCategory = 'Initial', strict = false, includeOpenOrders = true, liquidationBuffer) {
557
- return this.getSpotMarketAssetValue(undefined, marginCategory, includeOpenOrders, strict).add(this.getUnrealizedPNL(true, undefined, marginCategory, strict, liquidationBuffer));
588
+ getTotalCollateral(marginCategory = 'Initial', strict = false, includeOpenOrders = true, liquidationBuffer, perpMarketIndex) {
589
+ const marginCalc = this.getMarginCalculation(marginCategory, {
590
+ strict,
591
+ includeOpenOrders,
592
+ liquidationBuffer,
593
+ });
594
+ if (perpMarketIndex !== undefined) {
595
+ return marginCalc.isolatedMarginCalculations.get(perpMarketIndex)
596
+ .totalCollateral;
597
+ }
598
+ return marginCalc.totalCollateral;
558
599
  }
559
600
  getLiquidationBuffer() {
560
601
  // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
@@ -568,12 +609,24 @@ class User {
568
609
  * calculates User Health by comparing total collateral and maint. margin requirement
569
610
  * @returns : number (value from [0, 100])
570
611
  */
571
- getHealth() {
572
- if (this.isBeingLiquidated()) {
612
+ getHealth(perpMarketIndex) {
613
+ const marginCalc = this.getMarginCalculation('Maintenance');
614
+ if (this.isCrossMarginBeingLiquidated(marginCalc) && !perpMarketIndex) {
573
615
  return 0;
574
616
  }
575
- const totalCollateral = this.getTotalCollateral('Maintenance');
576
- const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
617
+ let totalCollateral;
618
+ let maintenanceMarginReq;
619
+ if (perpMarketIndex) {
620
+ const isolatedMarginCalc = marginCalc.isolatedMarginCalculations.get(perpMarketIndex);
621
+ if (isolatedMarginCalc) {
622
+ totalCollateral = isolatedMarginCalc.totalCollateral;
623
+ maintenanceMarginReq = isolatedMarginCalc.marginRequirement;
624
+ }
625
+ }
626
+ else {
627
+ totalCollateral = marginCalc.totalCollateral;
628
+ maintenanceMarginReq = marginCalc.marginRequirement;
629
+ }
577
630
  let health;
578
631
  if (maintenanceMarginReq.eq(numericConstants_1.ZERO) && totalCollateral.gte(numericConstants_1.ZERO)) {
579
632
  health = 100;
@@ -589,6 +642,8 @@ class User {
589
642
  }
590
643
  calculateWeightedPerpPositionLiability(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict = false, enteringHighLeverage = undefined) {
591
644
  const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
645
+ if (!market)
646
+ return numericConstants_1.ZERO;
592
647
  let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
593
648
  if ((0, types_1.isVariant)(market.status, 'settlement')) {
594
649
  valuationPrice = market.expiryPrice;
@@ -732,8 +787,8 @@ class User {
732
787
  * calculates current user leverage which is (total liability size) / (net asset value)
733
788
  * @returns : Precision TEN_THOUSAND
734
789
  */
735
- getLeverage(includeOpenOrders = true) {
736
- return this.calculateLeverageFromComponents(this.getLeverageComponents(includeOpenOrders));
790
+ getLeverage(includeOpenOrders = true, perpMarketIndex) {
791
+ return this.calculateLeverageFromComponents(this.getLeverageComponents(includeOpenOrders, undefined, perpMarketIndex));
737
792
  }
738
793
  calculateLeverageFromComponents({ perpLiabilityValue, perpPnl, spotAssetValue, spotLiabilityValue, }) {
739
794
  const totalLiabilityValue = perpLiabilityValue.add(spotLiabilityValue);
@@ -744,7 +799,24 @@ class User {
744
799
  }
745
800
  return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
746
801
  }
747
- getLeverageComponents(includeOpenOrders = true, marginCategory = undefined) {
802
+ getLeverageComponents(includeOpenOrders = true, marginCategory = undefined, perpMarketIndex) {
803
+ if (perpMarketIndex) {
804
+ const perpPosition = this.getPerpPositionOrEmpty(perpMarketIndex);
805
+ const perpLiability = this.calculateWeightedPerpPositionLiability(perpPosition, marginCategory, undefined, includeOpenOrders);
806
+ const perpMarket = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
807
+ const oraclePriceData = this.getOracleDataForPerpMarket(perpPosition.marketIndex);
808
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
809
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(perpMarket.quoteSpotMarketIndex);
810
+ const strictOracle = new strictOraclePrice_1.StrictOraclePrice(quoteOraclePriceData.price, quoteOraclePriceData.twap);
811
+ const positionUnrealizedPnl = (0, position_2.calculatePositionPNL)(perpMarket, perpPosition, true, oraclePriceData);
812
+ const spotAssetValue = (0, spotBalance_1.getStrictTokenValue)(perpPosition.isolatedPositionScaledBalance, quoteSpotMarket.decimals, strictOracle);
813
+ return {
814
+ perpLiabilityValue: perpLiability,
815
+ perpPnl: positionUnrealizedPnl,
816
+ spotAssetValue,
817
+ spotLiabilityValue: numericConstants_1.ZERO,
818
+ };
819
+ }
748
820
  const perpLiability = this.getTotalPerpPositionLiability(marginCategory, undefined, includeOpenOrders);
749
821
  const perpPnl = this.getUnrealizedPNL(true, undefined, marginCategory);
750
822
  const { totalAssetValue: spotAssetValue, totalLiabilityValue: spotLiabilityValue, } = this.getSpotMarketAssetAndLiabilityValue(undefined, marginCategory, undefined, includeOpenOrders);
@@ -893,20 +965,78 @@ class User {
893
965
  return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
894
966
  }
895
967
  canBeLiquidated() {
896
- const liquidationBuffer = this.getLiquidationBuffer();
897
- const totalCollateral = this.getTotalCollateral('Maintenance', undefined, undefined, liquidationBuffer);
898
- const marginRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
899
- const canBeLiquidated = totalCollateral.lt(marginRequirement);
900
- return {
901
- canBeLiquidated,
902
- marginRequirement,
903
- totalCollateral,
904
- };
968
+ // Deprecated signature retained for backward compatibility in type only
969
+ // but implementation now delegates to the new Map-based API and returns cross margin status.
970
+ const map = this.getLiquidationStatuses();
971
+ const cross = map.get('cross');
972
+ return cross
973
+ ? { ...cross, liquidationStatuses: map }
974
+ : {
975
+ canBeLiquidated: false,
976
+ marginRequirement: numericConstants_1.ZERO,
977
+ totalCollateral: numericConstants_1.ZERO,
978
+ liquidationStatuses: map,
979
+ };
980
+ }
981
+ /**
982
+ * New API: Returns liquidation status for cross and each isolated perp position.
983
+ * Map keys:
984
+ * - 'cross' for cross margin
985
+ * - marketIndex (number) for each isolated perp position
986
+ */
987
+ getLiquidationStatuses(marginCalc) {
988
+ // If not provided, use buffer-aware calc for canBeLiquidated checks
989
+ if (!marginCalc) {
990
+ const liquidationBuffer = this.getLiquidationBuffer();
991
+ marginCalc = this.getMarginCalculation('Maintenance', {
992
+ liquidationBuffer,
993
+ });
994
+ }
995
+ const result = new Map();
996
+ // Cross margin status
997
+ const crossTotalCollateral = marginCalc.totalCollateral;
998
+ const crossMarginRequirement = marginCalc.marginRequirement;
999
+ result.set('cross', {
1000
+ canBeLiquidated: crossTotalCollateral.lt(crossMarginRequirement),
1001
+ marginRequirement: crossMarginRequirement,
1002
+ totalCollateral: crossTotalCollateral,
1003
+ });
1004
+ // Isolated positions status
1005
+ for (const [marketIndex, isoCalc,] of marginCalc.isolatedMarginCalculations) {
1006
+ const isoTotalCollateral = isoCalc.totalCollateral;
1007
+ const isoMarginRequirement = isoCalc.marginRequirement;
1008
+ result.set(marketIndex, {
1009
+ canBeLiquidated: isoTotalCollateral.lt(isoMarginRequirement),
1010
+ marginRequirement: isoMarginRequirement,
1011
+ totalCollateral: isoTotalCollateral,
1012
+ });
1013
+ }
1014
+ return result;
905
1015
  }
906
- isBeingLiquidated() {
907
- return ((this.getUserAccount().status &
1016
+ isBeingLiquidated(marginCalc) {
1017
+ // Consider on-chain flags OR computed margin status (cross or any isolated)
1018
+ const hasOnChainFlag = (this.getUserAccount().status &
908
1019
  (types_1.UserStatus.BEING_LIQUIDATED | types_1.UserStatus.BANKRUPT)) >
909
- 0);
1020
+ 0;
1021
+ const calc = marginCalc !== null && marginCalc !== void 0 ? marginCalc : this.getMarginCalculation('Maintenance');
1022
+ return (hasOnChainFlag ||
1023
+ this.isCrossMarginBeingLiquidated(calc) ||
1024
+ this.isIsolatedMarginBeingLiquidated(calc));
1025
+ }
1026
+ /** Returns true if cross margin is currently below maintenance requirement (no buffer). */
1027
+ isCrossMarginBeingLiquidated(marginCalc) {
1028
+ const calc = marginCalc !== null && marginCalc !== void 0 ? marginCalc : this.getMarginCalculation('Maintenance');
1029
+ return calc.totalCollateral.lt(calc.marginRequirement);
1030
+ }
1031
+ /** Returns true if any isolated perp position is currently below its maintenance requirement (no buffer). */
1032
+ isIsolatedMarginBeingLiquidated(marginCalc) {
1033
+ const calc = marginCalc !== null && marginCalc !== void 0 ? marginCalc : this.getMarginCalculation('Maintenance');
1034
+ for (const [, isoCalc] of calc.isolatedMarginCalculations) {
1035
+ if (isoCalc.totalCollateral.lt(isoCalc.marginRequirement)) {
1036
+ return true;
1037
+ }
1038
+ }
1039
+ return false;
910
1040
  }
911
1041
  hasStatus(status) {
912
1042
  return (this.getUserAccount().status & status) > 0;
@@ -997,14 +1127,36 @@ class User {
997
1127
  * @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)
998
1128
  * @returns Precision : PRICE_PRECISION
999
1129
  */
1000
- liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false, offsetCollateral = numericConstants_1.ZERO, enteringHighLeverage = undefined) {
1001
- const totalCollateral = this.getTotalCollateral(marginCategory, false, includeOpenOrders);
1002
- const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders, enteringHighLeverage);
1003
- let freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1130
+ liquidationPrice(marketIndex, positionBaseSizeChange = numericConstants_1.ZERO, estimatedEntryPrice = numericConstants_1.ZERO, marginCategory = 'Maintenance', includeOpenOrders = false, offsetCollateral = numericConstants_1.ZERO, enteringHighLeverage = false, marginType) {
1131
+ const market = this.driftClient.getPerpMarketAccount(marketIndex);
1004
1132
  const oracle = this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
1005
1133
  const oraclePrice = this.driftClient.getOracleDataForPerpMarket(marketIndex).price;
1006
- const market = this.driftClient.getPerpMarketAccount(marketIndex);
1007
1134
  const currentPerpPosition = this.getPerpPositionOrEmpty(marketIndex);
1135
+ if (marginType === 'Isolated') {
1136
+ const marginCalculation = this.getMarginCalculation(marginCategory, {
1137
+ strict: false,
1138
+ includeOpenOrders,
1139
+ enteringHighLeverage,
1140
+ });
1141
+ const isolatedMarginCalculation = marginCalculation.isolatedMarginCalculations.get(marketIndex);
1142
+ const { totalCollateral, marginRequirement } = isolatedMarginCalculation;
1143
+ const freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1144
+ const freeCollateralDelta = this.calculateFreeCollateralDeltaForPerp(market, currentPerpPosition, positionBaseSizeChange, oraclePrice, marginCategory, includeOpenOrders, enteringHighLeverage);
1145
+ if (freeCollateralDelta.eq(numericConstants_1.ZERO)) {
1146
+ return new anchor_1.BN(-1);
1147
+ }
1148
+ const liqPriceDelta = freeCollateral
1149
+ .mul(numericConstants_1.QUOTE_PRECISION)
1150
+ .div(freeCollateralDelta);
1151
+ const liqPrice = oraclePrice.sub(liqPriceDelta);
1152
+ if (liqPrice.lt(numericConstants_1.ZERO)) {
1153
+ return new anchor_1.BN(-1);
1154
+ }
1155
+ return liqPrice;
1156
+ }
1157
+ const totalCollateral = this.getTotalCollateral(marginCategory, false, includeOpenOrders);
1158
+ const marginRequirement = this.getMarginRequirement(marginCategory, undefined, false, includeOpenOrders, enteringHighLeverage);
1159
+ let freeCollateral = anchor_1.BN.max(numericConstants_1.ZERO, totalCollateral.sub(marginRequirement)).add(offsetCollateral);
1008
1160
  positionBaseSizeChange = (0, orders_1.standardizeBaseAssetAmount)(positionBaseSizeChange, market.amm.orderStepSize);
1009
1161
  const freeCollateralChangeFromNewPosition = this.calculateEntriesEffectOnFreeCollateral(market, oraclePrice, currentPerpPosition, positionBaseSizeChange, estimatedEntryPrice, includeOpenOrders, enteringHighLeverage);
1010
1162
  freeCollateral = freeCollateral.add(freeCollateralChangeFromNewPosition);
@@ -2002,5 +2154,158 @@ class User {
2002
2154
  activeSpotPositions: activeSpotMarkets,
2003
2155
  };
2004
2156
  }
2157
+ /**
2158
+ * Compute a consolidated margin snapshot once, without caching.
2159
+ * Consumers can use this to avoid duplicating work across separate calls.
2160
+ */
2161
+ // TODO: need another param to tell it give it back leverage compnents
2162
+ // TODO: change get leverage functions need to pull the right values from
2163
+ getMarginCalculation(marginCategory = 'Initial', opts) {
2164
+ var _a, _b, _c;
2165
+ const strict = (_a = opts === null || opts === void 0 ? void 0 : opts.strict) !== null && _a !== void 0 ? _a : false;
2166
+ const enteringHighLeverage = (_b = opts === null || opts === void 0 ? void 0 : opts.enteringHighLeverage) !== null && _b !== void 0 ? _b : false;
2167
+ const includeOpenOrders = (_c = opts === null || opts === void 0 ? void 0 : opts.includeOpenOrders) !== null && _c !== void 0 ? _c : true; // TODO: remove this ??
2168
+ const marginBuffer = opts === null || opts === void 0 ? void 0 : opts.liquidationBuffer; // treat as MARGIN_BUFFER ratio if provided
2169
+ const marginRatioOverride = opts === null || opts === void 0 ? void 0 : opts.marginRatioOverride;
2170
+ // Equivalent to on-chain user_custom_margin_ratio
2171
+ let userCustomMarginRatio = marginCategory === 'Initial' ? this.getUserAccount().maxMarginRatio : 0;
2172
+ if (marginRatioOverride !== undefined) {
2173
+ userCustomMarginRatio = Math.max(userCustomMarginRatio, marginRatioOverride);
2174
+ }
2175
+ // Initialize calc via JS mirror of Rust MarginCalculation
2176
+ const ctx = marginCalculation_1.MarginContext.standard(marginCategory)
2177
+ .strictMode(strict)
2178
+ .setMarginBuffer(marginBuffer)
2179
+ .setMarginRatioOverride(userCustomMarginRatio);
2180
+ const calc = new marginCalculation_1.MarginCalculation(ctx);
2181
+ // SPOT POSITIONS
2182
+ // TODO: include open orders in the worst-case simulation in the same way on both spot and perp positions
2183
+ for (const spotPosition of this.getUserAccount().spotPositions) {
2184
+ if ((0, spotPosition_1.isSpotPositionAvailable)(spotPosition))
2185
+ continue;
2186
+ const spotMarket = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
2187
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
2188
+ const twap5 = strict
2189
+ ? (0, oracles_1.calculateLiveOracleTwap)(spotMarket.historicalOracleData, oraclePriceData, new anchor_1.BN(Math.floor(Date.now() / 1000)), numericConstants_1.FIVE_MINUTE)
2190
+ : undefined;
2191
+ const strictOracle = new strictOraclePrice_1.StrictOraclePrice(oraclePriceData.price, twap5);
2192
+ if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
2193
+ const tokenAmount = (0, spotBalance_1.getSignedTokenAmount)((0, spotBalance_2.getTokenAmount)(spotPosition.scaledBalance, spotMarket, spotPosition.balanceType), spotPosition.balanceType);
2194
+ if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
2195
+ // add deposit value to total collateral
2196
+ const tokenValue = (0, spotBalance_1.getStrictTokenValue)(tokenAmount, spotMarket.decimals, strictOracle);
2197
+ calc.addCrossMarginTotalCollateral(tokenValue);
2198
+ }
2199
+ else {
2200
+ // borrow on quote contributes to margin requirement
2201
+ const tokenValueAbs = (0, spotBalance_1.getStrictTokenValue)(tokenAmount, spotMarket.decimals, strictOracle).abs();
2202
+ calc.addCrossMarginRequirement(tokenValueAbs, tokenValueAbs);
2203
+ calc.addSpotLiability();
2204
+ }
2205
+ continue;
2206
+ }
2207
+ // Non-quote spot: worst-case simulation
2208
+ const { tokenAmount: worstCaseTokenAmount, ordersValue: worstCaseOrdersValue, tokenValue: worstCaseTokenValue, weightedTokenValue: worstCaseWeightedTokenValue, } = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarket, strictOracle, marginCategory, userCustomMarginRatio, includeOpenOrders);
2209
+ // open order IM
2210
+ calc.addCrossMarginRequirement(new anchor_1.BN(spotPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT), numericConstants_1.ZERO);
2211
+ if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
2212
+ // asset side increases total collateral (weighted)
2213
+ calc.addCrossMarginTotalCollateral(worstCaseWeightedTokenValue);
2214
+ }
2215
+ else if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
2216
+ // liability side increases margin requirement (weighted >= abs(token_value))
2217
+ const liabilityWeighted = worstCaseWeightedTokenValue.abs();
2218
+ calc.addCrossMarginRequirement(liabilityWeighted, worstCaseTokenValue.abs());
2219
+ calc.addSpotLiability();
2220
+ calc.addSpotLiabilityValue(worstCaseTokenValue.abs());
2221
+ }
2222
+ else if (spotPosition.openOrders !== 0) {
2223
+ calc.addSpotLiability();
2224
+ calc.addSpotLiabilityValue(worstCaseTokenValue.abs());
2225
+ }
2226
+ // orders value contributes to collateral or requirement
2227
+ if (worstCaseOrdersValue.gt(numericConstants_1.ZERO)) {
2228
+ calc.addCrossMarginTotalCollateral(worstCaseOrdersValue);
2229
+ }
2230
+ else if (worstCaseOrdersValue.lt(numericConstants_1.ZERO)) {
2231
+ const absVal = worstCaseOrdersValue.abs();
2232
+ calc.addCrossMarginRequirement(absVal, absVal);
2233
+ }
2234
+ }
2235
+ // PERP POSITIONS
2236
+ for (const marketPosition of this.getActivePerpPositions()) {
2237
+ const market = this.driftClient.getPerpMarketAccount(marketPosition.marketIndex);
2238
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
2239
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
2240
+ const oraclePriceData = this.getOracleDataForPerpMarket(market.marketIndex);
2241
+ // Worst-case perp liability and weighted pnl
2242
+ const { worstCaseBaseAssetAmount, worstCaseLiabilityValue } = (0, margin_1.calculateWorstCasePerpLiabilityValue)(marketPosition, market, oraclePriceData.price, includeOpenOrders);
2243
+ // margin ratio for this perp
2244
+ const customMarginRatio = Math.max(this.getUserAccount().maxMarginRatio, marketPosition.maxMarginRatio);
2245
+ let marginRatio = new anchor_1.BN((0, market_1.calculateMarketMarginRatio)(market, worstCaseBaseAssetAmount.abs(), marginCategory, customMarginRatio, this.isHighLeverageMode(marginCategory) || enteringHighLeverage));
2246
+ if ((0, types_1.isVariant)(market.status, 'settlement')) {
2247
+ marginRatio = numericConstants_1.ZERO;
2248
+ }
2249
+ // convert liability to quote value and apply margin ratio
2250
+ const quotePrice = strict
2251
+ ? anchor_1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min)
2252
+ : quoteOraclePriceData.price;
2253
+ let perpMarginRequirement = worstCaseLiabilityValue
2254
+ .mul(quotePrice)
2255
+ .div(numericConstants_1.PRICE_PRECISION)
2256
+ .mul(marginRatio)
2257
+ .div(numericConstants_1.MARGIN_PRECISION);
2258
+ // add open orders IM
2259
+ perpMarginRequirement = perpMarginRequirement.add(new anchor_1.BN(marketPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
2260
+ // weighted unrealized pnl
2261
+ let positionUnrealizedPnl = (0, position_2.calculatePositionPNL)(market, marketPosition, true, oraclePriceData);
2262
+ let pnlQuotePrice;
2263
+ if (strict && positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
2264
+ pnlQuotePrice = anchor_1.BN.min(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
2265
+ }
2266
+ else if (strict && positionUnrealizedPnl.lt(numericConstants_1.ZERO)) {
2267
+ pnlQuotePrice = anchor_1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
2268
+ }
2269
+ else {
2270
+ pnlQuotePrice = quoteOraclePriceData.price;
2271
+ }
2272
+ positionUnrealizedPnl = positionUnrealizedPnl
2273
+ .mul(pnlQuotePrice)
2274
+ .div(numericConstants_1.PRICE_PRECISION);
2275
+ // Add perp contribution: isolated vs cross
2276
+ const isIsolated = this.isPerpPositionIsolated(marketPosition);
2277
+ if (isIsolated) {
2278
+ // derive isolated quote deposit value, mirroring on-chain logic
2279
+ let depositValue = numericConstants_1.ZERO;
2280
+ if (marketPosition.isolatedPositionScaledBalance.gt(numericConstants_1.ZERO)) {
2281
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
2282
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
2283
+ const strictQuote = new strictOraclePrice_1.StrictOraclePrice(quoteOraclePriceData.price, strict
2284
+ ? quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
2285
+ : undefined);
2286
+ const quoteTokenAmount = (0, spotBalance_2.getTokenAmount)(marketPosition.isolatedPositionScaledBalance, quoteSpotMarket, types_2.SpotBalanceType.DEPOSIT);
2287
+ depositValue = (0, spotBalance_1.getStrictTokenValue)(quoteTokenAmount, quoteSpotMarket.decimals, strictQuote);
2288
+ }
2289
+ calc.addIsolatedMarginCalculation(market.marketIndex, depositValue, positionUnrealizedPnl, worstCaseLiabilityValue, perpMarginRequirement);
2290
+ calc.addPerpLiability();
2291
+ calc.addPerpLiabilityValue(worstCaseLiabilityValue);
2292
+ }
2293
+ else {
2294
+ // cross: add to global requirement and collateral
2295
+ calc.addCrossMarginRequirement(perpMarginRequirement, worstCaseLiabilityValue);
2296
+ calc.addCrossMarginTotalCollateral(positionUnrealizedPnl);
2297
+ const hasPerpLiability = !marketPosition.baseAssetAmount.eq(numericConstants_1.ZERO) ||
2298
+ marketPosition.quoteAssetAmount.lt(numericConstants_1.ZERO) ||
2299
+ marketPosition.openOrders !== 0;
2300
+ if (hasPerpLiability) {
2301
+ calc.addPerpLiability();
2302
+ }
2303
+ }
2304
+ }
2305
+ return calc;
2306
+ }
2307
+ isPerpPositionIsolated(perpPosition) {
2308
+ return (perpPosition.positionFlag & types_2.PositionFlag.IsolatedPosition) !== 0;
2309
+ }
2005
2310
  }
2006
2311
  exports.User = User;
@@ -1 +1 @@
1
- {"version":3,"file":"grpcMultiUserAccountSubscriber.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcMultiUserAccountSubscriber.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,WAAW,EAEX,SAAS,EAET,qBAAqB,EACrB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAW,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,qBAAa,8BAA8B;IAC1C,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,eAAe,CAA0C;IAEjE,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,SAAS,CAGb;IACJ,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,sBAAsB,CAA4C;IAC1E,OAAO,CAAC,WAAW,CAAc;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC;IAEtB,OAAO,CAAC,mBAAmB,CAgBzB;gBAGD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,SAAS,EACrB,eAAe,CAAC,EAAE,0BAA0B,CAAC,WAAW,CAAC;IAQ7C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA0ChC,OAAO,CAAC,oBAAoB,EAAE,SAAS,GAAG,qBAAqB;IA8FtE,OAAO,CAAC,aAAa;YAOP,YAAY;CAmE1B"}
1
+ {"version":3,"file":"grpcMultiUserAccountSubscriber.d.ts","sourceRoot":"","sources":["../../../src/accounts/grpcMultiUserAccountSubscriber.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,WAAW,EAEX,SAAS,EAET,qBAAqB,EACrB,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAW,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAE1E,qBAAa,8BAA8B;IAC1C,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,eAAe,CAA0C;IAEjE,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,SAAS,CAGb;IACJ,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,sBAAsB,CAA4C;IAC1E,OAAO,CAAC,WAAW,CAAc;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC;IAEtB,OAAO,CAAC,mBAAmB,CAgBzB;gBAGD,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,SAAS,EACrB,eAAe,CAAC,EAAE,0BAA0B,CAAC,WAAW,CAAC;IAQ7C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IA0ChC,OAAO,CAAC,oBAAoB,EAAE,SAAS,GAAG,qBAAqB;IAqGtE,OAAO,CAAC,aAAa;YAOP,YAAY;CAmE1B"}
@@ -76,7 +76,10 @@ class grpcMultiUserAccountSubscriber {
76
76
  this.listeners.set(key, new Set());
77
77
  this.keyToPk.set(key, userAccountPublicKey);
78
78
  this.pendingAddKeys.add(key);
79
- this.scheduleFlush();
79
+ if (this.isMultiSubscribed) {
80
+ // only schedule flush if already subscribed to the multi-subscriber
81
+ this.scheduleFlush();
82
+ }
80
83
  }
81
84
  };
82
85
  const perUser = {
@@ -110,6 +113,10 @@ class grpcMultiUserAccountSubscriber {
110
113
  this.updateData(account, 0);
111
114
  },
112
115
  updateData(userAccount, slot) {
116
+ const existingData = parent.userData.get(key);
117
+ if (existingData && existingData.slot > slot) {
118
+ return;
119
+ }
113
120
  parent.userData.set(key, { data: userAccount, slot });
114
121
  perUserEmitter.emit('userAccountUpdate', userAccount);
115
122
  perUserEmitter.emit('update');