@drift-labs/sdk 0.2.0-master.26 → 0.2.0-master.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +1 -0
  2. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +3 -0
  3. package/lib/accounts/types.d.ts +1 -0
  4. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +1 -0
  5. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +3 -0
  6. package/lib/admin.d.ts +5 -2
  7. package/lib/admin.js +30 -4
  8. package/lib/clearingHouse.d.ts +2 -0
  9. package/lib/clearingHouse.js +14 -1
  10. package/lib/clearingHouseUser.d.ts +11 -9
  11. package/lib/clearingHouseUser.js +182 -106
  12. package/lib/config.js +1 -1
  13. package/lib/dlob/DLOB.d.ts +73 -0
  14. package/lib/dlob/DLOB.js +557 -0
  15. package/lib/dlob/DLOBNode.d.ts +52 -0
  16. package/lib/dlob/DLOBNode.js +82 -0
  17. package/lib/dlob/NodeList.d.ts +26 -0
  18. package/lib/dlob/NodeList.js +138 -0
  19. package/lib/idl/clearing_house.json +122 -202
  20. package/lib/index.d.ts +5 -0
  21. package/lib/index.js +5 -0
  22. package/lib/math/market.d.ts +1 -1
  23. package/lib/math/market.js +1 -1
  24. package/lib/math/orders.d.ts +3 -3
  25. package/lib/math/orders.js +31 -16
  26. package/lib/math/spotBalance.d.ts +3 -0
  27. package/lib/math/spotBalance.js +18 -1
  28. package/lib/math/spotPosition.d.ts +5 -1
  29. package/lib/math/spotPosition.js +16 -1
  30. package/lib/types.d.ts +24 -23
  31. package/lib/types.js +9 -3
  32. package/lib/userMap/userMap.d.ts +25 -0
  33. package/lib/userMap/userMap.js +73 -0
  34. package/lib/userMap/userStatsMap.d.ts +19 -0
  35. package/lib/userMap/userStatsMap.js +68 -0
  36. package/package.json +6 -3
  37. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +4 -0
  38. package/src/accounts/types.ts +1 -0
  39. package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +6 -0
  40. package/src/addresses/marketAddresses.js +26 -0
  41. package/src/admin.ts +50 -8
  42. package/src/assert/assert.js +1 -1
  43. package/src/clearingHouse.ts +23 -1
  44. package/src/clearingHouseUser.ts +364 -165
  45. package/src/config.ts +1 -1
  46. package/src/constants/banks.js +42 -0
  47. package/src/constants/{perpMarkets.js → markets.js} +11 -11
  48. package/src/dlob/DLOB.ts +884 -0
  49. package/src/dlob/DLOBNode.ts +163 -0
  50. package/src/dlob/NodeList.ts +185 -0
  51. package/src/events/eventList.js +23 -66
  52. package/src/events/txEventCache.js +19 -22
  53. package/src/examples/makeTradeExample.js +157 -0
  54. package/src/factory/bigNum.js +180 -183
  55. package/src/factory/oracleClient.js +9 -9
  56. package/src/idl/clearing_house.json +122 -202
  57. package/src/index.ts +5 -0
  58. package/src/math/auction.js +10 -10
  59. package/src/math/conversion.js +3 -4
  60. package/src/math/funding.js +175 -223
  61. package/src/math/market.ts +1 -1
  62. package/src/math/orders.ts +29 -21
  63. package/src/math/repeg.js +40 -40
  64. package/src/math/spotBalance.ts +26 -0
  65. package/src/math/spotPosition.ts +42 -1
  66. package/src/math/trade.js +74 -81
  67. package/src/math/utils.js +7 -8
  68. package/src/math/utils.js.map +1 -0
  69. package/src/oracles/oracleClientCache.js +9 -10
  70. package/src/oracles/pythClient.js +17 -52
  71. package/src/oracles/quoteAssetOracleClient.js +13 -44
  72. package/src/oracles/switchboardClient.js +37 -69
  73. package/src/oracles/types.js +1 -1
  74. package/src/token/index.js +4 -4
  75. package/src/tx/types.js +1 -1
  76. package/src/tx/utils.js +6 -7
  77. package/src/types.ts +25 -23
  78. package/src/userMap/userMap.ts +100 -0
  79. package/src/userMap/userStatsMap.ts +110 -0
  80. package/src/userName.js +5 -5
  81. package/src/util/computeUnits.js +11 -46
  82. package/src/util/getTokenAddress.js +9 -0
  83. package/src/util/promiseTimeout.js +5 -5
  84. package/src/util/tps.js +12 -46
  85. package/src/wallet.js +18 -55
  86. package/tests/bn/test.ts +2 -3
  87. package/tests/dlob/helpers.ts +322 -0
  88. package/tests/dlob/test.ts +2865 -0
  89. package/my-script/.env +0 -7
  90. package/my-script/getUserStats.ts +0 -106
  91. package/my-script/multiConnections.ts +0 -119
  92. package/my-script/test-regex.ts +0 -11
  93. package/my-script/utils.ts +0 -52
  94. package/src/accounts/bulkAccountLoader.js +0 -249
  95. package/src/accounts/bulkUserStatsSubscription.js +0 -75
  96. package/src/accounts/bulkUserSubscription.js +0 -75
  97. package/src/accounts/fetch.js +0 -92
  98. package/src/accounts/pollingClearingHouseAccountSubscriber.js +0 -465
  99. package/src/accounts/pollingOracleSubscriber.js +0 -156
  100. package/src/accounts/pollingTokenAccountSubscriber.js +0 -141
  101. package/src/accounts/pollingUserAccountSubscriber.js +0 -208
  102. package/src/accounts/pollingUserStatsAccountSubscriber.js +0 -208
  103. package/src/accounts/types.js +0 -28
  104. package/src/accounts/utils.js +0 -7
  105. package/src/accounts/webSocketAccountSubscriber.js +0 -138
  106. package/src/accounts/webSocketClearingHouseAccountSubscriber.js +0 -433
  107. package/src/accounts/webSocketUserAccountSubscriber.js +0 -113
  108. package/src/accounts/webSocketUserStatsAccountSubsriber.js +0 -113
  109. package/src/addresses/pda.js +0 -186
  110. package/src/admin.js +0 -1284
  111. package/src/clearingHouse.js +0 -3433
  112. package/src/clearingHouseConfig.js +0 -2
  113. package/src/clearingHouseUser.js +0 -874
  114. package/src/clearingHouseUserConfig.js +0 -2
  115. package/src/clearingHouseUserStats.js +0 -115
  116. package/src/clearingHouseUserStatsConfig.js +0 -2
  117. package/src/config.js +0 -80
  118. package/src/constants/numericConstants.js +0 -48
  119. package/src/constants/spotMarkets.js +0 -51
  120. package/src/events/eventSubscriber.js +0 -202
  121. package/src/events/fetchLogs.js +0 -117
  122. package/src/events/pollingLogProvider.js +0 -113
  123. package/src/events/sort.js +0 -41
  124. package/src/events/types.js +0 -25
  125. package/src/events/webSocketLogProvider.js +0 -76
  126. package/src/index.js +0 -75
  127. package/src/math/amm.js +0 -422
  128. package/src/math/insurance.js +0 -27
  129. package/src/math/margin.js +0 -77
  130. package/src/math/market.js +0 -105
  131. package/src/math/oracles.js +0 -56
  132. package/src/math/orders.js +0 -153
  133. package/src/math/position.js +0 -172
  134. package/src/math/spotBalance.js +0 -176
  135. package/src/math/spotMarket.js +0 -8
  136. package/src/math/spotPosition.js +0 -8
  137. package/src/orderParams.js +0 -28
  138. package/src/serum/serumSubscriber.js +0 -102
  139. package/src/serum/types.js +0 -2
  140. package/src/slot/SlotSubscriber.js +0 -86
  141. package/src/tokenFaucet.js +0 -323
  142. package/src/tx/retryTxSender.js +0 -280
  143. package/src/types.js +0 -216
