@drift-labs/sdk 2.22.0-beta.3 → 2.23.0-beta.0

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.
@@ -37,6 +37,7 @@ import {
37
37
  PostOnlyParams,
38
38
  SpotBalanceType,
39
39
  PerpMarketExtendedInfo,
40
+ UserStatsAccount,
40
41
  } from './types';
41
42
  import * as anchor from '@project-serum/anchor';
42
43
  import driftIDL from './idl/drift.json';
@@ -776,6 +777,24 @@ export class DriftClient {
776
777
  );
777
778
  }
778
779
 
780
+ public async getReferredUserStatsAccountsByReferrer(
781
+ referrer: PublicKey
782
+ ): Promise<UserStatsAccount[]> {
783
+ const programAccounts = await this.program.account.userStats.all([
784
+ {
785
+ memcmp: {
786
+ offset: 40,
787
+ /** data to match, as base-58 encoded string and limited to less than 129 bytes */
788
+ bytes: bs58.encode(referrer.toBuffer()),
789
+ },
790
+ },
791
+ ]);
792
+
793
+ return programAccounts.map(
794
+ (programAccount) => programAccount.account as UserStatsAccount
795
+ );
796
+ }
797
+
779
798
  public async getReferrerNameAccountsForAuthority(
780
799
  authority: PublicKey
781
800
  ): Promise<ReferrerNameAccount[]> {
@@ -934,17 +953,32 @@ export class DriftClient {
934
953
  // if cache has more recent slot than user positions account slot, add market to remaining accounts
935
954
  // otherwise remove from slot
936
955
  if (slot > lastUserSlot) {
937
- const marketAccount = this.getPerpMarketAccount(marketIndex);
956
+ const perpMarketAccount = this.getPerpMarketAccount(marketIndex);
938
957
  perpMarketAccountMap.set(marketIndex, {
939
- pubkey: marketAccount.pubkey,
958
+ pubkey: perpMarketAccount.pubkey,
959
+ isSigner: false,
960
+ isWritable: false,
961
+ });
962
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
963
+ pubkey: perpMarketAccount.amm.oracle,
940
964
  isSigner: false,
941
965
  isWritable: false,
942
966
  });
943
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
944
- pubkey: marketAccount.amm.oracle,
967
+ const spotMarketAccount = this.getSpotMarketAccount(
968
+ perpMarketAccount.quoteSpotMarketIndex
969
+ );
970
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
971
+ pubkey: spotMarketAccount.pubkey,
945
972
  isSigner: false,
946
973
  isWritable: false,
947
974
  });
975
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
976
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
977
+ pubkey: spotMarketAccount.oracle,
978
+ isSigner: false,
979
+ isWritable: false,
980
+ });
981
+ }
948
982
  } else {
949
983
  this.perpMarketLastSlotCache.delete(marketIndex);
950
984
  }
