@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/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;
@@ -363,13 +365,36 @@ export class User {
363
365
  }
364
366
 
365
367
  /**
366
- * calculates Buying Power = FC * MAX_LEVERAGE
368
+ * calculates Buying Power = free collateral / initial margin ratio
367
369
  * @returns : Precision QUOTE_PRECISION
368
370
  */
369
371
  public getBuyingPower(marketIndex: number): BN {
370
- return this.getFreeCollateral()
371
- .mul(this.getMaxLeverage(marketIndex, 'Initial'))
372
- .div(TEN_THOUSAND);
372
+ const perpPosition = this.getPerpPosition(marketIndex);
373
+ const worstCaseBaseAssetAmount = perpPosition
374
+ ? calculateWorstCaseBaseAssetAmount(perpPosition)
375
+ : ZERO;
376
+
377
+ const freeCollateral = this.getFreeCollateral();
378
+
379
+ return this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
380
+ marketIndex,
381
+ freeCollateral,
382
+ worstCaseBaseAssetAmount
383
+ );
384
+ }
385
+
386
+ getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
387
+ marketIndex: number,
388
+ freeCollateral: BN,
389
+ baseAssetAmount: BN
390
+ ): BN {
391
+ const marginRatio = calculateMarketMarginRatio(
392
+ this.driftClient.getPerpMarketAccount(marketIndex),
393
+ baseAssetAmount,
394
+ 'Initial'
395
+ );
396
+
397
+ return freeCollateral.mul(MARGIN_PRECISION).div(new BN(marginRatio));
373
398
  }
374
399
 
375
400
  /**
@@ -388,7 +413,8 @@ export class User {
388
413
  */
389
414
  public getMarginRequirement(
390
415
  marginCategory: MarginCategory,
391
- liquidationBuffer?: BN
416
+ liquidationBuffer?: BN,
417
+ strict = false
392
418
  ): BN {
393
419
  return this.getTotalPerpPositionValue(
394
420
  marginCategory,
@@ -399,7 +425,8 @@ export class User {
399
425
  undefined,
400
426
  marginCategory,
401
427
  liquidationBuffer,
402
- true
428
+ true,
429
+ strict
403
430
  )
404
431
  );
405
432
  }