@@ -21,7 +21,7 @@ import {
21
21
  PRICE_TO_QUOTE_PRECISION,
22
22
  MARGIN_PRECISION,
23
23
  SPOT_MARKET_WEIGHT_PRECISION,
24
- SPOT_MARKET_BALANCE_PRECISION_EXP,
24
+ QUOTE_SPOT_MARKET_INDEX,
25
25
  } from './constants/numericConstants';
26
26
  import {
27
27
  UserAccountSubscriber,
@@ -39,6 +39,7 @@ import {
39
39
  calculateTradeSlippage,
40
40
  BN,
41
41
  SpotMarketAccount,
42
+ getTokenValue,
42
43
  } from '.';
43
44
  import {
44
45
  getTokenAmount,
@@ -53,6 +54,10 @@ import { OraclePriceData } from './oracles/types';
53
54
  import { ClearingHouseUserConfig } from './clearingHouseUserConfig';
54
55
  import { PollingUserAccountSubscriber } from './accounts/pollingUserAccountSubscriber';
55
56
  import { WebSocketUserAccountSubscriber } from './accounts/webSocketUserAccountSubscriber';
57
+ import {
58
+ getWorstCaseTokenAmounts,
59
+ isSpotPositionAvailable,
60
+ } from './math/spotPosition';
56
61
  export class ClearingHouseUser {
57
62
  clearingHouse: ClearingHouse;
58
63
  userAccountPublicKey: PublicKey;
@@ -313,93 +318,21 @@ export class ClearingHouseUser {
313
318
  * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
314
319
  */
315
320
  public getMarginRequirement(
316
- type: MarginCategory,
321
+ marginCategory: MarginCategory,
317
322
  liquidationBuffer?: BN
318
323
  ): BN {
319
- return this.getUserAccount()
320
- .perpPositions.reduce((marginRequirement, perpPosition) => {
321
- const market = this.clearingHouse.getPerpMarketAccount(
322
- perpPosition.marketIndex
323
- );
324
-
325
- if (perpPosition.lpShares.gt(ZERO)) {
326
- // is an lp
327
- // clone so we dont mutate the position
328
- perpPosition = this.getClonedPosition(perpPosition);
329
-
330
- // settle position
331
- const [settledPosition, dustBaa, _] = this.getSettledLPPosition(
332
- market.marketIndex
333
- );
334
- perpPosition.baseAssetAmount =
335
- settledPosition.baseAssetAmount.add(dustBaa);
336
- perpPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
337
-
338
- // open orders
339
- let openAsks;
340
- if (market.amm.maxBaseAssetReserve > market.amm.baseAssetReserve) {
341
- openAsks = market.amm.maxBaseAssetReserve
342
- .sub(market.amm.baseAssetReserve)
343
- .mul(perpPosition.lpShares)
344
- .div(market.amm.sqrtK)
345
- .mul(new BN(-1));
346
- } else {
347
- openAsks = ZERO;
348
- }
349
-
350
- let openBids;
351
- if (market.amm.minBaseAssetReserve < market.amm.baseAssetReserve) {
352
- openBids = market.amm.baseAssetReserve
353
- .sub(market.amm.minBaseAssetReserve)
354
- .mul(perpPosition.lpShares)
355
- .div(market.amm.sqrtK);
356
- } else {
357
- openBids = ZERO;
358
- }
359
-
360
- perpPosition.openAsks = perpPosition.openAsks.add(openAsks);
361
- perpPosition.openBids = perpPosition.openBids.add(openBids);
362
- }
363
-
364
- let valuationPrice = this.getOracleDataForMarket(
365
- market.marketIndex
366
- ).price;
367
-
368
- if (isVariant(market.status, 'settlement')) {
369
- valuationPrice = market.settlementPrice;
370
- }
371
-
372
- const worstCaseBaseAssetAmount =
373
- calculateWorstCaseBaseAssetAmount(perpPosition);
374
-
375
- const worstCaseAssetValue = worstCaseBaseAssetAmount
376
- .abs()
377
- .mul(valuationPrice)
378
- .div(AMM_TO_QUOTE_PRECISION_RATIO.mul(MARK_PRICE_PRECISION));
379
-
380
- const positionMarginRequirement = worstCaseAssetValue
381
- .mul(
382
- new BN(
383
- calculateMarketMarginRatio(
384
- market,
385
- worstCaseBaseAssetAmount.abs(),
386
- type
387
- )
388
- )
389
- )
390
- .div(MARGIN_PRECISION);
391
-
392
- if (liquidationBuffer !== undefined) {
393
- positionMarginRequirement.add(
394
- worstCaseAssetValue.mul(liquidationBuffer).div(MARGIN_PRECISION)
395
- );
396
- }
397
-
398
- return marginRequirement.add(positionMarginRequirement);
399
- }, ZERO)
400
- .add(
401
- this.getSpotMarketLiabilityValue(undefined, type, liquidationBuffer)
402
- );
324
+ return this.getTotalPerpPositionValue(
325
+ marginCategory,
326
+ liquidationBuffer,
327
+ true
328
+ ).add(
329
+ this.getSpotMarketLiabilityValue(
330
+ undefined,
331
+ marginCategory,
332
+ liquidationBuffer,
333
+ true
334
+ )
335
+ );
403
336
  }
404
337
 
405
338
  /**
@@ -482,74 +415,165 @@ export class ClearingHouseUser {
482
415
 
483
416
  public getSpotMarketLiabilityValue(
484
417
  marketIndex?: BN,
485
- withWeightMarginCategory?: MarginCategory,
486
- liquidationBuffer?: BN
418
+ marginCategory?: MarginCategory,
419
+ liquidationBuffer?: BN,
420
+ includeOpenOrders?: boolean
487
421
  ): BN {
488
422
  return this.getUserAccount().spotPositions.reduce(
489
423
  (totalLiabilityValue, spotPosition) => {
490
424
  if (
491
- spotPosition.balance.eq(ZERO) ||
492
- isVariant(spotPosition.balanceType, 'deposit') ||
425
+ isSpotPositionAvailable(spotPosition) ||
493
426
  (marketIndex !== undefined &&
494
427
  !spotPosition.marketIndex.eq(marketIndex))
495
428
  ) {
496
429
  return totalLiabilityValue;
497
430
  }
498
431
 
499
- // Todo this needs to account for whether it's based on initial or maintenance requirements
500
432
  const spotMarketAccount: SpotMarketAccount =
501
433
  this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
502
434
 
503
- const tokenAmount = getTokenAmount(
504
- spotPosition.balance,
505
- spotMarketAccount,
506
- spotPosition.balanceType
435
+ if (spotPosition.marketIndex.eq(QUOTE_SPOT_MARKET_INDEX)) {
436
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
437
+ const tokenAmount = getTokenAmount(
438
+ spotPosition.balance,
439
+ spotMarketAccount,
440
+ spotPosition.balanceType
441
+ );
442
+
443
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
444
+ if (marginCategory === 'Initial') {
445
+ weight = BN.max(
446
+ weight,
447
+ new BN(this.getUserAccount().customMarginRatio)
448
+ );
449
+ }
450
+
451
+ const weightedTokenValue = tokenAmount
452
+ .mul(weight)
453
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
454
+
455
+ return totalLiabilityValue.add(weightedTokenValue);
456
+ } else {
457
+ return totalLiabilityValue;
458
+ }
459
+ }
460
+
461
+ const oraclePriceData = this.getOracleDataForSpotMarket(
462
+ spotPosition.marketIndex
507
463
  );
508
464
 
509
- let liabilityValue = tokenAmount
510
- .mul(
511
- this.getOracleDataForSpotMarket(spotMarketAccount.marketIndex).price
512
- )
513
- .div(MARK_PRICE_PRECISION)
514
- .div(
515
- new BN(10).pow(
516
- new BN(spotMarketAccount.decimals).sub(
517
- SPOT_MARKET_BALANCE_PRECISION_EXP
518
- )
519
- )
465
+ if (!includeOpenOrders) {
466
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
467
+ const tokenAmount = getTokenAmount(
468
+ spotPosition.balance,
469
+ spotMarketAccount,
470
+ spotPosition.balanceType
471
+ );
472
+ const liabilityValue = this.getSpotLiabilityValue(
473
+ tokenAmount,
474
+ oraclePriceData,
475
+ spotMarketAccount,
476
+ marginCategory,
477
+ liquidationBuffer
478
+ );
479
+ return totalLiabilityValue.add(liabilityValue);
480
+ } else {
481
+ return totalLiabilityValue;
482
+ }
483
+ }
484
+
485
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
486
+ getWorstCaseTokenAmounts(
487
+ spotPosition,
488
+ spotMarketAccount,
489
+ this.getOracleDataForSpotMarket(spotPosition.marketIndex)
520
490
  );
521
491
 
522
- if (withWeightMarginCategory !== undefined) {
523
- let weight = calculateLiabilityWeight(
524
- tokenAmount,
492
+ let newTotalLiabilityValue = totalLiabilityValue;
493
+ if (worstCaseTokenAmount.lt(ZERO)) {
494
+ const baseLiabilityValue = this.getSpotLiabilityValue(
495
+ worstCaseTokenAmount,
496
+ oraclePriceData,
525
497
  spotMarketAccount,
526
- withWeightMarginCategory
498
+ marginCategory,
499
+ liquidationBuffer
527
500
  );
528
501
 
529
- if (liquidationBuffer !== undefined) {
530
- weight = weight.add(liquidationBuffer);
502
+ newTotalLiabilityValue =
503
+ newTotalLiabilityValue.add(baseLiabilityValue);
504
+ }
505
+
506
+ if (worstCaseQuoteTokenAmount.lt(ZERO)) {
507
+ let weight = SPOT_MARKET_WEIGHT_PRECISION;
508
+ if (marginCategory === 'Initial') {
509
+ weight = BN.max(
510
+ weight,
511
+ new BN(this.getUserAccount().customMarginRatio)
512
+ );
531
513
  }
532
514
 
533
- liabilityValue = liabilityValue
515
+ const weightedTokenValue = worstCaseQuoteTokenAmount
534
516
  .mul(weight)
535
517
  .div(SPOT_MARKET_WEIGHT_PRECISION);
518
+
519
+ newTotalLiabilityValue =
520
+ newTotalLiabilityValue.add(weightedTokenValue);
536
521
  }
537
522
 
538
- return totalLiabilityValue.add(liabilityValue);
523
+ return newTotalLiabilityValue;
539
524
  },
540
525
  ZERO
541
526
  );
542
527
  }
543
528
 
529
+ getSpotLiabilityValue(
530
+ tokenAmount: BN,
531
+ oraclePriceData: OraclePriceData,
532
+ spotMarketAccount: SpotMarketAccount,
533
+ marginCategory?: MarginCategory,
534
+ liquidationBuffer?: BN
535
+ ): BN {
536
+ let liabilityValue = getTokenValue(
537
+ tokenAmount,
538
+ spotMarketAccount.decimals,
539
+ oraclePriceData
540
+ );
541
+
542
+ if (marginCategory !== undefined) {
543
+ let weight = calculateLiabilityWeight(
544
+ tokenAmount,
545
+ spotMarketAccount,
546
+ marginCategory
547
+ );
548
+
549
+ if (marginCategory === 'Initial') {
550
+ weight = BN.max(
551
+ weight,
552
+ new BN(this.getUserAccount().customMarginRatio)
553
+ );
554
+ }
555
+
556
+ if (liquidationBuffer !== undefined) {
557
+ weight = weight.add(liquidationBuffer);
558
+ }
559
+
560
+ liabilityValue = liabilityValue
561
+ .mul(weight)
562
+ .div(SPOT_MARKET_WEIGHT_PRECISION);
563
+ }
564
+
565
+ return liabilityValue;
566
+ }
567
+
544
568
  public getSpotMarketAssetValue(
545
569
  marketIndex?: BN,
546
- withWeightMarginCategory?: MarginCategory
570
+ marginCategory?: MarginCategory,
571
+ includeOpenOrders?: boolean
547
572
  ): BN {
548
573
  return this.getUserAccount().spotPositions.reduce(
549
574
  (totalAssetValue, spotPosition) => {
550
575
  if (
551
- spotPosition.balance.eq(ZERO) ||
552
- isVariant(spotPosition.balanceType, 'borrow') ||
576
+ isSpotPositionAvailable(spotPosition) ||
553
577
  (marketIndex !== undefined &&
554
578
  !spotPosition.marketIndex.eq(marketIndex))
555
579
  ) {
@@ -560,39 +584,99 @@ export class ClearingHouseUser {
560
584
  const spotMarketAccount: SpotMarketAccount =
561
585
  this.clearingHouse.getSpotMarketAccount(spotPosition.marketIndex);
562
586
 
563
- const tokenAmount = getTokenAmount(
564
- spotPosition.balance,
565
- spotMarketAccount,
566
- spotPosition.balanceType
587
+ if (spotPosition.marketIndex.eq(QUOTE_SPOT_MARKET_INDEX)) {
588
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
589
+ const tokenAmount = getTokenAmount(
590
+ spotPosition.balance,
591
+ spotMarketAccount,
592
+ spotPosition.balanceType
593
+ );
594
+
595
+ return totalAssetValue.add(tokenAmount);
596
+ } else {
597
+ return totalAssetValue;
598
+ }
599
+ }
600
+
601
+ const oraclePriceData = this.getOracleDataForSpotMarket(
602
+ spotPosition.marketIndex
567
603
  );
568
604
 
569
- let assetValue = tokenAmount
570
- .mul(
571
- this.getOracleDataForSpotMarket(spotMarketAccount.marketIndex).price
572
- )
573
- .div(MARK_PRICE_PRECISION)
574
- .div(
575
- new BN(10).pow(
576
- new BN(spotMarketAccount.decimals).sub(
577
- SPOT_MARKET_BALANCE_PRECISION_EXP
578
- )
579
- )
605
+ if (!includeOpenOrders) {
606
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
607
+ const tokenAmount = getTokenAmount(
608
+ spotPosition.balance,
609
+ spotMarketAccount,
610
+ spotPosition.balanceType
611
+ );
612
+ const assetValue = this.getSpotAssetValue(
613
+ tokenAmount,
614
+ oraclePriceData,
615
+ spotMarketAccount,
616
+ marginCategory
617
+ );
618
+ return totalAssetValue.add(assetValue);
619
+ } else {
620
+ return totalAssetValue;
621
+ }
622
+ }
623
+
624
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] =
625
+ getWorstCaseTokenAmounts(
626
+ spotPosition,
627
+ spotMarketAccount,
628
+ this.getOracleDataForSpotMarket(spotPosition.marketIndex)
580
629
  );
581
- if (withWeightMarginCategory !== undefined) {
582
- const weight = calculateAssetWeight(
583
- tokenAmount,
630
+
631
+ let newTotalAssetValue = totalAssetValue;
632
+ if (worstCaseTokenAmount.gt(ZERO)) {
633
+ const baseAssetValue = this.getSpotAssetValue(
634
+ worstCaseTokenAmount,
635
+ oraclePriceData,
584
636
  spotMarketAccount,
585
- withWeightMarginCategory
637
+ marginCategory
638
+ );
639
+
640
+ newTotalAssetValue = newTotalAssetValue.add(baseAssetValue);
641
+ }
642
+
643
+ if (worstCaseQuoteTokenAmount.gt(ZERO)) {
644
+ newTotalAssetValue = newTotalAssetValue.add(
645
+ worstCaseQuoteTokenAmount
586
646
  );
587
- assetValue = assetValue.mul(weight).div(SPOT_MARKET_WEIGHT_PRECISION);
588
647
  }
589
648
 
590
- return totalAssetValue.add(assetValue);
649
+ return newTotalAssetValue;
591
650
  },
592
651
  ZERO
593
652
  );
594
653
  }
595
654
 
655
+ getSpotAssetValue(
656
+ tokenAmount: BN,
657
+ oraclePriceData: OraclePriceData,
658
+ spotMarketAccount: SpotMarketAccount,
659
+ marginCategory?: MarginCategory
660
+ ): BN {
661
+ let assetValue = getTokenValue(
662
+ tokenAmount,
663
+ spotMarketAccount.decimals,
664
+ oraclePriceData
665
+ );
666
+
667
+ if (marginCategory !== undefined) {
668
+ const weight = calculateAssetWeight(
669
+ tokenAmount,
670
+ spotMarketAccount,
671
+ marginCategory
672
+ );
673
+
674
+ assetValue = assetValue.mul(weight).div(SPOT_MARKET_WEIGHT_PRECISION);
675
+ }
676
+
677
+ return assetValue;
678
+ }
679
+
596
680
  public getNetSpotMarketValue(withWeightMarginCategory?: MarginCategory): BN {
597
681
  return this.getSpotMarketAssetValue(
598
682
  undefined,
@@ -607,7 +691,7 @@ export class ClearingHouseUser {
607
691
  * @returns : Precision QUOTE_PRECISION
608
692
  */
609
693
  public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN {
610
- return this.getSpotMarketAssetValue(undefined, marginCategory).add(
694
+ return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
611
695
  this.getUnrealizedPNL(true, undefined, marginCategory)
612
696
  );
613
697
  }
@@ -616,19 +700,99 @@ export class ClearingHouseUser {
616
700
  * calculates sum of position value across all positions in margin system
617
701
  * @returns : Precision QUOTE_PRECISION
618
702
  */
619
- getTotalPositionValue(): BN {
703
+ getTotalPerpPositionValue(
704
+ marginCategory?: MarginCategory,
705
+ liquidationBuffer?: BN,
706
+ includeOpenOrders?: boolean
707
+ ): BN {
620
708
  return this.getUserAccount().perpPositions.reduce(
621
- (positionValue, perpPosition) => {
709
+ (totalPerpValue, perpPosition) => {
622
710
  const market = this.clearingHouse.getPerpMarketAccount(
623
711
  perpPosition.marketIndex
624
712
  );
625
- const posVal = calculateBaseAssetValueWithOracle(
626
- market,
627
- perpPosition,
628
- this.getOracleDataForMarket(market.marketIndex)
629
- );
630
713
 
631
- return positionValue.add(posVal);
714
+ if (perpPosition.lpShares.gt(ZERO)) {
715
+ // is an lp
716
+ // clone so we dont mutate the position
717
+ perpPosition = this.getClonedPosition(perpPosition);
718
+
719
+ // settle position
720
+ const [settledPosition, dustBaa, _] = this.getSettledLPPosition(
721
+ market.marketIndex
722
+ );
723
+ perpPosition.baseAssetAmount =
724
+ settledPosition.baseAssetAmount.add(dustBaa);
725
+ perpPosition.quoteAssetAmount = settledPosition.quoteAssetAmount;
726
+
727
+ // open orders
728
+ let openAsks;
729
+ if (market.amm.maxBaseAssetReserve > market.amm.baseAssetReserve) {
730
+ openAsks = market.amm.maxBaseAssetReserve
731
+ .sub(market.amm.baseAssetReserve)
732
+ .mul(perpPosition.lpShares)
733
+ .div(market.amm.sqrtK)
734
+ .mul(new BN(-1));
735
+ } else {
736
+ openAsks = ZERO;
737
+ }
738
+
739
+ let openBids;
740
+ if (market.amm.minBaseAssetReserve < market.amm.baseAssetReserve) {
741
+ openBids = market.amm.baseAssetReserve
742
+ .sub(market.amm.minBaseAssetReserve)
743
+ .mul(perpPosition.lpShares)
744
+ .div(market.amm.sqrtK);
745
+ } else {
746
+ openBids = ZERO;
747
+ }
748
+
749
+ perpPosition.openAsks = perpPosition.openAsks.add(openAsks);
750
+ perpPosition.openBids = perpPosition.openBids.add(openBids);
751
+ }
752
+
753
+ let valuationPrice = this.getOracleDataForMarket(
754
+ market.marketIndex
755
+ ).price;
756
+
757
+ if (isVariant(market.status, 'settlement')) {
758
+ valuationPrice = market.settlementPrice;
759
+ }
760
+
761
+ const baseAssetAmount = includeOpenOrders
762
+ ? calculateWorstCaseBaseAssetAmount(perpPosition)
763
+ : perpPosition.baseAssetAmount;
764
+
765
+ let baseAssetValue = baseAssetAmount
766
+ .abs()
767
+ .mul(valuationPrice)
768
+ .div(AMM_TO_QUOTE_PRECISION_RATIO.mul(MARK_PRICE_PRECISION));
769
+
770
+ if (marginCategory) {
771
+ let marginRatio = new BN(
772
+ calculateMarketMarginRatio(
773
+ market,
774
+ baseAssetAmount.abs(),
775
+ marginCategory
776
+ )
777
+ );
778
+
779
+ if (marginCategory === 'Initial') {
780
+ marginRatio = BN.max(
781
+ marginRatio,
782
+ new BN(this.getUserAccount().customMarginRatio)
783
+ );
784
+ }
785
+
786
+ if (liquidationBuffer !== undefined) {
787
+ marginRatio = marginRatio.add(liquidationBuffer);
788
+ }
789
+
790
+ baseAssetValue = baseAssetValue
791
+ .mul(marginRatio)
792
+ .div(MARGIN_PRECISION);
793
+ }
794
+
795
+ return totalPerpValue.add(baseAssetValue);
632
796
  },
633
797
  ZERO
634
798
  );
@@ -638,7 +802,7 @@ export class ClearingHouseUser {
638
802
  * calculates position value in margin system
639
803
  * @returns : Precision QUOTE_PRECISION
640
804
  */
641
- public getPositionValue(
805
+ public getPerpPositionValue(
642
806
  marketIndex: BN,
643
807
  oraclePriceData: OraclePriceData
644
808
  ): BN {
@@ -732,13 +896,31 @@ export class ClearingHouseUser {
732
896
  * calculates current user leverage across all positions
733
897
  * @returns : Precision TEN_THOUSAND
734
898
  */
735
- public getLeverage(): BN {
736
- const totalCollateral = this.getTotalCollateral();
737
- const totalPositionValue = this.getTotalPositionValue();
738
- if (totalPositionValue.eq(ZERO) && totalCollateral.eq(ZERO)) {
899
+ public getLeverage(marginCategory?: MarginCategory): BN {
900
+ const totalLiabilityValue = this.getTotalPerpPositionValue(
901
+ marginCategory,
902
+ undefined,
903
+ true
904
+ ).add(
905
+ this.getSpotMarketLiabilityValue(
906
+ undefined,
907
+ marginCategory,
908
+ undefined,
909
+ true
910
+ )
911
+ );
912
+
913
+ const totalAssetValue = this.getSpotMarketAssetValue(
914
+ undefined,
915
+ marginCategory,
916
+ true
917
+ ).add(this.getUnrealizedPNL(true, undefined, marginCategory));
918
+
919
+ if (totalAssetValue.eq(ZERO) && totalLiabilityValue.eq(ZERO)) {
739
920
  return ZERO;
740
921
  }
741
- return totalPositionValue.mul(TEN_THOUSAND).div(totalCollateral);
922
+
923
+ return totalLiabilityValue.mul(TEN_THOUSAND).div(totalAssetValue);
742
924
  }
743
925
 
744
926
  /**
@@ -768,14 +950,31 @@ export class ClearingHouseUser {
768
950
  * calculates margin ratio: total collateral / |total position value|
769
951
  * @returns : Precision TEN_THOUSAND
770
952
  */
771
- public getMarginRatio(): BN {
772
- const totalPositionValue = this.getTotalPositionValue();
953
+ public getMarginRatio(marginCategory?: MarginCategory): BN {
954
+ const totalLiabilityValue = this.getTotalPerpPositionValue(
955
+ marginCategory,
956
+ undefined,
957
+ true
958
+ ).add(
959
+ this.getSpotMarketLiabilityValue(
960
+ undefined,
961
+ marginCategory,
962
+ undefined,
963
+ true
964
+ )
965
+ );
773
966
 
774
- if (totalPositionValue.eq(ZERO)) {
967
+ if (totalLiabilityValue.eq(ZERO)) {
775
968
  return BN_MAX;
776
969
  }
777
970
 
778
- return this.getTotalCollateral().mul(TEN_THOUSAND).div(totalPositionValue);
971
+ const totalAssetValue = this.getSpotMarketAssetValue(
972
+ undefined,
973
+ marginCategory,
974
+ true
975
+ ).add(this.getUnrealizedPNL(true, undefined, marginCategory));
976
+
977
+ return totalAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
779
978
  }
780
979
 
781
980
  public canBeLiquidated(): boolean {
@@ -849,7 +1048,7 @@ export class ClearingHouseUser {
849
1048
 
850
1049
  // calculate the total position value ignoring any value from the target market of the trade
851
1050
  const totalPositionValueExcludingTargetMarket =
852
- this.getTotalPositionValueExcludingMarket(perpPosition.marketIndex);
1051
+ this.getTotalPerpPositionValueExcludingMarket(perpPosition.marketIndex);
853
1052
 
854
1053
  const currentPerpPosition =
855
1054
  this.getUserPosition(perpPosition.marketIndex) ||
@@ -1081,7 +1280,7 @@ export class ClearingHouseUser {
1081
1280
  // add any position we have on the opposite side of the current trade, because we can "flip" the size of this position without taking any extra leverage.
1082
1281
  const oppositeSizeValueUSDC = targetingSameSide
1083
1282
  ? ZERO
1084
- : this.getPositionValue(targetMarketIndex, oracleData);
1283
+ : this.getPerpPositionValue(targetMarketIndex, oracleData);
1085
1284
 
1086
1285
  let maxPositionSize = this.getBuyingPower(targetMarketIndex);
1087
1286
  if (maxPositionSize.gte(ZERO)) {
@@ -1100,7 +1299,7 @@ export class ClearingHouseUser {
1100
1299
  if (!targetingSameSide) {
1101
1300
  const market =
1102
1301
  this.clearingHouse.getPerpMarketAccount(targetMarketIndex);
1103
- const perpPositionValue = this.getPositionValue(
1302
+ const perpPositionValue = this.getPerpPositionValue(
1104
1303
  targetMarketIndex,
1105
1304
  oracleData
1106
1305
  );
@@ -1154,7 +1353,7 @@ export class ClearingHouseUser {
1154
1353
 
1155
1354
  const oracleData = this.getOracleDataForMarket(targetMarketIndex);
1156
1355
 
1157
- let currentPositionQuoteAmount = this.getPositionValue(
1356
+ let currentPositionQuoteAmount = this.getPerpPositionValue(
1158
1357
  targetMarketIndex,
1159
1358
  oracleData
1160
1359
  );
@@ -1175,7 +1374,7 @@ export class ClearingHouseUser {
1175
1374
  .abs();
1176
1375
 
1177
1376
  const totalPositionAfterTradeExcludingTargetMarket =
1178
- this.getTotalPositionValueExcludingMarket(targetMarketIndex);
1377
+ this.getTotalPerpPositionValueExcludingMarket(targetMarketIndex);
1179
1378
 
1180
1379
  const totalCollateral = this.getTotalCollateral();
1181
1380
  if (totalCollateral.gt(ZERO)) {
@@ -1208,7 +1407,7 @@ export class ClearingHouseUser {
1208
1407
  * @param marketToIgnore
1209
1408
  * @returns positionValue : Precision QUOTE_PRECISION
1210
1409
  */
1211
- private getTotalPositionValueExcludingMarket(marketToIgnore: BN): BN {
1410
+ private getTotalPerpPositionValueExcludingMarket(marketToIgnore: BN): BN {
1212
1411
  const currentPerpPosition =
1213
1412
  this.getUserPosition(marketToIgnore) ||
1214
1413
  this.getEmptyPosition(marketToIgnore);
@@ -1217,13 +1416,13 @@ export class ClearingHouseUser {
1217
1416
 
1218
1417
  let currentPerpPositionValueUSDC = ZERO;
1219
1418
  if (currentPerpPosition) {
1220
- currentPerpPositionValueUSDC = this.getPositionValue(
1419
+ currentPerpPositionValueUSDC = this.getPerpPositionValue(
1221
1420
  marketToIgnore,
1222
1421
  oracleData
1223
1422
  );
1224
1423
  }
1225
1424
 
1226
- return this.getTotalPositionValue().sub(currentPerpPositionValueUSDC);
1425
+ return this.getTotalPerpPositionValue().sub(currentPerpPositionValueUSDC);
1227
1426
  }
1228
1427
 
1229
1428
  private getOracleDataForMarket(marketIndex: BN): OraclePriceData {