@drift-labs/sdk 2.13.0-beta.0 → 2.13.0-beta.2

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;
@@ -238,13 +239,20 @@ class User {
238
239
  return [position, remainderBaa, pnl];
239
240
  }
240
241
  /**
241
- * calculates Buying Power = FC * MAX_LEVERAGE
242
+ * calculates Buying Power = free collateral / initial margin ratio
242
243
  * @returns : Precision QUOTE_PRECISION
243
244
  */
244
245
  getBuyingPower(marketIndex) {
245
- return this.getFreeCollateral()
246
- .mul(this.getMaxLeverage(marketIndex, 'Initial'))
247
- .div(numericConstants_1.TEN_THOUSAND);
246
+ const perpPosition = this.getPerpPosition(marketIndex);
247
+ const worstCaseBaseAssetAmount = perpPosition
248
+ ? margin_1.calculateWorstCaseBaseAssetAmount(perpPosition)
249
+ : numericConstants_1.ZERO;
250
+ const freeCollateral = this.getFreeCollateral();
251
+ return this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(marketIndex, freeCollateral, worstCaseBaseAssetAmount);
252
+ }
253
+ getBuyingPowerFromFreeCollateralAndBaseAssetAmount(marketIndex, freeCollateral, baseAssetAmount) {
254
+ const marginRatio = _1.calculateMarketMarginRatio(this.driftClient.getPerpMarketAccount(marketIndex), baseAssetAmount, 'Initial');
255
+ return freeCollateral.mul(numericConstants_1.MARGIN_PRECISION).div(new _1.BN(marginRatio));
248
256
  }
249
257
  /**
250
258
  * calculates Free Collateral = Total collateral - initial margin requirement
@@ -259,14 +267,14 @@ class User {
259
267
  /**
260
268
  * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
261
269
  */
262
- getMarginRequirement(marginCategory, liquidationBuffer) {
263
- return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true));
270
+ getMarginRequirement(marginCategory, liquidationBuffer, strict = false) {
271
+ return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
264
272
  }
265
273
  /**
266
274
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
267
275
  */
268
276
  getInitialMarginRequirement() {
269
- return this.getMarginRequirement('Initial');
277
+ return this.getMarginRequirement('Initial', undefined, true);
270
278
  }
