@drift-labs/sdk 2.22.0 → 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.
@@ -1,7 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="bn.js" />
3
3
  import { AnchorProvider, BN, Program, ProgramAccount } from '@project-serum/anchor';
4
- import { StateAccount, IWallet, PositionDirection, UserAccount, PerpMarketAccount, OrderParams, Order, SpotMarketAccount, SpotPosition, MakerInfo, TakerInfo, OptionalOrderParams, OrderType, ReferrerInfo, MarketType, TxParams, SerumV3FulfillmentConfigAccount, ReferrerNameAccount, OrderTriggerCondition, PerpMarketExtendedInfo } from './types';
4
+ import { StateAccount, IWallet, PositionDirection, UserAccount, PerpMarketAccount, OrderParams, Order, SpotMarketAccount, SpotPosition, MakerInfo, TakerInfo, OptionalOrderParams, OrderType, ReferrerInfo, MarketType, TxParams, SerumV3FulfillmentConfigAccount, ReferrerNameAccount, OrderTriggerCondition, PerpMarketExtendedInfo, UserStatsAccount } from './types';
5
5
  import * as anchor from '@project-serum/anchor';
6
6
  import { Connection, PublicKey, TransactionSignature, ConfirmOptions, Transaction, TransactionInstruction, AccountMeta, Signer, AddressLookupTableAccount } from '@solana/web3.js';
7
7
  import { TokenFaucet } from './tokenFaucet';
@@ -104,6 +104,7 @@ export declare class DriftClient {
104
104
  fetchAllUserAccounts(includeIdle?: boolean): Promise<ProgramAccount<UserAccount>[]>;
105
105
  getUserAccountsForDelegate(delegate: PublicKey): Promise<UserAccount[]>;
106
106
  getUserAccountsForAuthority(authority: PublicKey): Promise<UserAccount[]>;
107
+ getReferredUserStatsAccountsByReferrer(referrer: PublicKey): Promise<UserStatsAccount[]>;
107
108
  getReferrerNameAccountsForAuthority(authority: PublicKey): Promise<ReferrerNameAccount[]>;
108
109
  deleteUser(subAccountId?: number, txParams?: TxParams): Promise<TransactionSignature>;
109
110
  getUser(subAccountId?: number): User;
@@ -464,6 +464,18 @@ class DriftClient {
464
464
  ]);
465
465
  return programAccounts.map((programAccount) => programAccount.account);
466
466
  }
467
+ async getReferredUserStatsAccountsByReferrer(referrer) {
468
+ const programAccounts = await this.program.account.userStats.all([
469
+ {
470
+ memcmp: {
471
+ offset: 40,
472
+ /** data to match, as base-58 encoded string and limited to less than 129 bytes */
473
+ bytes: bs58_1.default.encode(referrer.toBuffer()),
474
+ },
475
+ },
476
+ ]);
477
+ return programAccounts.map((programAccount) => programAccount.account);
478
+ }
467
479
  async getReferrerNameAccountsForAuthority(authority) {
468
480
  const programAccounts = await this.program.account.referrerName.all([
469
481
  {
@@ -559,17 +571,30 @@ class DriftClient {
559
571
  // if cache has more recent slot than user positions account slot, add market to remaining accounts
560
572
  // otherwise remove from slot
561
573
  if (slot > lastUserSlot) {
562
- const marketAccount = this.getPerpMarketAccount(marketIndex);
574
+ const perpMarketAccount = this.getPerpMarketAccount(marketIndex);
563
575
  perpMarketAccountMap.set(marketIndex, {
564
- pubkey: marketAccount.pubkey,
576
+ pubkey: perpMarketAccount.pubkey,
565
577
  isSigner: false,
566
578
  isWritable: false,
567
579
  });
568
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
569
- pubkey: marketAccount.amm.oracle,
580
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
581
+ pubkey: perpMarketAccount.amm.oracle,
570
582
  isSigner: false,
571
583
  isWritable: false,
572
584
  });
585
+ const spotMarketAccount = this.getSpotMarketAccount(perpMarketAccount.quoteSpotMarketIndex);
586
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
587
+ pubkey: spotMarketAccount.pubkey,
588
+ isSigner: false,
589
+ isWritable: false,
590
+ });
591
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
592
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
593
+ pubkey: spotMarketAccount.oracle,
594
+ isSigner: false,
595
+ isWritable: false,
596
+ });
597
+ }
573
598
  }
