@drift-labs/sdk 2.13.0-beta.0 → 2.13.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.
package/lib/user.js CHANGED
@@ -11,6 +11,7 @@ const margin_1 = require("./math/margin");
11
11
  const pollingUserAccountSubscriber_1 = require("./accounts/pollingUserAccountSubscriber");
12
12
  const webSocketUserAccountSubscriber_1 = require("./accounts/webSocketUserAccountSubscriber");
13
13
  const spotPosition_1 = require("./math/spotPosition");
14
+ const oracles_1 = require("./math/oracles");
14
15
  class User {
15
16
  constructor(config) {
16
17
  var _a;
@@ -259,14 +260,14 @@ class User {
259
260
  /**
260
261
  * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
261
262
  */
262
- getMarginRequirement(marginCategory, liquidationBuffer) {
263
- return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true));
263
+ getMarginRequirement(marginCategory, liquidationBuffer, strict = false) {
264
+ return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
264
265
  }
265
266
  /**
266
267
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
267
268
  */
268
269
  getInitialMarginRequirement() {
269
- return this.getMarginRequirement('Initial');
270
+ return this.getMarginRequirement('Initial', undefined, true);
270
271
  }
271
272
  /**
272
273
  * @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
@@ -317,7 +318,8 @@ class User {
317
318
  return pnl.add(_1.calculatePositionFundingPNL(market, perpPosition));
318
319
  }, numericConstants_1.ZERO);
319
320
  }
320
- getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders) {
321
+ getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false, now) {
322
+ now = now || new _1.BN(new Date().getTime() / 1000);
321
323
  return this.getUserAccount().spotPositions.reduce((totalLiabilityValue, spotPosition) => {
322
324
  if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
323
325
  (marketIndex !== undefined &&
@@ -345,7 +347,7 @@ class User {
345
347
  if (!includeOpenOrders) {
346
348
  if (types_1.isVariant(spotPosition.balanceType, 'borrow')) {
347
349
  const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
348
- const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
350
+ const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
349
351
  return totalLiabilityValue.add(liabilityValue);
350
352
  }
351
353
  else {
@@ -355,7 +357,7 @@ class User {
355
357
  const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
356
358
  let newTotalLiabilityValue = totalLiabilityValue;
357
359
  if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
358
- const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
360
+ const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
359
361
  newTotalLiabilityValue =
360
362
  newTotalLiabilityValue.add(baseLiabilityValue);
361
363
  }
@@ -375,8 +377,16 @@ class User {
375
377
  return newTotalLiabilityValue;
376
378
  }, numericConstants_1.ZERO);
377
379
  }
378
- getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer) {
379
- let liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
380
+ getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict = false, now) {
381
+ let liabilityValue = null;
382
+ if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
383
+ const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
384
+ );
385
+ liabilityValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
386
+ }
387
+ else {
388
+ liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
389
+ }
380
390
  if (marginCategory !== undefined) {
381
391
  let weight = spotBalance_1.calculateLiabilityWeight(tokenAmount, spotMarketAccount, marginCategory);
382
392
  if (marginCategory === 'Initial') {
@@ -391,7 +401,8 @@ class User {
391
401
  }
392
402
  return liabilityValue;
393
403
  }
394
- getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders) {
404
+ getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders, strict = false, now) {
405
+ now = now || new _1.BN(new Date().getTime() / 1000);
395
406
  return this.getUserAccount().spotPositions.reduce((totalAssetValue, spotPosition) => {
396
407
  if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
397
408
  (marketIndex !== undefined &&
@@ -413,7 +424,7 @@ class User {
413
424
  if (!includeOpenOrders) {
414
425
  if (types_1.isVariant(spotPosition.balanceType, 'deposit')) {
415
426
  const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
416
- const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
427
+ const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
417
428
  return totalAssetValue.add(assetValue);
418
429
  }
419
430
  else {
@@ -423,7 +434,7 @@ class User {
423
434
  const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
424
435
  let newTotalAssetValue = totalAssetValue;
425
436
  if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
426
- const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
437
+ const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
427
438
  newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
428
439
  }
429
440
  if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
@@ -432,8 +443,16 @@ class User {
432
443
  return newTotalAssetValue;
433
444
  }, numericConstants_1.ZERO);
434
445
  }
435
- getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory) {
436
- let assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
446
+ getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict = false, now) {
447
+ let assetValue = null;
448
+ if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
449
+ const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
450
+ );
451
+ assetValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
452
+ }
453
+ else {
454
+ assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
455
+ }
437
456
  if (marginCategory !== undefined) {
438
457
  const weight = spotBalance_1.calculateAssetWeight(tokenAmount, spotMarketAccount, marginCategory);
439
458
  assetValue = assetValue.mul(weight).div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
@@ -470,12 +489,8 @@ class User {
470
489
  health = 0;
471
490
  }
472
491
  else {
473
- // const totalCollateral = this.getTotalCollateral('Initial');
474
- // const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
475
- const marginRatio = this.getMarginRatio().toNumber() / numericConstants_1.MARGIN_PRECISION.toNumber();
476
- const maintenanceRatio = (maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
477
- marginRatio;
478
- const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
492
+ const healthP1 = Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
493
+ 100) + 1;
479
494
  health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
480
495
  if (health > 1) {
481
496
  health = Math.round(health);
@@ -599,16 +614,19 @@ class User {
599
614
  return [exitPrice, pnl];
600
615
  }
601
616
  /**
602
- * calculates current user leverage across all positions
617
+ * calculates current user leverage which is (total liability size) / (net asset value)
603
618
  * @returns : Precision TEN_THOUSAND
604
619
  */
605
620
  getLeverage() {
606
- const totalLiabilityValue = this.getTotalLiabilityValue();
621
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
622
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
623
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
607
624
  const totalAssetValue = this.getTotalAssetValue();
608
- if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilityValue.eq(numericConstants_1.ZERO)) {
625
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
626
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
609
627
  return numericConstants_1.ZERO;
610
628
  }
611
- return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(totalAssetValue);
629
+ return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
612
630
  }
613
631
  getTotalLiabilityValue(marginCategory) {
614
632
  return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, undefined, true));