271
279
  /**
272
280
  * @returns The maintenance margin requirement in USDC. : QUOTE_PRECISION
@@ -317,7 +325,8 @@ class User {
317
325
  return pnl.add(_1.calculatePositionFundingPNL(market, perpPosition));
318
326
  }, numericConstants_1.ZERO);
319
327
  }
320
- getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders) {
328
+ getSpotMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false, now) {
329
+ now = now || new _1.BN(new Date().getTime() / 1000);
321
330
  return this.getUserAccount().spotPositions.reduce((totalLiabilityValue, spotPosition) => {
322
331
  if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
323
332
  (marketIndex !== undefined &&
@@ -345,7 +354,7 @@ class User {
345
354
  if (!includeOpenOrders) {
346
355
  if (types_1.isVariant(spotPosition.balanceType, 'borrow')) {
347
356
  const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
348
- const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
357
+ const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
349
358
  return totalLiabilityValue.add(liabilityValue);
350
359
  }
351
360
  else {
@@ -355,7 +364,7 @@ class User {
355
364
  const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
356
365
  let newTotalLiabilityValue = totalLiabilityValue;
357
366
  if (worstCaseTokenAmount.lt(numericConstants_1.ZERO)) {
358
- const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer);
367
+ const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
359
368
  newTotalLiabilityValue =
360
369
  newTotalLiabilityValue.add(baseLiabilityValue);
361
370
  }
@@ -375,8 +384,16 @@ class User {
375
384
  return newTotalLiabilityValue;
376
385
  }, numericConstants_1.ZERO);
377
386
  }
378
- getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer) {
379
- let liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
387
+ getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict = false, now) {
388
+ let liabilityValue = null;
389
+ if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
390
+ const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
391
+ );
392
+ liabilityValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
393
+ }
394
+ else {
395
+ liabilityValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
396
+ }
380
397
  if (marginCategory !== undefined) {
381
398
  let weight = spotBalance_1.calculateLiabilityWeight(tokenAmount, spotMarketAccount, marginCategory);
382
399
  if (marginCategory === 'Initial') {
@@ -391,7 +408,8 @@ class User {
391
408
  }
392
409
  return liabilityValue;
393
410
  }
394
- getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders) {
411
+ getSpotMarketAssetValue(marketIndex, marginCategory, includeOpenOrders, strict = false, now) {
412
+ now = now || new _1.BN(new Date().getTime() / 1000);
395
413
  return this.getUserAccount().spotPositions.reduce((totalAssetValue, spotPosition) => {
396
414
  if (spotPosition_1.isSpotPositionAvailable(spotPosition) ||
397
415
  (marketIndex !== undefined &&
@@ -413,7 +431,7 @@ class User {
413
431
  if (!includeOpenOrders) {
414
432
  if (types_1.isVariant(spotPosition.balanceType, 'deposit')) {
415
433
  const tokenAmount = spotBalance_1.getTokenAmount(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
416
- const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
434
+ const assetValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
417
435
  return totalAssetValue.add(assetValue);
418
436
  }
419
437
  else {
@@ -423,7 +441,7 @@ class User {
423
441
  const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = spotPosition_1.getWorstCaseTokenAmounts(spotPosition, spotMarketAccount, this.getOracleDataForSpotMarket(spotPosition.marketIndex));
424
442
  let newTotalAssetValue = totalAssetValue;
425
443
  if (worstCaseTokenAmount.gt(numericConstants_1.ZERO)) {
426
- const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory);
444
+ const baseAssetValue = this.getSpotAssetValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
427
445
  newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
428
446
  }
429
447
  if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO)) {
@@ -432,8 +450,16 @@ class User {
432
450
  return newTotalAssetValue;
433
451
  }, numericConstants_1.ZERO);
434
452
  }
435
- getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory) {
436
- let assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
453
+ getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict = false, now) {
454
+ let assetValue = null;
455
+ if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
456
+ const estOracleTwap = oracles_1.calculateLiveOracleTwap(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
457
+ );
458
+ assetValue = _1.getStrictTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
459
+ }
460
+ else {
461
+ assetValue = _1.getTokenValue(tokenAmount, spotMarketAccount.decimals, oraclePriceData);
462
+ }
437
463
  if (marginCategory !== undefined) {
438
464
  const weight = spotBalance_1.calculateAssetWeight(tokenAmount, spotMarketAccount, marginCategory);
439
465
  assetValue = assetValue.mul(weight).div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
@@ -470,12 +496,8 @@ class User {
470
496
  health = 0;
471
497
  }
472
498
  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;
499
+ const healthP1 = Math.max(0, (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
500
+ 100) + 1;
479
501
  health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
480
502
  if (health > 1) {
481
503
  health = Math.round(health);
@@ -542,10 +564,10 @@ class User {
542
564
  * calculates position value in margin system
543
565
  * @returns : Precision QUOTE_PRECISION
544
566
  */
