@drift-labs/sdk 2.49.0-beta.8 → 2.52.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.
- package/VERSION +1 -1
- package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +2 -2
- package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +9 -6
- package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +0 -1
- package/lib/adminClient.d.ts +1 -0
- package/lib/adminClient.js +8 -0
- package/lib/constants/perpMarkets.js +40 -0
- package/lib/constants/spotMarkets.js +10 -0
- package/lib/decode/user.d.ts +3 -0
- package/lib/decode/user.js +329 -0
- package/lib/driftClient.d.ts +5 -1
- package/lib/driftClient.js +28 -7
- package/lib/examples/loadDlob.js +10 -5
- package/lib/idl/drift.json +32 -2
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/math/state.d.ts +5 -0
- package/lib/math/state.js +27 -0
- package/lib/math/superStake.d.ts +43 -0
- package/lib/math/superStake.js +64 -22
- package/lib/orderSubscriber/OrderSubscriber.d.ts +3 -0
- package/lib/orderSubscriber/OrderSubscriber.js +17 -3
- package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
- package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
- package/lib/orderSubscriber/types.d.ts +4 -1
- package/lib/types.d.ts +2 -1
- package/lib/user.d.ts +2 -1
- package/lib/user.js +13 -5
- package/lib/userMap/PollingSubscription.d.ts +15 -0
- package/lib/userMap/PollingSubscription.js +28 -0
- package/lib/userMap/WebsocketSubscription.d.ts +23 -0
- package/lib/userMap/WebsocketSubscription.js +41 -0
- package/lib/userMap/userMap.d.ts +16 -18
- package/lib/userMap/userMap.js +73 -34
- package/lib/userMap/userMapConfig.d.ts +21 -0
- package/lib/userMap/userMapConfig.js +2 -0
- package/package.json +2 -1
- package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +8 -6
- package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -1
- package/src/adminClient.ts +14 -0
- package/src/constants/perpMarkets.ts +40 -0
- package/src/constants/spotMarkets.ts +10 -0
- package/src/decode/user.ts +358 -0
- package/src/driftClient.ts +48 -7
- package/src/examples/loadDlob.ts +11 -6
- package/src/idl/drift.json +33 -3
- package/src/index.ts +2 -1
- package/src/math/state.ts +26 -0
- package/src/math/superStake.ts +108 -20
- package/src/orderSubscriber/OrderSubscriber.ts +33 -7
- package/src/orderSubscriber/WebsocketSubscription.ts +17 -16
- package/src/orderSubscriber/types.ts +15 -2
- package/src/types.ts +2 -1
- package/src/user.ts +19 -6
- package/src/userMap/PollingSubscription.ts +48 -0
- package/src/userMap/WebsocketSubscription.ts +76 -0
- package/src/userMap/userMap.ts +103 -70
- package/src/userMap/userMapConfig.ts +34 -0
- package/tests/amm/test.ts +6 -3
- package/tests/decode/test.ts +266 -0
- package/tests/decode/userAccountBufferStrings.ts +102 -0
- package/tests/dlob/helpers.ts +1 -0
- package/tests/dlob/test.ts +10 -8
- package/tests/user/helpers.ts +0 -1
package/src/math/superStake.ts
CHANGED
|
@@ -14,6 +14,38 @@ import { LAMPORTS_PRECISION, ZERO } from '../constants/numericConstants';
|
|
|
14
14
|
import fetch from 'node-fetch';
|
|
15
15
|
import { checkSameDate } from './utils';
|
|
16
16
|
|
|
17
|
+
export type BSOL_STATS_API_RESPONSE = {
|
|
18
|
+
success: boolean;
|
|
19
|
+
stats?: {
|
|
20
|
+
conversion: {
|
|
21
|
+
bsol_to_sol: number;
|
|
22
|
+
sol_to_bsol: number;
|
|
23
|
+
};
|
|
24
|
+
apy: {
|
|
25
|
+
base: number;
|
|
26
|
+
blze: number;
|
|
27
|
+
total: number;
|
|
28
|
+
lending: number;
|
|
29
|
+
liquidity: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type BSOL_EMISSIONS_API_RESPONSE = {
|
|
35
|
+
success: boolean;
|
|
36
|
+
emissions?: {
|
|
37
|
+
lend: number;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export async function fetchBSolMetrics() {
|
|
42
|
+
return await fetch('https://stake.solblaze.org/api/v1/stats');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function fetchBSolDriftEmissions() {
|
|
46
|
+
return await fetch('https://stake.solblaze.org/api/v1/drift_emissions');
|
|
47
|
+
}
|
|
48
|
+
|
|
17
49
|
export async function findBestSuperStakeIxs({
|
|
18
50
|
marketIndex,
|
|
19
51
|
amount,
|
|
@@ -56,6 +88,16 @@ export async function findBestSuperStakeIxs({
|
|
|
56
88
|
userAccountPublicKey,
|
|
57
89
|
onlyDirectRoutes,
|
|
58
90
|
});
|
|
91
|
+
} else if (marketIndex === 8) {
|
|
92
|
+
return findBestLstSuperStakeIxs({
|
|
93
|
+
amount,
|
|
94
|
+
lstMint: driftClient.getSpotMarketAccount(8).mint,
|
|
95
|
+
lstMarketIndex: 8,
|
|
96
|
+
jupiterClient,
|
|
97
|
+
driftClient,
|
|
98
|
+
userAccountPublicKey,
|
|
99
|
+
onlyDirectRoutes,
|
|
100
|
+
});
|
|
59
101
|
} else {
|
|
60
102
|
throw new Error(`Unsupported superstake market index: ${marketIndex}`);
|
|
61
103
|
}
|
|
@@ -153,16 +195,53 @@ export async function findBestJitoSolSuperStakeIxs({
|
|
|
153
195
|
lookupTables: AddressLookupTableAccount[];
|
|
154
196
|
method: 'jupiter' | 'marinade';
|
|
155
197
|
price: number;
|
|
198
|
+
}> {
|
|
199
|
+
return await findBestLstSuperStakeIxs({
|
|
200
|
+
amount,
|
|
201
|
+
jupiterClient,
|
|
202
|
+
driftClient,
|
|
203
|
+
userAccountPublicKey,
|
|
204
|
+
onlyDirectRoutes,
|
|
205
|
+
lstMint: driftClient.getSpotMarketAccount(6).mint,
|
|
206
|
+
lstMarketIndex: 6,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Finds best Jupiter Swap instructions for a generic lstMint
|
|
212
|
+
*
|
|
213
|
+
* Without doing any extra steps like checking if you can get a better rate by staking directly with that LST platform
|
|
214
|
+
*/
|
|
215
|
+
export async function findBestLstSuperStakeIxs({
|
|
216
|
+
amount,
|
|
217
|
+
lstMint,
|
|
218
|
+
jupiterClient,
|
|
219
|
+
driftClient,
|
|
220
|
+
userAccountPublicKey,
|
|
221
|
+
onlyDirectRoutes,
|
|
222
|
+
lstMarketIndex,
|
|
223
|
+
}: {
|
|
224
|
+
amount: BN;
|
|
225
|
+
lstMint: PublicKey;
|
|
226
|
+
lstMarketIndex: number;
|
|
227
|
+
jupiterClient: JupiterClient;
|
|
228
|
+
driftClient: DriftClient;
|
|
229
|
+
userAccountPublicKey?: PublicKey;
|
|
230
|
+
onlyDirectRoutes?: boolean;
|
|
231
|
+
}): Promise<{
|
|
232
|
+
ixs: TransactionInstruction[];
|
|
233
|
+
lookupTables: AddressLookupTableAccount[];
|
|
234
|
+
method: 'jupiter' | 'marinade';
|
|
235
|
+
price: number;
|
|
156
236
|
}> {
|
|
157
237
|
const solMint = driftClient.getSpotMarketAccount(1).mint;
|
|
158
|
-
const JitoSolMint = driftClient.getSpotMarketAccount(6).mint;
|
|
159
238
|
|
|
160
239
|
let jupiterPrice;
|
|
161
240
|
let bestRoute;
|
|
162
241
|
try {
|
|
163
242
|
const jupiterRoutes = await jupiterClient.getRoutes({
|
|
164
243
|
inputMint: solMint,
|
|
165
|
-
outputMint:
|
|
244
|
+
outputMint: lstMint,
|
|
166
245
|
amount,
|
|
167
246
|
onlyDirectRoutes,
|
|
168
247
|
});
|
|
@@ -176,7 +255,7 @@ export async function findBestJitoSolSuperStakeIxs({
|
|
|
176
255
|
|
|
177
256
|
const { ixs, lookupTables } = await driftClient.getJupiterSwapIx({
|
|
178
257
|
inMarketIndex: 1,
|
|
179
|
-
outMarketIndex:
|
|
258
|
+
outMarketIndex: lstMarketIndex,
|
|
180
259
|
route: bestRoute,
|
|
181
260
|
jupiterClient,
|
|
182
261
|
amount,
|
|
@@ -322,10 +401,27 @@ export async function calculateSolEarned({
|
|
|
322
401
|
}
|
|
323
402
|
};
|
|
324
403
|
|
|
404
|
+
const getBSolPrice = async (timestamps: number[]) => {
|
|
405
|
+
// Currently there's only one bSOL price, no timestamped data
|
|
406
|
+
// So just use the same price for every timestamp for now
|
|
407
|
+
const response = await fetchBSolMetrics();
|
|
408
|
+
if (response.status === 200) {
|
|
409
|
+
const data = (await response.json()) as BSOL_STATS_API_RESPONSE;
|
|
410
|
+
const bSolRatio = data?.stats?.conversion?.bsol_to_sol;
|
|
411
|
+
if (bSolRatio) {
|
|
412
|
+
timestamps.forEach((timestamp) => lstRatios.set(timestamp, bSolRatio));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// This block kind of assumes the record are all from the same market
|
|
418
|
+
// Otherwise the following code that checks the record.marketIndex would break
|
|
325
419
|
if (marketIndex === 2) {
|
|
326
420
|
await Promise.all(timestamps.map(getMsolPrice));
|
|
327
421
|
} else if (marketIndex === 6) {
|
|
328
422
|
lstRatios = await getJitoSolHistoricalPriceMap(timestamps);
|
|
423
|
+
} else if (marketIndex === 8) {
|
|
424
|
+
await getBSolPrice(timestamps);
|
|
329
425
|
}
|
|
330
426
|
|
|
331
427
|
let solEarned = ZERO;
|
|
@@ -336,23 +432,15 @@ export async function calculateSolEarned({
|
|
|
336
432
|
} else {
|
|
337
433
|
solEarned = solEarned.add(record.amount);
|
|
338
434
|
}
|
|
339
|
-
} else if (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
} else if (record.marketIndex === 6) {
|
|
350
|
-
const jitoSolRatio = lstRatios.get(record.ts.toNumber());
|
|
351
|
-
const jitoSolRatioBN = new BN(jitoSolRatio * LAMPORTS_PER_SOL);
|
|
352
|
-
|
|
353
|
-
const solAmount = record.amount
|
|
354
|
-
.mul(jitoSolRatioBN)
|
|
355
|
-
.div(LAMPORTS_PRECISION);
|
|
435
|
+
} else if (
|
|
436
|
+
record.marketIndex === 2 ||
|
|
437
|
+
record.marketIndex === 6 ||
|
|
438
|
+
record.marketIndex === 8
|
|
439
|
+
) {
|
|
440
|
+
const lstRatio = lstRatios.get(record.ts.toNumber());
|
|
441
|
+
const lstRatioBN = new BN(lstRatio * LAMPORTS_PER_SOL);
|
|
442
|
+
|
|
443
|
+
const solAmount = record.amount.mul(lstRatioBN).div(LAMPORTS_PRECISION);
|
|
356
444
|
if (isVariant(record.direction, 'deposit')) {
|
|
357
445
|
solEarned = solEarned.sub(solAmount);
|
|
358
446
|
} else {
|
|
@@ -10,6 +10,7 @@ import { WebsocketSubscription } from './WebsocketSubscription';
|
|
|
10
10
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
11
11
|
import { EventEmitter } from 'events';
|
|
12
12
|
import { BN } from '../index';
|
|
13
|
+
import { decodeUser } from '../decode/user';
|
|
13
14
|
|
|
14
15
|
export class OrderSubscriber {
|
|
15
16
|
driftClient: DriftClient;
|
|
@@ -22,6 +23,7 @@ export class OrderSubscriber {
|
|
|
22
23
|
fetchPromiseResolver: () => void;
|
|
23
24
|
|
|
24
25
|
mostRecentSlot: number;
|
|
26
|
+
decodeFn: (name: string, data: Buffer) => UserAccount;
|
|
25
27
|
|
|
26
28
|
constructor(config: OrderSubscriberConfig) {
|
|
27
29
|
this.driftClient = config.driftClient;
|
|
@@ -39,6 +41,14 @@ export class OrderSubscriber {
|
|
|
39
41
|
resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
|
|
40
42
|
});
|
|
41
43
|
}
|
|
44
|
+
if (config.fastDecode ?? true) {
|
|
45
|
+
this.decodeFn = (name, data) => decodeUser(data);
|
|
46
|
+
} else {
|
|
47
|
+
this.decodeFn =
|
|
48
|
+
this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
|
|
49
|
+
this.driftClient.program.account.user.coder.accounts
|
|
50
|
+
);
|
|
51
|
+
}
|
|
42
52
|
this.eventEmitter = new EventEmitter();
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -94,12 +104,16 @@ export class OrderSubscriber {
|
|
|
94
104
|
programAccount.account.data,
|
|
95
105
|
slot
|
|
96
106
|
);
|
|
107
|
+
// give event loop a chance to breathe
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
for (const key of this.usersAccounts.keys()) {
|
|
100
112
|
if (!programAccountSet.has(key)) {
|
|
101
113
|
this.usersAccounts.delete(key);
|
|
102
114
|
}
|
|
115
|
+
// give event loop a chance to breathe
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
103
117
|
}
|
|
104
118
|
} catch (e) {
|
|
105
119
|
console.error(e);
|
|
@@ -119,6 +133,13 @@ export class OrderSubscriber {
|
|
|
119
133
|
this.mostRecentSlot = slot;
|
|
120
134
|
}
|
|
121
135
|
|
|
136
|
+
this.eventEmitter.emit(
|
|
137
|
+
'updateReceived',
|
|
138
|
+
new PublicKey(key),
|
|
139
|
+
slot,
|
|
140
|
+
dataType
|
|
141
|
+
);
|
|
142
|
+
|
|
122
143
|
const slotAndUserAccount = this.usersAccounts.get(key);
|
|
123
144
|
if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
|
|
124
145
|
let userAccount: UserAccount;
|
|
@@ -139,15 +160,19 @@ export class OrderSubscriber {
|
|
|
139
160
|
return;
|
|
140
161
|
}
|
|
141
162
|
|
|
142
|
-
userAccount =
|
|
143
|
-
this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
|
|
144
|
-
'User',
|
|
145
|
-
buffer
|
|
146
|
-
) as UserAccount;
|
|
163
|
+
userAccount = this.decodeFn('User', buffer) as UserAccount;
|
|
147
164
|
} else {
|
|
148
165
|
userAccount = data as UserAccount;
|
|
149
166
|
}
|
|
150
167
|
|
|
168
|
+
this.eventEmitter.emit(
|
|
169
|
+
'userUpdated',
|
|
170
|
+
userAccount,
|
|
171
|
+
new PublicKey(key),
|
|
172
|
+
slot,
|
|
173
|
+
dataType
|
|
174
|
+
);
|
|
175
|
+
|
|
151
176
|
const newOrders = userAccount.orders.filter(
|
|
152
177
|
(order) =>
|
|
153
178
|
order.slot.toNumber() > (slotAndUserAccount?.slot ?? 0) &&
|
|
@@ -155,11 +180,12 @@ export class OrderSubscriber {
|
|
|
155
180
|
);
|
|
156
181
|
if (newOrders.length > 0) {
|
|
157
182
|
this.eventEmitter.emit(
|
|
158
|
-
'
|
|
183
|
+
'orderCreated',
|
|
159
184
|
userAccount,
|
|
160
185
|
newOrders,
|
|
161
186
|
new PublicKey(key),
|
|
162
|
-
slot
|
|
187
|
+
slot,
|
|
188
|
+
dataType
|
|
163
189
|
);
|
|
164
190
|
}
|
|
165
191
|
if (userAccount.hasOpenOrder) {
|
|
@@ -10,7 +10,7 @@ export class WebsocketSubscription {
|
|
|
10
10
|
private skipInitialLoad: boolean;
|
|
11
11
|
private resubTimeoutMs?: number;
|
|
12
12
|
|
|
13
|
-
private subscriber
|
|
13
|
+
private subscriber?: WebSocketProgramAccountSubscriber<UserAccount>;
|
|
14
14
|
|
|
15
15
|
constructor({
|
|
16
16
|
orderSubscriber,
|
|
@@ -30,22 +30,22 @@ export class WebsocketSubscription {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public async subscribe(): Promise<void> {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
'OrderSubscriber',
|
|
36
|
-
'User',
|
|
37
|
-
this.orderSubscriber.driftClient.program,
|
|
38
|
-
this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(
|
|
39
|
-
this.orderSubscriber.driftClient.program.account.user.coder.accounts
|
|
40
|
-
),
|
|
41
|
-
{
|
|
42
|
-
filters: [getUserFilter(), getNonIdleUserFilter()],
|
|
43
|
-
commitment: this.commitment,
|
|
44
|
-
},
|
|
45
|
-
this.resubTimeoutMs
|
|
46
|
-
);
|
|
33
|
+
if (this.subscriber) {
|
|
34
|
+
return;
|
|
47
35
|
}
|
|
48
36
|
|
|
37
|
+
this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
|
|
38
|
+
'OrderSubscriber',
|
|
39
|
+
'User',
|
|
40
|
+
this.orderSubscriber.driftClient.program,
|
|
41
|
+
this.orderSubscriber.decodeFn,
|
|
42
|
+
{
|
|
43
|
+
filters: [getUserFilter(), getNonIdleUserFilter()],
|
|
44
|
+
commitment: this.commitment,
|
|
45
|
+
},
|
|
46
|
+
this.resubTimeoutMs
|
|
47
|
+
);
|
|
48
|
+
|
|
49
49
|
await this.subscriber.subscribe(
|
|
50
50
|
(accountId: PublicKey, account: UserAccount, context: Context) => {
|
|
51
51
|
const userKey = accountId.toBase58();
|
|
@@ -65,6 +65,7 @@ export class WebsocketSubscription {
|
|
|
65
65
|
|
|
66
66
|
public async unsubscribe(): Promise<void> {
|
|
67
67
|
if (!this.subscriber) return;
|
|
68
|
-
this.subscriber.unsubscribe();
|
|
68
|
+
await this.subscriber.unsubscribe();
|
|
69
|
+
this.subscriber = undefined;
|
|
69
70
|
}
|
|
70
71
|
}
|
|
@@ -16,13 +16,26 @@ export type OrderSubscriberConfig = {
|
|
|
16
16
|
resubTimeoutMs?: number;
|
|
17
17
|
commitment?: Commitment;
|
|
18
18
|
};
|
|
19
|
+
fastDecode?: boolean;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export interface OrderSubscriberEvents {
|
|
22
|
-
|
|
23
|
+
orderCreated: (
|
|
23
24
|
account: UserAccount,
|
|
24
25
|
updatedOrders: Order[],
|
|
25
26
|
pubkey: PublicKey,
|
|
26
|
-
slot: number
|
|
27
|
+
slot: number,
|
|
28
|
+
dataType: 'raw' | 'decoded'
|
|
29
|
+
) => void;
|
|
30
|
+
userUpdated: (
|
|
31
|
+
account: UserAccount,
|
|
32
|
+
pubkey: PublicKey,
|
|
33
|
+
slot: number,
|
|
34
|
+
dataType: 'raw' | 'decoded'
|
|
35
|
+
) => void;
|
|
36
|
+
updateReceived: (
|
|
37
|
+
pubkey: PublicKey,
|
|
38
|
+
slot: number,
|
|
39
|
+
dataType: 'raw' | 'decoded'
|
|
27
40
|
) => void;
|
|
28
41
|
}
|
package/src/types.ts
CHANGED
|
@@ -552,6 +552,7 @@ export type StateAccount = {
|
|
|
552
552
|
lpCooldownTime: BN;
|
|
553
553
|
initialPctToLiquidate: number;
|
|
554
554
|
liquidationDuration: number;
|
|
555
|
+
maxInitializeUserFee: number;
|
|
555
556
|
};
|
|
556
557
|
|
|
557
558
|
export type PerpMarketAccount = {
|
|
@@ -869,8 +870,8 @@ export type Order = {
|
|
|
869
870
|
marketIndex: number;
|
|
870
871
|
price: BN;
|
|
871
872
|
baseAssetAmount: BN;
|
|
872
|
-
baseAssetAmountFilled: BN;
|
|
873
873
|
quoteAssetAmount: BN;
|
|
874
|
+
baseAssetAmountFilled: BN;
|
|
874
875
|
quoteAssetAmountFilled: BN;
|
|
875
876
|
direction: PositionDirection;
|
|
876
877
|
reduceOnly: boolean;
|
package/src/user.ts
CHANGED
|
@@ -422,7 +422,8 @@ export class User {
|
|
|
422
422
|
public getPerpPositionWithLPSettle(
|
|
423
423
|
marketIndex: number,
|
|
424
424
|
originalPosition?: PerpPosition,
|
|
425
|
-
burnLpShares = false
|
|
425
|
+
burnLpShares = false,
|
|
426
|
+
includeRemainderInBaseAmount = false
|
|
426
427
|
): [PerpPosition, BN, BN] {
|
|
427
428
|
originalPosition =
|
|
428
429
|
originalPosition ??
|
|
@@ -600,7 +601,16 @@ export class User {
|
|
|
600
601
|
position.lastCumulativeFundingRate = ZERO;
|
|
601
602
|
}
|
|
602
603
|
|
|
603
|
-
|
|
604
|
+
const remainderBeforeRemoval = new BN(position.remainderBaseAssetAmount);
|
|
605
|
+
|
|
606
|
+
if (includeRemainderInBaseAmount) {
|
|
607
|
+
position.baseAssetAmount = position.baseAssetAmount.add(
|
|
608
|
+
remainderBeforeRemoval
|
|
609
|
+
);
|
|
610
|
+
position.remainderBaseAssetAmount = 0;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return [position, remainderBeforeRemoval, pnl];
|
|
604
614
|
}
|
|
605
615
|
|
|
606
616
|
/**
|
|
@@ -1559,16 +1569,19 @@ export class User {
|
|
|
1559
1569
|
);
|
|
1560
1570
|
}
|
|
1561
1571
|
|
|
1572
|
+
getNetUsdValue(): BN {
|
|
1573
|
+
const netSpotValue = this.getNetSpotMarketValue();
|
|
1574
|
+
const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
|
|
1575
|
+
return netSpotValue.add(unrealizedPnl);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1562
1578
|
/**
|
|
1563
1579
|
* Calculates the all time P&L of the user.
|
|
1564
1580
|
*
|
|
1565
1581
|
* Net withdraws + Net spot market value + Net unrealized P&L -
|
|
1566
1582
|
*/
|
|
1567
1583
|
getTotalAllTimePnl(): BN {
|
|
1568
|
-
const
|
|
1569
|
-
const unrealizedPnl = this.getUnrealizedPNL(true, undefined, undefined);
|
|
1570
|
-
|
|
1571
|
-
const netUsdValue = netBankValue.add(unrealizedPnl);
|
|
1584
|
+
const netUsdValue = this.getNetUsdValue();
|
|
1572
1585
|
const totalDeposits = this.getUserAccount().totalDeposits;
|
|
1573
1586
|
const totalWithdraws = this.getUserAccount().totalWithdraws;
|
|
1574
1587
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { UserMap } from './userMap';
|
|
2
|
+
|
|
3
|
+
export class PollingSubscription {
|
|
4
|
+
private userMap: UserMap;
|
|
5
|
+
private frequency: number;
|
|
6
|
+
private skipInitialLoad: boolean;
|
|
7
|
+
|
|
8
|
+
intervalId?: ReturnType<typeof setTimeout>;
|
|
9
|
+
|
|
10
|
+
constructor({
|
|
11
|
+
userMap,
|
|
12
|
+
frequency,
|
|
13
|
+
skipInitialLoad = false,
|
|
14
|
+
}: {
|
|
15
|
+
userMap: UserMap;
|
|
16
|
+
frequency: number;
|
|
17
|
+
skipInitialLoad?: boolean;
|
|
18
|
+
includeIdle?: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
this.userMap = userMap;
|
|
21
|
+
this.frequency = frequency;
|
|
22
|
+
this.skipInitialLoad = skipInitialLoad;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async subscribe(): Promise<void> {
|
|
26
|
+
if (this.intervalId) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (this.frequency > 0) {
|
|
31
|
+
this.intervalId = setInterval(
|
|
32
|
+
this.userMap.sync.bind(this.userMap),
|
|
33
|
+
this.frequency
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!this.skipInitialLoad) {
|
|
38
|
+
await this.userMap.sync();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async unsubscribe(): Promise<void> {
|
|
43
|
+
if (this.intervalId) {
|
|
44
|
+
clearInterval(this.intervalId);
|
|
45
|
+
this.intervalId = undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { UserMap } from './userMap';
|
|
2
|
+
import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
|
|
3
|
+
import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
|
|
4
|
+
import { UserAccount } from '../types';
|
|
5
|
+
import { Commitment, Context, PublicKey } from '@solana/web3.js';
|
|
6
|
+
|
|
7
|
+
export class WebsocketSubscription {
|
|
8
|
+
private userMap: UserMap;
|
|
9
|
+
private commitment: Commitment;
|
|
10
|
+
private skipInitialLoad: boolean;
|
|
11
|
+
private resubTimeoutMs?: number;
|
|
12
|
+
private includeIdle?: boolean;
|
|
13
|
+
private decodeFn: (name: string, data: Buffer) => UserAccount;
|
|
14
|
+
|
|
15
|
+
private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
userMap,
|
|
19
|
+
commitment,
|
|
20
|
+
skipInitialLoad = false,
|
|
21
|
+
resubTimeoutMs,
|
|
22
|
+
includeIdle = false,
|
|
23
|
+
decodeFn,
|
|
24
|
+
}: {
|
|
25
|
+
userMap: UserMap;
|
|
26
|
+
commitment: Commitment;
|
|
27
|
+
skipInitialLoad?: boolean;
|
|
28
|
+
resubTimeoutMs?: number;
|
|
29
|
+
includeIdle?: boolean;
|
|
30
|
+
decodeFn: (name: string, data: Buffer) => UserAccount;
|
|
31
|
+
}) {
|
|
32
|
+
this.userMap = userMap;
|
|
33
|
+
this.commitment = commitment;
|
|
34
|
+
this.skipInitialLoad = skipInitialLoad;
|
|
35
|
+
this.resubTimeoutMs = resubTimeoutMs;
|
|
36
|
+
this.includeIdle = includeIdle || false;
|
|
37
|
+
this.decodeFn = decodeFn;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async subscribe(): Promise<void> {
|
|
41
|
+
if (!this.subscriber) {
|
|
42
|
+
const filters = [getUserFilter()];
|
|
43
|
+
if (!this.includeIdle) {
|
|
44
|
+
filters.push(getNonIdleUserFilter());
|
|
45
|
+
}
|
|
46
|
+
this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
|
|
47
|
+
'UserMap',
|
|
48
|
+
'User',
|
|
49
|
+
this.userMap.driftClient.program,
|
|
50
|
+
this.decodeFn,
|
|
51
|
+
{
|
|
52
|
+
filters,
|
|
53
|
+
commitment: this.commitment,
|
|
54
|
+
},
|
|
55
|
+
this.resubTimeoutMs
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await this.subscriber.subscribe(
|
|
60
|
+
(accountId: PublicKey, account: UserAccount, context: Context) => {
|
|
61
|
+
const userKey = accountId.toBase58();
|
|
62
|
+
this.userMap.updateUserAccount(userKey, account, context.slot);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (!this.skipInitialLoad) {
|
|
67
|
+
await this.userMap.sync();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async unsubscribe(): Promise<void> {
|
|
72
|
+
if (!this.subscriber) return;
|
|
73
|
+
await this.subscriber.unsubscribe();
|
|
74
|
+
this.subscriber = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|