@@ -957,15 +991,15 @@ export class DriftClient {
957
991
  // if cache has more recent slot than user positions account slot, add market to remaining accounts
958
992
  // otherwise remove from slot
959
993
  if (slot > lastUserSlot) {
960
- const marketAccount = this.getSpotMarketAccount(marketIndex);
994
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
961
995
  spotMarketAccountMap.set(marketIndex, {
962
- pubkey: marketAccount.pubkey,
996
+ pubkey: spotMarketAccount.pubkey,
963
997
  isSigner: false,
964
998
  isWritable: false,
965
999
  });
966
- if (!marketAccount.oracle.equals(PublicKey.default)) {
967
- oracleAccountMap.set(marketAccount.oracle.toString(), {
968
- pubkey: marketAccount.oracle,
1000
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
1001
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
1002
+ pubkey: spotMarketAccount.oracle,
969
1003
  isSigner: false,
970
1004
  isWritable: false,
971
1005
  });
@@ -977,50 +1011,80 @@ export class DriftClient {
977
1011
  }
978
1012
 
979
1013
  if (params.readablePerpMarketIndex !== undefined) {
980
- const marketAccount = this.getPerpMarketAccount(
1014
+ const perpMarketAccount = this.getPerpMarketAccount(
981
1015
  params.readablePerpMarketIndex
982
1016
  );
983
1017
  perpMarketAccountMap.set(params.readablePerpMarketIndex, {
984
- pubkey: marketAccount.pubkey,
1018
+ pubkey: perpMarketAccount.pubkey,
1019
+ isSigner: false,
1020
+ isWritable: false,
1021
+ });
1022
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
1023
+ pubkey: perpMarketAccount.amm.oracle,
985
1024
  isSigner: false,
986
1025
  isWritable: false,
987
1026
  });
988
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
989
- pubkey: marketAccount.amm.oracle,
1027
+ const spotMarketAccount = this.getSpotMarketAccount(
1028
+ perpMarketAccount.quoteSpotMarketIndex
1029
+ );
1030
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
1031
+ pubkey: spotMarketAccount.pubkey,
990
1032
  isSigner: false,
991
1033
  isWritable: false,
992
1034
  });
1035
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
1036
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
1037
+ pubkey: spotMarketAccount.oracle,
1038
+ isSigner: false,
1039
+ isWritable: false,
1040
+ });
1041
+ }
1042
+ }
1043
+
1044
+ if (params.readableSpotMarketIndexes !== undefined) {
1045
+ for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) {
1046
+ const spotMarketAccount = this.getSpotMarketAccount(
1047
+ readableSpotMarketIndex
1048
+ );
1049
+ spotMarketAccountMap.set(readableSpotMarketIndex, {
1050
+ pubkey: spotMarketAccount.pubkey,
1051
+ isSigner: false,
1052
+ isWritable: false,
1053
+ });
1054
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
1055
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
1056
+ pubkey: spotMarketAccount.oracle,
1057
+ isSigner: false,
1058
+ isWritable: false,
1059
+ });
1060
+ }
1061
+ }
993
1062
  }
994
1063
 
995
1064
  if (params.writablePerpMarketIndexes !== undefined) {
996
1065
  for (const writablePerpMarketIndex of params.writablePerpMarketIndexes) {
997
- const marketAccount = this.getPerpMarketAccount(
1066
+ const perpMarketAccount = this.getPerpMarketAccount(
998
1067
  writablePerpMarketIndex
999
1068
  );
1000
1069
  perpMarketAccountMap.set(writablePerpMarketIndex, {
1001
- pubkey: marketAccount.pubkey,
1070
+ pubkey: perpMarketAccount.pubkey,
1002
1071
  isSigner: false,
1003
1072
  isWritable: true,
1004
1073
  });
1005
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1006
- pubkey: marketAccount.amm.oracle,
1074
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
1075
+ pubkey: perpMarketAccount.amm.oracle,
1007
1076
  isSigner: false,
1008
1077
  isWritable: false,
1009
1078
  });
1010
- }
1011
- }
1012
-
1013
- if (params.readableSpotMarketIndexes !== undefined) {
1014
- for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) {
1015
1079
  const spotMarketAccount = this.getSpotMarketAccount(
1016
- readableSpotMarketIndex
1080
+ perpMarketAccount.quoteSpotMarketIndex
1017
1081
  );
1018
- spotMarketAccountMap.set(readableSpotMarketIndex, {
1082
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
1019
1083
  pubkey: spotMarketAccount.pubkey,
1020
1084
  isSigner: false,
1021
1085
  isWritable: false,
1022
1086
  });
1023
- if (spotMarketAccount.marketIndex !== 0) {
1087
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
1024
1088
  oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
1025
1089
  pubkey: spotMarketAccount.oracle,
1026
1090
  isSigner: false,
@@ -1090,27 +1154,52 @@ export class DriftClient {
1090
1154
  !spotPosition.openAsks.eq(ZERO) ||
1091
1155
  !spotPosition.openBids.eq(ZERO)
1092
1156
  ) {
1157
+ const quoteSpotMarket = this.getQuoteSpotMarketAccount();
1093
1158
  spotMarketAccountMap.set(QUOTE_SPOT_MARKET_INDEX, {
1094
- pubkey: this.getQuoteSpotMarketAccount().pubkey,
1159
+ pubkey: quoteSpotMarket.pubkey,
1095
1160
  isSigner: false,
1096
1161
  isWritable: false,
1097
1162
  });
1163
+ if (!quoteSpotMarket.oracle.equals(PublicKey.default)) {
1164
+ oracleAccountMap.set(quoteSpotMarket.oracle.toString(), {
1165
+ pubkey: quoteSpotMarket.oracle,
1166
+ isSigner: false,
1167
+ isWritable: false,
1168
+ });
1169
+ }
1098
1170
  }
1099
1171
  }
1100
1172
  }