@@ -408,7 +435,7 @@ export class User {
408
435
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
409
436
  */
410
437
  public getInitialMarginRequirement(): BN {
411
- return this.getMarginRequirement('Initial');
438
+ return this.getMarginRequirement('Initial', undefined, true);
412
439
  }
413
440
 
414
441
  /**
@@ -500,8 +527,11 @@ export class User {
500
527
  marketIndex?: number,
501
528
  marginCategory?: MarginCategory,
502
529
  liquidationBuffer?: BN,
503
- includeOpenOrders?: boolean
530
+ includeOpenOrders?: boolean,
531
+ strict = false,
532
+ now?: BN
504
533
  ): BN {
534
+ now = now || new BN(new Date().getTime() / 1000);
505
535
  return this.getUserAccount().spotPositions.reduce(
506
536
  (totalLiabilityValue, spotPosition) => {
507
537
  if (
@@ -557,7 +587,9 @@ export class User {
557
587
  oraclePriceData,
558
588
  spotMarketAccount,
559
589
  marginCategory,
560
- liquidationBuffer
590
+ liquidationBuffer,
591
+ strict,
592
+ now
561
593
  );
562
594
  return totalLiabilityValue.add(liabilityValue);
563
595
  } else {
@@ -579,7 +611,9 @@ export class User {
579
611
  oraclePriceData,
580
612
  spotMarketAccount,
581
613
  marginCategory,
582
- liquidationBuffer
614
+ liquidationBuffer,
615
+ strict,
616
+ now
583
617
  );
584
618
 
585
619
  newTotalLiabilityValue =
@@ -619,13 +653,32 @@ export class User {
619
653
  oraclePriceData: OraclePriceData,
620
654
  spotMarketAccount: SpotMarketAccount,
621
655
  marginCategory?: MarginCategory,
622
- liquidationBuffer?: BN
656
+ liquidationBuffer?: BN,
657
+ strict = false,
658
+ now?: BN
623
659
  ): BN {
624
- let liabilityValue = getTokenValue(
625
- tokenAmount,
626
- spotMarketAccount.decimals,
627
- oraclePriceData
628
- );
660
+ let liabilityValue = null;
661
+
662
+ if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
663
+ const estOracleTwap = calculateLiveOracleTwap(
664
+ spotMarketAccount.historicalOracleData,
665
+ oraclePriceData,
666
+ now,
667
+ FIVE_MINUTE // 5MIN
668
+ );
669
+ liabilityValue = getStrictTokenValue(
670
+ tokenAmount,
671
+ spotMarketAccount.decimals,
672
+ oraclePriceData,
673
+ estOracleTwap
674
+ );
675
+ } else {
676
+ liabilityValue = getTokenValue(
677
+ tokenAmount,
678
+ spotMarketAccount.decimals,
679
+ oraclePriceData
680
+ );
681
+ }
629
682
 
630
683
  if (marginCategory !== undefined) {
631
684
  let weight = calculateLiabilityWeight(
@@ -653,8 +706,11 @@ export class User {
653
706
  public getSpotMarketAssetValue(
654
707
  marketIndex?: number,
655
708
  marginCategory?: MarginCategory,
656
- includeOpenOrders?: boolean
709
+ includeOpenOrders?: boolean,
710
+ strict = false,
711
+ now?: BN
657
712
  ): BN {
713
+ now = now || new BN(new Date().getTime() / 1000);
658
714
  return this.getUserAccount().spotPositions.reduce(
659
715
  (totalAssetValue, spotPosition) => {
660
716
  if (
@@ -698,7 +754,9 @@ export class User {
698
754
  tokenAmount,
699
755
  oraclePriceData,
700
756
  spotMarketAccount,
701
- marginCategory
757
+ marginCategory,
758
+ strict,
759
+ now
702
760
  );
703
761
  return totalAssetValue.add(assetValue);
704
762
  } else {
@@ -719,7 +777,9 @@ export class User {
719
777
  worstCaseTokenAmount,
720
778
  oraclePriceData,
721
779
  spotMarketAccount,
722
- marginCategory
780
+ marginCategory,
781
+ strict,
782
+ now
723
783
  );
724
784
 
725
785
  newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
@@ -741,13 +801,31 @@ export class User {
741
801
  tokenAmount: BN,
742
802
  oraclePriceData: OraclePriceData,
743
803
  spotMarketAccount: SpotMarketAccount,
744
- marginCategory?: MarginCategory
804
+ marginCategory?: MarginCategory,
805
+ strict = false,
806
+ now?: BN
745
807
  ): BN {
746
- let assetValue = getTokenValue(
747
- tokenAmount,
748
- spotMarketAccount.decimals,
749
- oraclePriceData
750
- );
808
+ let assetValue = null;
809
+ if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
810
+ const estOracleTwap = calculateLiveOracleTwap(
811
+ spotMarketAccount.historicalOracleData,
812
+ oraclePriceData,
813
+ now,
814
+ FIVE_MINUTE // 5MIN
815
+ );
816
+ assetValue = getStrictTokenValue(
817
+ tokenAmount,
818
+ spotMarketAccount.decimals,
819
+ oraclePriceData,
820
+ estOracleTwap
821
+ );
822
+ } else {
823
+ assetValue = getTokenValue(
824
+ tokenAmount,
825
+ spotMarketAccount.decimals,
826
+ oraclePriceData
827
+ );
828
+ }
751
829
 
752
830
  if (marginCategory !== undefined) {
753
831
  const weight = calculateAssetWeight(
@@ -805,17 +883,12 @@ export class User {
805
883
  } else if (totalCollateral.lte(ZERO)) {
806
884
  health = 0;
807
885
  } 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;
886
+ const healthP1 =
887
+ Math.max(
888
+ 0,
889
+ (1 - maintenanceMarginReq.toNumber() / totalCollateral.toNumber()) *
890
+ 100
891
+ ) + 1;
819
892
 
820
893
  health = Math.min(1, Math.log(healthP1) / Math.log(100)) * 100;
821
894
  if (health > 1) {
@@ -928,7 +1001,8 @@ export class User {
928
1001
  */
929
1002
  public getPerpPositionValue(
930
1003
  marketIndex: number,
931
- oraclePriceData: OraclePriceData
1004
+ oraclePriceData: OraclePriceData,
1005
+ includeOpenOrders = false
932
1006
  ): BN {
933
1007
  const userPosition =
934
1008
  this.getPerpPosition(marketIndex) || this.getEmptyPosition(marketIndex);
@@ -938,7 +1012,8 @@ export class User {
938
1012
  return calculateBaseAssetValueWithOracle(
939
1013
  market,
940
1014
  userPosition,
941
- oraclePriceData
1015
+ oraclePriceData,
1016
+ includeOpenOrders
942
1017
  );
943
1018
  }
944
1019
 
@@ -1017,19 +1092,32 @@ export class User {
1017
1092
  }
1018
1093
 
1019
1094
  /**
1020
- * calculates current user leverage across all positions
1095
+ * calculates current user leverage which is (total liability size) / (net asset value)
1021
1096
  * @returns : Precision TEN_THOUSAND
1022
1097
  */
1023
1098
  public getLeverage(): BN {
1024
- const totalLiabilityValue = this.getTotalLiabilityValue();
1099
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1100
+ undefined,
1101
+ undefined,
1102
+ true
1103
+ );
1104
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1105
+ undefined,
1106
+ undefined,
1107
+ undefined,
1108
+ true
1109
+ );
1110
+
1111
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1025
1112
 
1026
1113
  const totalAssetValue = this.getTotalAssetValue();
1114
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1027
1115
 
1028
- if (totalAssetValue.eq(ZERO) && totalLiabilityValue.eq(ZERO)) {
1116
+ if (netAssetValue.eq(ZERO)) {
1029
1117
  return ZERO;
1030
1118
  }
1031
1119
 
1032
- return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
1120
+ return totalLiabilityValue.mul(TEN_THOUSAND).div(netAssetValue);
1033
1121
  }
1034
1122
 
1035
1123
  getTotalLiabilityValue(marginCategory?: MarginCategory): BN {
@@ -1060,12 +1148,27 @@ export class User {
1060
1148
  ): BN {
1061
1149
  const market = this.driftClient.getPerpMarketAccount(marketIndex);
1062
1150
 
1151
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1152
+ undefined,
1153
+ undefined,
1154
+ true
1155
+ );
1156
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1157
+ undefined,
1158
+ undefined,
1159
+ undefined,
1160
+ true
1161
+ );
1162
+
1063
1163
  const totalAssetValue = this.getTotalAssetValue();
1064
- if (totalAssetValue.eq(ZERO)) {
1164
+
1165
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1166
+
1167
+ if (netAssetValue.eq(ZERO)) {
1065
1168
  return ZERO;
1066
1169
  }
1067
1170
 
1068
- const totalLiabilityValue = this.getTotalLiabilityValue();
1171
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1069
1172
 
1070
1173
  const marginRatio = calculateMarketMarginRatio(
1071
1174
  market,
@@ -1083,7 +1186,7 @@ export class User {
1083
1186
  return totalLiabilityValue
1084
1187
  .add(additionalLiabilities)
1085
1188
  .mul(TEN_THOUSAND)
1086
- .div(totalAssetValue);
1189
+ .div(netAssetValue);
1087
1190
  }
1088
1191
 
1089
1192
  /**
@@ -1091,15 +1194,28 @@ export class User {
1091
1194
  * @returns : Precision TEN_THOUSAND
1092
1195
  */
1093
1196
  public getMarginRatio(marginCategory?: MarginCategory): BN {
1094
- const totalLiabilityValue = this.getTotalLiabilityValue(marginCategory);
1197
+ const totalPerpLiability = this.getTotalPerpPositionValue(
1198
+ undefined,
1199
+ undefined,
1200
+ true
1201
+ );
1202
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1203
+ undefined,
1204
+ undefined,
1205
+ undefined,
1206
+ true
1207
+ );
1208
+
1209
+ const totalLiabilityValue = totalPerpLiability.add(totalSpotLiability);
1095
1210
 
1096
1211
  if (totalLiabilityValue.eq(ZERO)) {
1097
1212
  return BN_MAX;
1098
1213
  }
1099
1214
 
1100
1215
  const totalAssetValue = this.getTotalAssetValue(marginCategory);
1216
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1101
1217
 
1102
- return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1218
+ return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1103
1219
  }
1104
1220
 
1105
1221
  public canBeLiquidated(): boolean {
@@ -1246,11 +1362,16 @@ export class User {
1246
1362
  for 10x long, BTC down $400:
1247
1363
  3. (10k - 4k) / (100k - 4k) = 6k/96k => .0625 */
1248
1364
 
1249
- const totalCollateral = this.getTotalCollateral();
1365
+ const totalCollateral = this.getTotalCollateral('Maintenance');
1250
1366
 
1251
1367
  // calculate the total position value ignoring any value from the target market of the trade
1252
1368
  const totalPositionValueExcludingTargetMarket =
1253
- this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
1369
+ this.getTotalPerpPositionValueExcludingMarket(
1370
+ perpPosition.marketIndex,
1371
+ undefined,
1372
+ undefined,
1373
+ true
1374
+ );
1254
1375
 
1255
1376
  const currentPerpPosition =
1256
1377
  this.getPerpPosition(perpPosition.marketIndex) ||
@@ -1267,13 +1388,13 @@ export class User {
1267
1388
  marketIndex: perpPosition.marketIndex,
1268
1389
  baseAssetAmount: proposedBaseAssetAmount,
1269
1390
  remainderBaseAssetAmount: 0,
1270
- quoteAssetAmount: new BN(0),
1391
+ quoteAssetAmount: currentPerpPosition.quoteAssetAmount,
1271
1392
  lastCumulativeFundingRate: ZERO,
1272
1393
  quoteBreakEvenAmount: new BN(0),
1273
1394
  quoteEntryAmount: new BN(0),
1274
1395
  openOrders: 0,
1275
- openBids: new BN(0),
1276
- openAsks: new BN(0),
1396
+ openBids: currentPerpPosition.openBids,
1397
+ openAsks: currentPerpPosition.openAsks,
1277
1398
  settledPnl: ZERO,
1278
1399
  lpShares: ZERO,
1279
1400
  lastBaseAssetAmountPerLp: ZERO,
@@ -1289,44 +1410,36 @@ export class User {
1289
1410
  const proposedPerpPositionValue = calculateBaseAssetValueWithOracle(
1290
1411
  market,
1291
1412
  proposedPerpPosition,
1292
- this.getOracleDataForPerpMarket(market.marketIndex)
1413
+ this.getOracleDataForPerpMarket(market.marketIndex),
1414
+ true
1293
1415
  );
1294
1416
 
1295
1417
  // total position value after trade
1296
1418
  const totalPositionValueAfterTrade =
1297
1419
  totalPositionValueExcludingTargetMarket.add(proposedPerpPositionValue);
1298
1420
 
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
- );
1421
+ const marginRequirementOfAll = this.getMaintenanceMarginRequirement();
1422
+ const positionValue = calculateBaseAssetValueWithOracle(
1423
+ market,
1424
+ currentPerpPosition,
1425
+ this.getOracleDataForPerpMarket(market.marketIndex),
1426
+ true
1427
+ );
1428
+ const marginRequirementOfTargetMarket = positionValue
1429
+ .mul(
1430
+ new BN(
1431
+ calculateMarketMarginRatio(
1432
+ market,
1433
+ calculateWorstCaseBaseAssetAmount(currentPerpPosition).abs(),
1434
+ 'Maintenance'
1435
+ )
1436
+ )
1437
+ )
1438
+ .div(MARGIN_PRECISION);
1439
+
1440
+ const marginRequirementExcludingTargetMarket = marginRequirementOfAll.sub(
1441
+ marginRequirementOfTargetMarket
1442
+ );
1330
1443
 
1331
1444
  const freeCollateralExcludingTargetMarket = totalCollateral.sub(
1332
1445
  marginRequirementExcludingTargetMarket
@@ -1340,70 +1453,64 @@ export class User {
1340
1453
  return new BN(-1);
1341
1454
  }
1342
1455
 
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
- )
1456
+ const proposedWorstCastBaseAssetAmount =
1457
+ calculateWorstCaseBaseAssetAmount(proposedPerpPosition);
1458
+ const marginRequirementTargetMarket = proposedPerpPositionValue
1459
+ .mul(
1460
+ new BN(
1461
+ calculateMarketMarginRatio(
1462
+ market,
1463
+ proposedWorstCastBaseAssetAmount.abs(),
1464
+ 'Maintenance'
1354
1465
  )
1355
- .div(MARGIN_PRECISION)
1356
- );
1466
+ )
1467
+ )
1468
+ .div(MARGIN_PRECISION);
1469
+
1470
+ const marginRequirementAfterTrade =
1471
+ marginRequirementExcludingTargetMarket.add(marginRequirementTargetMarket);
1357
1472
  const freeCollateralAfterTrade = totalCollateral.sub(
1358
1473
  marginRequirementAfterTrade
1359
1474
  );
1360
1475
 
1361
- const marketMaxLeverage = this.getMaxLeverage(
1362
- proposedPerpPosition.marketIndex,
1363
- 'Maintenance'
1476
+ const marketMaxMaintLeverage = new BN(
1477
+ TEN_THOUSAND.mul(TEN_THOUSAND).toNumber() /
1478
+ calculateMarketMarginRatio(
1479
+ market,
1480
+ proposedWorstCastBaseAssetAmount.abs(),
1481
+ 'Maintenance'
1482
+ )
1364
1483
  );
1365
1484
 
1366
1485
  let priceDelta;
1367
1486
  if (proposedBaseAssetAmount.lt(ZERO)) {
1368
1487
  priceDelta = freeCollateralAfterTrade
1369
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1370
- .div(marketMaxLeverage.add(TEN_THOUSAND))
1488
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
1489
+ .div(marketMaxMaintLeverage.add(TEN_THOUSAND))
1371
1490
  .mul(PRICE_TO_QUOTE_PRECISION)
1372
1491
  .mul(AMM_RESERVE_PRECISION)
1373
1492
  .div(proposedBaseAssetAmount);
1374
1493
  } else {
1375
1494
  priceDelta = freeCollateralAfterTrade
1376
- .mul(marketMaxLeverage) // precision is TEN_THOUSAND
1377
- .div(marketMaxLeverage.sub(TEN_THOUSAND))
1495
+ .mul(marketMaxMaintLeverage) // precision is TEN_THOUSAND
1496
+ .div(marketMaxMaintLeverage.sub(TEN_THOUSAND))
1378
1497
  .mul(PRICE_TO_QUOTE_PRECISION)
1379
1498
  .mul(AMM_RESERVE_PRECISION)
1380
1499
  .div(proposedBaseAssetAmount);
1381
1500
  }
1382
1501
 
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
- }
1502
+ const currentPrice = this.getOracleDataForPerpMarket(
1503
+ perpPosition.marketIndex
1504
+ ).price;
1401
1505
 
1402
- if (priceDelta.gt(markPriceAfterTrade)) {
1506
+ if (
1507
+ priceDelta.gt(currentPrice) &&
1508
+ proposedPerpPosition.baseAssetAmount.gte(ZERO)
1509
+ ) {
1403
1510
  return new BN(-1);
1404
1511
  }
1405
1512
 
1406
- return markPriceAfterTrade.sub(priceDelta);
1513
+ return currentPrice.sub(priceDelta);
1407
1514
  }
1408
1515
 
1409
1516
  /**
@@ -1518,9 +1625,12 @@ export class User {
1518
1625
  const freeCollateralAfterClose = totalCollateral.sub(
1519
1626
  marginRequirementAfterClosing
1520
1627
  );
1521
- const buyingPowerAfterClose = freeCollateralAfterClose
1522
- .mul(this.getMaxLeverage(targetMarketIndex))
1523
- .div(TEN_THOUSAND);
1628
+ const buyingPowerAfterClose =
1629
+ this.getBuyingPowerFromFreeCollateralAndBaseAssetAmount(
1630
+ targetMarketIndex,
1631
+ freeCollateralAfterClose,
1632
+ ZERO
1633
+ );
1524
1634
  maxPositionSize = perpPositionValue.add(buyingPowerAfterClose);
1525
1635
  }
1526
1636
  } else {
@@ -1557,7 +1667,8 @@ export class User {
1557
1667
 
1558
1668
  let currentPositionQuoteAmount = this.getPerpPositionValue(
1559
1669
  targetMarketIndex,
1560
- oracleData
1670
+ oracleData,
1671
+ includeOpenOrders
1561
1672
  );
1562
1673
 
1563
1674
  const currentSide =
@@ -1585,21 +1696,29 @@ export class User {
1585
1696
 
1586
1697
  const totalAssetValue = this.getTotalAssetValue();
1587
1698
 
1588
- const totalPerpPositionValue = currentPerpPositionAfterTrade
1699
+ const totalPerpPositionLiability = currentPerpPositionAfterTrade
1589
1700
  .add(totalPositionAfterTradeExcludingTargetMarket)
1590
1701
  .abs();
1591
1702
 
1592
- const totalLiabilitiesAfterTrade = totalPerpPositionValue.add(
1593
- this.getSpotMarketLiabilityValue(undefined, undefined, undefined, false)
1703
+ const totalSpotLiability = this.getSpotMarketLiabilityValue(
1704
+ undefined,
1705
+ undefined,
1706
+ undefined,
1707
+ includeOpenOrders
1594
1708
  );
1595
1709
 
1596
- if (totalAssetValue.eq(ZERO) && totalLiabilitiesAfterTrade.eq(ZERO)) {
1710
+ const totalLiabilitiesAfterTrade =
1711
+ totalPerpPositionLiability.add(totalSpotLiability);
1712
+
1713
+ const netAssetValue = totalAssetValue.sub(totalSpotLiability);
1714
+
1715
+ if (netAssetValue.eq(ZERO)) {
1597
1716
  return ZERO;
1598
1717
  }
1599
1718
 
1600
1719
  const newLeverage = totalLiabilitiesAfterTrade
1601
1720
  .mul(TEN_THOUSAND)
1602
- .div(totalAssetValue);
1721
+ .div(netAssetValue);
1603
1722
 
1604
1723
  return newLeverage;
1605
1724
  }
@@ -1627,7 +1746,8 @@ export class User {
1627
1746
  const nowTs = new BN(Math.floor(Date.now() / 1000));
1628
1747
  const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1629
1748
 
1630
- const { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
1749
+ // eslint-disable-next-line prefer-const
1750
+ let { borrowLimit, withdrawLimit } = calculateWithdrawLimit(
1631
1751
  spotMarket,
1632
1752
  nowTs
1633
1753
  );
@@ -1636,6 +1756,12 @@ export class User {
1636
1756
  const oracleData = this.getOracleDataForSpotMarket(marketIndex);
1637
1757
  const precisionIncrease = TEN.pow(new BN(spotMarket.decimals - 6));
1638
1758
 
1759
+ const { canBypass, depositAmount: userDepositAmount } =
1760
+ this.canBypassWithdrawLimits(marketIndex);
1761
+ if (canBypass) {
1762
+ withdrawLimit = BN.max(withdrawLimit, userDepositAmount);
1763
+ }
1764
+
1639
1765
  const amountWithdrawable = freeCollateral
1640
1766
  .mul(MARGIN_PRECISION)
1641
1767
  .div(new BN(spotMarket.initialAssetWeight))
@@ -1643,22 +1769,8 @@ export class User {
1643
1769
  .div(oracleData.price)
1644
1770
  .mul(precisionIncrease);
1645
1771
 
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
1772
  const maxWithdrawValue = BN.min(
1661
- BN.min(amountWithdrawable, userSpotBalance),
1773
+ BN.min(amountWithdrawable, userDepositAmount),
1662
1774
  withdrawLimit.abs()
1663
1775
  );
1664
1776
 
@@ -1671,7 +1783,7 @@ export class User {
1671
1783
  false
1672
1784
  );
1673
1785
 
1674
- const freeCollatAfterWithdraw = userSpotBalance.gt(ZERO)
1786
+ const freeCollatAfterWithdraw = userDepositAmount.gt(ZERO)
1675
1787
  ? freeCollateral.sub(weightedAssetValue)
1676
1788
  : freeCollateral;
1677
1789
 
@@ -1691,6 +1803,61 @@ export class User {
1691
1803
  }
1692
1804
  }
1693
1805
 
1806
+ public canBypassWithdrawLimits(marketIndex: number): {
1807
+ canBypass: boolean;
1808
+ netDeposits: BN;
1809
+ depositAmount: BN;
1810
+ maxDepositAmount: BN;
1811
+ } {
1812
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
1813
+ const maxDepositAmount = spotMarket.withdrawGuardThreshold.div(new BN(10));
1814
+ const position = this.getSpotPosition(marketIndex);
1815
+
1816
+ const netDeposits = this.getUserAccount().totalDeposits.sub(
1817
+ this.getUserAccount().totalWithdraws
1818
+ );
1819
+
1820
+ if (!position) {
1821
+ return {
1822
+ canBypass: false,
1823
+ maxDepositAmount,
1824
+ depositAmount: ZERO,
1825
+ netDeposits,
1826
+ };
1827
+ }
1828
+
1829
+ if (isVariant(position.balanceType, 'borrow')) {
1830
+ return {
1831
+ canBypass: false,
1832
+ maxDepositAmount,
1833
+ netDeposits,
1834
+ depositAmount: ZERO,
1835
+ };
1836
+ }
1837
+
1838
+ const depositAmount = getTokenAmount(
1839
+ position.scaledBalance,
1840
+ spotMarket,
1841
+ 'deposit'
1842
+ );
1843
+
1844
+ if (netDeposits.lt(ZERO)) {
1845
+ return {
1846
+ canBypass: false,
1847
+ maxDepositAmount,
1848
+ depositAmount: ZERO,
1849
+ netDeposits,
1850
+ };
1851
+ }
1852
+
1853
+ return {
1854
+ canBypass: depositAmount.lt(maxDepositAmount),
1855
+ maxDepositAmount,
1856
+ netDeposits,
1857
+ depositAmount,
1858
+ };
1859
+ }
1860
+
1694
1861
  /**
1695
1862
  * Get the total position value, excluding any position coming from the given target market
1696
1863
  * @param marketToIgnore
@@ -1712,7 +1879,8 @@ export class User {
1712
1879
  if (currentPerpPosition) {
1713
1880
  currentPerpPositionValueUSDC = this.getPerpPositionValue(
1714
1881
  marketToIgnore,
1715
- oracleData
1882
+ oracleData,
1883
+ includeOpenOrders
1716
1884
  );
1717
1885
  }
1718
1886