545
- getPerpPositionValue(marketIndex, oraclePriceData) {
567
+ getPerpPositionValue(marketIndex, oraclePriceData, includeOpenOrders = false) {
546
568
  const userPosition = this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
547
569
  const market = this.driftClient.getPerpMarketAccount(userPosition.marketIndex);
548
- return margin_1.calculateBaseAssetValueWithOracle(market, userPosition, oraclePriceData);
570
+ return margin_1.calculateBaseAssetValueWithOracle(market, userPosition, oraclePriceData, includeOpenOrders);
549
571
  }
550
572
  getPositionSide(currentPosition) {
551
573
  if (currentPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
@@ -599,16 +621,19 @@ class User {
599
621
  return [exitPrice, pnl];
600
622
  }
601
623
  /**
602
- * calculates current user leverage across all positions
624
+ * calculates current user leverage which is (total liability size) / (net asset value)
603
625
  * @returns : Precision TEN_THOUSAND
604
626
  */
605
627
  getLeverage() {
606
- const totalLiabilityValue = this.getTotalLiabilityValue();
628
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
629
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
630
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
607
631
  const totalAssetValue = this.getTotalAssetValue();
608
- if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilityValue.eq(numericConstants_1.ZERO)) {
632
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
633
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
609
634
  return numericConstants_1.ZERO;
610
635
  }
611
- return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(totalAssetValue);
636
+ return totalLiabilityValue.mul(numericConstants_1.TEN_THOUSAND).div(netAssetValue);
612
637
  }
613
638
  getTotalLiabilityValue(marginCategory) {
614
639
  return this.getTotalPerpPositionValue(marginCategory, undefined, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, undefined, true));
@@ -623,11 +648,14 @@ class User {
623
648
  */
624
649
  getMaxLeverage(marketIndex, category = 'Initial') {
625
650
  const market = this.driftClient.getPerpMarketAccount(marketIndex);
651
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
652
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
626
653
  const totalAssetValue = this.getTotalAssetValue();
627
- if (totalAssetValue.eq(numericConstants_1.ZERO)) {
654
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
655
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
628
656
  return numericConstants_1.ZERO;
629
657
  }
630
- const totalLiabilityValue = this.getTotalLiabilityValue();
658
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
631
659
  const marginRatio = _1.calculateMarketMarginRatio(market,
632
660
  // worstCaseBaseAssetAmount.abs(),
633
661
  numericConstants_1.ZERO, // todo
@@ -640,19 +668,22 @@ class User {
640
668
  return totalLiabilityValue
641
669
  .add(additionalLiabilities)
642
670
  .mul(numericConstants_1.TEN_THOUSAND)
643
- .div(totalAssetValue);
671
+ .div(netAssetValue);
644
672
  }
645
673
  /**
646
674
  * calculates margin ratio: total collateral / |total position value|
647
675
  * @returns : Precision TEN_THOUSAND
648
676
  */
649
677
  getMarginRatio(marginCategory) {
650
- const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
678
+ const totalPerpLiability = this.getTotalPerpPositionValue(undefined, undefined, true);
679
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, true);
680
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
651
681
  if (totalLiabilityValue.eq(numericConstants_1.ZERO)) {
652
682
  return numericConstants_1.BN_MAX;
653
683
  }
654
684
  const totalAssetValue = this.getTotalAssetValue(marginCategory);
655
- return totalAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
685
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
686
+ return netAssetValue.mul(numericConstants_1.TEN_THOUSAND).div(totalLiabilityValue);
656
687
  }
657
688
  canBeLiquidated() {
658
689
  const totalCollateral = this.getTotalCollateral('Maintenance');
@@ -753,9 +784,9 @@ class User {
753
784
 
754
785
  for 10x long, BTC down $400:
755
786
  3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
756
- const totalCollateral = this.getTotalCollateral();
787
+ const totalCollateral = this.getTotalCollateral('Maintenance');
757
788
  // calculate the total position value ignoring any value from the target market of the trade
758
- const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
789
+ const totalPositionValueExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex, undefined, undefined, true);
759
790
  const currentPerpPosition = this.getPerpPosition(perpPosition.marketIndex) ||
760
791
  this.getEmptyPosition(perpPosition.marketIndex);
761
792
  const currentPerpPositionBaseSize = currentPerpPosition.baseAssetAmount;
@@ -765,13 +796,13 @@ class User {
765
796
  marketIndex: perpPosition.marketIndex,
766
797
  baseAssetAmount: proposedBaseAssetAmount,
767
798
  remainderBaseAssetAmount: 0,
768
- quoteAssetAmount: new _1.BN(0),
799
+ quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
769
800
  lastCumulativeFundingRate: numericConstants_1.ZERO,
770
801
  quoteBreakEvenAmount: new _1.BN(0),
771
802
  quoteEntryAmount: new _1.BN(0),
772
803
  openOrders: 0,
773
- openBids: new _1.BN(0),
774
- openAsks: new _1.BN(0),
804
+ openBids: currentPerpPosition.openBids,
805
+ openAsks: currentPerpPosition.openAsks,
775
806
  settledPnl: numericConstants_1.ZERO,
776
807
  lpShares: numericConstants_1.ZERO,
777
808
  lastBaseAssetAmountPerLp: numericConstants_1.ZERO,
@@ -780,62 +811,52 @@ class User {
780
811
  if (proposedBaseAssetAmount.eq(numericConstants_1.ZERO))
781
812
  return new _1.BN(-1);
782
813
  const market = this.driftClient.getPerpMarketAccount(proposedPerpPosition.marketIndex);
783
- const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex));
814
+ const proposedPerpPositionValue = margin_1.calculateBaseAssetValueWithOracle(market, proposedPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex), true);
784
815
  // total position value after trade
785
816
  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);
817
+ const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
818
+ const positionValue = margin_1.calculateBaseAssetValueWithOracle(market, currentPerpPosition, this.getOracleDataForPerpMarket(market.marketIndex), true);
819
+ const marginRequirementOfTargetMarket = positionValue
820
+ .mul(new _1.BN(_1.calculateMarketMarginRatio(market, margin_1.calculateWorstCaseBaseAssetAmount(currentPerpPosition).abs(), 'Maintenance')))
821
+ .div(numericConstants_1.MARGIN_PRECISION);
822
+ const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(marginRequirementOfTargetMarket);
797
823
  const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
798
824
  // if the position value after the trade is less than free collateral, there is no liq price
799
825
  if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
800
826
  proposedPerpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
801
827
  return new _1.BN(-1);
802
828
  }
803
- const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(proposedPerpPositionValue
804
- .mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedPerpPosition.baseAssetAmount.abs(), 'Maintenance')))
805
- .div(numericConstants_1.MARGIN_PRECISION));
829
+ const proposedWorstCastBaseAssetAmount = margin_1.calculateWorstCaseBaseAssetAmount(proposedPerpPosition);
830
+ const marginRequirementTargetMarket = proposedPerpPositionValue
831
+ .mul(new _1.BN(_1.calculateMarketMarginRatio(market, proposedWorstCastBaseAssetAmount.abs(), 'Maintenance')))
832
+ .div(numericConstants_1.MARGIN_PRECISION);
833
+ const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
806
834
  const freeCollateralAfterTrade = totalCollateral.sub(marginRequirementAfterTrade);