574
599
  else {
575
600
  this.perpMarketLastSlotCache.delete(marketIndex);
@@ -579,15 +604,15 @@ class DriftClient {
579
604
  // if cache has more recent slot than user positions account slot, add market to remaining accounts
580
605
  // otherwise remove from slot
581
606
  if (slot > lastUserSlot) {
582
- const marketAccount = this.getSpotMarketAccount(marketIndex);
607
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
583
608
  spotMarketAccountMap.set(marketIndex, {
584
- pubkey: marketAccount.pubkey,
609
+ pubkey: spotMarketAccount.pubkey,
585
610
  isSigner: false,
586
611
  isWritable: false,
587
612
  });
588
- if (!marketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
589
- oracleAccountMap.set(marketAccount.oracle.toString(), {
590
- pubkey: marketAccount.oracle,
613
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
614
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
615
+ pubkey: spotMarketAccount.oracle,
591
616
  isSigner: false,
592
617
  isWritable: false,
593
618
  });
@@ -599,42 +624,68 @@ class DriftClient {
599
624
  }
600
625
  }
601
626
  if (params.readablePerpMarketIndex !== undefined) {
602
- const marketAccount = this.getPerpMarketAccount(params.readablePerpMarketIndex);
627
+ const perpMarketAccount = this.getPerpMarketAccount(params.readablePerpMarketIndex);
603
628
  perpMarketAccountMap.set(params.readablePerpMarketIndex, {
604
- pubkey: marketAccount.pubkey,
629
+ pubkey: perpMarketAccount.pubkey,
605
630
  isSigner: false,
606
631
  isWritable: false,
607
632
  });
608
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
609
- pubkey: marketAccount.amm.oracle,
633
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
634
+ pubkey: perpMarketAccount.amm.oracle,
610
635
  isSigner: false,
611
636
  isWritable: false,
612
637
  });
638
+ const spotMarketAccount = this.getSpotMarketAccount(perpMarketAccount.quoteSpotMarketIndex);
639
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
640
+ pubkey: spotMarketAccount.pubkey,
641
+ isSigner: false,
642
+ isWritable: false,
643
+ });
644
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
645
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
646
+ pubkey: spotMarketAccount.oracle,
647
+ isSigner: false,
648
+ isWritable: false,
649
+ });
650
+ }
651
+ }
652
+ if (params.readableSpotMarketIndexes !== undefined) {
653
+ for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) {
654
+ const spotMarketAccount = this.getSpotMarketAccount(readableSpotMarketIndex);
655
+ spotMarketAccountMap.set(readableSpotMarketIndex, {
656
+ pubkey: spotMarketAccount.pubkey,
657
+ isSigner: false,
658
+ isWritable: false,
659
+ });
660
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
661
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
662
+ pubkey: spotMarketAccount.oracle,
663
+ isSigner: false,
664
+ isWritable: false,
665
+ });
666
+ }
667
+ }
613
668
  }
614
669
  if (params.writablePerpMarketIndexes !== undefined) {
615
670
  for (const writablePerpMarketIndex of params.writablePerpMarketIndexes) {
616
- const marketAccount = this.getPerpMarketAccount(writablePerpMarketIndex);
671
+ const perpMarketAccount = this.getPerpMarketAccount(writablePerpMarketIndex);
617
672
  perpMarketAccountMap.set(writablePerpMarketIndex, {
618
- pubkey: marketAccount.pubkey,
673
+ pubkey: perpMarketAccount.pubkey,
619
674
  isSigner: false,
620
675
  isWritable: true,
621
676
  });
622
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
623
- pubkey: marketAccount.amm.oracle,
677
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
678
+ pubkey: perpMarketAccount.amm.oracle,
624
679
  isSigner: false,
625
680
  isWritable: false,
626
681
  });