1101
1173
  for (const position of userAccount.perpPositions) {
1102
1174
  if (!positionIsAvailable(position)) {
1103
- const market = this.getPerpMarketAccount(position.marketIndex);
1175
+ const perpMarketAccount = this.getPerpMarketAccount(
1176
+ position.marketIndex
1177
+ );
1104
1178
  perpMarketAccountMap.set(position.marketIndex, {
1105
- pubkey: market.pubkey,
1179
+ pubkey: perpMarketAccount.pubkey,
1106
1180
  isWritable: false,
1107
1181
  isSigner: false,
1108
1182
  });
1109
- oracleAccountMap.set(market.amm.oracle.toString(), {
1110
- pubkey: market.amm.oracle,
1183
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
1184
+ pubkey: perpMarketAccount.amm.oracle,
1111
1185
  isWritable: false,
1112
1186
  isSigner: false,
1113
1187
  });
1188
+ const spotMarketAccount = this.getSpotMarketAccount(
1189
+ perpMarketAccount.quoteSpotMarketIndex
1190
+ );
1191
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
1192
+ pubkey: spotMarketAccount.pubkey,
1193
+ isSigner: false,
1194
+ isWritable: false,
1195
+ });
1196
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
1197
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
1198
+ pubkey: spotMarketAccount.oracle,
1199
+ isSigner: false,
1200
+ isWritable: false,
1201
+ });
1202
+ }
1114
1203
  }
1115
1204
  }
1116
1205
  }
@@ -22,6 +22,10 @@ export function getOracleClient(
22
22
  return new PythClient(connection, new BN(1000000));
23
23
  }
24
24
 
25
+ if (isVariant(oracleSource, 'pythStableCoin')) {
26
+ return new PythClient(connection, undefined, true);
27
+ }
28
+
25
29
  // if (isVariant(oracleSource, 'switchboard')) {
26
30
  // return new SwitchboardClient(connection);
27
31
  // }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.22.0-beta.3",
2
+ "version": "2.23.0-beta.0",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -4081,12 +4081,20 @@
4081
4081
  "defined": "ContractTier"
4082
4082
  }
4083
4083
  },
4084
+ {
4085
+ "name": "padding1",
4086
+ "type": "bool"
4087
+ },
4088
+ {
4089
+ "name": "quoteSpotMarketIndex",
4090
+ "type": "u16"
4091
+ },
4084
4092
  {
4085
4093
  "name": "padding",
4086
4094
  "type": {
4087
4095
  "array": [
4088
4096
  "u8",
4089
- 51
4097
+ 48
4090
4098
  ]
4091
4099
  }
4092
4100
  }
@@ -5119,7 +5127,7 @@
5119
5127
  "fields": [
5120
5128
  {
5121
5129
  "name": "revenueWithdrawSinceLastSettle",
5122
- "type": "i64"
5130
+ "type": "u64"
5123
5131
  },
5124
5132
  {
5125
5133
  "name": "maxRevenueWithdrawPerPeriod",
@@ -6383,6 +6391,9 @@
6383
6391
  },
6384
6392
  {
6385
6393
  "name": "Pyth1M"
6394
+ },
6395
+ {
6396
+ "name": "PythStableCoin"
6386
6397
  }
6387
6398
  ]