807
- const marketMaxLeverage = this.getMaxLeverage(proposedPerpPosition.marketIndex, 'Maintenance');
835
+ const marketMaxMaintLeverage = new _1.BN(numericConstants_1.TEN_THOUSAND.mul(numericConstants_1.TEN_THOUSAND).toNumber() /
836
+ _1.calculateMarketMarginRatio(market, proposedWorstCastBaseAssetAmount.abs(), 'Maintenance'));
808
837
  let priceDelta;
809
838
  if (proposedBaseAssetAmount.lt(numericConstants_1.ZERO)) {
810
839
  priceDelta = freeCollateralAfterTrade
811
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
812
- .div(marketMaxLeverage.add(numericConstants_1.TEN_THOUSAND))
840
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
841
+ .div(marketMaxMaintLeverage.add(numericConstants_1.TEN_THOUSAND))
813
842
  .mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
814
843
  .mul(numericConstants_1.AMM_RESERVE_PRECISION)
815
844
  .div(proposedBaseAssetAmount);
816
845
  }
817
846
  else {
818
847
  priceDelta = freeCollateralAfterTrade
819
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
820
- .div(marketMaxLeverage.sub(numericConstants_1.TEN_THOUSAND))
848
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
849
+ .div(marketMaxMaintLeverage.sub(numericConstants_1.TEN_THOUSAND))
821
850
  .mul(numericConstants_1.PRICE_TO_QUOTE_PRECISION)
822
851
  .mul(numericConstants_1.AMM_RESERVE_PRECISION)
823
852
  .div(proposedBaseAssetAmount);
824
853
  }
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)) {
854
+ const currentPrice = this.getOracleDataForPerpMarket(perpPosition.marketIndex).price;
855
+ if (priceDelta.gt(currentPrice) &&
856
+ proposedPerpPosition.baseAssetAmount.gte(numericConstants_1.ZERO)) {
836
857
  return new _1.BN(-1);
837
858
  }
838
- return markPriceAfterTrade.sub(priceDelta);
859
+ return currentPrice.sub(priceDelta);
839
860
  }