627
- }
628
- }
629
- if (params.readableSpotMarketIndexes !== undefined) {
630
- for (const readableSpotMarketIndex of params.readableSpotMarketIndexes) {
631
- const spotMarketAccount = this.getSpotMarketAccount(readableSpotMarketIndex);
632
- spotMarketAccountMap.set(readableSpotMarketIndex, {
682
+ const spotMarketAccount = this.getSpotMarketAccount(perpMarketAccount.quoteSpotMarketIndex);
683
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
633
684
  pubkey: spotMarketAccount.pubkey,
634
685
  isSigner: false,
635
686
  isWritable: false,
636
687
  });
637
- if (spotMarketAccount.marketIndex !== 0) {
688
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
638
689
  oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
639
690
  pubkey: spotMarketAccount.oracle,
640
691
  isSigner: false,
@@ -688,27 +739,48 @@ class DriftClient {
688
739
  }
689
740
  if (!spotPosition.openAsks.eq(numericConstants_1.ZERO) ||
690
741
  !spotPosition.openBids.eq(numericConstants_1.ZERO)) {
742
+ const quoteSpotMarket = this.getQuoteSpotMarketAccount();
691
743
  spotMarketAccountMap.set(numericConstants_1.QUOTE_SPOT_MARKET_INDEX, {
692
- pubkey: this.getQuoteSpotMarketAccount().pubkey,
744
+ pubkey: quoteSpotMarket.pubkey,
693
745
  isSigner: false,
694
746
  isWritable: false,
695
747
  });
748
+ if (!quoteSpotMarket.oracle.equals(web3_js_1.PublicKey.default)) {
749
+ oracleAccountMap.set(quoteSpotMarket.oracle.toString(), {
750
+ pubkey: quoteSpotMarket.oracle,
751
+ isSigner: false,
752
+ isWritable: false,
753
+ });
754
+ }
696
755
  }
697
756
  }
698
757
  }
699
758
  for (const position of userAccount.perpPositions) {
700
759
  if (!(0, position_1.positionIsAvailable)(position)) {
701
- const market = this.getPerpMarketAccount(position.marketIndex);
760
+ const perpMarketAccount = this.getPerpMarketAccount(position.marketIndex);
702
761
  perpMarketAccountMap.set(position.marketIndex, {
703
- pubkey: market.pubkey,
762
+ pubkey: perpMarketAccount.pubkey,
704
763
  isWritable: false,
705
764
  isSigner: false,
706
765
  });
707
- oracleAccountMap.set(market.amm.oracle.toString(), {
708
- pubkey: market.amm.oracle,
766
+ oracleAccountMap.set(perpMarketAccount.amm.oracle.toString(), {
767
+ pubkey: perpMarketAccount.amm.oracle,
709
768
  isWritable: false,
710
769
  isSigner: false,
711
770
  });
771
+ const spotMarketAccount = this.getSpotMarketAccount(perpMarketAccount.quoteSpotMarketIndex);
772
+ spotMarketAccountMap.set(perpMarketAccount.quoteSpotMarketIndex, {
773
+ pubkey: spotMarketAccount.pubkey,
774
+ isSigner: false,
775
+ isWritable: false,
776
+ });
777
+ if (!spotMarketAccount.oracle.equals(web3_js_1.PublicKey.default)) {
778
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
779
+ pubkey: spotMarketAccount.oracle,
780
+ isSigner: false,
781
+ isWritable: false,
782
+ });
783
+ }
712
784
  }
713
785
  }
714
786
  }