@@ -623,11 +641,14 @@ class User {
623
641
  */
624
642
  getMaxLeverage(marketIndex, category = 'Initial') {
625
643
  const market = this.driftClient.getPerpMarketAccount(marketIndex);
644
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
645
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
626
646
  const totalAssetValue = this.getTotalAssetValue();
627
- if (totalAssetValue.eq(numericConstants_1.ZERO)) {
647
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
648
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
628
649
  return numericConstants_1.ZERO;
629
650
  }
630
- const totalLiabilityValue = this.getTotalLiabilityValue();
651
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
631
652
  const marginRatio = _1.calculateMarketMarginRatio(market,
632
653
  // worstCaseBaseAssetAmount.abs(),
633
654
  numericConstants_1.ZERO, // todo
@@ -640,19 +661,22 @@ class User {
640
661
  return totalLiabilityValue
641
662
  .add(additionalLiabilities)
642
663
  .mul(numericConstants_1.TEN_THOUSAND)
643
- .div(totalAssetValue);
664
+ .div(netAssetValue);
644
665
  }
645
666
  /**
646
667
  * calculates margin ratio: total collateral / |total position value|
647
668
  * @returns : Precision TEN_THOUSAND
648
669
  */
649
670
  getMarginRatio(marginCategory) {
650
- const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
671
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
672
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
673
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
651
674
  if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
652
675
  return numericConstants_1.BN_MAX;
653
676
  }
654
677
  const totalAssetValue = this.getTotalAssetValue(marginCategory);
655
- return totalAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
678
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
679
+ return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
656
680
  }
657
681
  canBeLiquidated() {
658
682
  const totalCollateral = this.getTotalCollateral('Maintenance');
@@ -753,7 +777,7 @@ class User {
753
777
 
754
778
  for 10x long, BTC down $400:
755
779
  3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
756
- const totalCollateral = this.getTotalCollateral();
780
+ const totalCollateral = this.getTotalCollateral('Maintenance');
757
781
  // calculate the total position value ignoring any value from the target market of the trade
758
782
  const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
759
783
  const currentPerpPosition = this.getPerpPosition(perpPosition.marketIndex) ||
@@ -765,7 +789,7 @@ class User {
765
789
  marketIndex: perpPosition.marketIndex,
766
790
  baseAssetAmount: proposedBaseAssetAmount,
767
791
  remainderBaseAssetAmount: 0,
768
- quoteAssetAmount: new _1.BN(0),
792
+ quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
769
793
  lastCumulativeFundingRate: numericConstants_1.ZERO,
770
794
  quoteBreakEvenAmount: new _1.BN(0),
771
795
  quoteEntryAmount: new _1.BN(0),
@@ -783,59 +807,48 @@ class User {
783
807
  const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
784
808
  // total position value after trade
785
809
  const totalPositionValueAfterTrade = totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
786
- const marginRequirementExcludingTargetMarket = this.getUserAccount().perpPositions.reduce((totalMarginRequirement, position) => {
787
- if (position.marketIndex !== perpPosition.marketIndex) {
788
- const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
789
- const positionValue = margin_1.calculateBaseAssetValueWithOracle(market, position, this.getOracleDataForPerpMarket(market.marketIndex));
790
- const marketMarginRequirement = positionValue
791
- .mul(new _1.BN(_1.calculateMarketMarginRatio(market, position.baseAssetAmount.abs(), 'Maintenance')))
792
- .div(numericConstants_1.MARGIN_PRECISION);
793
- totalMarginRequirement = totalMarginRequirement.add(marketMarginRequirement);
794
- }
795
- return totalMarginRequirement;
796
- }, numericConstants_1.ZERO);
810
+ const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
811
+ const positionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
812
+ const marginRequirementOfTargetMarket = positionValue
813
+ .mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
814
+ .div(numericConstants_1.MARGIN_PRECISION);
815
+ const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(marginRequirementOfTargetMarket);
797
816
  const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
798
817
  // if the position value after the trade is less than free collateral, there is no liq price
799
818
  if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
800
819
  proposedPerpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
801
820
  return new _1.BN(-1);
802
821
  }
803
- const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(proposedPerpPositionValue
822
+ const marginRequirementTargetMarket = proposedPerpPositionValue
804
823
  .mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
805
- .div(numericConstants_1.MARGIN_PRECISION));
824
+ .div(numericConstants_1.MARGIN_PRECISION);
825
+ const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
806
826
  const freeCollateralAfterTrade = totalCollateral.sub(marginRequirementAfterTrade);
807
- const marketMaxLeverage = this.getMaxLeverage(proposedPerpPosition.marketIndex, 'Maintenance');
827
+ const marketMaxMaintLeverage = new _1.BN(numericConstants_1.TEN_THOUSAND.mul(numericConstants_1.TEN_THOUSAND).toNumber() /
828
+ _1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount, 'Maintenance'));
808
829
  let priceDelta;
809
830
  if (proposedBaseAssetAmount.lt(numericConstants_1.ZERO)) {
810
831
  priceDelta = freeCollateralAfterTrade
811
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
812
- .div(marketMaxLeverage.add(numericConstants_1.TEN_THOUSAND))
832
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
833
+ .div(marketMaxMaintLeverage.add(numericConstants_1.TEN_THOUSAND))
813
834
  .mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
814
835
  .mul(numericConstants_1.AMM_RESERVE_PRECISION)
815
836
  .div(proposedBaseAssetAmount);
816
837
  }
817
838
  else {
818
839
  priceDelta = freeCollateralAfterTrade
819
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
820
- .div(marketMaxLeverage.sub(numericConstants_1.TEN_THOUSAND))
840
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
841
+ .div(marketMaxMaintLeverage.sub(numericConstants_1.TEN_THOUSAND))
821
842
  .mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
822
843
  .mul(numericConstants_1.AMM_RESERVE_PRECISION)
823
844
  .div(proposedBaseAssetAmount);
824
845
  }
825
- let markPriceAfterTrade;
826
- if (positionBaseSizeChange.eq(numericConstants_1.ZERO)) {
827
- markPriceAfterTrade = _1.calculateReservePrice(this.driftClient.getPerpMarketAccount(perpPosition.marketIndex), this.getOracleDataForPerpMarket(perpPosition.marketIndex));
828
- }
829
- else {
830
- const direction = positionBaseSizeChange.gt(numericConstants_1.ZERO)
831
- ? _1.PositionDirection.LONG
832
- : _1.PositionDirection.SHORT;
833
- markPriceAfterTrade = _1.calculateTradeSlippage(direction, positionBaseSizeChange.abs(), this.driftClient.getPerpMarketAccount(perpPosition.marketIndex), 'base', this.getOracleDataForPerpMarket(perpPosition.marketIndex))[3]; // newPrice after swap
834
- }
835
- if (priceDelta.gt(markPriceAfterTrade)) {
846
+ const currentPrice = this.getOracleDataForPerpMarket(perpPosition.marketIndex).price;
847
+ if (priceDelta.gt(currentPrice) &&
848
+ proposedPerpPosition.baseAssetAmount.gte(numericConstants_1.ZERO)) {
836
849
  return new _1.BN(-1);
837
850
  }
838
- return markPriceAfterTrade.sub(priceDelta);
851
+ return currentPrice.sub(priceDelta);
839
852
  }
840
853
  /**
841
854
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
@@ -960,16 +973,18 @@ class User {
960
973
  .abs();
961
974
  const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex, undefined, undefined, includeOpenOrders);
962
975
  const totalAssetValue = this.getTotalAssetValue();
963
- const totalPerpPositionValue = currentPerpPositionAfterTrade
976
+ const totalPerpPositionLiability = currentPerpPositionAfterTrade
964
977
  .add(totalPositionAfterTradeExcludingTargetMarket)
965
978
  .abs();
966
- const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false));
967
- if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilitiesAfterTrade.eq(numericConstants_1.ZERO)) {
979
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, includeOpenOrders);
980
+ const totalLiabilitiesAfterTrade = totalPerpPositionLiability.add(totalSpotLiability);
981
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
982
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
968
983
  return numericConstants_1.ZERO;
969
984
  }
970
985
  const newLeverage = totalLiabilitiesAfterTrade
971
986
  .mul(numericConstants_1.TEN_THOUSAND)
972
- .div(totalAssetValue);
987
+ .div(netAssetValue);
973
988
  return newLeverage;
974
989
  }
975
990
  /**
@@ -992,28 +1007,28 @@ class User {
992
1007
  getWithdrawalLimit(marketIndex, reduceOnly) {
993
1008
  const nowTs = new _1.BN(Math.floor(Date.now() / 1000));
994
1009
  const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
995
- const { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
1010
+ // eslint-disable-next-line prefer-const
1011
+ let { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
996
1012
  const freeCollateral = this.getFreeCollateral();
997
1013
  const oracleData = this.getOracleDataForSpotMarket(marketIndex);
998
1014
  const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
1015
+ const { canBypass, depositAmount: userDepositAmount } = this.canBypassWithdrawLimits(marketIndex);
1016
+ if (canBypass) {
1017
+ withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
1018
+ }
999
1019
  const amountWithdrawable = freeCollateral
1000
1020
  .mul(numericConstants_1.MARGIN_PRECISION)
1001
1021
  .div(new _1.BN(spotMarket.initialAssetWeight))
1002
1022
  .mul(numericConstants_1.PRICE_PRECISION)
1003
1023
  .div(oracleData.price)
1004
1024
  .mul(precisionIncrease);
1005
- const userSpotPosition = this.getUserAccount().spotPositions.find((spotPosition) => types_1.isVariant(spotPosition.balanceType, 'deposit') &&
1006
- spotPosition.marketIndex == marketIndex);
1007
- const userSpotBalance = userSpotPosition
1008
- ? spotBalance_1.getTokenAmount(userSpotPosition.scaledBalance, this.driftClient.getSpotMarketAccount(marketIndex), _1.SpotBalanceType.DEPOSIT)
1009
- : numericConstants_1.ZERO;
1010
- const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userSpotBalance), withdrawLimit.abs());
1025
+ const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
1011
1026
  if (reduceOnly) {
1012
1027
  return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
1013
1028
  }
1014
1029
  else {
1015
1030
  const weightedAssetValue = this.getSpotMarketAssetValue(marketIndex, 'Initial', false);
1016
- const freeCollatAfterWithdraw = userSpotBalance.gt(numericConstants_1.ZERO)
1031
+ const freeCollatAfterWithdraw = userDepositAmount.gt(numericConstants_1.ZERO)
1017
1032
  ? freeCollateral.sub(weightedAssetValue)
1018
1033
  : freeCollateral;
1019
1034
  const maxLiabilityAllowed = freeCollatAfterWithdraw
@@ -1026,6 +1041,43 @@ class User {
1026
1041
  return _1.BN.max(maxBorrowValue, numericConstants_1.ZERO);
1027
1042
  }
1028
1043
  }
1044
+ canBypassWithdrawLimits(marketIndex) {
1045
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1046
+ const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new _1.BN(10));
1047
+ const position = this.getSpotPosition(marketIndex);
1048
+ const netDeposits = this.getUserAccount().totalDeposits.sub(this.getUserAccount().totalWithdraws);
1049
+ if (!position) {
1050
+ return {
1051
+ canBypass: false,
1052
+ maxDepositAmount,
1053
+ depositAmount: numericConstants_1.ZERO,
1054
+ netDeposits,
1055
+ };
1056
+ }
1057
+ if (types_1.isVariant(position.balanceType, 'borrow')) {
1058
+ return {
1059
+ canBypass: false,
1060
+ maxDepositAmount,
1061
+ netDeposits,
1062
+ depositAmount: numericConstants_1.ZERO,
1063
+ };
1064
+ }
1065
+ const depositAmount = spotBalance_1.getTokenAmount(position.scaledBalance, spotMarket, 'deposit');
1066
+ if (netDeposits.lt(numericConstants_1.ZERO)) {
1067
+ return {
1068
+ canBypass: false,
1069
+ maxDepositAmount,
1070
+ depositAmount: numericConstants_1.ZERO,
1071
+ netDeposits,
1072
+ };
1073
+ }
1074
+ return {
1075
+ canBypass: depositAmount.lt(maxDepositAmount),
1076
+ maxDepositAmount,
1077
+ netDeposits,
1078
+ depositAmount,
1079
+ };
1080
+ }
1029
1081
  /**
1030
1082
  * Get the total position value, excluding any position coming from the given target market
1031
1083
  * @param marketToIgnore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.13.0-beta.0",
3
+ "version": "2.13.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -83,6 +83,8 @@ export const MARGIN_PRECISION = TEN_THOUSAND;
83
83
  export const BID_ASK_SPREAD_PRECISION = new BN(1000000); // 10^6
84
84
  export const LIQUIDATION_PCT_PRECISION = TEN_THOUSAND;
85
85
 
86
+ export const FIVE_MINUTE = new BN(60 * 5);
87
+ export const ONE_HOUR = new BN(60 * 60);
86
88
  export const ONE_YEAR = new BN(31536000);
87
89
 
88
90
  export const QUOTE_SPOT_MARKET_INDEX = 0;
@@ -2950,6 +2950,70 @@ export class DriftClient {
2950
2950
  maxTs: openOrder.maxTs,
2951
2951
  auctionStartPrice: openOrder.auctionStartPrice,
2952
2952
  auctionEndPrice: openOrder.auctionEndPrice,
2953
+ userOrderId: openOrder.userOrderId,
2954
+ };
2955
+ const placeOrderIx = await this.getPlacePerpOrderIx(newOrderParams);
2956
+
2957
+ const tx = new Transaction();
2958
+ tx.add(
2959
+ ComputeBudgetProgram.requestUnits({
2960
+ units: 1_000_000,
2961
+ additionalFee: 0,
2962
+ })
2963
+ );
2964
+ tx.add(cancelOrderIx);
2965
+ tx.add(placeOrderIx);
2966
+ const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
2967
+ this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
2968
+ return txSig;
2969
+ }
2970
+
2971
+ /**
2972
+ * Modifies an open order by closing it and replacing it with a new order.
2973
+ * @param userOrderId: The open order to modify
2974
+ * @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
2975
+ * @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
2976
+ * @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
2977
+ * @returns
2978
+ */
2979
+ public async modifyPerpOrderByUserOrderId(
2980
+ userOrderId: number,
2981
+ newBaseAmount?: BN,
2982
+ newLimitPrice?: BN,
2983
+ newOraclePriceOffset?: number
2984
+ ): Promise<TransactionSignature> {
2985
+ if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
2986
+ throw new Error(
2987
+ `Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`
2988
+ );
2989
+ }
2990
+
2991
+ const openOrder = this.getUser().getOrderByUserOrderId(userOrderId);
2992
+ if (!openOrder) {
2993
+ throw new Error(
2994
+ `No open order with user order id ${userOrderId.toString()}`
2995
+ );
2996
+ }
2997
+ const cancelOrderIx = await this.getCancelOrderIx(openOrder.orderId);
2998
+
2999
+ const newOrderParams: OptionalOrderParams = {
3000
+ orderType: openOrder.orderType,
3001
+ marketType: openOrder.marketType,
3002
+ direction: openOrder.direction,
3003
+ baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
3004
+ price: newLimitPrice || openOrder.price,
3005
+ marketIndex: openOrder.marketIndex,
3006
+ reduceOnly: openOrder.reduceOnly,
3007
+ postOnly: openOrder.postOnly,
3008
+ immediateOrCancel: openOrder.immediateOrCancel,
3009
+ triggerPrice: openOrder.triggerPrice,
3010
+ triggerCondition: openOrder.triggerCondition,
3011
+ oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
3012
+ auctionDuration: openOrder.auctionDuration,
3013
+ maxTs: openOrder.maxTs,
3014
+ auctionStartPrice: openOrder.auctionStartPrice,
3015
+ auctionEndPrice: openOrder.auctionEndPrice,
3016
+ userOrderId: openOrder.userOrderId,
2953
3017
  };
2954
3018
  const placeOrderIx = await this.getPlacePerpOrderIx(newOrderParams);
2955
3019
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.13.0-beta.0",
2
+ "version": "2.13.0-beta.1",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -5,6 +5,7 @@ import {
5
5
  MarginCategory,
6
6
  SpotMarketAccount,
7
7
  SpotBalanceType,
8
+ MarketType,
8
9
  } from '../types';
9
10
  import {
10
11
  calculateAmmReservesAfterSwap,
@@ -12,6 +13,7 @@ import {
12
13
  calculateUpdatedAMMSpreadReserves,
13
14
  getSwapDirection,
14
15
  calculateUpdatedAMM,
16
+ calculateMarketOpenBidAsk,
15
17
  } from './amm';
16
18
  import {
17
19
  calculateSizeDiscountAssetWeight,
@@ -25,6 +27,7 @@ import {
25
27
  ZERO,
26
28
  } from '../constants/numericConstants';
27
29
  import { getTokenAmount } from './spotBalance';
30
+ import { DLOB } from '../dlob/DLOB';
28
31
 
29
32
  /**
30
33
  * Calculates market mark price
@@ -232,3 +235,51 @@ export function calculateNetUserPnlImbalance(
232
235
 
233
236
  return imbalance;
234
237
  }
238
+
239
+ export function calculateAvailablePerpLiquidity(
240
+ market: PerpMarketAccount,
241
+ oraclePriceData: OraclePriceData,
242
+ dlob: DLOB,
243
+ slot: number
244
+ ): { bids: BN; asks: BN } {
245
+ let [bids, asks] = calculateMarketOpenBidAsk(
246
+ market.amm.baseAssetReserve,
247
+ market.amm.minBaseAssetReserve,
248
+ market.amm.maxBaseAssetReserve,
249
+ market.amm.orderStepSize
250
+ );
251
+
252
+ asks = asks.abs();
253
+
254
+ const bidPrice = calculateBidPrice(market, oraclePriceData);
255
+ const askPrice = calculateAskPrice(market, oraclePriceData);
256
+
257
+ for (const bid of dlob.getMakerLimitBids(
258
+ market.marketIndex,
259
+ slot,
260
+ MarketType.PERP,
261
+ oraclePriceData,
262
+ askPrice
263
+ )) {
264
+ bids = bids.add(
265
+ bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled)
266
+ );
267
+ }
268
+
269
+ for (const ask of dlob.getMakerLimitAsks(
270
+ market.marketIndex,
271
+ slot,
272
+ MarketType.PERP,
273
+ oraclePriceData,
274
+ bidPrice
275
+ )) {
276
+ asks = asks.add(
277
+ ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled)
278
+ );
279
+ }
280
+
281
+ return {
282
+ bids: bids,
283
+ asks: asks,
284
+ };
285
+ }
@@ -6,8 +6,10 @@ import {
6
6
  PRICE_PRECISION,
7
7
  ONE,
8
8
  ZERO,
9
+ FIVE_MINUTE,
10
+ ONE_HOUR,
9
11
  } from '../constants/numericConstants';
10
- import { BN, PerpMarketAccount } from '../index';
12
+ import { BN, HistoricalOracleData, PerpMarketAccount } from '../index';
11
13
  import { assert } from '../assert/assert';
12
14
 
13
15
  export function oraclePriceBands(
@@ -68,7 +70,7 @@ export function isOracleTooDivergent(
68
70
  const sinceLastUpdate = now.sub(
69
71
  amm.historicalOracleData.lastOraclePriceTwapTs
70
72
  );
71
- const sinceStart = BN.max(ZERO, new BN(60 * 5).sub(sinceLastUpdate));
73
+ const sinceStart = BN.max(ZERO, FIVE_MINUTE.sub(sinceLastUpdate));
72
74
  const oracleTwap5min = amm.historicalOracleData.lastOraclePriceTwap5Min
73
75
  .mul(sinceStart)
74
76
  .add(oraclePriceData.price)
@@ -90,29 +92,36 @@ export function isOracleTooDivergent(
90
92
  }
91
93
 
92
94
  export function calculateLiveOracleTwap(
93
- amm: AMM,
95
+ histOracleData: HistoricalOracleData,
94
96
  oraclePriceData: OraclePriceData,
95
- now: BN
97
+ now: BN,
98
+ period: BN
96
99
  ): BN {
100
+ let oracleTwap = undefined;
101
+ if (period.eq(ONE_HOUR)) {
102
+ //todo: assumes its 1hr
103
+ // period = amm.fundingPeriod;
104
+ oracleTwap = histOracleData.lastOraclePriceTwap;
105
+ } else if (period.eq(FIVE_MINUTE)) {
106
+ histOracleData.lastOraclePriceTwap5Min;
107
+ } else {
108
+ throw Error('unsupported twap period passed');
109
+ }
110
+
97
111
  const sinceLastUpdate = BN.max(
98
112
  ONE,
99
- now.sub(amm.historicalOracleData.lastOraclePriceTwapTs)
113
+ now.sub(histOracleData.lastOraclePriceTwapTs)
100
114
  );
101
- const sinceStart = BN.max(ZERO, amm.fundingPeriod.sub(sinceLastUpdate));
115
+ const sinceStart = BN.max(ZERO, period.sub(sinceLastUpdate));
102
116
 
103
- const clampRange = amm.historicalOracleData.lastOraclePriceTwap.div(
104
- new BN(3)
105
- );
117
+ const clampRange = oracleTwap.div(new BN(3));
106
118
 
107
119
  const clampedOraclePrice = BN.min(
108
- amm.historicalOracleData.lastOraclePriceTwap.add(clampRange),
109
- BN.max(
110
- oraclePriceData.price,
111
- amm.historicalOracleData.lastOraclePriceTwap.sub(clampRange)
112
- )
120
+ oracleTwap.add(clampRange),
121
+ BN.max(oraclePriceData.price, oracleTwap.sub(clampRange))
113
122
  );
114
123
 
115
- const newOracleTwap = amm.historicalOracleData.lastOraclePriceTwap
124
+ const newOracleTwap = oracleTwap
116
125
  .mul(sinceStart)
117
126
  .add(clampedOraclePrice.mul(sinceLastUpdate))
118
127
  .div(sinceStart.add(sinceLastUpdate));
@@ -131,7 +140,12 @@ export function calculateLiveOracleStd(
131
140
  );
132
141
  const sinceStart = BN.max(ZERO, amm.fundingPeriod.sub(sinceLastUpdate));
133
142
 
134
- const liveOracleTwap = calculateLiveOracleTwap(amm, oraclePriceData, now);
143
+ const liveOracleTwap = calculateLiveOracleTwap(
144
+ amm.historicalOracleData,
145
+ oraclePriceData,
146
+ now,
147
+ amm.fundingPeriod
148
+ );
135
149
 
136
150
  const priceDeltaVsTwap = oraclePriceData.price.sub(liveOracleTwap).abs();
137
151