@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/src/user.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  QUOTE_SPOT_MARKET_INDEX,
27
27
  TEN,
28
28
  OPEN_ORDER_MARGIN_REQUIREMENT,
29
+ FIVE_MINUTE,
29
30
  } from './constants/numericConstants';
30
31
  import {
31
32
  UserAccountSubscriber,
@@ -40,11 +41,10 @@ import {
40
41
  calculateUnrealizedAssetWeight,
41
42
  calculateMarketMarginRatio,
42
43
  PositionDirection,
43
- calculateTradeSlippage,
44
44
  BN,
45
45
  SpotMarketAccount,
46
46
  getTokenValue,
47
- SpotBalanceType,
47
+ getStrictTokenValue,
48
48
  } from '.';
49
49
  import {
50
50
  getTokenAmount,
@@ -66,6 +66,8 @@ import {
66
66
  isSpotPositionAvailable,
67
67
  } from './math/spotPosition';
68
68
 
69
+ import { calculateLiveOracleTwap } from './math/oracles';
70
+
69
71
  export class User {
70
72
  driftClient: DriftClient;
71
73
  userAccountPublicKey: PublicKey;
@@ -388,7 +390,8 @@ export class User {
388
390
  */
389
391
  public getMarginRequirement(
390
392
  marginCategory: MarginCategory,
391
- liquidationBuffer?: BN
393
+ liquidationBuffer?: BN,
394
+ strict = false
392
395
  ): BN {
393
396
  return this.getTotalPerpPositionValue(
394
397
  marginCategory,
@@ -399,7 +402,8 @@ export class User {
399
402
  undefined,
400
403
  marginCategory,
401
404
  liquidationBuffer,
402
- true
405
+ true,
406
+ strict
403
407
  )
404
408
  );
405
409
  }
@@ -408,7 +412,7 @@ export class User {
408
412
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
409
413
  */
410
414
  public getInitialMarginRequirement(): BN {
411
- return this.getMarginRequirement('Initial');
415
+ return this.getMarginRequirement('Initial', undefined, true);
412
416
  }
413
417
 
414
418
  /**
@@ -500,8 +504,11 @@ export class User {
500
504
  marketIndex?: number,
501
505
  marginCategory?: MarginCategory,
502
506
  liquidationBuffer?: BN,
503
- includeOpenOrders?: boolean
507
+ includeOpenOrders?: boolean,
508
+ strict = false,
509
+ now?: BN
504
510
  ): BN {
511
+ now = now || new BN(new Date().getTime() / 1000);
505
512
  return this.getUserAccount().spotPositions.reduce(
506
513
  (totalLiabilityValue, spotPosition) => {
507
514
  if (
@@ -557,7 +564,9 @@ export class User {
557
564
  oraclePriceData,
558
565
  spotMarketAccount,
559
566
  marginCategory,
560
- liquidationBuffer
567
+ liquidationBuffer,
568
+ strict,
569
+ now
561
570
  );
562
571
  return totalLiabilityValue.add(liabilityValue);
563
572
  } else {
@@ -579,7 +588,9 @@ export class User {
579
588
  oraclePriceData,
580
589
  spotMarketAccount,
581
590
  marginCategory,
582
- liquidationBuffer
591
+ liquidationBuffer,
592
+ strict,
593
+ now
583
594
  );
584
595
 
585
596
  newTotalLiabilityValue =
@@ -619,13 +630,32 @@ export class User {
619
630
  oraclePriceData: OraclePriceData,
620
631
  spotMarketAccount: SpotMarketAccount,
621
632
  marginCategory?: MarginCategory,
622
- liquidationBuffer?: BN
633
+ liquidationBuffer?: BN,
634
+ strict = false,
635
+ now?: BN
623
636
  ): BN {
624
- let liabilityValue = getTokenValue(
625
- tokenAmount,
626
- spotMarketAccount.decimals,
627
- oraclePriceData
628
- );
637
+ let liabilityValue = null;
638
+
639
+ if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
640
+ const estOracleTwap = calculateLiveOracleTwap(
641
+ spotMarketAccount.historicalOracleData,
642
+ oraclePriceData,
643
+ now,
644
+ FIVE_MINUTE // 5MIN
645
+ );
646
+ liabilityValue = getStrictTokenValue(
647
+ tokenAmount,
648
+ spotMarketAccount.decimals,
649
+ oraclePriceData,
650
+ estOracleTwap
651
+ );
652
+ } else {
653
+ liabilityValue = getTokenValue(
654
+ tokenAmount,
655
+ spotMarketAccount.decimals,
656
+ oraclePriceData
657
+ );
658
+ }
629
659
 
630
660
  if (marginCategory !== undefined) {
631
661
  let weight = calculateLiabilityWeight(
@@ -653,8 +683,11 @@ export class User {
653
683
  public getSpotMarketAssetValue(
654
684
  marketIndex?: number,
655
685
  marginCategory?: MarginCategory,
656
- includeOpenOrders?: boolean
686
+ includeOpenOrders?: boolean,
687
+ strict = false,
688
+ now?: BN
657
689
  ): BN {
690
+ now = now || new BN(new Date().getTime() / 1000);
658
691
  return this.getUserAccount().spotPositions.reduce(
659
692
  (totalAssetValue, spotPosition) => {
660
693
  if (
@@ -698,7 +731,9 @@ export class User {
698
731
  tokenAmount,
699
732
  oraclePriceData,
700
733
  spotMarketAccount,
701
- marginCategory
734
+ marginCategory,
735
+ strict,
736
+ now
702
737
  );
703
738
  return totalAssetValue.add(assetValue);
704
739
  } else {
@@ -719,7 +754,9 @@ export class User {
719
754
  worstCaseTokenAmount,
720
755
  oraclePriceData,
721
756
  spotMarketAccount,
722
- marginCategory
757
+ marginCategory,
758
+ strict,
759
+ now
723
760
  );
724
761
 
725
762
  newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
@@ -741,13 +778,31 @@ export class User {
741
778
  tokenAmount: BN,
742
779
  oraclePriceData: OraclePriceData,
743
780
  spotMarketAccount: SpotMarketAccount,
744
- marginCategory?: MarginCategory
781
+ marginCategory?: MarginCategory,
782
+ strict = false,
783
+ now?: BN
745
784
  ): BN {
746
- let assetValue = getTokenValue(
747
- tokenAmount,
748
- spotMarketAccount.decimals,
749
- oraclePriceData
750
- );
785
+ let assetValue = null;
786
+ if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
787
+ const estOracleTwap = calculateLiveOracleTwap(
788
+ spotMarketAccount.historicalOracleData,
789
+ oraclePriceData,
790
+ now,
791
+ FIVE_MINUTE // 5MIN
792
+ );
793
+ assetValue = getStrictTokenValue(
794
+ tokenAmount,
795
+ spotMarketAccount.decimals,
796
+ oraclePriceData,
797
+ estOracleTwap
798
+ );
799
+ } else {
800
+ assetValue = getTokenValue(
801
+ tokenAmount,
802
+ spotMarketAccount.decimals,
803
+ oraclePriceData
804
+ );
805
+ }
751
806
 
752
807
  if (marginCategory !== undefined) {
753
808
  const weight = calculateAssetWeight(
@@ -805,17 +860,12 @@ export class User {
805
860
  } else if (totalCollateral.lte(ZERO)) {
806
861
  health = 0;
807
862
  } else {
808
- // const totalCollateral = this.getTotalCollateral('Initial');
809
- // const maintenanceMarginReq = this.getMaintenanceMarginRequirement();
810
-
811
- const marginRatio =
812
- this.getMarginRatio().toNumber() / MARGIN_PRECISION.toNumber();
813
-
814
- const maintenanceRatio =
815
- (maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
816
- marginRatio;
817
-
818
- const healthP1 = Math.max(0, (marginRatio - maintenanceRatio) * 100) + 1;
863
+ const healthP1 =
864
+ Math.max(
865
+ 0,
866
+ (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
867
+ 100
868
+ ) + 1;
819
869
 
820
870
  health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
821
871
  if (health > 1) {
@@ -1017,19 +1067,32 @@ export class User {
1017
1067
  }
1018
1068
 
1019
1069
  /**
1020
- * calculates current user leverage across all positions
1070
+ * calculates current user leverage which is (total liability size) / (net asset value)
1021
1071
  * @returns : Precision TEN_THOUSAND
1022
1072
  */
1023
1073
  public getLeverage(): BN {
1024
- const totalLiabilityValue = this.getTotalLiabilityValue();
1074
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1075
+ undefined,
1076
+ undefined,
1077
+ true
1078
+ );
1079
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1080
+ undefined,
1081
+ undefined,
1082
+ undefined,
1083
+ true
1084
+ );
1085
+
1086
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1025
1087
 
1026
1088
  const totalAssetValue = this.getTotalAssetValue();
1089
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1027
1090
 
1028
- if (totalAssetValue.eq(ZERO) && totalLiabilityValue.eq(ZERO)) {
1091
+ if (netAssetValue.eq(ZERO)) {
1029
1092
  return ZERO;
1030
1093
  }
1031
1094
 
1032
- return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
1095
+ return totalLiabilityValue.mul(TEN_THOUSAND).div(netAssetValue);
1033
1096
  }
1034
1097
 
1035
1098
  getTotalLiabilityValue(marginCategory?: MarginCategory): BN {
@@ -1060,12 +1123,27 @@ export class User {
1060
1123
  ): BN {
1061
1124
  const market = this.driftClient.getPerpMarketAccount(marketIndex);
1062
1125
 
1126
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1127
+ undefined,
1128
+ undefined,
1129
+ true
1130
+ );
1131
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1132
+ undefined,
1133
+ undefined,
1134
+ undefined,
1135
+ true
1136
+ );
1137
+
1063
1138
  const totalAssetValue = this.getTotalAssetValue();
1064
- if (totalAssetValue.eq(ZERO)) {
1139
+
1140
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1141
+
1142
+ if (netAssetValue.eq(ZERO)) {
1065
1143
  return ZERO;
1066
1144
  }
1067
1145
 
1068
- const totalLiabilityValue = this.getTotalLiabilityValue();
1146
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1069
1147
 
1070
1148
  const marginRatio = calculateMarketMarginRatio(
1071
1149
  market,
@@ -1083,7 +1161,7 @@ export class User {
1083
1161
  return totalLiabilityValue
1084
1162
  .add(additionalLiabilities)
1085
1163
  .mul(TEN_THOUSAND)
1086
- .div(totalAssetValue);
1164
+ .div(netAssetValue);
1087
1165
  }
1088
1166
 
1089
1167
  /**
@@ -1091,15 +1169,28 @@ export class User {
1091
1169
  * @returns : Precision TEN_THOUSAND
1092
1170
  */
1093
1171
  public getMarginRatio(marginCategory?: MarginCategory): BN {
1094
- const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
1172
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1173
+ undefined,
1174
+ undefined,
1175
+ true
1176
+ );
1177
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1178
+ undefined,
1179
+ undefined,
1180
+ undefined,
1181
+ true
1182
+ );
1183
+
1184
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1095
1185
 
1096
1186
  if (totalLiabilityValue.eq(ZERO)) {
1097
1187
  return BN_MAX;
1098
1188
  }
1099
1189
 
1100
1190
  const totalAssetValue = this.getTotalAssetValue(marginCategory);
1191
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1101
1192
 
1102
- return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1193
+ return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1103
1194
  }
1104
1195
 
1105
1196
  public canBeLiquidated(): boolean {
@@ -1246,7 +1337,7 @@ export class User {
1246
1337
  for 10x long, BTC down $400:
1247
1338
  3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
1248
1339
 
1249
- const totalCollateral = this.getTotalCollateral();
1340
+ const totalCollateral = this.getTotalCollateral('Maintenance');
1250
1341
 
1251
1342
  // calculate the total position value ignoring any value from the target market of the trade
1252
1343
  const totalPositionValueExcludingTargetMarket =
@@ -1267,7 +1358,7 @@ export class User {
1267
1358
  marketIndex: perpPosition.marketIndex,
1268
1359
  baseAssetAmount: proposedBaseAssetAmount,
1269
1360
  remainderBaseAssetAmount: 0,
1270
- quoteAssetAmount: new BN(0),
1361
+ quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
1271
1362
  lastCumulativeFundingRate: ZERO,
1272
1363
  quoteBreakEvenAmount: new BN(0),
1273
1364
  quoteEntryAmount: new BN(0),
@@ -1296,37 +1387,27 @@ export class User {
1296
1387
  const totalPositionValueAfterTrade =
1297
1388
  totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
1298
1389
 
1299
- const marginRequirementExcludingTargetMarket =
1300
- this.getUserAccount().perpPositions.reduce(
1301
- (totalMarginRequirement, position) => {
1302
- if (position.marketIndex !== perpPosition.marketIndex) {
1303
- const market = this.driftClient.getPerpMarketAccount(
1304
- position.marketIndex
1305
- );
1306
- const positionValue = calculateBaseAssetValueWithOracle(
1307
- market,
1308
- position,
1309
- this.getOracleDataForPerpMarket(market.marketIndex)
1310
- );
1311
- const marketMarginRequirement = positionValue
1312
- .mul(
1313
- new BN(
1314
- calculateMarketMarginRatio(
1315
- market,
1316
- position.baseAssetAmount.abs(),
1317
- 'Maintenance'
1318
- )
1319
- )
1320
- )
1321
- .div(MARGIN_PRECISION);
1322
- totalMarginRequirement = totalMarginRequirement.add(
1323
- marketMarginRequirement
1324
- );
1325
- }
1326
- return totalMarginRequirement;
1327
- },
1328
- ZERO
1329
- );
1390
+ const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
1391
+ const positionValue = calculateBaseAssetValueWithOracle(
1392
+ market,
1393
+ proposedPerpPosition,
1394
+ this.getOracleDataForPerpMarket(market.marketIndex)
1395
+ );
1396
+ const marginRequirementOfTargetMarket = positionValue
1397
+ .mul(
1398
+ new BN(
1399
+ calculateMarketMarginRatio(
1400
+ market,
1401
+ proposedPerpPosition.baseAssetAmount.abs(),
1402
+ 'Maintenance'
1403
+ )
1404
+ )
1405
+ )
1406
+ .div(MARGIN_PRECISION);
1407
+
1408
+ const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(
1409
+ marginRequirementOfTargetMarket
1410
+ );
1330
1411
 
1331
1412
  const freeCollateralExcludingTargetMarket = totalCollateral.sub(
1332
1413
  marginRequirementExcludingTargetMarket
@@ -1340,70 +1421,62 @@ export class User {
1340
1421
  return new BN(-1);
1341
1422
  }
1342
1423
 
1343
- const marginRequirementAfterTrade =
1344
- marginRequirementExcludingTargetMarket.add(
1345
- proposedPerpPositionValue
1346
- .mul(
1347
- new BN(
1348
- calculateMarketMarginRatio(
1349
- market,
1350
- proposedPerpPosition.baseAssetAmount.abs(),
1351
- 'Maintenance'
1352
- )
1353
- )
1424
+ const marginRequirementTargetMarket = proposedPerpPositionValue
1425
+ .mul(
1426
+ new BN(
1427
+ calculateMarketMarginRatio(
1428
+ market,
1429
+ proposedPerpPosition.baseAssetAmount.abs(),
1430
+ 'Maintenance'
1354
1431
  )
1355
- .div(MARGIN_PRECISION)
1356
- );
1432
+ )
1433
+ )
1434
+ .div(MARGIN_PRECISION);
1435
+
1436
+ const marginRequirementAfterTrade =
1437
+ marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
1357
1438
  const freeCollateralAfterTrade = totalCollateral.sub(
1358
1439
  marginRequirementAfterTrade
1359
1440
  );
1360
1441
 
1361
- const marketMaxLeverage = this.getMaxLeverage(
1362
- proposedPerpPosition.marketIndex,
1363
- 'Maintenance'
1442
+ const marketMaxMaintLeverage = new BN(
1443
+ TEN_THOUSAND.mul(TEN_THOUSAND).toNumber() /
1444
+ calculateMarketMarginRatio(
1445
+ market,
1446
+ proposedPerpPosition.baseAssetAmount,
1447
+ 'Maintenance'
1448
+ )
1364
1449
  );
1365
1450
 
1366
1451
  let priceDelta;
1367
1452
  if (proposedBaseAssetAmount.lt(ZERO)) {
1368
1453
  priceDelta = freeCollateralAfterTrade
1369
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1370
- .div(marketMaxLeverage.add(TEN_THOUSAND))
1454
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
1455
+ .div(marketMaxMaintLeverage.add(TEN_THOUSAND))
1371
1456
  .mul(PRICE_TO_QUOTE_PRECISION)
1372
1457
  .mul(AMM_RESERVE_PRECISION)
1373
1458
  .div(proposedBaseAssetAmount);
1374
1459
  } else {
1375
1460
  priceDelta = freeCollateralAfterTrade
1376
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1377
- .div(marketMaxLeverage.sub(TEN_THOUSAND))
1461
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
1462
+ .div(marketMaxMaintLeverage.sub(TEN_THOUSAND))
1378
1463
  .mul(PRICE_TO_QUOTE_PRECISION)
1379
1464
  .mul(AMM_RESERVE_PRECISION)
1380
1465
  .div(proposedBaseAssetAmount);
1381
1466
  }
1382
1467
 
1383
- let markPriceAfterTrade;
1384
- if (positionBaseSizeChange.eq(ZERO)) {
1385
- markPriceAfterTrade = calculateReservePrice(
1386
- this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
1387
- this.getOracleDataForPerpMarket(perpPosition.marketIndex)
1388
- );
1389
- } else {
1390
- const direction = positionBaseSizeChange.gt(ZERO)
1391
- ? PositionDirection.LONG
1392
- : PositionDirection.SHORT;
1393
- markPriceAfterTrade = calculateTradeSlippage(
1394
- direction,
1395
- positionBaseSizeChange.abs(),
1396
- this.driftClient.getPerpMarketAccount(perpPosition.marketIndex),
1397
- 'base',
1398
- this.getOracleDataForPerpMarket(perpPosition.marketIndex)
1399
- )[3]; // newPrice after swap
1400
- }
1468
+ const currentPrice = this.getOracleDataForPerpMarket(
1469
+ perpPosition.marketIndex
1470
+ ).price;
1401
1471
 
1402
- if (priceDelta.gt(markPriceAfterTrade)) {
1472
+ if (
1473
+ priceDelta.gt(currentPrice) &&
1474
+ proposedPerpPosition.baseAssetAmount.gte(ZERO)
1475
+ ) {
1403
1476
  return new BN(-1);
1404
1477
  }
1405
1478
 
1406
- return markPriceAfterTrade.sub(priceDelta);
1479
+ return currentPrice.sub(priceDelta);
1407
1480
  }
1408
1481
 
1409
1482
  /**
@@ -1585,21 +1658,29 @@ export class User {
1585
1658
 
1586
1659
  const totalAssetValue = this.getTotalAssetValue();
1587
1660
 
1588
- const totalPerpPositionValue = currentPerpPositionAfterTrade
1661
+ const totalPerpPositionLiability = currentPerpPositionAfterTrade
1589
1662
  .add(totalPositionAfterTradeExcludingTargetMarket)
1590
1663
  .abs();
1591
1664
 
1592
- const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(
1593
- this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false)
1665
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1666
+ undefined,
1667
+ undefined,
1668
+ undefined,
1669
+ includeOpenOrders
1594
1670
  );
1595
1671
 
1596
- if (totalAssetValue.eq(ZERO) && totalLiabilitiesAfterTrade.eq(ZERO)) {
1672
+ const totalLiabilitiesAfterTrade =
1673
+ totalPerpPositionLiability.add(totalSpotLiability);
1674
+
1675
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1676
+
1677
+ if (netAssetValue.eq(ZERO)) {
1597
1678
  return ZERO;
1598
1679
  }
1599
1680
 
1600
1681
  const newLeverage = totalLiabilitiesAfterTrade
1601
1682
  .mul(TEN_THOUSAND)
1602
- .div(totalAssetValue);
1683
+ .div(netAssetValue);
1603
1684
 
1604
1685
  return newLeverage;
1605
1686
  }
@@ -1627,7 +1708,8 @@ export class User {
1627
1708
  const nowTs = new BN(Math.floor(Date.now() / 1000));
1628
1709
  const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1629
1710
 
1630
- const { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
1711
+ // eslint-disable-next-line prefer-const
1712
+ let { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
1631
1713
  spotMarket,
1632
1714
  nowTs
1633
1715
  );
@@ -1636,6 +1718,12 @@ export class User {
1636
1718
  const oracleData = this.getOracleDataForSpotMarket(marketIndex);
1637
1719
  const precisionIncrease = TEN.pow(new BN(spotMarket.decimals - 6));
1638
1720
 
1721
+ const { canBypass, depositAmount: userDepositAmount } =
1722
+ this.canBypassWithdrawLimits(marketIndex);
1723
+ if (canBypass) {
1724
+ withdrawLimit = BN.max(withdrawLimit, userDepositAmount);
1725
+ }
1726
+
1639
1727
  const amountWithdrawable = freeCollateral
1640
1728
  .mul(MARGIN_PRECISION)
1641
1729
  .div(new BN(spotMarket.initialAssetWeight))
@@ -1643,22 +1731,8 @@ export class User {
1643
1731
  .div(oracleData.price)
1644
1732
  .mul(precisionIncrease);
1645
1733
 
1646
- const userSpotPosition = this.getUserAccount().spotPositions.find(
1647
- (spotPosition) =>
1648
- isVariant(spotPosition.balanceType, 'deposit') &&
1649
- spotPosition.marketIndex == marketIndex
1650
- );
1651
-
1652
- const userSpotBalance = userSpotPosition
1653
- ? getTokenAmount(
1654
- userSpotPosition.scaledBalance,
1655
- this.driftClient.getSpotMarketAccount(marketIndex),
1656
- SpotBalanceType.DEPOSIT
1657
- )
1658
- : ZERO;
1659
-
1660
1734
  const maxWithdrawValue = BN.min(
1661
- BN.min(amountWithdrawable, userSpotBalance),
1735
+ BN.min(amountWithdrawable, userDepositAmount),
1662
1736
  withdrawLimit.abs()
1663
1737
  );
1664
1738
 
@@ -1671,7 +1745,7 @@ export class User {
1671
1745
  false
1672
1746
  );
1673
1747
 
1674
- const freeCollatAfterWithdraw = userSpotBalance.gt(ZERO)
1748
+ const freeCollatAfterWithdraw = userDepositAmount.gt(ZERO)
1675
1749
  ? freeCollateral.sub(weightedAssetValue)
1676
1750
  : freeCollateral;
1677
1751
 
@@ -1691,6 +1765,61 @@ export class User {
1691
1765
  }
1692
1766
  }
1693
1767
 
1768
+ public canBypassWithdrawLimits(marketIndex: number): {
1769
+ canBypass: boolean;
1770
+ netDeposits: BN;
1771
+ depositAmount: BN;
1772
+ maxDepositAmount: BN;
1773
+ } {
1774
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1775
+ const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new BN(10));
1776
+ const position = this.getSpotPosition(marketIndex);
1777
+
1778
+ const netDeposits = this.getUserAccount().totalDeposits.sub(
1779
+ this.getUserAccount().totalWithdraws
1780
+ );
1781
+
1782
+ if (!position) {
1783
+ return {
1784
+ canBypass: false,
1785
+ maxDepositAmount,
1786
+ depositAmount: ZERO,
1787
+ netDeposits,
1788
+ };
1789
+ }
1790
+
1791
+ if (isVariant(position.balanceType, 'borrow')) {
1792
+ return {
1793
+ canBypass: false,
1794
+ maxDepositAmount,
1795
+ netDeposits,
1796
+ depositAmount: ZERO,
1797
+ };
1798
+ }
1799
+
1800
+ const depositAmount = getTokenAmount(
1801
+ position.scaledBalance,
1802
+ spotMarket,
1803
+ 'deposit'
1804
+ );
1805
+
1806
+ if (netDeposits.lt(ZERO)) {
1807
+ return {
1808
+ canBypass: false,
1809
+ maxDepositAmount,
1810
+ depositAmount: ZERO,
1811
+ netDeposits,
1812
+ };
1813
+ }
1814
+
1815
+ return {
1816
+ canBypass: depositAmount.lt(maxDepositAmount),
1817
+ maxDepositAmount,
1818
+ netDeposits,
1819
+ depositAmount,
1820
+ };
1821
+ }
1822
+
1694
1823
  /**
1695
1824
  * Get the total position value, excluding any position coming from the given target market
1696
1825
  * @param marketToIgnore
package/tests/amm/test.ts CHANGED
@@ -206,9 +206,10 @@ describe('AMM Tests', () => {
206
206
  mockAmm.historicalOracleData.lastOraclePriceTwapTs = now.sub(new BN(11));
207
207
 
208
208
  const liveOracleTwap = calculateLiveOracleTwap(
209
- mockAmm,
209
+ mockAmm.historicalOracleData,
210
210
  oraclePriceData,
211
- now
211
+ now,
212
+ mockAmm.fundingPeriod
212
213
  );
213
214
  console.log('liveOracleTwap:', liveOracleTwap.toNumber());
214
215
  assert(liveOracleTwap.eq(new BN(13539488)));