@@ -16,6 +16,9 @@ function getOracleClient(oracleSource, connection) {
16
16
  if ((0, types_1.isVariant)(oracleSource, 'pyth1M')) {
17
17
  return new pythClient_1.PythClient(connection, new anchor_1.BN(1000000));
18
18
  }
19
+ if ((0, types_1.isVariant)(oracleSource, 'pythStableCoin')) {
20
+ return new pythClient_1.PythClient(connection, undefined, true);
21
+ }
19
22
  // if (isVariant(oracleSource, 'switchboard')) {
20
23
  // return new SwitchboardClient(connection);
21
24
  // }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.22.0",
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
  }
@@ -6383,6 +6391,9 @@
6383
6391
  },
6384
6392
  {
6385
6393
  "name": "Pyth1M"
6394
+ },
6395
+ {
6396
+ "name": "PythStableCoin"
6386
6397
  }
6387
6398
  ]
6388
6399
  }
@@ -6,7 +6,8 @@ import { BN } from '@project-serum/anchor';
6
6
  export declare class PythClient implements OracleClient {
7
7
  private connection;
8
8
  private multiple;
9
- constructor(connection: Connection, multiple?: BN);
9
+ private stableCoin;
10
+ constructor(connection: Connection, multiple?: BN, stableCoin?: boolean);
10
11
  getOraclePriceData(pricePublicKey: PublicKey): Promise<OraclePriceData>;
11
12
  getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData;
12
13
  }
@@ -5,9 +5,10 @@ const client_1 = require("@pythnetwork/client");
5
5
  const anchor_1 = require("@project-serum/anchor");
6
6
  const numericConstants_1 = require("../constants/numericConstants");
