@drift-labs/sdk 2.37.1-beta.1 → 2.37.1-beta.10

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,17 +1,13 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.RetryTxSender = void 0;
7
- const web3_js_1 = require("@solana/web3.js");
8
4
  const anchor_1 = require("@coral-xyz/anchor");
9
- const assert_1 = __importDefault(require("assert"));
10
- const bs58_1 = __importDefault(require("bs58"));
5
+ const baseTxSender_1 = require("./baseTxSender");
11
6
  const DEFAULT_TIMEOUT = 35000;
12
7
  const DEFAULT_RETRY = 8000;
13
- class RetryTxSender {
8
+ class RetryTxSender extends baseTxSender_1.BaseTxSender {
14
9
  constructor({ connection, wallet, opts = anchor_1.AnchorProvider.defaultOptions(), timeout = DEFAULT_TIMEOUT, retrySleep = DEFAULT_RETRY, additionalConnections = new Array(), }) {
10
+ super({ connection, wallet, opts, timeout, additionalConnections });
15
11
  this.timoutCount = 0;
16
12
  this.connection = connection;
17
13
  this.wallet = wallet;
@@ -20,66 +16,11 @@ class RetryTxSender {
20
16
  this.retrySleep = retrySleep;
21
17
  this.additionalConnections = additionalConnections;
22
18
  }
23
- async send(tx, additionalSigners, opts, preSigned) {
24
- if (additionalSigners === undefined) {
25
- additionalSigners = [];
26
- }
27
- if (opts === undefined) {
28
- opts = this.opts;
29
- }
30
- const signedTx = preSigned
31
- ? tx
32
- : await this.prepareTx(tx, additionalSigners, opts);
33
- return this.sendRawTransaction(signedTx.serialize(), opts);
34
- }
35
- async prepareTx(tx, additionalSigners, opts) {
36
- tx.feePayer = this.wallet.publicKey;
37
- tx.recentBlockhash = (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash;
38
- additionalSigners
39
- .filter((s) => s !== undefined)
40
- .forEach((kp) => {
41
- tx.partialSign(kp);
19
+ async sleep(reference) {
20
+ return new Promise((resolve) => {
21
+ reference.resolve = resolve;
22
+ setTimeout(resolve, this.retrySleep);
42
23
  });
43
- const signedTx = await this.wallet.signTransaction(tx);
44
- return signedTx;
45
- }
46
- async getVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts) {
47
- if (additionalSigners === undefined) {
48
- additionalSigners = [];
49
- }
50
- if (opts === undefined) {
51
- opts = this.opts;
52
- }
53
- const message = new web3_js_1.TransactionMessage({
54
- payerKey: this.wallet.publicKey,
55
- recentBlockhash: (await this.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash,
56
- instructions: ixs,
57
- }).compileToV0Message(lookupTableAccounts);
58
- const tx = new web3_js_1.VersionedTransaction(message);
59
- return tx;
60
- }
61
- async sendVersionedTransaction(tx, additionalSigners, opts, preSigned) {
62
- let signedTx;
63
- if (preSigned) {
64
- signedTx = tx;
65
- // @ts-ignore
66
- }
67
- else if (this.wallet.payer) {
68
- // @ts-ignore
69
- tx.sign((additionalSigners !== null && additionalSigners !== void 0 ? additionalSigners : []).concat(this.wallet.payer));
70
- signedTx = tx;
71
- }
72
- else {
73
- additionalSigners === null || additionalSigners === void 0 ? void 0 : additionalSigners.filter((s) => s !== undefined).forEach((kp) => {
74
- tx.sign([kp]);
75
- });
76
- // @ts-ignore
77
- signedTx = await this.wallet.signTransaction(tx);
78
- }
79
- if (opts === undefined) {
80
- opts = this.opts;
81
- }
82
- return this.sendRawTransaction(signedTx.serialize(), opts);
83
24
  }
84
25
  async sendRawTransaction(rawTransaction, opts) {
85
26
  const startTime = this.getTimestamp();
@@ -130,97 +71,5 @@ class RetryTxSender {
130
71
  }
131
72
  return { txSig: txid, slot };
132
73
  }
133
- async confirmTransaction(signature, commitment) {
134
- let decodedSignature;
135
- try {
136
- decodedSignature = bs58_1.default.decode(signature);
137
- }
138
- catch (err) {
139
- throw new Error('signature must be base58 encoded: ' + signature);
140
- }
141
- (0, assert_1.default)(decodedSignature.length === 64, 'signature has invalid length');
142
- const start = Date.now();
143
- const subscriptionCommitment = commitment || this.opts.commitment;
144
- const subscriptionIds = new Array();
145
- const connections = [this.connection, ...this.additionalConnections];
146
- let response = null;
147
- const promises = connections.map((connection, i) => {
148
- let subscriptionId;
149
- const confirmPromise = new Promise((resolve, reject) => {
150
- try {
151
- subscriptionId = connection.onSignature(signature, (result, context) => {
152
- subscriptionIds[i] = undefined;
153
- response = {
154
- context,
155
- value: result,
156
- };
157
- resolve(null);
158
- }, subscriptionCommitment);
159
- }
160
- catch (err) {
161
- reject(err);
162
- }
163
- });
164
- subscriptionIds.push(subscriptionId);
165
- return confirmPromise;
166
- });
167
- try {
168
- await this.promiseTimeout(promises, this.timeout);
169
- }
170
- finally {
171
- for (const [i, subscriptionId] of subscriptionIds.entries()) {
172
- if (subscriptionId) {
173
- connections[i].removeSignatureListener(subscriptionId);
174
- }
175
- }
176
- }
177
- if (response === null) {
178
- this.timoutCount += 1;
179
- const duration = (Date.now() - start) / 1000;
180
- throw new Error(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`);
181
- }
182
- return response;
183
- }
184
- getTimestamp() {
185
- return new Date().getTime();
186
- }
187
- async sleep(reference) {
188
- return new Promise((resolve) => {
189
- reference.resolve = resolve;
190
- setTimeout(resolve, this.retrySleep);
191
- });
192
- }
193
- promiseTimeout(promises, timeoutMs) {
194
- let timeoutId;
195
- const timeoutPromise = new Promise((resolve) => {
196
- timeoutId = setTimeout(() => resolve(null), timeoutMs);
197
- });
198
- return Promise.race([...promises, timeoutPromise]).then((result) => {
199
- clearTimeout(timeoutId);
200
- return result;
201
- });
202
- }
203
- sendToAdditionalConnections(rawTx, opts) {
204
- this.additionalConnections.map((connection) => {
205
- connection.sendRawTransaction(rawTx, opts).catch((e) => {
206
- console.error(
207
- // @ts-ignore
208
- `error sending tx to additional connection ${connection._rpcEndpoint}`);
209
- console.error(e);
210
- });
211
- });
212
- }
213
- addAdditionalConnection(newConnection) {
214
- const alreadyUsingConnection = this.additionalConnections.filter((connection) => {
215
- // @ts-ignore
216
- return connection._rpcEndpoint === newConnection.rpcEndpoint;
217
- }).length > 0;
218
- if (!alreadyUsingConnection) {
219
- this.additionalConnections.push(newConnection);
220
- }
221
- }
222
- getTimeoutCount() {
223
- return this.timoutCount;
224
- }
225
74
  }
226
75
  exports.RetryTxSender = RetryTxSender;
package/lib/types.d.ts CHANGED
@@ -50,6 +50,9 @@ export declare class UserStatus {
50
50
  static readonly BANKRUPT: {
51
51
  bankrupt: {};
52
52
  };
53
+ static readonly REDUCE_ONLY: {
54
+ reduceOnly: {};
55
+ };
53
56
  }
54
57
  export declare class ContractType {
55
58
  static readonly PERPETUAL: {
@@ -818,6 +821,7 @@ export type AMM = {
818
821
  bidQuoteAssetReserve: BN;
819
822
  askBaseAssetReserve: BN;
820
823
  askQuoteAssetReserve: BN;
824
+ perLpBase: number;
821
825
  };
822
826
  export type PerpPosition = {
823
827
  baseAssetAmount: BN;
@@ -834,6 +838,7 @@ export type PerpPosition = {
834
838
  remainderBaseAssetAmount: number;
835
839
  lastBaseAssetAmountPerLp: BN;
836
840
  lastQuoteAssetAmountPerLp: BN;
841
+ perLpBase: number;
837
842
  };
838
843
  export type UserStatsAccount = {
839
844
  numberOfSubAccounts: number;
@@ -1114,3 +1119,16 @@ export type PerpMarketExtendedInfo = {
1114
1119
  pnlPoolValue: BN;
1115
1120
  contractTier: ContractTier;
1116
1121
  };
1122
+ export type HealthComponents = {
1123
+ deposits: HealthComponent[];
1124
+ borrows: HealthComponent[];
1125
+ perpPositions: HealthComponent[];
1126
+ perpPnl: HealthComponent[];
1127
+ };
1128
+ export type HealthComponent = {
1129
+ marketIndex: number;
1130
+ size: BN;
1131
+ value: BN;
1132
+ weight: BN;
1133
+ weightedValue: BN;
1134
+ };
package/lib/types.js CHANGED
@@ -33,6 +33,7 @@ exports.UserStatus = UserStatus;
33
33
  UserStatus.ACTIVE = { active: {} };
34
34
  UserStatus.BEING_LIQUIDATED = { beingLiquidated: {} };
35
35
  UserStatus.BANKRUPT = { bankrupt: {} };
36
+ UserStatus.REDUCE_ONLY = { reduceOnly: {} };
36
37
  class ContractType {
37
38
  }
38
39
  exports.ContractType = ContractType;
package/lib/user.d.ts CHANGED
@@ -3,7 +3,7 @@ import { PublicKey } from '@solana/web3.js';
3
3
  import { EventEmitter } from 'events';
4
4
  import StrictEventEmitter from 'strict-event-emitter-types';
5
5
  import { DriftClient } from './driftClient';
6
- import { MarginCategory, Order, UserAccount, PerpPosition, SpotPosition, PerpMarketAccount } from './types';
6
+ import { MarginCategory, Order, UserAccount, PerpPosition, SpotPosition, PerpMarketAccount, HealthComponents } from './types';
7
7
  import { UserAccountSubscriber, UserAccountEvents, DataAndSlot } from './accounts/types';
8
8
  import { PositionDirection, BN, SpotMarketAccount, MarketType } from '.';
9
9
  import { OraclePriceData } from './oracles/types';
@@ -153,6 +153,12 @@ export declare class User {
153
153
  * @returns : number (value from [0, 100])
154
154
  */
155
155
  getHealth(): number;
156
+ calculateWeightedPerpPositionValue(perpPosition: PerpPosition, marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean, strict?: boolean): BN;
157
+ /**
158
+ * calculates position value of a single perp market in margin system
159
+ * @returns : Precision QUOTE_PRECISION
160
+ */
161
+ getPerpMarketLiabilityValue(marketIndex: number, marginCategory?: MarginCategory, liquidationBuffer?: BN, includeOpenOrders?: boolean, strict?: boolean): BN;
156
162
  /**
157
163
  * calculates sum of position value across all positions in margin system
158
164
  * @returns : Precision QUOTE_PRECISION
@@ -352,6 +358,9 @@ export declare class User {
352
358
  perpTier: number;
353
359
  spotTier: number;
354
360
  };
361
+ getHealthComponents({ marginCategory, }: {
362
+ marginCategory: MarginCategory;
363
+ }): HealthComponents;
355
364
  /**
356
365
  * Get the total position value, excluding any position coming from the given target market
357
366
  * @param marketToIgnore
package/lib/user.js CHANGED
@@ -145,6 +145,7 @@ class User {
145
145
  lpShares: numericConstants_1.ZERO,
146
146
  lastBaseAssetAmountPerLp: numericConstants_1.ZERO,
147
147
  lastQuoteAssetAmountPerLp: numericConstants_1.ZERO,
148
+ perLpBase: 0,
148
149
  };
149
150
  }
150
151
  getClonedPosition(position) {
@@ -258,17 +259,54 @@ class User {
258
259
  }
259
260
  const position = this.getClonedPosition(originalPosition);
260
261
  const market = this.driftClient.getPerpMarketAccount(position.marketIndex);
262
+ if (market.amm.perLpBase != position.perLpBase) {
263
+ // perLpBase = 1 => per 10 LP shares, perLpBase = -1 => per 0.1 LP shares
264
+ const expoDiff = market.amm.perLpBase - position.perLpBase;
265
+ const marketPerLpRebaseScalar = new _1.BN(10 ** Math.abs(expoDiff));
266
+ if (expoDiff > 0) {
267
+ position.lastBaseAssetAmountPerLp =
268
+ position.lastBaseAssetAmountPerLp.mul(marketPerLpRebaseScalar);
269
+ position.lastQuoteAssetAmountPerLp =
270
+ position.lastQuoteAssetAmountPerLp.mul(marketPerLpRebaseScalar);
271
+ }
272
+ else {
273
+ position.lastBaseAssetAmountPerLp =
274
+ position.lastBaseAssetAmountPerLp.div(marketPerLpRebaseScalar);
275
+ position.lastQuoteAssetAmountPerLp =
276
+ position.lastQuoteAssetAmountPerLp.div(marketPerLpRebaseScalar);
277
+ }
278
+ position.perLpBase = position.perLpBase + expoDiff;
279
+ }
261
280
  const nShares = position.lpShares;
262
281
  // incorp unsettled funding on pre settled position
263
282
  const quoteFundingPnl = (0, _1.calculatePositionFundingPNL)(market, position);
283
+ let baseUnit = numericConstants_1.AMM_RESERVE_PRECISION;
284
+ if (market.amm.perLpBase == position.perLpBase) {
285
+ if (position.perLpBase >= 0 &&
286
+ position.perLpBase <= numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
287
+ const marketPerLpRebase = new _1.BN(10 ** market.amm.perLpBase);
288
+ baseUnit = baseUnit.mul(marketPerLpRebase);
289
+ }
290
+ else if (position.perLpBase < 0 &&
291
+ position.perLpBase >= -numericConstants_1.AMM_RESERVE_PRECISION_EXP.toNumber()) {
292
+ const marketPerLpRebase = new _1.BN(10 ** Math.abs(market.amm.perLpBase));
293
+ baseUnit = baseUnit.div(marketPerLpRebase);
294
+ }
295
+ else {
296
+ throw 'cannot calc';
297
+ }
298
+ }
299
+ else {
300
+ throw 'market.amm.perLpBase != position.perLpBase';
301
+ }
264
302
  const deltaBaa = market.amm.baseAssetAmountPerLp
265
303
  .sub(position.lastBaseAssetAmountPerLp)
266
304
  .mul(nShares)
267
- .div(numericConstants_1.AMM_RESERVE_PRECISION);
305
+ .div(baseUnit);
268
306
  const deltaQaa = market.amm.quoteAssetAmountPerLp
269
307
  .sub(position.lastQuoteAssetAmountPerLp)
270
308
  .mul(nShares)
271
- .div(numericConstants_1.AMM_RESERVE_PRECISION);
309
+ .div(baseUnit);
272
310
  function sign(v) {
273
311
  return v.isNeg() ? new _1.BN(-1) : new _1.BN(1);
274
312
  }
@@ -464,7 +502,7 @@ class User {
464
502
  }
465
503
  positionUnrealizedPnl = positionUnrealizedPnl
466
504
  .mul(quotePrice)
467
- .div(new _1.BN(numericConstants_1.PRICE_PRECISION));
505
+ .div(numericConstants_1.PRICE_PRECISION);
468
506
  if (withWeightMarginCategory !== undefined) {
469
507
  if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
470
508
  positionUnrealizedPnl = positionUnrealizedPnl
@@ -660,64 +698,81 @@ class User {
660
698
  }
661
699
  return health;
662
700
  }
701
+ calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
702
+ const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
703
+ if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
704
+ // is an lp, clone so we dont mutate the position
705
+ perpPosition = this.getPerpPositionWithLPSettle(market.marketIndex, this.getClonedPosition(perpPosition), !!marginCategory)[0];
706
+ }
707
+ let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
708
+ if ((0, types_1.isVariant)(market.status, 'settlement')) {
709
+ valuationPrice = market.expiryPrice;
710
+ }
711
+ const baseAssetAmount = includeOpenOrders
712
+ ? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
713
+ : perpPosition.baseAssetAmount;
714
+ let baseAssetValue = baseAssetAmount
715
+ .abs()
716
+ .mul(valuationPrice)
717
+ .div(numericConstants_1.BASE_PRECISION);
718
+ if (marginCategory) {
719
+ let marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(market, baseAssetAmount.abs(), marginCategory));
720
+ if (marginCategory === 'Initial') {
721
+ marginRatio = _1.BN.max(marginRatio, new _1.BN(this.getUserAccount().maxMarginRatio));
722
+ }
723
+ if (liquidationBuffer !== undefined) {
724
+ marginRatio = marginRatio.add(liquidationBuffer);
725
+ }
726
+ if ((0, types_1.isVariant)(market.status, 'settlement')) {
727
+ marginRatio = numericConstants_1.ZERO;
728
+ }
729
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
730
+ const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
731
+ let quotePrice;
732
+ if (strict) {
733
+ quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
734
+ }
735
+ else {
736
+ quotePrice = quoteOraclePriceData.price;
737
+ }
738
+ baseAssetValue = baseAssetValue
739
+ .mul(quotePrice)
740
+ .div(numericConstants_1.PRICE_PRECISION)
741
+ .mul(marginRatio)
742
+ .div(numericConstants_1.MARGIN_PRECISION);
743
+ if (includeOpenOrders) {
744
+ baseAssetValue = baseAssetValue.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
745
+ if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
746
+ baseAssetValue = baseAssetValue.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, valuationPrice
747
+ .mul(market.amm.orderStepSize)
748
+ .mul(numericConstants_1.QUOTE_PRECISION)
749
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
750
+ .div(numericConstants_1.PRICE_PRECISION)));
751
+ }
752
+ }
753
+ }
754
+ return baseAssetValue;
755
+ }
756
+ /**
757
+ * calculates position value of a single perp market in margin system
758
+ * @returns : Precision QUOTE_PRECISION
759
+ */
760
+ getPerpMarketLiabilityValue(marketIndex, marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
761
+ const perpPosition = this.getPerpPosition(marketIndex);
762
+ if (!perpPosition) {
763
+ return numericConstants_1.ZERO;
764
+ }
765
+ else {
766
+ return this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
767
+ }
768
+ }
663
769
  /**
664
770
  * calculates sum of position value across all positions in margin system
665
771
  * @returns : Precision QUOTE_PRECISION
666
772
  */
667
773
  getTotalPerpPositionValue(marginCategory, liquidationBuffer, includeOpenOrders, strict = false) {
668
774
  return this.getActivePerpPositions().reduce((totalPerpValue, perpPosition) => {
669
- const market = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
670
- if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
671
- // is an lp, clone so we dont mutate the position
672
- perpPosition = this.getPerpPositionWithLPSettle(market.marketIndex, this.getClonedPosition(perpPosition), !!marginCategory)[0];
673
- }
674
- let valuationPrice = this.getOracleDataForPerpMarket(market.marketIndex).price;
675
- if ((0, types_1.isVariant)(market.status, 'settlement')) {
676
- valuationPrice = market.expiryPrice;
677
- }
678
- const baseAssetAmount = includeOpenOrders
679
- ? (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition)
680
- : perpPosition.baseAssetAmount;
681
- let baseAssetValue = baseAssetAmount
682
- .abs()
683
- .mul(valuationPrice)
684
- .div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO.mul(numericConstants_1.PRICE_PRECISION));
685
- if (marginCategory) {
686
- let marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(market, baseAssetAmount.abs(), marginCategory));
687
- if (marginCategory === 'Initial') {
688
- marginRatio = _1.BN.max(marginRatio, new _1.BN(this.getUserAccount().maxMarginRatio));
689
- }
690
- if (liquidationBuffer !== undefined) {
691
- marginRatio = marginRatio.add(liquidationBuffer);
692
- }
693
- if ((0, types_1.isVariant)(market.status, 'settlement')) {
694
- marginRatio = numericConstants_1.ZERO;
695
- }
696
- const quoteSpotMarket = this.driftClient.getSpotMarketAccount(market.quoteSpotMarketIndex);
697
- const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
698
- let quotePrice;
699
- if (strict) {
700
- quotePrice = _1.BN.max(quoteOraclePriceData.price, quoteSpotMarket.historicalOracleData.lastOraclePriceTwap5Min);
701
- }
702
- else {
703
- quotePrice = quoteOraclePriceData.price;
704
- }
705
- baseAssetValue = baseAssetValue
706
- .mul(quotePrice)
707
- .div(numericConstants_1.PRICE_PRECISION)
708
- .mul(marginRatio)
709
- .div(numericConstants_1.MARGIN_PRECISION);
710
- if (includeOpenOrders) {
711
- baseAssetValue = baseAssetValue.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
712
- if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
713
- baseAssetValue = baseAssetValue.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, valuationPrice
714
- .mul(market.amm.orderStepSize)
715
- .mul(numericConstants_1.QUOTE_PRECISION)
716
- .div(numericConstants_1.AMM_RESERVE_PRECISION)
717
- .div(numericConstants_1.PRICE_PRECISION)));
718
- }
719
- }
720
- }
775
+ const baseAssetValue = this.calculateWeightedPerpPositionValue(perpPosition, marginCategory, liquidationBuffer, includeOpenOrders, strict);
721
776
  return totalPerpValue.add(baseAssetValue);
722
777
  }, numericConstants_1.ZERO);
723
778
  }
@@ -1589,12 +1644,15 @@ class User {
1589
1644
  if (canBypass) {
1590
1645
  withdrawLimit = _1.BN.max(withdrawLimit, userDepositAmount);
1591
1646
  }
1592
- const amountWithdrawable = freeCollateral
1593
- .mul(numericConstants_1.MARGIN_PRECISION)
1594
- .div(new _1.BN(spotMarket.initialAssetWeight))
1595
- .mul(numericConstants_1.PRICE_PRECISION)
1596
- .div(oracleData.price)
1597
- .mul(precisionIncrease);
1647
+ const assetWeight = (0, spotBalance_1.calculateAssetWeight)(userDepositAmount, spotMarket, 'Initial');
1648
+ const amountWithdrawable = assetWeight.eq(numericConstants_1.ZERO)
1649
+ ? userDepositAmount
1650
+ : freeCollateral
1651
+ .mul(numericConstants_1.MARGIN_PRECISION)
1652
+ .div(assetWeight)
1653
+ .mul(numericConstants_1.PRICE_PRECISION)
1654
+ .div(oracleData.price)
1655
+ .mul(precisionIncrease);
1598
1656
  const maxWithdrawValue = _1.BN.min(_1.BN.min(amountWithdrawable, userDepositAmount), withdrawLimit.abs());
1599
1657
  if (reduceOnly) {
1600
1658
  return _1.BN.max(maxWithdrawValue, numericConstants_1.ZERO);
@@ -1702,6 +1760,146 @@ class User {
1702
1760
  spotTier: safestSpotTier,
1703
1761
  };
1704
1762
  }
1763
+ getHealthComponents({ marginCategory, }) {
1764
+ const healthComponents = {
1765
+ deposits: [],
1766
+ borrows: [],
1767
+ perpPositions: [],
1768
+ perpPnl: [],
1769
+ };
1770
+ for (const perpPosition of this.getActivePerpPositions()) {
1771
+ const perpMarket = this.driftClient.getPerpMarketAccount(perpPosition.marketIndex);
1772
+ const oraclePriceData = this.driftClient.getOraclePriceDataAndSlot(perpMarket.amm.oracle).data;
1773
+ const oraclePrice = oraclePriceData.price;
1774
+ const worstCaseBaseAmount = (0, margin_1.calculateWorstCaseBaseAssetAmount)(perpPosition);
1775
+ const marginRatio = new _1.BN((0, _1.calculateMarketMarginRatio)(perpMarket, worstCaseBaseAmount.abs(), marginCategory));
1776
+ const quoteSpotMarket = this.driftClient.getSpotMarketAccount(perpMarket.quoteSpotMarketIndex);
1777
+ const quoteOraclePriceData = this.driftClient.getOraclePriceDataAndSlot(quoteSpotMarket.oracle).data;
1778
+ const baseAssetValue = worstCaseBaseAmount
1779
+ .abs()
1780
+ .mul(oraclePrice)
1781
+ .div(numericConstants_1.BASE_PRECISION);
1782
+ let marginRequirement = baseAssetValue
1783
+ .mul(quoteOraclePriceData.price)
1784
+ .div(numericConstants_1.PRICE_PRECISION)
1785
+ .mul(marginRatio)
1786
+ .div(numericConstants_1.MARGIN_PRECISION);
1787
+ marginRequirement = marginRequirement.add(new _1.BN(perpPosition.openOrders).mul(numericConstants_1.OPEN_ORDER_MARGIN_REQUIREMENT));
1788
+ if (perpPosition.lpShares.gt(numericConstants_1.ZERO)) {
1789
+ marginRequirement = marginRequirement.add(_1.BN.max(numericConstants_1.QUOTE_PRECISION, oraclePrice
1790
+ .mul(perpMarket.amm.orderStepSize)
1791
+ .mul(numericConstants_1.QUOTE_PRECISION)
1792
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
1793
+ .div(numericConstants_1.PRICE_PRECISION)));
1794
+ }
1795
+ healthComponents.perpPositions.push({
1796
+ marketIndex: perpMarket.marketIndex,
1797
+ size: worstCaseBaseAmount,
1798
+ value: baseAssetValue,
1799
+ weight: marginRatio,
1800
+ weightedValue: marginRequirement,
1801
+ });
1802
+ const settledPerpPosition = this.getPerpPositionWithLPSettle(perpPosition.marketIndex, perpPosition)[0];
1803
+ const positionUnrealizedPnl = (0, _1.calculatePositionPNL)(perpMarket, settledPerpPosition, true, oraclePriceData);
1804
+ let pnlWeight;
1805
+ if (positionUnrealizedPnl.gt(numericConstants_1.ZERO)) {
1806
+ pnlWeight = (0, _1.calculateUnrealizedAssetWeight)(perpMarket, quoteSpotMarket, positionUnrealizedPnl, marginCategory, oraclePriceData);
1807
+ }
1808
+ else {
1809
+ pnlWeight = numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION;
1810
+ }
1811
+ const pnlValue = positionUnrealizedPnl
1812
+ .mul(quoteOraclePriceData.price)
1813
+ .div(numericConstants_1.PRICE_PRECISION);
1814
+ const wegithedPnlValue = pnlValue
1815
+ .mul(pnlWeight)
1816
+ .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
1817
+ healthComponents.perpPnl.push({
1818
+ marketIndex: perpMarket.marketIndex,
1819
+ size: positionUnrealizedPnl,
1820
+ value: pnlValue,
1821
+ weight: pnlWeight,
1822
+ weightedValue: wegithedPnlValue,
1823
+ });
1824
+ }
1825
+ let netQuoteValue = numericConstants_1.ZERO;
1826
+ for (const spotPosition of this.getActiveSpotPositions()) {
1827
+ const spotMarketAccount = this.driftClient.getSpotMarketAccount(spotPosition.marketIndex);
1828
+ const oraclePriceData = this.getOracleDataForSpotMarket(spotPosition.marketIndex);
1829
+ if (spotPosition.marketIndex === numericConstants_1.QUOTE_SPOT_MARKET_INDEX) {
1830
+ const tokenAmount = (0, _1.getSignedTokenAmount)((0, spotBalance_1.getTokenAmount)(spotPosition.scaledBalance, spotMarketAccount, spotPosition.balanceType), spotPosition.balanceType);
1831
+ netQuoteValue = netQuoteValue.add(tokenAmount);
1832
+ continue;
1833
+ }
1834
+ const [worstCaseTokenAmount, worstCaseQuoteTokenAmount] = (0, spotPosition_1.getWorstCaseTokenAmounts)(spotPosition, spotMarketAccount, oraclePriceData);
1835
+ netQuoteValue = netQuoteValue.add(worstCaseQuoteTokenAmount);
1836
+ const baseAssetValue = (0, _1.getTokenValue)(worstCaseTokenAmount.abs(), spotMarketAccount.decimals, oraclePriceData);
1837
+ const isLiability = (0, types_1.isVariant)(spotPosition.balanceType, 'borrow');
1838
+ let weight;
1839
+ if (isLiability) {
1840
+ weight = (0, spotBalance_1.calculateLiabilityWeight)(worstCaseTokenAmount.abs(), spotMarketAccount, marginCategory);
1841
+ }
1842
+ else {
1843
+ weight = (0, spotBalance_1.calculateAssetWeight)(worstCaseTokenAmount, spotMarketAccount, marginCategory);
1844
+ }
1845
+ const weightedValue = baseAssetValue
1846
+ .mul(weight)
1847
+ .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
1848
+ if (isLiability) {
1849
+ healthComponents.borrows.push({
1850
+ marketIndex: spotMarketAccount.marketIndex,
1851
+ size: worstCaseTokenAmount,
1852
+ value: baseAssetValue,
1853
+ weight: weight,
1854
+ weightedValue: weightedValue,
1855
+ });
1856
+ }
1857
+ else {
1858
+ healthComponents.deposits.push({
1859
+ marketIndex: spotMarketAccount.marketIndex,
1860
+ size: worstCaseTokenAmount,
1861
+ value: baseAssetValue,
1862
+ weight: weight,
1863
+ weightedValue: weightedValue,
1864
+ });
1865
+ }
1866
+ }
1867
+ if (!netQuoteValue.eq(numericConstants_1.ZERO)) {
1868
+ const spotMarketAccount = this.driftClient.getQuoteSpotMarketAccount();
1869
+ const oraclePriceData = this.getOracleDataForSpotMarket(numericConstants_1.QUOTE_SPOT_MARKET_INDEX);
1870
+ const baseAssetValue = (0, _1.getTokenValue)(netQuoteValue.abs(), spotMarketAccount.decimals, oraclePriceData);
1871
+ const isLiability = netQuoteValue.lt(numericConstants_1.ZERO);
1872
+ let weight;
1873
+ if (isLiability) {
1874
+ weight = (0, spotBalance_1.calculateLiabilityWeight)(netQuoteValue.abs(), spotMarketAccount, marginCategory);
1875
+ }
1876
+ else {
1877
+ weight = (0, spotBalance_1.calculateAssetWeight)(netQuoteValue, spotMarketAccount, marginCategory);
1878
+ }
1879
+ const weightedValue = baseAssetValue
1880
+ .mul(weight)
1881
+ .div(numericConstants_1.SPOT_MARKET_WEIGHT_PRECISION);
1882
+ if (isLiability) {
1883
+ healthComponents.borrows.push({
1884
+ marketIndex: spotMarketAccount.marketIndex,
1885
+ size: netQuoteValue,
1886
+ value: baseAssetValue,
1887
+ weight: weight,
1888
+ weightedValue: weightedValue,
1889
+ });
1890
+ }
1891
+ else {
1892
+ healthComponents.deposits.push({
1893
+ marketIndex: spotMarketAccount.marketIndex,
1894
+ size: netQuoteValue,
1895
+ value: baseAssetValue,
1896
+ weight: weight,
1897
+ weightedValue: weightedValue,
1898
+ });
1899
+ }
1900
+ }
1901
+ return healthComponents;
1902
+ }
1705
1903
  /**
1706
1904
  * Get the total position value, excluding any position coming from the given target market
1707
1905
  * @param marketToIgnore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.37.1-beta.1",
3
+ "version": "2.37.1-beta.10",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -55,7 +55,7 @@
55
55
  "eslint-plugin-prettier": "^3.4.0",
56
56
  "lodash": "^4.17.21",
57
57
  "mocha": "^10.0.0",
58
- "prettier": "^2.4.1",
58
+ "prettier": "^3.0.1",
59
59
  "ts-node": "^10.8.0",
60
60
  "typescript": "^4.9.5"
61
61
  },