@drift-labs/sdk 2.136.0-beta.0 → 2.136.0-beta.2
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/browser/accounts/types.d.ts +2 -0
- package/lib/browser/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
- package/lib/browser/accounts/webSocketAccountSubscriberV2.js +211 -39
- package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +87 -0
- package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
- package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.d.ts +145 -0
- package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
- package/lib/browser/accounts/websocketProgramUserAccountSubscriber.d.ts +22 -0
- package/lib/browser/accounts/websocketProgramUserAccountSubscriber.js +54 -0
- package/lib/browser/driftClient.js +22 -18
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/factory/bigNum.d.ts +2 -2
- package/lib/browser/factory/bigNum.js +20 -5
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/memcmp.d.ts +2 -0
- package/lib/browser/memcmp.js +19 -1
- package/lib/browser/oracles/oracleId.d.ts +5 -0
- package/lib/browser/oracles/oracleId.js +46 -1
- package/lib/browser/user.js +12 -5
- package/lib/browser/userConfig.d.ts +3 -0
- package/lib/node/accounts/types.d.ts +2 -0
- package/lib/node/accounts/types.d.ts.map +1 -1
- package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
- package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketAccountSubscriberV2.js +211 -39
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +88 -0
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts +146 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts +23 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts.map +1 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.js +54 -0
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +22 -18
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/factory/bigNum.d.ts +2 -2
- package/lib/node/factory/bigNum.d.ts.map +1 -1
- package/lib/node/factory/bigNum.js +20 -5
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/memcmp.d.ts +2 -0
- package/lib/node/memcmp.d.ts.map +1 -1
- package/lib/node/memcmp.js +19 -1
- package/lib/node/oracles/oracleId.d.ts +5 -0
- package/lib/node/oracles/oracleId.d.ts.map +1 -1
- package/lib/node/oracles/oracleId.js +46 -1
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +12 -5
- package/lib/node/userConfig.d.ts +3 -0
- package/lib/node/userConfig.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/accounts/README_WebSocketAccountSubscriberV2.md +41 -0
- package/src/accounts/types.ts +3 -0
- package/src/accounts/webSocketAccountSubscriberV2.ts +243 -42
- package/src/accounts/webSocketDriftClientAccountSubscriberV2.ts +745 -0
- package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +995 -0
- package/src/accounts/websocketProgramUserAccountSubscriber.ts +94 -0
- package/src/driftClient.ts +13 -7
- package/src/driftClientConfig.ts +15 -8
- package/src/factory/bigNum.ts +22 -5
- package/src/index.ts +4 -0
- package/src/memcmp.ts +17 -0
- package/src/oracles/oracleId.ts +34 -0
- package/src/user.ts +21 -9
- package/src/userConfig.ts +3 -0
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -53
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -54
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +0 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -596
|
@@ -0,0 +1,745 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AccountSubscriber,
|
|
3
|
+
DataAndSlot,
|
|
4
|
+
DelistedMarketSetting,
|
|
5
|
+
DriftClientAccountEvents,
|
|
6
|
+
DriftClientAccountSubscriber,
|
|
7
|
+
NotSubscribedError,
|
|
8
|
+
ResubOpts,
|
|
9
|
+
} from './types';
|
|
10
|
+
import {
|
|
11
|
+
isVariant,
|
|
12
|
+
PerpMarketAccount,
|
|
13
|
+
SpotMarketAccount,
|
|
14
|
+
StateAccount,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import { Program } from '@coral-xyz/anchor';
|
|
17
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
18
|
+
import { EventEmitter } from 'events';
|
|
19
|
+
import {
|
|
20
|
+
getDriftStateAccountPublicKey,
|
|
21
|
+
getPerpMarketPublicKey,
|
|
22
|
+
getSpotMarketPublicKey,
|
|
23
|
+
} from '../addresses/pda';
|
|
24
|
+
import { Context, PublicKey } from '@solana/web3.js';
|
|
25
|
+
import {
|
|
26
|
+
Commitment,
|
|
27
|
+
SolanaRpcSubscriptionsApi,
|
|
28
|
+
Rpc,
|
|
29
|
+
RpcSubscriptions,
|
|
30
|
+
createSolanaClient,
|
|
31
|
+
} from 'gill';
|
|
32
|
+
import { OracleInfo, OraclePriceData } from '../oracles/types';
|
|
33
|
+
import { OracleClientCache } from '../oracles/oracleClientCache';
|
|
34
|
+
import { QUOTE_ORACLE_PRICE_DATA } from '../oracles/quoteAssetOracleClient';
|
|
35
|
+
import { findAllMarketAndOracles } from '../config';
|
|
36
|
+
import { findDelistedPerpMarketsAndOracles } from './utils';
|
|
37
|
+
import {
|
|
38
|
+
getOracleId,
|
|
39
|
+
getPublicKeyAndSourceFromOracleId,
|
|
40
|
+
} from '../oracles/oracleId';
|
|
41
|
+
import { OracleSource } from '../types';
|
|
42
|
+
import {
|
|
43
|
+
getPerpMarketAccountsFilter,
|
|
44
|
+
getSpotMarketAccountsFilter,
|
|
45
|
+
} from '../memcmp';
|
|
46
|
+
import { WebSocketProgramAccountsSubscriberV2 } from './webSocketProgramAccountsSubscriberV2';
|
|
47
|
+
import { WebSocketAccountSubscriberV2 } from './webSocketAccountSubscriberV2';
|
|
48
|
+
const ORACLE_DEFAULT_ID = getOracleId(
|
|
49
|
+
PublicKey.default,
|
|
50
|
+
OracleSource.QUOTE_ASSET
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
export class WebSocketDriftClientAccountSubscriberV2
|
|
54
|
+
implements DriftClientAccountSubscriber
|
|
55
|
+
{
|
|
56
|
+
isSubscribed: boolean;
|
|
57
|
+
program: Program;
|
|
58
|
+
commitment?: Commitment;
|
|
59
|
+
perpMarketIndexes: number[];
|
|
60
|
+
spotMarketIndexes: number[];
|
|
61
|
+
oracleInfos: OracleInfo[];
|
|
62
|
+
oracleClientCache = new OracleClientCache();
|
|
63
|
+
|
|
64
|
+
resubOpts?: ResubOpts;
|
|
65
|
+
shouldFindAllMarketsAndOracles: boolean;
|
|
66
|
+
skipInitialData: boolean = true;
|
|
67
|
+
|
|
68
|
+
eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
|
|
69
|
+
stateAccountSubscriber?: WebSocketAccountSubscriberV2<StateAccount>;
|
|
70
|
+
perpMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<PerpMarketAccount>;
|
|
71
|
+
perpMarketAccountLatestData = new Map<
|
|
72
|
+
number,
|
|
73
|
+
DataAndSlot<PerpMarketAccount>
|
|
74
|
+
>();
|
|
75
|
+
spotMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<SpotMarketAccount>;
|
|
76
|
+
spotMarketAccountLatestData = new Map<
|
|
77
|
+
number,
|
|
78
|
+
DataAndSlot<SpotMarketAccount>
|
|
79
|
+
>();
|
|
80
|
+
perpOracleMap = new Map<number, PublicKey>();
|
|
81
|
+
perpOracleStringMap = new Map<number, string>();
|
|
82
|
+
spotOracleMap = new Map<number, PublicKey>();
|
|
83
|
+
spotOracleStringMap = new Map<number, string>();
|
|
84
|
+
oracleSubscribers = new Map<string, AccountSubscriber<OraclePriceData>>();
|
|
85
|
+
delistedMarketSetting: DelistedMarketSetting;
|
|
86
|
+
|
|
87
|
+
initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
|
|
88
|
+
initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
|
|
89
|
+
initialOraclePriceData: Map<string, OraclePriceData>;
|
|
90
|
+
|
|
91
|
+
protected isSubscribing = false;
|
|
92
|
+
protected subscriptionPromise: Promise<boolean>;
|
|
93
|
+
protected subscriptionPromiseResolver: (val: boolean) => void;
|
|
94
|
+
|
|
95
|
+
private rpc: Rpc<any>;
|
|
96
|
+
private rpcSubscriptions: RpcSubscriptions<SolanaRpcSubscriptionsApi> &
|
|
97
|
+
string;
|
|
98
|
+
|
|
99
|
+
public constructor(
|
|
100
|
+
program: Program,
|
|
101
|
+
perpMarketIndexes: number[],
|
|
102
|
+
spotMarketIndexes: number[],
|
|
103
|
+
oracleInfos: OracleInfo[],
|
|
104
|
+
shouldFindAllMarketsAndOracles: boolean,
|
|
105
|
+
delistedMarketSetting: DelistedMarketSetting,
|
|
106
|
+
resubOpts?: ResubOpts,
|
|
107
|
+
commitment?: Commitment,
|
|
108
|
+
skipInitialData?: boolean
|
|
109
|
+
) {
|
|
110
|
+
this.isSubscribed = false;
|
|
111
|
+
this.program = program;
|
|
112
|
+
this.eventEmitter = new EventEmitter();
|
|
113
|
+
this.perpMarketIndexes = perpMarketIndexes;
|
|
114
|
+
this.spotMarketIndexes = spotMarketIndexes;
|
|
115
|
+
this.oracleInfos = oracleInfos;
|
|
116
|
+
this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
|
|
117
|
+
this.delistedMarketSetting = delistedMarketSetting;
|
|
118
|
+
this.resubOpts = resubOpts;
|
|
119
|
+
this.commitment = commitment;
|
|
120
|
+
this.skipInitialData = skipInitialData ?? false;
|
|
121
|
+
|
|
122
|
+
const { rpc, rpcSubscriptions } = createSolanaClient({
|
|
123
|
+
urlOrMoniker: this.program.provider.connection.rpcEndpoint,
|
|
124
|
+
});
|
|
125
|
+
this.rpc = rpc;
|
|
126
|
+
this.rpcSubscriptions = rpcSubscriptions;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
public async subscribe(): Promise<boolean> {
|
|
130
|
+
try {
|
|
131
|
+
const startTime = performance.now();
|
|
132
|
+
if (this.isSubscribed) {
|
|
133
|
+
console.log(
|
|
134
|
+
`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() skipped - already subscribed`
|
|
135
|
+
);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (this.isSubscribing) {
|
|
140
|
+
console.log(
|
|
141
|
+
`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() waiting for existing subscription`
|
|
142
|
+
);
|
|
143
|
+
return await this.subscriptionPromise;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.isSubscribing = true;
|
|
147
|
+
|
|
148
|
+
// Initialize subscriptionPromiseResolver to a no-op function
|
|
149
|
+
this.subscriptionPromiseResolver = () => {};
|
|
150
|
+
|
|
151
|
+
this.subscriptionPromise = new Promise((res) => {
|
|
152
|
+
this.subscriptionPromiseResolver = res;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const [perpMarketAccountPubkeys, spotMarketAccountPubkeys] =
|
|
156
|
+
await Promise.all([
|
|
157
|
+
Promise.all(
|
|
158
|
+
this.perpMarketIndexes.map((marketIndex) =>
|
|
159
|
+
getPerpMarketPublicKey(this.program.programId, marketIndex)
|
|
160
|
+
)
|
|
161
|
+
),
|
|
162
|
+
Promise.all(
|
|
163
|
+
this.spotMarketIndexes.map((marketIndex) =>
|
|
164
|
+
getSpotMarketPublicKey(this.program.programId, marketIndex)
|
|
165
|
+
)
|
|
166
|
+
),
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
// Profile findAllMarketsAndOracles if needed
|
|
170
|
+
let findAllMarketsDuration = 0;
|
|
171
|
+
if (this.shouldFindAllMarketsAndOracles) {
|
|
172
|
+
const findAllMarketsStartTime = performance.now();
|
|
173
|
+
const {
|
|
174
|
+
perpMarketIndexes,
|
|
175
|
+
perpMarketAccounts,
|
|
176
|
+
spotMarketIndexes,
|
|
177
|
+
spotMarketAccounts,
|
|
178
|
+
oracleInfos,
|
|
179
|
+
} = await findAllMarketAndOracles(this.program);
|
|
180
|
+
this.perpMarketIndexes = perpMarketIndexes;
|
|
181
|
+
this.spotMarketIndexes = spotMarketIndexes;
|
|
182
|
+
this.oracleInfos = oracleInfos;
|
|
183
|
+
// front run and set the initial data here to save extra gma call in set initial data
|
|
184
|
+
this.initialPerpMarketAccountData = new Map(
|
|
185
|
+
perpMarketAccounts.map((market) => [market.marketIndex, market])
|
|
186
|
+
);
|
|
187
|
+
this.initialSpotMarketAccountData = new Map(
|
|
188
|
+
spotMarketAccounts.map((market) => [market.marketIndex, market])
|
|
189
|
+
);
|
|
190
|
+
const findAllMarketsEndTime = performance.now();
|
|
191
|
+
findAllMarketsDuration =
|
|
192
|
+
findAllMarketsEndTime - findAllMarketsStartTime;
|
|
193
|
+
console.log(
|
|
194
|
+
`[PROFILING] findAllMarketAndOracles completed in ${findAllMarketsDuration.toFixed(
|
|
195
|
+
2
|
|
196
|
+
)}ms (${perpMarketAccounts.length} perp markets, ${
|
|
197
|
+
spotMarketAccounts.length
|
|
198
|
+
} spot markets)`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Create subscribers
|
|
203
|
+
this.perpMarketAllAccountsSubscriber =
|
|
204
|
+
new WebSocketProgramAccountsSubscriberV2<PerpMarketAccount>(
|
|
205
|
+
'PerpMarketAccountsSubscriber',
|
|
206
|
+
'PerpMarket',
|
|
207
|
+
this.program,
|
|
208
|
+
this.program.account.perpMarket.coder.accounts.decodeUnchecked.bind(
|
|
209
|
+
this.program.account.perpMarket.coder.accounts
|
|
210
|
+
),
|
|
211
|
+
{
|
|
212
|
+
filters: [getPerpMarketAccountsFilter()],
|
|
213
|
+
commitment: this.commitment,
|
|
214
|
+
},
|
|
215
|
+
this.resubOpts,
|
|
216
|
+
perpMarketAccountPubkeys // because we pass these in, it will monitor these accounts and fetch them right away
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
this.spotMarketAllAccountsSubscriber =
|
|
220
|
+
new WebSocketProgramAccountsSubscriberV2<SpotMarketAccount>(
|
|
221
|
+
'SpotMarketAccountsSubscriber',
|
|
222
|
+
'SpotMarket',
|
|
223
|
+
this.program,
|
|
224
|
+
this.program.account.spotMarket.coder.accounts.decodeUnchecked.bind(
|
|
225
|
+
this.program.account.spotMarket.coder.accounts
|
|
226
|
+
),
|
|
227
|
+
{
|
|
228
|
+
filters: [getSpotMarketAccountsFilter()],
|
|
229
|
+
commitment: this.commitment,
|
|
230
|
+
},
|
|
231
|
+
this.resubOpts,
|
|
232
|
+
spotMarketAccountPubkeys // because we pass these in, it will monitor these accounts and fetch them right away
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Run all subscriptions in parallel
|
|
236
|
+
await Promise.all([
|
|
237
|
+
// Perp market subscription
|
|
238
|
+
this.perpMarketAllAccountsSubscriber.subscribe(
|
|
239
|
+
(
|
|
240
|
+
_accountId: PublicKey,
|
|
241
|
+
data: PerpMarketAccount,
|
|
242
|
+
context: Context,
|
|
243
|
+
_buffer: Buffer
|
|
244
|
+
) => {
|
|
245
|
+
if (
|
|
246
|
+
this.delistedMarketSetting !== DelistedMarketSetting.Subscribe &&
|
|
247
|
+
isVariant(data.status, 'delisted')
|
|
248
|
+
) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
this.perpMarketAccountLatestData.set(data.marketIndex, {
|
|
252
|
+
data,
|
|
253
|
+
slot: context.slot,
|
|
254
|
+
});
|
|
255
|
+
this.eventEmitter.emit('perpMarketAccountUpdate', data);
|
|
256
|
+
this.eventEmitter.emit('update');
|
|
257
|
+
}
|
|
258
|
+
),
|
|
259
|
+
// Spot market subscription
|
|
260
|
+
this.spotMarketAllAccountsSubscriber.subscribe(
|
|
261
|
+
(
|
|
262
|
+
_accountId: PublicKey,
|
|
263
|
+
data: SpotMarketAccount,
|
|
264
|
+
context: Context,
|
|
265
|
+
_buffer: Buffer
|
|
266
|
+
) => {
|
|
267
|
+
if (
|
|
268
|
+
this.delistedMarketSetting !== DelistedMarketSetting.Subscribe &&
|
|
269
|
+
isVariant(data.status, 'delisted')
|
|
270
|
+
) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
this.spotMarketAccountLatestData.set(data.marketIndex, {
|
|
274
|
+
data,
|
|
275
|
+
slot: context.slot,
|
|
276
|
+
});
|
|
277
|
+
this.eventEmitter.emit('spotMarketAccountUpdate', data);
|
|
278
|
+
this.eventEmitter.emit('update');
|
|
279
|
+
}
|
|
280
|
+
),
|
|
281
|
+
// State account subscription
|
|
282
|
+
(async () => {
|
|
283
|
+
const statePublicKey = await getDriftStateAccountPublicKey(
|
|
284
|
+
this.program.programId
|
|
285
|
+
);
|
|
286
|
+
this.stateAccountSubscriber = new WebSocketAccountSubscriberV2(
|
|
287
|
+
'state',
|
|
288
|
+
this.program,
|
|
289
|
+
statePublicKey,
|
|
290
|
+
undefined,
|
|
291
|
+
undefined,
|
|
292
|
+
this.commitment as Commitment,
|
|
293
|
+
this.rpcSubscriptions,
|
|
294
|
+
this.rpc
|
|
295
|
+
);
|
|
296
|
+
await Promise.all([
|
|
297
|
+
this.stateAccountSubscriber.fetch(),
|
|
298
|
+
this.stateAccountSubscriber.subscribe((data: StateAccount) => {
|
|
299
|
+
this.eventEmitter.emit('stateAccountUpdate', data);
|
|
300
|
+
this.eventEmitter.emit('update');
|
|
301
|
+
}),
|
|
302
|
+
]);
|
|
303
|
+
})(),
|
|
304
|
+
(async () => {
|
|
305
|
+
await this.setInitialData();
|
|
306
|
+
const subscribeToOraclesStartTime = performance.now();
|
|
307
|
+
await this.subscribeToOracles();
|
|
308
|
+
const subscribeToOraclesEndTime = performance.now();
|
|
309
|
+
const duration =
|
|
310
|
+
subscribeToOraclesEndTime - subscribeToOraclesStartTime;
|
|
311
|
+
return duration;
|
|
312
|
+
})(),
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
// const initialPerpMarketDataFromLatestData = new Map(
|
|
316
|
+
// Array.from(this.perpMarketAccountLatestData.values()).map((data) => [
|
|
317
|
+
// data.data.marketIndex,
|
|
318
|
+
// data.data,
|
|
319
|
+
// ])
|
|
320
|
+
// );
|
|
321
|
+
// const initialSpotMarketDataFromLatestData = new Map(
|
|
322
|
+
// Array.from(this.spotMarketAccountLatestData.values()).map((data) => [
|
|
323
|
+
// data.data.marketIndex,
|
|
324
|
+
// data.data,
|
|
325
|
+
// ])
|
|
326
|
+
// );
|
|
327
|
+
// this.initialPerpMarketAccountData = initialPerpMarketDataFromLatestData;
|
|
328
|
+
// this.initialSpotMarketAccountData = initialSpotMarketDataFromLatestData;
|
|
329
|
+
|
|
330
|
+
await this.handleDelistedMarketOracles();
|
|
331
|
+
|
|
332
|
+
await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
|
|
333
|
+
|
|
334
|
+
this.eventEmitter.emit('update');
|
|
335
|
+
// delete initial data
|
|
336
|
+
this.removeInitialData();
|
|
337
|
+
|
|
338
|
+
const totalDuration = performance.now() - startTime;
|
|
339
|
+
console.log(
|
|
340
|
+
`[PROFILING] WebSocketDriftClientAccountSubscriberV2.subscribe() completed in ${totalDuration.toFixed(
|
|
341
|
+
2
|
|
342
|
+
)}ms`
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Resolve the subscription promise
|
|
346
|
+
this.isSubscribed = true;
|
|
347
|
+
this.isSubscribing = false;
|
|
348
|
+
// Before calling subscriptionPromiseResolver, check if it's defined
|
|
349
|
+
if (this.subscriptionPromiseResolver) {
|
|
350
|
+
this.subscriptionPromiseResolver(true);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return true;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('Subscription failed:', error);
|
|
356
|
+
this.isSubscribing = false;
|
|
357
|
+
this.subscriptionPromiseResolver(false);
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
chunks = <T>(array: readonly T[], size: number): T[][] => {
|
|
363
|
+
const result: T[][] = [];
|
|
364
|
+
for (let i = 0; i < array.length; i += size) {
|
|
365
|
+
result.push(array.slice(i, i + size));
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
public async fetch(): Promise<void> {
|
|
371
|
+
await this.setInitialData();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* This is a no-op method that always returns true.
|
|
376
|
+
* Unlike the previous implementation, we don't need to manually subscribe to individual perp markets
|
|
377
|
+
* because we automatically receive updates for all program account changes via a single websocket subscription.
|
|
378
|
+
* This means any new perp markets will automatically be included without explicit subscription.
|
|
379
|
+
* @param marketIndex The perp market index to add (unused)
|
|
380
|
+
* @returns Promise that resolves to true
|
|
381
|
+
*/
|
|
382
|
+
public addPerpMarket(_marketIndex: number): Promise<boolean> {
|
|
383
|
+
return Promise.resolve(true);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* This is a no-op method that always returns true.
|
|
388
|
+
* Unlike the previous implementation, we don't need to manually subscribe to individual spot markets
|
|
389
|
+
* because we automatically receive updates for all program account changes via a single websocket subscription.
|
|
390
|
+
* This means any new spot markets will automatically be included without explicit subscription.
|
|
391
|
+
* @param marketIndex The spot market index to add (unused)
|
|
392
|
+
* @returns Promise that resolves to true
|
|
393
|
+
*/
|
|
394
|
+
public addSpotMarket(_marketIndex: number): Promise<boolean> {
|
|
395
|
+
return Promise.resolve(true);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// TODO: need more options to skip loading perp market and spot market data. Because of how we fetch within the program account subscribers, I am commenting this all out
|
|
399
|
+
async setInitialData(): Promise<void> {
|
|
400
|
+
const connection = this.program.provider.connection;
|
|
401
|
+
// Profile oracle initial data setup
|
|
402
|
+
const oracleSetupStartTime = performance.now();
|
|
403
|
+
const oracleAccountPubkeyChunks = this.chunks(
|
|
404
|
+
this.oracleInfos.map((oracleInfo) => oracleInfo.publicKey),
|
|
405
|
+
100
|
|
406
|
+
);
|
|
407
|
+
const oracleAccountInfos = (
|
|
408
|
+
await Promise.all(
|
|
409
|
+
oracleAccountPubkeyChunks.map((oracleAccountPublicKeysChunk) =>
|
|
410
|
+
connection.getMultipleAccountsInfo(oracleAccountPublicKeysChunk)
|
|
411
|
+
)
|
|
412
|
+
)
|
|
413
|
+
).flat();
|
|
414
|
+
this.initialOraclePriceData = new Map(
|
|
415
|
+
this.oracleInfos.reduce((result, oracleInfo, i) => {
|
|
416
|
+
if (!oracleAccountInfos[i]) {
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const oracleClient = this.oracleClientCache.get(
|
|
421
|
+
oracleInfo.source,
|
|
422
|
+
connection,
|
|
423
|
+
this.program
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
const oraclePriceData = oracleClient.getOraclePriceDataFromBuffer(
|
|
427
|
+
oracleAccountInfos[i].data
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
result.push([
|
|
431
|
+
getOracleId(oracleInfo.publicKey, oracleInfo.source),
|
|
432
|
+
oraclePriceData,
|
|
433
|
+
]);
|
|
434
|
+
return result;
|
|
435
|
+
}, [])
|
|
436
|
+
);
|
|
437
|
+
const oracleSetupEndTime = performance.now();
|
|
438
|
+
const oracleSetupDuration = oracleSetupEndTime - oracleSetupStartTime;
|
|
439
|
+
if (this.resubOpts?.logResubMessages) {
|
|
440
|
+
console.log(
|
|
441
|
+
`[PROFILING] Oracle initial data setup completed in ${oracleSetupDuration.toFixed(
|
|
442
|
+
2
|
|
443
|
+
)}ms (${this.initialOraclePriceData.size} oracles)`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// emit initial oracle price data
|
|
448
|
+
Array.from(this.initialOraclePriceData.entries()).forEach(
|
|
449
|
+
([oracleId, oraclePriceData]) => {
|
|
450
|
+
const { publicKey, source } =
|
|
451
|
+
getPublicKeyAndSourceFromOracleId(oracleId);
|
|
452
|
+
this.eventEmitter.emit(
|
|
453
|
+
'oraclePriceUpdate',
|
|
454
|
+
publicKey,
|
|
455
|
+
source,
|
|
456
|
+
oraclePriceData
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
this.eventEmitter.emit('update');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
removeInitialData() {
|
|
464
|
+
this.initialPerpMarketAccountData = new Map();
|
|
465
|
+
this.initialSpotMarketAccountData = new Map();
|
|
466
|
+
this.initialOraclePriceData = new Map();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async subscribeToOracles(): Promise<boolean> {
|
|
470
|
+
const startTime = performance.now();
|
|
471
|
+
|
|
472
|
+
// Filter out default oracles and duplicates to avoid unnecessary subscriptions
|
|
473
|
+
const validOracleInfos = this.oracleInfos.filter(
|
|
474
|
+
(oracleInfo) =>
|
|
475
|
+
!this.oracleSubscribers.has(
|
|
476
|
+
getOracleId(oracleInfo.publicKey, oracleInfo.source)
|
|
477
|
+
)
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
await Promise.all(
|
|
481
|
+
validOracleInfos.map((oracleInfo) => this.subscribeToOracle(oracleInfo))
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
const totalDuration = performance.now() - startTime;
|
|
485
|
+
console.log(
|
|
486
|
+
`[PROFILING] subscribeToOracles() completed in ${totalDuration.toFixed(
|
|
487
|
+
2
|
|
488
|
+
)}ms`
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async subscribeToOracle(oracleInfo: OracleInfo): Promise<boolean> {
|
|
495
|
+
try {
|
|
496
|
+
const oracleId = getOracleId(oracleInfo.publicKey, oracleInfo.source);
|
|
497
|
+
|
|
498
|
+
const client = this.oracleClientCache.get(
|
|
499
|
+
oracleInfo.source,
|
|
500
|
+
this.program.provider.connection,
|
|
501
|
+
this.program
|
|
502
|
+
);
|
|
503
|
+
const accountSubscriber =
|
|
504
|
+
new WebSocketAccountSubscriberV2<OraclePriceData>(
|
|
505
|
+
'oracle',
|
|
506
|
+
this.program,
|
|
507
|
+
oracleInfo.publicKey,
|
|
508
|
+
(buffer: Buffer) => {
|
|
509
|
+
return client.getOraclePriceDataFromBuffer(buffer);
|
|
510
|
+
},
|
|
511
|
+
this.resubOpts,
|
|
512
|
+
this.commitment,
|
|
513
|
+
this.rpcSubscriptions,
|
|
514
|
+
this.rpc
|
|
515
|
+
);
|
|
516
|
+
const initialOraclePriceData = this.initialOraclePriceData?.get(oracleId);
|
|
517
|
+
if (initialOraclePriceData) {
|
|
518
|
+
accountSubscriber.setData(initialOraclePriceData);
|
|
519
|
+
}
|
|
520
|
+
await accountSubscriber.subscribe((data: OraclePriceData) => {
|
|
521
|
+
this.eventEmitter.emit(
|
|
522
|
+
'oraclePriceUpdate',
|
|
523
|
+
oracleInfo.publicKey,
|
|
524
|
+
oracleInfo.source,
|
|
525
|
+
data
|
|
526
|
+
);
|
|
527
|
+
this.eventEmitter.emit('update');
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
this.oracleSubscribers.set(oracleId, accountSubscriber);
|
|
531
|
+
|
|
532
|
+
return true;
|
|
533
|
+
} catch (error) {
|
|
534
|
+
console.error(
|
|
535
|
+
`Failed to subscribe to oracle ${oracleInfo.publicKey.toString()}:`,
|
|
536
|
+
error
|
|
537
|
+
);
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async unsubscribeFromMarketAccounts(): Promise<void> {
|
|
543
|
+
await this.perpMarketAllAccountsSubscriber.unsubscribe();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async unsubscribeFromSpotMarketAccounts(): Promise<void> {
|
|
547
|
+
await this.spotMarketAllAccountsSubscriber.unsubscribe();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async unsubscribeFromOracles(): Promise<void> {
|
|
551
|
+
await Promise.all(
|
|
552
|
+
Array.from(this.oracleSubscribers.values()).map((accountSubscriber) =>
|
|
553
|
+
accountSubscriber.unsubscribe()
|
|
554
|
+
)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public async unsubscribe(): Promise<void> {
|
|
559
|
+
if (!this.isSubscribed) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (this.subscriptionPromise) {
|
|
564
|
+
await this.subscriptionPromise;
|
|
565
|
+
}
|
|
566
|
+
await Promise.all([
|
|
567
|
+
this.stateAccountSubscriber?.unsubscribe(),
|
|
568
|
+
this.unsubscribeFromMarketAccounts(),
|
|
569
|
+
this.unsubscribeFromSpotMarketAccounts(),
|
|
570
|
+
this.unsubscribeFromOracles(),
|
|
571
|
+
]);
|
|
572
|
+
|
|
573
|
+
this.isSubscribed = false;
|
|
574
|
+
this.isSubscribing = false;
|
|
575
|
+
this.subscriptionPromiseResolver = () => {};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async addOracle(oracleInfo: OracleInfo): Promise<boolean> {
|
|
579
|
+
const oracleId = getOracleId(oracleInfo.publicKey, oracleInfo.source);
|
|
580
|
+
if (this.oracleSubscribers.has(oracleId)) {
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (oracleInfo.publicKey.equals(PublicKey.default)) {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return this.subscribeToOracle(oracleInfo);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async setPerpOracleMap() {
|
|
592
|
+
const perpMarkets = this.getMarketAccountsAndSlots();
|
|
593
|
+
const addOraclePromises = [];
|
|
594
|
+
for (const perpMarket of perpMarkets) {
|
|
595
|
+
if (!perpMarket || !perpMarket.data) {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
const perpMarketAccount = perpMarket.data;
|
|
599
|
+
const perpMarketIndex = perpMarketAccount.marketIndex;
|
|
600
|
+
const oracle = perpMarketAccount.amm.oracle;
|
|
601
|
+
const oracleId = getOracleId(oracle, perpMarket.data.amm.oracleSource);
|
|
602
|
+
if (!this.oracleSubscribers.has(oracleId)) {
|
|
603
|
+
addOraclePromises.push(
|
|
604
|
+
this.addOracle({
|
|
605
|
+
publicKey: oracle,
|
|
606
|
+
source: perpMarket.data.amm.oracleSource,
|
|
607
|
+
})
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
this.perpOracleMap.set(perpMarketIndex, oracle);
|
|
611
|
+
this.perpOracleStringMap.set(perpMarketIndex, oracleId);
|
|
612
|
+
}
|
|
613
|
+
await Promise.all(addOraclePromises);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async setSpotOracleMap() {
|
|
617
|
+
const spotMarkets = this.getSpotMarketAccountsAndSlots();
|
|
618
|
+
const addOraclePromises = [];
|
|
619
|
+
for (const spotMarket of spotMarkets) {
|
|
620
|
+
if (!spotMarket || !spotMarket.data) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const spotMarketAccount = spotMarket.data;
|
|
624
|
+
const spotMarketIndex = spotMarketAccount.marketIndex;
|
|
625
|
+
const oracle = spotMarketAccount.oracle;
|
|
626
|
+
const oracleId = getOracleId(oracle, spotMarketAccount.oracleSource);
|
|
627
|
+
if (!this.oracleSubscribers.has(oracleId)) {
|
|
628
|
+
addOraclePromises.push(
|
|
629
|
+
this.addOracle({
|
|
630
|
+
publicKey: oracle,
|
|
631
|
+
source: spotMarketAccount.oracleSource,
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
this.spotOracleMap.set(spotMarketIndex, oracle);
|
|
636
|
+
this.spotOracleStringMap.set(spotMarketIndex, oracleId);
|
|
637
|
+
}
|
|
638
|
+
await Promise.all(addOraclePromises);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
async handleDelistedMarketOracles(): Promise<void> {
|
|
642
|
+
if (this.delistedMarketSetting === DelistedMarketSetting.Subscribe) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const { oracles } = findDelistedPerpMarketsAndOracles(
|
|
647
|
+
this.getMarketAccountsAndSlots(),
|
|
648
|
+
this.getSpotMarketAccountsAndSlots()
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
for (const oracle of oracles) {
|
|
652
|
+
const oracleId = getOracleId(oracle.publicKey, oracle.source);
|
|
653
|
+
if (this.oracleSubscribers.has(oracleId)) {
|
|
654
|
+
await this.oracleSubscribers.get(oracleId).unsubscribe();
|
|
655
|
+
if (this.delistedMarketSetting === DelistedMarketSetting.Discard) {
|
|
656
|
+
this.oracleSubscribers.delete(oracleId);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
assertIsSubscribed(): void {
|
|
663
|
+
if (!this.isSubscribed) {
|
|
664
|
+
throw new NotSubscribedError(
|
|
665
|
+
'You must call `subscribe` before using this function'
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
public getStateAccountAndSlot(): DataAndSlot<StateAccount> {
|
|
671
|
+
this.assertIsSubscribed();
|
|
672
|
+
return this.stateAccountSubscriber.dataAndSlot;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
public getMarketAccountAndSlot(
|
|
676
|
+
marketIndex: number
|
|
677
|
+
): DataAndSlot<PerpMarketAccount> | undefined {
|
|
678
|
+
this.assertIsSubscribed();
|
|
679
|
+
return this.perpMarketAccountLatestData.get(marketIndex);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
public getMarketAccountsAndSlots(): DataAndSlot<PerpMarketAccount>[] {
|
|
683
|
+
return Array.from(this.perpMarketAccountLatestData.values());
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
public getSpotMarketAccountAndSlot(
|
|
687
|
+
marketIndex: number
|
|
688
|
+
): DataAndSlot<SpotMarketAccount> | undefined {
|
|
689
|
+
this.assertIsSubscribed();
|
|
690
|
+
return this.spotMarketAccountLatestData.get(marketIndex);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
public getSpotMarketAccountsAndSlots(): DataAndSlot<SpotMarketAccount>[] {
|
|
694
|
+
return Array.from(this.spotMarketAccountLatestData.values());
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
public getOraclePriceDataAndSlot(
|
|
698
|
+
oracleId: string
|
|
699
|
+
): DataAndSlot<OraclePriceData> | undefined {
|
|
700
|
+
this.assertIsSubscribed();
|
|
701
|
+
if (oracleId === ORACLE_DEFAULT_ID) {
|
|
702
|
+
return {
|
|
703
|
+
data: QUOTE_ORACLE_PRICE_DATA,
|
|
704
|
+
slot: 0,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return this.oracleSubscribers.get(oracleId)?.dataAndSlot;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
public getOraclePriceDataAndSlotForPerpMarket(
|
|
711
|
+
marketIndex: number
|
|
712
|
+
): DataAndSlot<OraclePriceData> | undefined {
|
|
713
|
+
const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
|
|
714
|
+
const oracle = this.perpOracleMap.get(marketIndex);
|
|
715
|
+
const oracleId = this.perpOracleStringMap.get(marketIndex);
|
|
716
|
+
if (!perpMarketAccount || !oracleId) {
|
|
717
|
+
return undefined;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (!perpMarketAccount.data.amm.oracle.equals(oracle)) {
|
|
721
|
+
// If the oracle has changed, we need to update the oracle map in background
|
|
722
|
+
this.setPerpOracleMap();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return this.getOraclePriceDataAndSlot(oracleId);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
public getOraclePriceDataAndSlotForSpotMarket(
|
|
729
|
+
marketIndex: number
|
|
730
|
+
): DataAndSlot<OraclePriceData> | undefined {
|
|
731
|
+
const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
|
|
732
|
+
const oracle = this.spotOracleMap.get(marketIndex);
|
|
733
|
+
const oracleId = this.spotOracleStringMap.get(marketIndex);
|
|
734
|
+
if (!spotMarketAccount || !oracleId) {
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!spotMarketAccount.data.oracle.equals(oracle)) {
|
|
739
|
+
// If the oracle has changed, we need to update the oracle map in background
|
|
740
|
+
this.setSpotOracleMap();
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
return this.getOraclePriceDataAndSlot(oracleId);
|
|
744
|
+
}
|
|
745
|
+
}
|