@drift-labs/sdk 2.31.1-beta.2 → 2.31.1-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.31.1-beta.2
1
+ 2.31.1-beta.4
@@ -0,0 +1,23 @@
1
+ /// <reference types="node" />
2
+ import { DataAndSlot, UserAccountEvents, UserAccountSubscriber } from './types';
3
+ import { PublicKey } from '@solana/web3.js';
4
+ import StrictEventEmitter from 'strict-event-emitter-types';
5
+ import { EventEmitter } from 'events';
6
+ import { UserAccount } from '../types';
7
+ export declare class MockUserAccountSubscriber implements UserAccountSubscriber {
8
+ isSubscribed: boolean;
9
+ eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
10
+ userAccountPublicKey: PublicKey;
11
+ callbackId?: string;
12
+ errorCallbackId?: string;
13
+ user: DataAndSlot<UserAccount>;
14
+ constructor(userAccountPublicKey: PublicKey, data: UserAccount, slot: number);
15
+ subscribe(_userAccount?: UserAccount): Promise<boolean>;
16
+ addToAccountLoader(): Promise<void>;
17
+ fetch(): Promise<void>;
18
+ doesAccountExist(): boolean;
19
+ unsubscribe(): Promise<void>;
20
+ assertIsSubscribed(): void;
21
+ getUserAccountAndSlot(): DataAndSlot<UserAccount>;
22
+ updateData(userAccount: UserAccount, slot: number): void;
23
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MockUserAccountSubscriber = void 0;
4
+ const events_1 = require("events");
5
+ class MockUserAccountSubscriber {
6
+ constructor(userAccountPublicKey, data, slot) {
7
+ this.isSubscribed = true;
8
+ this.eventEmitter = new events_1.EventEmitter();
9
+ this.userAccountPublicKey = userAccountPublicKey;
10
+ this.user = { data, slot };
11
+ }
12
+ async subscribe(_userAccount) {
13
+ return true;
14
+ }
15
+ async addToAccountLoader() { }
16
+ async fetch() { }
17
+ doesAccountExist() {
18
+ return this.user !== undefined;
19
+ }
20
+ async unsubscribe() { }
21
+ assertIsSubscribed() { }
22
+ getUserAccountAndSlot() {
23
+ return this.user;
24
+ }
25
+ updateData(userAccount, slot) {
26
+ this.user = { data: userAccount, slot };
27
+ this.eventEmitter.emit('userAccountUpdate', userAccount);
28
+ this.eventEmitter.emit('update');
29
+ }
30
+ }
31
+ exports.MockUserAccountSubscriber = MockUserAccountSubscriber;
@@ -14,6 +14,7 @@ import { User } from './user';
14
14
  import { UserSubscriptionConfig } from './userConfig';
15
15
  import { UserStats } from './userStats';
16
16
  import { JupiterClient, Route, SwapMode } from './jupiter/jupiterClient';
17
+ import { UserStatsSubscriptionConfig } from './userStatsConfig';
17
18
  type RemainingAccountParams = {
18
19
  userAccounts: UserAccount[];
19
20
  writablePerpMarketIndexes?: number[];
@@ -36,6 +37,7 @@ export declare class DriftClient {
36
37
  userStats?: UserStats;
37
38
  activeSubAccountId: number;
38
39
  userAccountSubscriptionConfig: UserSubscriptionConfig;
40
+ userStatsAccountSubscriptionConfig: UserStatsSubscriptionConfig;
39
41
  accountSubscriber: DriftClientAccountSubscriber;
40
42
  eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
41
43
  _isSubscribed: boolean;
@@ -93,15 +93,24 @@ class DriftClient {
93
93
  ? new Map([[this.authority.toString(), config.subAccountIds]])
94
94
  : new Map();
95
95
  this.includeDelegates = (_f = config.includeDelegates) !== null && _f !== void 0 ? _f : false;
96
- this.userAccountSubscriptionConfig =
97
- ((_g = config.accountSubscription) === null || _g === void 0 ? void 0 : _g.type) === 'polling'
98
- ? {
99
- type: 'polling',
100
- accountLoader: config.accountSubscription.accountLoader,
101
- }
102
- : {
103
- type: 'websocket',
104
- };
96
+ if (((_g = config.accountSubscription) === null || _g === void 0 ? void 0 : _g.type) === 'polling') {
97
+ this.userAccountSubscriptionConfig = {
98
+ type: 'polling',
99
+ accountLoader: config.accountSubscription.accountLoader,
100
+ };
101
+ this.userStatsAccountSubscriptionConfig = {
102
+ type: 'polling',
103
+ accountLoader: config.accountSubscription.accountLoader,
104
+ };
105
+ }
106
+ else {
107
+ this.userAccountSubscriptionConfig = {
108
+ type: 'websocket',
109
+ };
110
+ this.userStatsAccountSubscriptionConfig = {
111
+ type: 'websocket',
112
+ };
113
+ }
105
114
  if (config.userStats) {
106
115
  this.userStats = new userStats_1.UserStats({
107
116
  driftClient: this,
@@ -304,7 +313,7 @@ class DriftClient {
304
313
  this.userStats = new userStats_1.UserStats({
305
314
  driftClient: this,
306
315
  userStatsAccountPublicKey: this.getUserStatsAccountPublicKey(),
307
- accountSubscription: this.userAccountSubscriptionConfig,
316
+ accountSubscription: this.userStatsAccountSubscriptionConfig,
308
317
  });
309
318
  await this.userStats.subscribe();
310
319
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.31.1-beta.1",
2
+ "version": "2.31.1-beta.3",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -0,0 +1,4 @@
1
+ import { PerpMarketAccount, SpotMarketAccount } from '../types';
2
+ export declare function getPerpMarketTierNumber(perpMarket: PerpMarketAccount): number;
3
+ export declare function getSpotMarketTierNumber(spotMarket: SpotMarketAccount): number;
4
+ export declare function perpTierIsAsSafeAs(perpTier: number, otherPerpTier: number, otherSpotTier: number): boolean;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.perpTierIsAsSafeAs = exports.getSpotMarketTierNumber = exports.getPerpMarketTierNumber = void 0;
4
+ const types_1 = require("../types");
5
+ function getPerpMarketTierNumber(perpMarket) {
6
+ if ((0, types_1.isVariant)(perpMarket.contractTier, 'a')) {
7
+ return 0;
8
+ }
9
+ else if ((0, types_1.isVariant)(perpMarket.contractTier, 'b')) {
10
+ return 1;
11
+ }
12
+ else if ((0, types_1.isVariant)(perpMarket.contractTier, 'c')) {
13
+ return 2;
14
+ }
15
+ else if ((0, types_1.isVariant)(perpMarket.contractTier, 'speculative')) {
16
+ return 3;
17
+ }
18
+ else if ((0, types_1.isVariant)(perpMarket.contractTier, 'isolated')) {
19
+ return 4;
20
+ }
21
+ else {
22
+ return 5;
23
+ }
24
+ }
25
+ exports.getPerpMarketTierNumber = getPerpMarketTierNumber;
26
+ function getSpotMarketTierNumber(spotMarket) {
27
+ if ((0, types_1.isVariant)(spotMarket.assetTier, 'collateral')) {
28
+ return 0;
29
+ }
30
+ else if ((0, types_1.isVariant)(spotMarket.assetTier, 'protected')) {
31
+ return 1;
32
+ }
33
+ else if ((0, types_1.isVariant)(spotMarket.assetTier, 'cross')) {
34
+ return 2;
35
+ }
36
+ else if ((0, types_1.isVariant)(spotMarket.assetTier, 'isolated')) {
37
+ return 3;
38
+ }
39
+ else if ((0, types_1.isVariant)(spotMarket.assetTier, 'unlisted')) {
40
+ return 4;
41
+ }
42
+ else {
43
+ return 5;
44
+ }
45
+ }
46
+ exports.getSpotMarketTierNumber = getSpotMarketTierNumber;
47
+ function perpTierIsAsSafeAs(perpTier, otherPerpTier, otherSpotTier) {
48
+ const asSafeAsPerp = perpTier <= otherPerpTier;
49
+ const asSafeAsSpot = otherSpotTier === 4 || (otherSpotTier >= 2 && perpTier <= 2);
50
+ return asSafeAsSpot && asSafeAsPerp;
51
+ }
52
+ exports.perpTierIsAsSafeAs = perpTierIsAsSafeAs;
package/lib/user.d.ts CHANGED
@@ -107,6 +107,7 @@ export declare class User {
107
107
  */
108
108
  getMaintenanceMarginRequirement(liquidationBuffer?: BN): BN;
109
109
  getActivePerpPositions(): PerpPosition[];
110
+ getActiveSpotPositions(): SpotPosition[];
110
111
  /**
111
112
  * calculates unrealized position price pnl
112
113
  * @returns : Precision QUOTE_PRECISION
@@ -185,7 +186,11 @@ export declare class User {
185
186
  * @returns : Precision TEN_THOUSAND
186
187
  */
187
188
  getMarginRatio(): BN;
188
- canBeLiquidated(): boolean;
189
+ canBeLiquidated(): {
190
+ canBeLiquidated: boolean;
191
+ marginRequirement: BN;
192
+ totalCollateral: BN;
193
+ };
189
194
  isBeingLiquidated(): boolean;
190
195
  isBankrupt(): boolean;
191
196
  /**
@@ -277,6 +282,10 @@ export declare class User {
277
282
  maxDepositAmount: BN;
278
283
  };
279
284
  canMakeIdle(slot: BN, slotsBeforeIdle: BN): boolean;
285
+ getSafestTiers(): {
286
+ perpTier: number;
287
+ spotTier: number;
288
+ };
280
289
  /**
281
290
  * Get the total position value, excluding any position coming from the given target market
282
291
  * @param marketToIgnore
package/lib/user.js CHANGED
@@ -12,6 +12,7 @@ const pollingUserAccountSubscriber_1 = require("./accounts/pollingUserAccountSub
12
12
  const webSocketUserAccountSubscriber_1 = require("./accounts/webSocketUserAccountSubscriber");
13
13
  const spotPosition_1 = require("./math/spotPosition");
14
14
  const oracles_1 = require("./math/oracles");
15
+ const tiers_1 = require("./math/tiers");
15
16
  class User {
16
17
  get isSubscribed() {
17
18
  return this._isSubscribed && this.accountSubscriber.isSubscribed;
@@ -20,13 +21,16 @@ class User {
20
21
  this._isSubscribed = val;
21
22
  }
22
23
  constructor(config) {
23
- var _a;
24
+ var _a, _b;
24
25
  this._isSubscribed = false;
25
26
  this.driftClient = config.driftClient;
26
27
  this.userAccountPublicKey = config.userAccountPublicKey;
27
28
  if (((_a = config.accountSubscription) === null || _a === void 0 ? void 0 : _a.type) === 'polling') {
28
29
  this.accountSubscriber = new pollingUserAccountSubscriber_1.PollingUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.accountLoader);
29
30
  }
31
+ else if (((_b = config.accountSubscription) === null || _b === void 0 ? void 0 : _b.type) === 'custom') {
32
+ this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
33
+ }
30
34
  else {
31
35
  this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey);
32
36
  }
@@ -305,6 +309,9 @@ class User {
305
309
  !(pos.openOrders == 0) ||
306
310
  !pos.lpShares.eq(numericConstants_1.ZERO));
307
311
  }
312
+ getActiveSpotPositions() {
313
+ return this.getUserAccount().spotPositions.filter((pos) => !(0, spotPosition_1.isSpotPositionAvailable)(pos));
314
+ }
308
315
  /**
309
316
  * calculates unrealized position price pnl
310
317
  * @returns : Precision QUOTE_PRECISION
@@ -775,12 +782,16 @@ class User {
775
782
  const totalCollateral = this.getTotalCollateral('Maintenance');
776
783
  // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
777
784
  let liquidationBuffer = undefined;
778
- const isBeingLiquidated = (0, types_1.isVariant)(this.getUserAccount().status, 'beingLiquidated');
779
- if (isBeingLiquidated) {
785
+ if (this.isBeingLiquidated()) {
780
786
  liquidationBuffer = new _1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio);
781
787
  }
782
- const maintenanceRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
783
- return totalCollateral.lt(maintenanceRequirement);
788
+ const marginRequirement = this.getMaintenanceMarginRequirement(liquidationBuffer);
789
+ const canBeLiquidated = totalCollateral.lt(marginRequirement);
790
+ return {
791
+ canBeLiquidated,
792
+ marginRequirement,
793
+ totalCollateral,
794
+ };
784
795
  }
785
796
  isBeingLiquidated() {
786
797
  return (0, types_1.isOneOfVariant)(this.getUserAccount().status, [
@@ -1281,6 +1292,23 @@ class User {
1281
1292
  }
1282
1293
  return true;
1283
1294
  }
1295
+ getSafestTiers() {
1296
+ let safestPerpTier = 4;
1297
+ let safestSpotTier = 4;
1298
+ for (const perpPosition of this.getActivePerpPositions()) {
1299
+ safestPerpTier = Math.min(safestPerpTier, (0, tiers_1.getPerpMarketTierNumber)(this.driftClient.getPerpMarketAccount(perpPosition.marketIndex)));
1300
+ }
1301
+ for (const spotPosition of this.getActiveSpotPositions()) {
1302
+ if ((0, types_1.isVariant)(spotPosition.balanceType, 'deposit')) {
1303
+ continue;
1304
+ }
1305
+ safestSpotTier = Math.min(safestSpotTier, (0, tiers_1.getSpotMarketTierNumber)(this.driftClient.getSpotMarketAccount(spotPosition.marketIndex)));
1306
+ }
1307
+ return {
1308
+ perpTier: safestPerpTier,
1309
+ spotTier: safestSpotTier,
1310
+ };
1311
+ }
1284
1312
  /**
1285
1313
  * Get the total position value, excluding any position coming from the given target market
1286
1314
  * @param marketToIgnore
@@ -1,6 +1,7 @@
1
1
  import { DriftClient } from './driftClient';
2
2
  import { PublicKey } from '@solana/web3.js';
3
3
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4
+ import { UserAccountSubscriber } from './accounts/types';
4
5
  export type UserConfig = {
5
6
  accountSubscription?: UserSubscriptionConfig;
6
7
  driftClient: DriftClient;
@@ -11,4 +12,7 @@ export type UserSubscriptionConfig = {
11
12
  } | {
12
13
  type: 'polling';
13
14
  accountLoader: BulkAccountLoader;
15
+ } | {
16
+ type: 'custom';
17
+ userAccountSubscriber: UserAccountSubscriber;
14
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.31.1-beta.2",
3
+ "version": "2.31.1-beta.4",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -0,0 +1,53 @@
1
+ import { DataAndSlot, UserAccountEvents, UserAccountSubscriber } from './types';
2
+ import { PublicKey } from '@solana/web3.js';
3
+ import StrictEventEmitter from 'strict-event-emitter-types';
4
+ import { EventEmitter } from 'events';
5
+ import { UserAccount } from '../types';
6
+
7
+ export class MockUserAccountSubscriber implements UserAccountSubscriber {
8
+ isSubscribed: boolean;
9
+ eventEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents>;
10
+ userAccountPublicKey: PublicKey;
11
+
12
+ callbackId?: string;
13
+ errorCallbackId?: string;
14
+
15
+ user: DataAndSlot<UserAccount>;
16
+
17
+ public constructor(
18
+ userAccountPublicKey: PublicKey,
19
+ data: UserAccount,
20
+ slot: number
21
+ ) {
22
+ this.isSubscribed = true;
23
+ this.eventEmitter = new EventEmitter();
24
+ this.userAccountPublicKey = userAccountPublicKey;
25
+ this.user = { data, slot };
26
+ }
27
+
28
+ async subscribe(_userAccount?: UserAccount): Promise<boolean> {
29
+ return true;
30
+ }
31
+
32
+ async addToAccountLoader(): Promise<void> {}
33
+
34
+ async fetch(): Promise<void> {}
35
+
36
+ doesAccountExist(): boolean {
37
+ return this.user !== undefined;
38
+ }
39
+
40
+ async unsubscribe(): Promise<void> {}
41
+
42
+ assertIsSubscribed(): void {}
43
+
44
+ public getUserAccountAndSlot(): DataAndSlot<UserAccount> {
45
+ return this.user;
46
+ }
47
+
48
+ public updateData(userAccount: UserAccount, slot: number): void {
49
+ this.user = { data: userAccount, slot };
50
+ this.eventEmitter.emit('userAccountUpdate', userAccount);
51
+ this.eventEmitter.emit('update');
52
+ }
53
+ }
@@ -115,6 +115,7 @@ import { fetchUserStatsAccount } from './accounts/fetch';
115
115
  import { castNumberToSpotPrecision } from './math/spotMarket';
116
116
  import { JupiterClient, Route, SwapMode } from './jupiter/jupiterClient';
117
117
  import { getNonIdleUserFilter } from './memcmp';
118
+ import { UserStatsSubscriptionConfig } from './userStatsConfig';
118
119
 
119
120
  type RemainingAccountParams = {
120
121
  userAccounts: UserAccount[];
@@ -139,6 +140,7 @@ export class DriftClient {
139
140
  userStats?: UserStats;
140
141
  activeSubAccountId: number;
141
142
  userAccountSubscriptionConfig: UserSubscriptionConfig;
143
+ userStatsAccountSubscriptionConfig: UserStatsSubscriptionConfig;
142
144
  accountSubscriber: DriftClientAccountSubscriber;
143
145
  eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
144
146
  _isSubscribed = false;
@@ -206,15 +208,23 @@ export class DriftClient {
206
208
  : new Map<string, number[]>();
207
209
 
208
210
  this.includeDelegates = config.includeDelegates ?? false;
209
- this.userAccountSubscriptionConfig =
210
- config.accountSubscription?.type === 'polling'
211
- ? {
212
- type: 'polling',
213
- accountLoader: config.accountSubscription.accountLoader,
214
- }
215
- : {
216
- type: 'websocket',
217
- };
211
+ if (config.accountSubscription?.type === 'polling') {
212
+ this.userAccountSubscriptionConfig = {
213
+ type: 'polling',
214
+ accountLoader: config.accountSubscription.accountLoader,
215
+ };
216
+ this.userStatsAccountSubscriptionConfig = {
217
+ type: 'polling',
218
+ accountLoader: config.accountSubscription.accountLoader,
219
+ };
220
+ } else {
221
+ this.userAccountSubscriptionConfig = {
222
+ type: 'websocket',
223
+ };
224
+ this.userStatsAccountSubscriptionConfig = {
225
+ type: 'websocket',
226
+ };
227
+ }
218
228
 
219
229
  if (config.userStats) {
220
230
  this.userStats = new UserStats({
@@ -542,7 +552,7 @@ export class DriftClient {
542
552
  this.userStats = new UserStats({
543
553
  driftClient: this,
544
554
  userStatsAccountPublicKey: this.getUserStatsAccountPublicKey(),
545
- accountSubscription: this.userAccountSubscriptionConfig,
555
+ accountSubscription: this.userStatsAccountSubscriptionConfig,
546
556
  });
547
557
 
548
558
  await this.userStats.subscribe();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.31.1-beta.2",
2
+ "version": "2.31.1-beta.4",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -0,0 +1,44 @@
1
+ import { isVariant, PerpMarketAccount, SpotMarketAccount } from '../types';
2
+
3
+ export function getPerpMarketTierNumber(perpMarket: PerpMarketAccount): number {
4
+ if (isVariant(perpMarket.contractTier, 'a')) {
5
+ return 0;
6
+ } else if (isVariant(perpMarket.contractTier, 'b')) {
7
+ return 1;
8
+ } else if (isVariant(perpMarket.contractTier, 'c')) {
9
+ return 2;
10
+ } else if (isVariant(perpMarket.contractTier, 'speculative')) {
11
+ return 3;
12
+ } else if (isVariant(perpMarket.contractTier, 'isolated')) {
13
+ return 4;
14
+ } else {
15
+ return 5;
16
+ }
17
+ }
18
+
19
+ export function getSpotMarketTierNumber(spotMarket: SpotMarketAccount): number {
20
+ if (isVariant(spotMarket.assetTier, 'collateral')) {
21
+ return 0;
22
+ } else if (isVariant(spotMarket.assetTier, 'protected')) {
23
+ return 1;
24
+ } else if (isVariant(spotMarket.assetTier, 'cross')) {
25
+ return 2;
26
+ } else if (isVariant(spotMarket.assetTier, 'isolated')) {
27
+ return 3;
28
+ } else if (isVariant(spotMarket.assetTier, 'unlisted')) {
29
+ return 4;
30
+ } else {
31
+ return 5;
32
+ }
33
+ }
34
+
35
+ export function perpTierIsAsSafeAs(
36
+ perpTier: number,
37
+ otherPerpTier: number,
38
+ otherSpotTier: number
39
+ ): boolean {
40
+ const asSafeAsPerp = perpTier <= otherPerpTier;
41
+ const asSafeAsSpot =
42
+ otherSpotTier === 4 || (otherSpotTier >= 2 && perpTier <= 2);
43
+ return asSafeAsSpot && asSafeAsPerp;
44
+ }
package/src/user.ts CHANGED
@@ -72,6 +72,7 @@ import {
72
72
  } from './math/spotPosition';
73
73
 
74
74
  import { calculateLiveOracleTwap } from './math/oracles';
75
+ import { getPerpMarketTierNumber, getSpotMarketTierNumber } from './math/tiers';
75
76
 
76
77
  export class User {
77
78
  driftClient: DriftClient;
@@ -97,6 +98,8 @@ export class User {
97
98
  config.userAccountPublicKey,
98
99
  config.accountSubscription.accountLoader
99
100
  );
101
+ } else if (config.accountSubscription?.type === 'custom') {
102
+ this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
100
103
  } else {
101
104
  this.accountSubscriber = new WebSocketUserAccountSubscriber(
102
105
  config.driftClient.program,
@@ -490,6 +493,12 @@ export class User {
490
493
  );
491
494
  }
492
495
 
496
+ public getActiveSpotPositions(): SpotPosition[] {
497
+ return this.getUserAccount().spotPositions.filter(
498
+ (pos) => !isSpotPositionAvailable(pos)
499
+ );
500
+ }
501
+
493
502
  /**
494
503
  * calculates unrealized position price pnl
495
504
  * @returns : Precision QUOTE_PRECISION
@@ -1435,24 +1444,29 @@ export class User {
1435
1444
  return netAssetValue.mul(TEN_THOUSAND).div(totalLiabilityValue);
1436
1445
  }
1437
1446
 
1438
- public canBeLiquidated(): boolean {
1447
+ public canBeLiquidated(): {
1448
+ canBeLiquidated: boolean;
1449
+ marginRequirement: BN;
1450
+ totalCollateral: BN;
1451
+ } {
1439
1452
  const totalCollateral = this.getTotalCollateral('Maintenance');
1440
1453
 
1441
1454
  // if user being liq'd, can continue to be liq'd until total collateral above the margin requirement plus buffer
1442
1455
  let liquidationBuffer = undefined;
1443
- const isBeingLiquidated = isVariant(
1444
- this.getUserAccount().status,
1445
- 'beingLiquidated'
1446
- );
1447
-
1448
- if (isBeingLiquidated) {
1456
+ if (this.isBeingLiquidated()) {
1449
1457
  liquidationBuffer = new BN(
1450
1458
  this.driftClient.getStateAccount().liquidationMarginBufferRatio
1451
1459
  );
1452
1460
  }
1453
- const maintenanceRequirement =
1461
+ const marginRequirement =
1454
1462
  this.getMaintenanceMarginRequirement(liquidationBuffer);
1455
- return totalCollateral.lt(maintenanceRequirement);
1463
+ const canBeLiquidated = totalCollateral.lt(marginRequirement);
1464
+
1465
+ return {
1466
+ canBeLiquidated,
1467
+ marginRequirement,
1468
+ totalCollateral,
1469
+ };
1456
1470
  }
1457
1471
 
1458
1472
  public isBeingLiquidated(): boolean {
@@ -2304,6 +2318,38 @@ export class User {
2304
2318
  return true;
2305
2319
  }
2306
2320
 
2321
+ public getSafestTiers(): { perpTier: number; spotTier: number } {
2322
+ let safestPerpTier = 4;
2323
+ let safestSpotTier = 4;
2324
+
2325
+ for (const perpPosition of this.getActivePerpPositions()) {
2326
+ safestPerpTier = Math.min(
2327
+ safestPerpTier,
2328
+ getPerpMarketTierNumber(
2329
+ this.driftClient.getPerpMarketAccount(perpPosition.marketIndex)
2330
+ )
2331
+ );
2332
+ }
2333
+
2334
+ for (const spotPosition of this.getActiveSpotPositions()) {
2335
+ if (isVariant(spotPosition.balanceType, 'deposit')) {
2336
+ continue;
2337
+ }
2338
+
2339
+ safestSpotTier = Math.min(
2340
+ safestSpotTier,
2341
+ getSpotMarketTierNumber(
2342
+ this.driftClient.getSpotMarketAccount(spotPosition.marketIndex)
2343
+ )
2344
+ );
2345
+ }
2346
+
2347
+ return {
2348
+ perpTier: safestPerpTier,
2349
+ spotTier: safestSpotTier,
2350
+ };
2351
+ }
2352
+
2307
2353
  /**
2308
2354
  * Get the total position value, excluding any position coming from the given target market
2309
2355
  * @param marketToIgnore
@@ -2345,6 +2391,7 @@ export class User {
2345
2391
 
2346
2392
  return oracleData;
2347
2393
  }
2394
+
2348
2395
  private getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
2349
2396
  const oracleKey = this.driftClient.getSpotMarketAccount(marketIndex).oracle;
2350
2397
 
package/src/userConfig.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DriftClient } from './driftClient';
2
2
  import { PublicKey } from '@solana/web3.js';
3
3
  import { BulkAccountLoader } from './accounts/bulkAccountLoader';
4
+ import { UserAccountSubscriber } from './accounts/types';
4
5
 
5
6
  export type UserConfig = {
6
7
  accountSubscription?: UserSubscriptionConfig;
@@ -15,4 +16,8 @@ export type UserSubscriptionConfig =
15
16
  | {
16
17
  type: 'polling';
17
18
  accountLoader: BulkAccountLoader;
19
+ }
20
+ | {
21
+ type: 'custom';
22
+ userAccountSubscriber: UserAccountSubscriber;
18
23
  };