840
861
  /**
841
862
  * Calculates the estimated liquidation price for a position after closing a quote amount of the position.
@@ -920,9 +941,7 @@ class User {
920
941
  }
921
942
  else {
922
943
  const freeCollateralAfterClose = totalCollateral.sub(marginRequirementAfterClosing);
923
- const buyingPowerAfterClose = freeCollateralAfterClose
924
- .mul(this.getMaxLeverage(targetMarketIndex))
925
- .div(numericConstants_1.TEN_THOUSAND);
944
+ const buyingPowerAfterClose = this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(targetMarketIndex, freeCollateralAfterClose, numericConstants_1.ZERO);
926
945
  maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
927
946
  }
928
947
  }
@@ -947,7 +966,7 @@ class User {
947
966
  const currentPosition = this.getPerpPosition(targetMarketIndex) ||
948
967
  this.getEmptyPosition(targetMarketIndex);
949
968
  const oracleData = this.getOracleDataForPerpMarket(targetMarketIndex);
950
- let currentPositionQuoteAmount = this.getPerpPositionValue(targetMarketIndex, oracleData);
969
+ let currentPositionQuoteAmount = this.getPerpPositionValue(targetMarketIndex, oracleData, includeOpenOrders);
951
970
  const currentSide = currentPosition && currentPosition.baseAssetAmount.isNeg()
952
971
  ? _1.PositionDirection.SHORT
953
972
  : _1.PositionDirection.LONG;
@@ -960,16 +979,18 @@ class User {
960
979
  .abs();
961
980
  const totalPositionAfterTradeExcludingTargetMarket = this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex, undefined, undefined, includeOpenOrders);
962
981
  const totalAssetValue = this.getTotalAssetValue();
963
- const totalPerpPositionValue = currentPerpPositionAfterTrade
982
+ const totalPerpPositionLiability = currentPerpPositionAfterTrade
964
983
  .add(totalPositionAfterTradeExcludingTargetMarket)
965
984
  .abs();
966
- const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false));
967
- if (totalAssetValue.eq(numericConstants_1.ZERO) && totalLiabilitiesAfterTrade.eq(numericConstants_1.ZERO)) {
985
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(undefined, undefined, undefined, includeOpenOrders);
986
+ const totalLiabilitiesAfterTrade = totalPerpPositionLiability.add(totalSpotLiability);
987
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
988
+ if (netAssetValue.eq(numericConstants_1.ZERO)) {
968
989
  return numericConstants_1.ZERO;
969
990
  }
970
991
  const newLeverage = totalLiabilitiesAfterTrade
971
992
  .mul(numericConstants_1.TEN_THOUSAND)
972
- .div(totalAssetValue);
993
+ .div(netAssetValue);
973
994
  return newLeverage;
974
995
  }
975
996
  /**
@@ -992,28 +1013,28 @@ class User {
992
1013
  getWithdrawalLimit(marketIndex, reduceOnly) {
993
1014
  const nowTs = new _1.BN(Math.floor(Date.now() / 1000));
994
1015
  const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
995
- const { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
1016
+ // eslint-disable-next-line prefer-const
1017
+ let { borrowLimit, withdrawLimit } = spotBalance_1.calculateWithdrawLimit(spotMarket, nowTs);
996
1018
  const freeCollateral = this.getFreeCollateral();
997
1019
  const oracleData = this.getOracleDataForSpotMarket(marketIndex);
998
1020
  const precisionIncrease = numericConstants_1.TEN.pow(new _1.BN(spotMarket.decimals - 6));
1021
+ const { canBypass, depositAmount: userDepositAmount } = this.canBypassWithdrawLimits(marketIndex);
1022
+ if (canBypass) {
1023
+ withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
1024
+ }
999
1025
  const amountWithdrawable = freeCollateral
1000
1026
  .mul(numericConstants_1.MARGIN_PRECISION)
1001
1027
  .div(new _1.BN(spotMarket.initialAssetWeight))
1002
1028
  .mul(numericConstants_1.PRICE_PRECISION)
1003
1029
  .div(oracleData.price)
1004
1030
  .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());
1031
+ const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
1011
1032
  if (reduceOnly) {
1012
1033
  return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
1013
1034
  }
1014
1035
  else {
1015
1036
  const weightedAssetValue = this.getSpotMarketAssetValue(marketIndex, 'Initial', false);
1016
- const freeCollatAfterWithdraw = userSpotBalance.gt(numericConstants_1.ZERO)
1037
+ const freeCollatAfterWithdraw = userDepositAmount.gt(numericConstants_1.ZERO)
1017
1038
  ? freeCollateral.sub(weightedAssetValue)
1018
1039
  : freeCollateral;
1019
1040
  const maxLiabilityAllowed = freeCollatAfterWithdraw
@@ -1026,6 +1047,43 @@ class User {
1026
1047
  return _1.BN.max(maxBorrowValue, numericConstants_1.ZERO);
1027
1048
  }
1028
1049
  }
1050
+ canBypassWithdrawLimits(marketIndex) {
1051
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1052
+ const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new _1.BN(10));
1053
+ const position = this.getSpotPosition(marketIndex);
1054
+ const netDeposits = this.getUserAccount().totalDeposits.sub(this.getUserAccount().totalWithdraws);
1055
+ if (!position) {
1056
+ return {
1057
+ canBypass: false,
1058
+ maxDepositAmount,
1059
+ depositAmount: numericConstants_1.ZERO,
1060
+ netDeposits,
1061
+ };
1062
+ }
1063
+ if (types_1.isVariant(position.balanceType, 'borrow')) {
1064
+ return {
1065
+ canBypass: false,
1066
+ maxDepositAmount,
1067
+ netDeposits,
1068
+ depositAmount: numericConstants_1.ZERO,
1069
+ };
1070
+ }
1071
+ const depositAmount = spotBalance_1.getTokenAmount(position.scaledBalance, spotMarket, 'deposit');
1072
+ if (netDeposits.lt(numericConstants_1.ZERO)) {
1073
+ return {
1074
+ canBypass: false,
1075
+ maxDepositAmount,
1076
+ depositAmount: numericConstants_1.ZERO,
1077
+ netDeposits,
1078
+ };
1079
+ }
1080
+ return {
1081
+ canBypass: depositAmount.lt(maxDepositAmount),
1082
+ maxDepositAmount,
1083
+ netDeposits,
1084
+ depositAmount,
1085
+ };
1086
+ }
1029
1087
  /**
1030
1088
  * Get the total position value, excluding any position coming from the given target market
1031
1089
  * @param marketToIgnore
@@ -1037,7 +1095,7 @@ class User {
1037
1095
  const oracleData = this.getOracleDataForPerpMarket(marketToIgnore);
1038
1096
  let currentPerpPositionValueUSDC = numericConstants_1.ZERO;
1039
1097
  if (currentPerpPosition) {
1040
- currentPerpPositionValueUSDC = this.getPerpPositionValue(marketToIgnore, oracleData);
1098
+ currentPerpPositionValueUSDC = this.getPerpPositionValue(marketToIgnore, oracleData, includeOpenOrders);
1041
1099
  }
1042
1100
  return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders).sub(currentPerpPositionValueUSDC);
1043
1101
  }
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.2",
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.2",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -22,7 +22,7 @@ export function calculateSizePremiumLiabilityWeight(
22
22
  return liabilityWeight;
23
23
  }
24
24
 
25
- const sizeSqrt = squareRootBN(size.mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
25
+ const sizeSqrt = squareRootBN(size.abs().mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
26
26
 
27
27
  const denom0 = BN.max(new BN(1), SPOT_MARKET_IMF_PRECISION.div(imfFactor));
28
28
  assert(denom0.gt(ZERO));
@@ -57,7 +57,7 @@ export function calculateSizeDiscountAssetWeight(
57
57
  return assetWeight;
58
58
  }
59
59
 
60
- const sizeSqrt = squareRootBN(size.mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
60
+ const sizeSqrt = squareRootBN(size.abs().mul(new BN(10)).add(new BN(1))); //1e9 -> 1e10 -> 1e5
61
61
  const imfNumerator = SPOT_MARKET_IMF_PRECISION.add(
62
62
  SPOT_MARKET_IMF_PRECISION.div(new BN(10))
63
63
  );
@@ -106,17 +106,19 @@ export function calculateOraclePriceForPerpMargin(
106
106
  export function calculateBaseAssetValueWithOracle(
107
107
  market: PerpMarketAccount,
108
108
  perpPosition: PerpPosition,
109
- oraclePriceData: OraclePriceData
109
+ oraclePriceData: OraclePriceData,
110
+ includeOpenOrders = false
110
111
  ): BN {
111
112
  let price = oraclePriceData.price;
112
113
  if (isVariant(market.status, 'settlement')) {
113
114
  price = market.expiryPrice;
114
115
  }
115
116
 
116
- return perpPosition.baseAssetAmount
117
- .abs()
118
- .mul(price)
119
- .div(AMM_RESERVE_PRECISION);
117
+ const baseAssetAmount = includeOpenOrders
118
+ ? calculateWorstCaseBaseAssetAmount(perpPosition)
119
+ : perpPosition.baseAssetAmount;
120
+
121
+ return baseAssetAmount.abs().mul(price).div(AMM_RESERVE_PRECISION);
120
122
  }
121
123
 
122
124
  export function calculateWorstCaseBaseAssetAmount(