7
7
  class PythClient {
8
- constructor(connection, multiple = numericConstants_1.ONE) {
8
+ constructor(connection, multiple = numericConstants_1.ONE, stableCoin = false) {
9
9
  this.connection = connection;
10
10
  this.multiple = multiple;
11
+ this.stableCoin = stableCoin;
11
12
  }
12
13
  async getOraclePriceData(pricePublicKey) {
13
14
  const accountInfo = await this.connection.getAccountInfo(pricePublicKey);
@@ -15,10 +16,15 @@ class PythClient {
15
16
  }
16
17
  getOraclePriceDataFromBuffer(buffer) {
17
18
  const priceData = (0, client_1.parsePriceData)(buffer);
19
+ const confidence = convertPythPrice(priceData.confidence, priceData.exponent, this.multiple);
20
+ let price = convertPythPrice(priceData.aggregate.price, priceData.exponent, this.multiple);
21
+ if (this.stableCoin) {
22
+ price = getStableCoinPrice(price, confidence);
23
+ }
18
24
  return {
19
- price: convertPythPrice(priceData.aggregate.price, priceData.exponent, this.multiple),
25
+ price,
20
26
  slot: new anchor_1.BN(priceData.lastSlot.toString()),
21
- confidence: convertPythPrice(priceData.confidence, priceData.exponent, this.multiple),
27
+ confidence,
22
28
  twap: convertPythPrice(priceData.twap.value, priceData.exponent, this.multiple),
23
29
  twapConfidence: convertPythPrice(priceData.twac.value, priceData.exponent, this.multiple),
24
30
  hasSufficientNumberOfDataPoints: true,
@@ -34,3 +40,12 @@ function convertPythPrice(price, exponent, multiple) {
34
40
  .div(pythPrecision);
35
41
  }
36
42
  exports.convertPythPrice = convertPythPrice;
43
+ const fiveBPS = new anchor_1.BN(500);
44
+ function getStableCoinPrice(price, confidence) {
45
+ if (price.sub(numericConstants_1.QUOTE_PRECISION).abs().lt(anchor_1.BN.min(confidence, fiveBPS))) {
46
+ return numericConstants_1.QUOTE_PRECISION;
47
+ }
48
+ else {
49
+ return price;
50
+ }
51
+ }
package/lib/types.d.ts CHANGED
@@ -139,6 +139,9 @@ export declare class OracleSource {
139
139
  static readonly QUOTE_ASSET: {
140
140
  quoteAsset: {};
141
141
  };
142
+ static readonly PYTH_STABLE_COIN: {
143
+ pythStableCoin: {};
144
+ };
142
145
  }
143
146
  export declare class OrderType {
144
147
  static readonly LIMIT: {
@@ -635,6 +638,7 @@ export type PerpMarketAccount = {
635
638
  quoteSettledInsurance: BN;
636
639
  quoteMaxInsurance: BN;
637
640
  };
641
+ quoteSpotMarketIndex: number;
638
642
  };
639
643
  export type HistoricalOracleData = {
640
644
  lastOraclePrice: BN;
package/lib/types.js CHANGED
@@ -82,6 +82,7 @@ OracleSource.PYTH_1K = { pyth1K: {} };
82
82
  OracleSource.PYTH_1M = { pyth1M: {} };
83
83
  // static readonly SWITCHBOARD = { switchboard: {} };
84
84
  OracleSource.QUOTE_ASSET = { quoteAsset: {} };
85
+ OracleSource.PYTH_STABLE_COIN = { pythStableCoin: {} };
85
86
  class OrderType {
86
87
  }
87
88
  exports.OrderType = OrderType;
package/lib/user.d.ts CHANGED
@@ -104,7 +104,7 @@ export declare class User {
104
104
  * calculates unrealized position price pnl
105
105
  * @returns : Precision QUOTE_PRECISION
106
106
  */
107
- getUnrealizedPNL(withFunding?: boolean, marketIndex?: number, withWeightMarginCategory?: MarginCategory): BN;
107
+ getUnrealizedPNL(withFunding?: boolean, marketIndex?: number, withWeightMarginCategory?: MarginCategory, strict?: boolean): BN;
108
108
  /**
109
109
  * calculates unrealized funding payment pnl
110
110
  * @returns : Precision QUOTE_PRECISION
@@ -125,7 +125,7 @@ export declare class User {
125
125
  * calculates TotalCollateral: collateral + unrealized pnl
126
126
  * @returns : Precision QUOTE_PRECISION
127
127
  */
128
- getTotalCollateral(marginCategory?: MarginCategory): BN;
128
+ getTotalCollateral(marginCategory?: MarginCategory, strict?: boolean): BN;
129
129
  /**
130
130
  * calculates User Health by comparing total collateral and maint. margin requirement
131
131
  * @returns : number (value from [0, 100])
@@ -135,7 +135,7 @@ export declare class User {
135
135
  * calculates sum of position value across all positions in margin system
136
136
  * @returns : Precision QUOTE_PRECISION
137
137
  */
138
- getTotalPerpPositionValue(marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean): BN;
138
+ getTotalPerpPositionValue(marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean, strict?: boolean): BN;
139
139
  /**
140
140
  * calculates position value in margin system
141
141
  * @returns : Precision QUOTE_PRECISION
package/lib/user.js CHANGED
@@ -268,7 +268,7 @@ class User {
268
268
  * @returns The margin requirement of a certain type (Initial or Maintenance) in USDC. : QUOTE_PRECISION
269
269
  */
270
270
  getMarginRequirement(marginCategory, liquidationBuffer, strict = false) {
271
- return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
271
+ return this.getTotalPerpPositionValue(marginCategory, liquidationBuffer, true, strict).add(this.getSpotMarketLiabilityValue(undefined, marginCategory, liquidationBuffer, true, strict));
272
272
  }
273
273
  /**
274
274
  * @returns The initial margin requirement in USDC. : QUOTE_PRECISION
@@ -292,17 +292,31 @@ class User {
292
292
  * calculates unrealized position price pnl
293
293
  * @returns : Precision QUOTE_PRECISION
294
294
  */
295
- getUnrealizedPNL(withFunding, marketIndex, withWeightMarginCategory) {
296
- const quoteSpotMarket = this.driftClient.getQuoteSpotMarketAccount();
295
+ getUnrealizedPNL(withFunding, marketIndex, withWeightMarginCategory, strict = false) {
297
296
  return this.getActivePerpPositions()
298
297
  .filter((pos) => (marketIndex ? pos.marketIndex === marketIndex : true))
299
298
  .reduce((unrealizedPnl, perpPosition) => {
300
299
  const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
301
300
  const oraclePriceData = this.getOracleDataForPerpMarket(market.marketIndex);
301
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
302
+ const quoteOraclePriceData = this.getOracleDataForSpotMarket(market.quoteSpotMarketIndex);
302
303
  if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
303
304
  perpPosition = this.getSettledLPPosition(perpPosition.marketIndex)[0];
304
305
  }
305
306
  let positionUnrealizedPnl = (0, _1.calculatePositionPNL)(market, perpPosition, withFunding, oraclePriceData);
307
+ let quotePrice;
308
+ if (strict && positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
309
+ quotePrice = _1.BN.min(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
310
+ }
311
+ else if (strict && positionUnrealizedPnl.lt(numericConstants_1.ZERO)) {
312
+ quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
313
+ }
314
+ else {
315
+ quotePrice = quoteOraclePriceData.price;
316
+ }
317
+ positionUnrealizedPnl = positionUnrealizedPnl
318
+ .mul(quotePrice)
319
+ .div(new _1.BN(numericConstants_1.PRICE_PRECISION));
306
320
  if (withWeightMarginCategory !== undefined) {
307
321
  if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
308
322
  positionUnrealizedPnl = positionUnrealizedPnl
@@ -340,31 +354,24 @@ class User {
340
354
  continue;
341
355
  }
342
356
  const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
357
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
343
358
  if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX &&
344
359
  countForQuote) {
360
+ const tokenAmount = (0, _1.getSignedTokenAmount)((0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType), spotPosition.balanceType);
345
361
  if ((0, types_1.isVariant)(spotPosition.balanceType, 'borrow')) {
346
- const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
347
- let weight = numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION;
348
- if (marginCategory === 'Initial') {
349
- weight = _1.BN.max(weight, new _1.BN(this.getUserAccount().maxMarginRatio));
350
- }
351
- const weightedTokenValue = tokenAmount
352
- .mul(weight)
353
- .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
362
+ const weightedTokenValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now).abs();
354
363
  netQuoteValue = netQuoteValue.sub(weightedTokenValue);
355
- continue;
356
364
  }
357
365
  else {
358
- const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
359
- netQuoteValue = netQuoteValue.add(tokenAmount);
360
- continue;
366
+ const weightedTokenValue = this.getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict, now);
367
+ netQuoteValue = netQuoteValue.add(weightedTokenValue);
361
368
  }
369
+ continue;
362
370
  }
363
- const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
364
371
  if (!includeOpenOrders && countForBase) {
365
372
  if ((0, types_1.isVariant)(spotPosition.balanceType, 'borrow')) {
366
- const tokenAmount = (0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType);
367
- const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
373
+ const tokenAmount = (0, _1.getSignedTokenAmount)((0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType), _1.SpotBalanceType.BORROW);
374
+ const liabilityValue = this.getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now).abs();
368
375
  totalLiabilityValue = totalLiabilityValue.add(liabilityValue);
369
376
  continue;
370
377
  }
@@ -381,7 +388,7 @@ class User {
381
388
  totalAssetValue = totalAssetValue.add(baseAssetValue);
382
389
  }
383
390
  if (worstCaseTokenAmount.lt(numericConstants_1.ZERO) && countForBase) {
384
- const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount.abs(), oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now);
391
+ const baseLiabilityValue = this.getSpotLiabilityValue(worstCaseTokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict, now).abs();
385
392
  totalLiabilityValue = totalLiabilityValue.add(baseLiabilityValue);
386
393
  }
387
394
  if (worstCaseQuoteTokenAmount.gt(numericConstants_1.ZERO) && countForQuote) {
@@ -416,7 +423,7 @@ class User {
416
423
  }
417
424
  getSpotLiabilityValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, liquidationBuffer, strict = false, now) {
418
425
  let liabilityValue = null;
419
- if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
426
+ if (strict) {
420
427
  const estOracleTwap = (0, oracles_1.calculateLiveOracleTwap)(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
421
428
  );
422
429
  liabilityValue = (0, _1.getStrictTokenValue)(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
@@ -444,7 +451,7 @@ class User {
444
451
  }
445
452
  getSpotAssetValue(tokenAmount, oraclePriceData, spotMarketAccount, marginCategory, strict = false, now) {
446
453
  let assetValue = null;
447
- if (strict && spotMarketAccount.marketIndex != numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
454
+ if (strict) {
448
455
  const estOracleTwap = (0, oracles_1.calculateLiveOracleTwap)(spotMarketAccount.historicalOracleData, oraclePriceData, now, numericConstants_1.FIVE_MINUTE // 5MIN
449
456
  );
450
457
  assetValue = (0, _1.getStrictTokenValue)(tokenAmount, spotMarketAccount.decimals, oraclePriceData, estOracleTwap);
@@ -474,8 +481,8 @@ class User {
474
481
  * calculates TotalCollateral: collateral + unrealized pnl
475
482
  * @returns : Precision QUOTE_PRECISION
476
483
  */
477
- getTotalCollateral(marginCategory = 'Initial') {
478
- return this.getSpotMarketAssetValue(undefined, marginCategory, true).add(this.getUnrealizedPNL(true, undefined, marginCategory));
484
+ getTotalCollateral(marginCategory = 'Initial', strict = false) {
485
+ return this.getSpotMarketAssetValue(undefined, marginCategory, true, strict).add(this.getUnrealizedPNL(true, undefined, marginCategory, strict));
479
486
  }
480
487
  /**
481
488
  * calculates User Health by comparing total collateral and maint. margin requirement
@@ -513,7 +520,7 @@ class User {
513
520
  * calculates sum of position value across all positions in margin system
514
521
  * @returns : Precision QUOTE_PRECISION
515
522
  */
516
- getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders) {
523
+ getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
517
524
  return this.getActivePerpPositions().reduce((totalPerpValue, perpPosition) => {
518
525
  const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
519
526
  if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
@@ -551,7 +558,18 @@ class User {
551
558
  if ((0, types_1.isVariant)(market.status, 'settlement')) {
552
559
  marginRatio = numericConstants_1.ZERO;
553
560
  }
561
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
562
+ const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
563
+ let quotePrice;
564
+ if (strict) {
565
+ quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
566
+ }
567
+ else {
568
+ quotePrice = quoteOraclePriceData.price;
569
+ }
554
570
  baseAssetValue = baseAssetValue
571
+ .mul(quotePrice)
572
+ .div(numericConstants_1.PRICE_PRECISION)
555
573
  .mul(marginRatio)
556
574
  .div(numericConstants_1.MARGIN_PRECISION);
557
575
  if (includeOpenOrders) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.22.0",
3
+ "version": "2.23.0-beta.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -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",
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
  }
@@ -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
+ }
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
 
@@ -172,6 +172,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
172
172
  quoteSettledInsurance: new BN(0),
173
173
  quoteMaxInsurance: new BN(0),
174
174
  },
175
+ quoteSpotMarketIndex: 0,
175
176
  },
176
177
  {
177
178
  status: MarketStatus.INITIALIZED,
@@ -208,6 +209,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
208
209
  quoteSettledInsurance: new BN(0),
209
210
  quoteMaxInsurance: new BN(0),
210
211
  },
212
+ quoteSpotMarketIndex: 0,
211
213
  },
212
214
  {
213
215
  status: MarketStatus.INITIALIZED,
@@ -244,6 +246,7 @@ export const mockPerpMarkets: Array<PerpMarketAccount> = [
244
246
  quoteSettledInsurance: new BN(0),
245
247
  quoteMaxInsurance: new BN(0),
246
248
  },
249
+ quoteSpotMarketIndex: 0,
247
250
  },
248
251
  ];
249
252