@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 +1 -1
- package/lib/accounts/mockUserAccountSubscriber.d.ts +23 -0
- package/lib/accounts/mockUserAccountSubscriber.js +31 -0
- package/lib/driftClient.d.ts +2 -0
- package/lib/driftClient.js +19 -10
- package/lib/idl/drift.json +1 -1
- package/lib/math/tiers.d.ts +4 -0
- package/lib/math/tiers.js +52 -0
- package/lib/user.d.ts +10 -1
- package/lib/user.js +33 -5
- package/lib/userConfig.d.ts +4 -0
- package/package.json +1 -1
- package/src/accounts/mockUserAccountSubscriber.ts +53 -0
- package/src/driftClient.ts +20 -10
- package/src/idl/drift.json +1 -1
- package/src/math/tiers.ts +44 -0
- package/src/user.ts +56 -9
- package/src/userConfig.ts +5 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.31.1-beta.
|
|
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;
|
package/lib/driftClient.d.ts
CHANGED
|
@@ -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;
|
package/lib/driftClient.js
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
:
|
|
103
|
-
|
|
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.
|
|
316
|
+
accountSubscription: this.userStatsAccountSubscriptionConfig,
|
|
308
317
|
});
|
|
309
318
|
await this.userStats.subscribe();
|
|
310
319
|
}
|
package/lib/idl/drift.json
CHANGED
|
@@ -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():
|
|
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
|
-
|
|
779
|
-
if (isBeingLiquidated) {
|
|
785
|
+
if (this.isBeingLiquidated()) {
|
|
780
786
|
liquidationBuffer = new _1.BN(this.driftClient.getStateAccount().liquidationMarginBufferRatio);
|
|
781
787
|
}
|
|
782
|
-
const
|
|
783
|
-
|
|
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
|
package/lib/userConfig.d.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
|
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
|
@@ -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
|
+
}
|
package/src/driftClient.ts
CHANGED
|
@@ -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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
:
|
|
216
|
-
|
|
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.
|
|
555
|
+
accountSubscription: this.userStatsAccountSubscriptionConfig,
|
|
546
556
|
});
|
|
547
557
|
|
|
548
558
|
await this.userStats.subscribe();
|
package/src/idl/drift.json
CHANGED
|
@@ -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():
|
|
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
|
-
|
|
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
|
|
1461
|
+
const marginRequirement =
|
|
1454
1462
|
this.getMaintenanceMarginRequirement(liquidationBuffer);
|
|
1455
|
-
|
|
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
|
};
|