6388
6399
  }
@@ -2,15 +2,26 @@ import { parsePriceData } from '@pythnetwork/client';
2
2
  import { Connection, PublicKey } from '@solana/web3.js';
3
3
  import { OracleClient, OraclePriceData } from './types';
4
4
  import { BN } from '@project-serum/anchor';
5
- import { ONE, PRICE_PRECISION, TEN } from '../constants/numericConstants';
5
+ import {
6
+ ONE,
7
+ PRICE_PRECISION,
8
+ QUOTE_PRECISION,
9
+ TEN,
10
+ } from '../constants/numericConstants';
6
11
 
7
12
  export class PythClient implements OracleClient {
8
13
  private connection: Connection;
9
14
  private multiple: BN;
15
+ private stableCoin: boolean;
10
16
 
11
- public constructor(connection: Connection, multiple = ONE) {
17
+ public constructor(
18
+ connection: Connection,
19
+ multiple = ONE,
20
+ stableCoin = false
21
+ ) {
12
22
  this.connection = connection;
13
23
  this.multiple = multiple;
24
+ this.stableCoin = stableCoin;
14
25
  }
15
26
 
16
27
  public async getOraclePriceData(
@@ -22,18 +33,24 @@ export class PythClient implements OracleClient {
22
33
 
23
34
  public getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData {
24
35
  const priceData = parsePriceData(buffer);
36
+ const confidence = convertPythPrice(
37
+ priceData.confidence,
38
+ priceData.exponent,
39
+ this.multiple
40
+ );
41
+ let price = convertPythPrice(
42
+ priceData.aggregate.price,
43
+ priceData.exponent,
44
+ this.multiple
45
+ );
46
+ if (this.stableCoin) {
47
+ price = getStableCoinPrice(price, confidence);
48
+ }
49
+
25
50
  return {
26
- price: convertPythPrice(
27
- priceData.aggregate.price,
28
- priceData.exponent,
29
- this.multiple
30
- ),
51
+ price,
31
52
  slot: new BN(priceData.lastSlot.toString()),
32
- confidence: convertPythPrice(
33
- priceData.confidence,
34
- priceData.exponent,
35
- this.multiple
36
- ),
53
+ confidence,
37
54
  twap: convertPythPrice(
38
55
  priceData.twap.value,
39
56
  priceData.exponent,
@@ -60,3 +77,12 @@ export function convertPythPrice(
60
77
  .mul(PRICE_PRECISION)
61
78
  .div(pythPrecision);
62
79
  }
80
+
81
+ const fiveBPS = new BN(500);
82
+ function getStableCoinPrice(price: BN, confidence: BN): BN {
83
+ if (price.sub(QUOTE_PRECISION).abs().lt(BN.min(confidence, fiveBPS))) {
84
+ return QUOTE_PRECISION;
85
+ } else {
86
+ return price;
87
+ }
88
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTokenAccount = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ function parseTokenAccount(data) {
7
+ const accountInfo = spl_token_1.AccountLayout.decode(data);
8
+ accountInfo.mint = new web3_js_1.PublicKey(accountInfo.mint);
9
+ accountInfo.owner = new web3_js_1.PublicKey(accountInfo.owner);
10
+ accountInfo.amount = spl_token_1.u64.fromBuffer(accountInfo.amount);
11
+ if (accountInfo.delegateOption === 0) {
12
+ accountInfo.delegate = null;
13
+ // eslint-disable-next-line new-cap
14
+ accountInfo.delegatedAmount = new spl_token_1.u64(0);
15
+ }
16
+ else {
17
+ accountInfo.delegate = new web3_js_1.PublicKey(accountInfo.delegate);
18
+ accountInfo.delegatedAmount = spl_token_1.u64.fromBuffer(accountInfo.delegatedAmount);
19
+ }
20
+ accountInfo.isInitialized = accountInfo.state !== 0;
21
+ accountInfo.isFrozen = accountInfo.state === 2;
22
+ if (accountInfo.isNativeOption === 1) {
23
+ accountInfo.rentExemptReserve = spl_token_1.u64.fromBuffer(accountInfo.isNative);
24
+ accountInfo.isNative = true;
25
+ }
26
+ else {
27
+ accountInfo.rentExemptReserve = null;
28
+ accountInfo.isNative = false;
29
+ }
30
+ if (accountInfo.closeAuthorityOption === 0) {
31
+ accountInfo.closeAuthority = null;
32
+ }
33
+ else {
34
+ accountInfo.closeAuthority = new web3_js_1.PublicKey(accountInfo.closeAuthority);
35
+ }
36
+ return accountInfo;
37
+ }
38
+ exports.parseTokenAccount = parseTokenAccount;
package/src/types.ts CHANGED
@@ -80,6 +80,7 @@ export class OracleSource {
80
80
  static readonly PYTH_1M = { pyth1M: {} };
81
81
  // static readonly SWITCHBOARD = { switchboard: {} };
82
82
  static readonly QUOTE_ASSET = { quoteAsset: {} };
83
+ static readonly PYTH_STABLE_COIN = { pythStableCoin: {} };
83
84
  }
84
85
 
85
86
  export class OrderType {
@@ -560,6 +561,7 @@ export type PerpMarketAccount = {
560
561
  quoteSettledInsurance: BN;
561
562
  quoteMaxInsurance: BN;
562
563
  };
564
+ quoteSpotMarketIndex: number;
563
565
  };
564
566
 
565
567
  export type HistoricalOracleData = {
package/src/user.ts CHANGED
@@ -424,7 +424,8 @@ export class User {
424
424
  return this.getTotalPerpPositionValue(
425
425
  marginCategory,
426
426
  liquidationBuffer,
427
- true
427
+ true,
428
+ strict
428
429
  ).add(
429
430
  this.getSpotMarketLiabilityValue(
430
431
  undefined,
@@ -467,9 +468,9 @@ export class User {
467
468
  public getUnrealizedPNL(
468
469
  withFunding?: boolean,
469
470
  marketIndex?: number,
470
- withWeightMarginCategory?: MarginCategory
471
+ withWeightMarginCategory?: MarginCategory,
472
+ strict = false
471
473
  ): BN {
472
- const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
473
474
  return this.getActivePerpPositions()
474
475
  .filter((pos) => (marketIndex ? pos.marketIndex === marketIndex : true))
475
476
  .reduce((unrealizedPnl, perpPosition) => {
@@ -480,6 +481,13 @@ export class User {
480
481
  market.marketIndex
481
482
  );
482
483
 
484
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(
485
+ market.quoteSpotMarketIndex
486
+ );
487
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(
488
+ market.quoteSpotMarketIndex
489
+ );
490
+
483
491
  if (perpPosition.lpShares.gt(ZERO)) {
484
492
  perpPosition = this.getSettledLPPosition(perpPosition.marketIndex)[0];
485
493
  }
@@ -491,6 +499,25 @@ export class User {
491
499
  oraclePriceData
492
500
  );
493
501
 
502
+ let quotePrice;
503
+ if (strict && positionUnrealizedPnl.gt(ZERO)) {
504
+ quotePrice = BN.min(
505
+ quoteOraclePriceData.price,
506
+ quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
507
+ );
508
+ } else if (strict && positionUnrealizedPnl.lt(ZERO)) {
509
+ quotePrice = BN.max(
510
+ quoteOraclePriceData.price,
511
+ quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
512
+ );
513
+ } else {
514
+ quotePrice = quoteOraclePriceData.price;
515
+ }
516
+
517
+ positionUnrealizedPnl = positionUnrealizedPnl
518
+ .mul(quotePrice)
519
+ .div(new BN(PRICE_PRECISION));
520
+
494
521
  if (withWeightMarginCategory !== undefined) {
495
522
  if (positionUnrealizedPnl.gt(ZERO)) {
496
523
  positionUnrealizedPnl = positionUnrealizedPnl
@@ -558,55 +585,60 @@ export class User {
558
585
  const spotMarketAccount: SpotMarketAccount =
559
586
  this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
560
587
 
588
+ const oraclePriceData = this.getOracleDataForSpotMarket(
589
+ spotPosition.marketIndex
590
+ );
591
+
561
592
  if (
562
593
  spotPosition.marketIndex === QUOTE_SPOT_MARKET_INDEX &&
563
594
  countForQuote
564
595
  ) {
565
- if (isVariant(spotPosition.balanceType, 'borrow')) {
566
- const tokenAmount = getTokenAmount(
596
+ const tokenAmount = getSignedTokenAmount(
597
+ getTokenAmount(
567
598
  spotPosition.scaledBalance,
568
599
  spotMarketAccount,
569
600
  spotPosition.balanceType
570
- );
571
-
572
- let weight = SPOT_MARKET_WEIGHT_PRECISION;
573
- if (marginCategory === 'Initial') {
574
- weight = BN.max(
575
- weight,
576
- new BN(this.getUserAccount().maxMarginRatio)
577
- );
578
- }
601
+ ),
602
+ spotPosition.balanceType
603
+ );
579
604
 
580
- const weightedTokenValue = tokenAmount
581
- .mul(weight)
582
- .div(SPOT_MARKET_WEIGHT_PRECISION);
605
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
606
+ const weightedTokenValue = this.getSpotLiabilityValue(
607
+ tokenAmount,
608
+ oraclePriceData,
609
+ spotMarketAccount,
610
+ marginCategory,
611
+ liquidationBuffer,
612
+ strict,
613
+ now
614
+ ).abs();
583
615
 
584
616
  netQuoteValue = netQuoteValue.sub(weightedTokenValue);
585
-
586
- continue;
587
617
  } else {
588
- const tokenAmount = getTokenAmount(
589
- spotPosition.scaledBalance,
618
+ const weightedTokenValue = this.getSpotAssetValue(
619
+ tokenAmount,
620
+ oraclePriceData,
590
621
  spotMarketAccount,
591
- spotPosition.balanceType
622
+ marginCategory,
623
+ strict,
624
+ now
592
625
  );
593
626
 
594
- netQuoteValue = netQuoteValue.add(tokenAmount);
595
-
596
- continue;
627
+ netQuoteValue = netQuoteValue.add(weightedTokenValue);
597
628
  }
598
- }
599
629
 
600
- const oraclePriceData = this.getOracleDataForSpotMarket(
601
- spotPosition.marketIndex
602
- );
630
+ continue;
631
+ }
603
632
 
604
633
  if (!includeOpenOrders && countForBase) {
605
634
  if (isVariant(spotPosition.balanceType, 'borrow')) {
606
- const tokenAmount = getTokenAmount(
607
- spotPosition.scaledBalance,
608
- spotMarketAccount,
609
- spotPosition.balanceType
635
+ const tokenAmount = getSignedTokenAmount(
636
+ getTokenAmount(
637
+ spotPosition.scaledBalance,
638
+ spotMarketAccount,
639
+ spotPosition.balanceType
640
+ ),
641
+ SpotBalanceType.BORROW
610
642
  );
611
643
  const liabilityValue = this.getSpotLiabilityValue(
612
644
  tokenAmount,
@@ -616,7 +648,7 @@ export class User {
616
648
  liquidationBuffer,
617
649
  strict,
618
650
  now
619
- );
651
+ ).abs();
620
652
  totalLiabilityValue = totalLiabilityValue.add(liabilityValue);
621
653
 
622
654
  continue;
@@ -662,14 +694,14 @@ export class User {
662
694
 
663
695
  if (worstCaseTokenAmount.lt(ZERO) && countForBase) {
664
696
  const baseLiabilityValue = this.getSpotLiabilityValue(
665
- worstCaseTokenAmount.abs(),
697
+ worstCaseTokenAmount,
666
698
  oraclePriceData,
667
699
  spotMarketAccount,
668
700
  marginCategory,
669
701
  liquidationBuffer,
670
702
  strict,
671
703
  now
672
- );
704
+ ).abs();
673
705
 
674
706
  totalLiabilityValue = totalLiabilityValue.add(baseLiabilityValue);
675
707
  }
@@ -738,7 +770,7 @@ export class User {
738
770
  ): BN {
739
771
  let liabilityValue = null;
740
772
 
741
- if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
773
+ if (strict) {
742
774
  const estOracleTwap = calculateLiveOracleTwap(
743
775
  spotMarketAccount.historicalOracleData,
744
776
  oraclePriceData,
@@ -809,7 +841,7 @@ export class User {
809
841
  now?: BN
810
842
  ): BN {
811
843
  let assetValue = null;
812
- if (strict && spotMarketAccount.marketIndex != QUOTE_SPOT_MARKET_INDEX) {
844
+ if (strict) {
813
845
  const estOracleTwap = calculateLiveOracleTwap(
814
846
  spotMarketAccount.historicalOracleData,
815
847
  oraclePriceData,
@@ -886,10 +918,16 @@ export class User {
886
918
  * calculates TotalCollateral: collateral + unrealized pnl
887
919
  * @returns : Precision QUOTE_PRECISION
888
920
  */
889
- public getTotalCollateral(marginCategory: MarginCategory = 'Initial'): BN {
890
- return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(
891
- this.getUnrealizedPNL(true, undefined, marginCategory)
892
- );
921
+ public getTotalCollateral(
922
+ marginCategory: MarginCategory = 'Initial',
923
+ strict = false
924
+ ): BN {
925
+ return this.getSpotMarketAssetValue(
926
+ undefined,
927
+ marginCategory,
928
+ true,
929
+ strict
930
+ ).add(this.getUnrealizedPNL(true, undefined, marginCategory, strict));
893
931
  }
894
932
 
895
933
  /**
@@ -941,7 +979,8 @@ export class User {
941
979
  getTotalPerpPositionValue(
942
980
  marginCategory?: MarginCategory,
943
981
  liquidationBuffer?: BN,
944
- includeOpenOrders?: boolean
982
+ includeOpenOrders?: boolean,
983
+ strict = false
945
984
  ): BN {
946
985
  return this.getActivePerpPositions().reduce(
947
986
  (totalPerpValue, perpPosition) => {
@@ -1011,7 +1050,27 @@ export class User {
1011
1050
  marginRatio = ZERO;
1012
1051
  }
1013
1052
 
1053
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(
1054
+ market.quoteSpotMarketIndex
1055
+ );
1056
+ const quoteOraclePriceData =
1057
+ this.driftClient.getOraclePriceDataAndSlot(
1058
+ quoteSpotMarket.oracle
1059
+ ).data;
1060
+
1061
+ let quotePrice;
1062
+ if (strict) {
1063
+ quotePrice = BN.max(
1064
+ quoteOraclePriceData.price,
1065
+ quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min
1066
+ );
1067
+ } else {
1068
+ quotePrice = quoteOraclePriceData.price;
1069
+ }
1070
+
1014
1071
  baseAssetValue = baseAssetValue
1072
+ .mul(quotePrice)
1073
+ .div(PRICE_PRECISION)
1015
1074
  .mul(marginRatio)
1016
1075
  .div(MARGIN_PRECISION);
1017
1076