@drift-labs/sdk 2.145.0-beta.2 → 2.145.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/browser/accounts/types.d.ts +13 -1
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +53 -0
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
- package/lib/browser/addresses/pda.d.ts +9 -0
- package/lib/browser/addresses/pda.js +60 -1
- package/lib/browser/adminClient.d.ts +160 -8
- package/lib/browser/adminClient.js +754 -18
- package/lib/browser/constituentMap/constituentMap.d.ts +64 -0
- package/lib/browser/constituentMap/constituentMap.js +170 -0
- package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.d.ts +24 -0
- package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
- package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.d.ts +24 -0
- package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
- package/lib/browser/driftClient.d.ts +89 -2
- package/lib/browser/driftClient.js +486 -27
- package/lib/browser/driftClientConfig.d.ts +2 -7
- package/lib/browser/idl/drift.json +4304 -1380
- package/lib/browser/index.d.ts +1 -4
- package/lib/browser/index.js +2 -9
- package/lib/browser/memcmp.d.ts +3 -1
- package/lib/browser/memcmp.js +19 -1
- package/lib/browser/types.d.ts +147 -0
- package/lib/browser/types.js +13 -1
- package/lib/node/accounts/types.d.ts +13 -1
- package/lib/node/accounts/types.d.ts.map +1 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +54 -0
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
- package/lib/node/addresses/pda.d.ts +9 -0
- package/lib/node/addresses/pda.d.ts.map +1 -1
- package/lib/node/addresses/pda.js +60 -1
- package/lib/node/adminClient.d.ts +160 -8
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +754 -18
- package/lib/node/constituentMap/constituentMap.d.ts +65 -0
- package/lib/node/constituentMap/constituentMap.d.ts.map +1 -0
- package/lib/node/constituentMap/constituentMap.js +170 -0
- package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts +25 -0
- package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts.map +1 -0
- package/lib/node/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
- package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts +25 -0
- package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts.map +1 -0
- package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
- package/lib/node/driftClient.d.ts +89 -2
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +486 -27
- package/lib/node/driftClientConfig.d.ts +2 -7
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +4304 -1380
- package/lib/node/index.d.ts +1 -4
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +2 -9
- package/lib/node/memcmp.d.ts +3 -1
- package/lib/node/memcmp.d.ts.map +1 -1
- package/lib/node/memcmp.js +19 -1
- package/lib/node/types.d.ts +147 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +13 -1
- package/package.json +1 -1
- package/src/accounts/types.ts +20 -0
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +596 -0
- package/src/addresses/pda.ts +115 -1
- package/src/adminClient.ts +1612 -41
- package/src/constituentMap/constituentMap.ts +285 -0
- package/src/constituentMap/pollingConstituentAccountSubscriber.ts +97 -0
- package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +112 -0
- package/src/driftClient.ts +1097 -17
- package/src/driftClientConfig.ts +8 -15
- package/src/idl/drift.json +4304 -1380
- package/src/index.ts +1 -4
- package/src/memcmp.ts +23 -1
- package/src/types.ts +160 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { BufferAndSlot, ProgramAccountSubscriber, ResubOpts } from './types';
|
|
2
|
+
import { AnchorProvider, Program } from '@coral-xyz/anchor';
|
|
3
|
+
import { Commitment, Context, MemcmpFilter, PublicKey } from '@solana/web3.js';
|
|
4
|
+
import {
|
|
5
|
+
AccountInfoBase,
|
|
6
|
+
AccountInfoWithBase58EncodedData,
|
|
7
|
+
AccountInfoWithBase64EncodedData,
|
|
8
|
+
createSolanaClient,
|
|
9
|
+
isAddress,
|
|
10
|
+
type Address,
|
|
11
|
+
type Commitment as GillCommitment,
|
|
12
|
+
} from 'gill';
|
|
13
|
+
import bs58 from 'bs58';
|
|
14
|
+
|
|
15
|
+
export class WebSocketProgramAccountSubscriberV2<T>
|
|
16
|
+
implements ProgramAccountSubscriber<T>
|
|
17
|
+
{
|
|
18
|
+
subscriptionName: string;
|
|
19
|
+
accountDiscriminator: string;
|
|
20
|
+
bufferAndSlot?: BufferAndSlot;
|
|
21
|
+
bufferAndSlotMap: Map<string, BufferAndSlot> = new Map();
|
|
22
|
+
program: Program;
|
|
23
|
+
decodeBuffer: (accountName: string, ix: Buffer) => T;
|
|
24
|
+
onChange: (
|
|
25
|
+
accountId: PublicKey,
|
|
26
|
+
data: T,
|
|
27
|
+
context: Context,
|
|
28
|
+
buffer: Buffer
|
|
29
|
+
) => void;
|
|
30
|
+
listenerId?: number;
|
|
31
|
+
resubOpts?: ResubOpts;
|
|
32
|
+
isUnsubscribing = false;
|
|
33
|
+
timeoutId?: ReturnType<typeof setTimeout>;
|
|
34
|
+
options: { filters: MemcmpFilter[]; commitment?: Commitment };
|
|
35
|
+
|
|
36
|
+
receivingData = false;
|
|
37
|
+
|
|
38
|
+
// Gill client components
|
|
39
|
+
private rpc: ReturnType<typeof createSolanaClient>['rpc'];
|
|
40
|
+
private rpcSubscriptions: ReturnType<
|
|
41
|
+
typeof createSolanaClient
|
|
42
|
+
>['rpcSubscriptions'];
|
|
43
|
+
private abortController?: AbortController;
|
|
44
|
+
|
|
45
|
+
// Polling logic for specific accounts
|
|
46
|
+
private accountsToMonitor: Set<string> = new Set();
|
|
47
|
+
private pollingIntervalMs: number = 30000; // 30 seconds
|
|
48
|
+
private pollingTimeouts: Map<string, ReturnType<typeof setTimeout>> =
|
|
49
|
+
new Map();
|
|
50
|
+
private lastWsNotificationTime: Map<string, number> = new Map(); // Track last WS notification time per account
|
|
51
|
+
private accountsCurrentlyPolling: Set<string> = new Set(); // Track which accounts are being polled
|
|
52
|
+
private batchPollingTimeout?: ReturnType<typeof setTimeout>; // Single timeout for batch polling
|
|
53
|
+
|
|
54
|
+
public constructor(
|
|
55
|
+
subscriptionName: string,
|
|
56
|
+
accountDiscriminator: string,
|
|
57
|
+
program: Program,
|
|
58
|
+
decodeBufferFn: (accountName: string, ix: Buffer) => T,
|
|
59
|
+
options: { filters: MemcmpFilter[]; commitment?: Commitment } = {
|
|
60
|
+
filters: [],
|
|
61
|
+
},
|
|
62
|
+
resubOpts?: ResubOpts,
|
|
63
|
+
accountsToMonitor?: PublicKey[] // Optional list of accounts to poll
|
|
64
|
+
) {
|
|
65
|
+
this.subscriptionName = subscriptionName;
|
|
66
|
+
this.accountDiscriminator = accountDiscriminator;
|
|
67
|
+
this.program = program;
|
|
68
|
+
this.decodeBuffer = decodeBufferFn;
|
|
69
|
+
this.resubOpts = resubOpts;
|
|
70
|
+
if (this.resubOpts?.resubTimeoutMs < 1000) {
|
|
71
|
+
console.log(
|
|
72
|
+
'resubTimeoutMs should be at least 1000ms to avoid spamming resub'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
this.options = options;
|
|
76
|
+
this.receivingData = false;
|
|
77
|
+
|
|
78
|
+
// Initialize accounts to monitor
|
|
79
|
+
if (accountsToMonitor) {
|
|
80
|
+
accountsToMonitor.forEach((account) => {
|
|
81
|
+
this.accountsToMonitor.add(account.toBase58());
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Initialize gill client using the same RPC URL as the program provider
|
|
86
|
+
const rpcUrl = (this.program.provider as AnchorProvider).connection
|
|
87
|
+
.rpcEndpoint;
|
|
88
|
+
const { rpc, rpcSubscriptions } = createSolanaClient({
|
|
89
|
+
urlOrMoniker: rpcUrl,
|
|
90
|
+
});
|
|
91
|
+
this.rpc = rpc;
|
|
92
|
+
this.rpcSubscriptions = rpcSubscriptions;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async subscribe(
|
|
96
|
+
onChange: (
|
|
97
|
+
accountId: PublicKey,
|
|
98
|
+
data: T,
|
|
99
|
+
context: Context,
|
|
100
|
+
buffer: Buffer
|
|
101
|
+
) => void
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
if (this.listenerId != null || this.isUnsubscribing) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.onChange = onChange;
|
|
108
|
+
|
|
109
|
+
// Create abort controller for proper cleanup
|
|
110
|
+
const abortController = new AbortController();
|
|
111
|
+
this.abortController = abortController;
|
|
112
|
+
|
|
113
|
+
// Subscribe to program account changes using gill's rpcSubscriptions
|
|
114
|
+
const programId = this.program.programId.toBase58();
|
|
115
|
+
if (isAddress(programId)) {
|
|
116
|
+
const subscription = await this.rpcSubscriptions
|
|
117
|
+
.programNotifications(programId, {
|
|
118
|
+
commitment: this.options.commitment as GillCommitment,
|
|
119
|
+
encoding: 'base64',
|
|
120
|
+
filters: this.options.filters.map((filter) => ({
|
|
121
|
+
memcmp: {
|
|
122
|
+
offset: BigInt(filter.memcmp.offset),
|
|
123
|
+
bytes: filter.memcmp.bytes as any,
|
|
124
|
+
encoding: 'base64' as const,
|
|
125
|
+
},
|
|
126
|
+
})),
|
|
127
|
+
})
|
|
128
|
+
.subscribe({
|
|
129
|
+
abortSignal: abortController.signal,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
for await (const notification of subscription) {
|
|
133
|
+
if (this.resubOpts?.resubTimeoutMs) {
|
|
134
|
+
this.receivingData = true;
|
|
135
|
+
clearTimeout(this.timeoutId);
|
|
136
|
+
this.handleRpcResponse(
|
|
137
|
+
notification.context,
|
|
138
|
+
notification.value.account
|
|
139
|
+
);
|
|
140
|
+
this.setTimeout();
|
|
141
|
+
} else {
|
|
142
|
+
this.handleRpcResponse(
|
|
143
|
+
notification.context,
|
|
144
|
+
notification.value.account
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
this.listenerId = Math.random(); // Unique ID for logging purposes
|
|
151
|
+
|
|
152
|
+
if (this.resubOpts?.resubTimeoutMs) {
|
|
153
|
+
this.receivingData = true;
|
|
154
|
+
this.setTimeout();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Start monitoring for accounts that may need polling if no WS event is received
|
|
158
|
+
this.startMonitoringForAccounts();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
protected setTimeout(): void {
|
|
162
|
+
if (!this.onChange) {
|
|
163
|
+
throw new Error('onChange callback function must be set');
|
|
164
|
+
}
|
|
165
|
+
this.timeoutId = setTimeout(
|
|
166
|
+
async () => {
|
|
167
|
+
if (this.isUnsubscribing) {
|
|
168
|
+
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (this.receivingData) {
|
|
173
|
+
if (this.resubOpts?.logResubMessages) {
|
|
174
|
+
console.log(
|
|
175
|
+
`No ws data from ${this.subscriptionName} in ${this.resubOpts?.resubTimeoutMs}ms, resubscribing`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
await this.unsubscribe(true);
|
|
179
|
+
this.receivingData = false;
|
|
180
|
+
await this.subscribe(this.onChange);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
this.resubOpts?.resubTimeoutMs
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
handleRpcResponse(
|
|
188
|
+
context: { slot: bigint },
|
|
189
|
+
accountInfo?: AccountInfoBase &
|
|
190
|
+
(AccountInfoWithBase58EncodedData | AccountInfoWithBase64EncodedData)
|
|
191
|
+
): void {
|
|
192
|
+
const newSlot = Number(context.slot);
|
|
193
|
+
let newBuffer: Buffer | undefined = undefined;
|
|
194
|
+
|
|
195
|
+
if (accountInfo) {
|
|
196
|
+
// Extract data from gill response
|
|
197
|
+
if (accountInfo.data) {
|
|
198
|
+
// Handle different data formats from gill
|
|
199
|
+
if (Array.isArray(accountInfo.data)) {
|
|
200
|
+
// If it's a tuple [data, encoding]
|
|
201
|
+
const [data, encoding] = accountInfo.data;
|
|
202
|
+
|
|
203
|
+
if (encoding === ('base58' as any)) {
|
|
204
|
+
// Convert base58 to buffer using bs58
|
|
205
|
+
newBuffer = Buffer.from(bs58.decode(data));
|
|
206
|
+
} else {
|
|
207
|
+
newBuffer = Buffer.from(data, 'base64');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Convert gill's account key to PublicKey
|
|
214
|
+
// Note: accountInfo doesn't have a key property, we need to get it from the notification
|
|
215
|
+
// For now, we'll use a placeholder - this needs to be fixed based on the actual gill API
|
|
216
|
+
const accountId = new PublicKey('11111111111111111111111111111111'); // Placeholder
|
|
217
|
+
const accountIdString = accountId.toBase58();
|
|
218
|
+
|
|
219
|
+
const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
|
|
220
|
+
|
|
221
|
+
// Track WebSocket notification time for this account
|
|
222
|
+
this.lastWsNotificationTime.set(accountIdString, Date.now());
|
|
223
|
+
|
|
224
|
+
// If this account was being polled, stop polling it
|
|
225
|
+
if (this.accountsCurrentlyPolling.has(accountIdString)) {
|
|
226
|
+
this.accountsCurrentlyPolling.delete(accountIdString);
|
|
227
|
+
|
|
228
|
+
// If no more accounts are being polled, stop batch polling
|
|
229
|
+
if (
|
|
230
|
+
this.accountsCurrentlyPolling.size === 0 &&
|
|
231
|
+
this.batchPollingTimeout
|
|
232
|
+
) {
|
|
233
|
+
clearTimeout(this.batchPollingTimeout);
|
|
234
|
+
this.batchPollingTimeout = undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!existingBufferAndSlot) {
|
|
239
|
+
if (newBuffer) {
|
|
240
|
+
this.bufferAndSlotMap.set(accountIdString, {
|
|
241
|
+
buffer: newBuffer,
|
|
242
|
+
slot: newSlot,
|
|
243
|
+
});
|
|
244
|
+
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
245
|
+
this.onChange(accountId, account, { slot: newSlot }, newBuffer);
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (newSlot < existingBufferAndSlot.slot) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const oldBuffer = existingBufferAndSlot.buffer;
|
|
255
|
+
if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
|
|
256
|
+
this.bufferAndSlotMap.set(accountIdString, {
|
|
257
|
+
buffer: newBuffer,
|
|
258
|
+
slot: newSlot,
|
|
259
|
+
});
|
|
260
|
+
const account = this.decodeBuffer(this.accountDiscriminator, newBuffer);
|
|
261
|
+
this.onChange(accountId, account, { slot: newSlot }, newBuffer);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private startMonitoringForAccounts(): void {
|
|
266
|
+
// Clear any existing polling timeouts
|
|
267
|
+
this.clearPollingTimeouts();
|
|
268
|
+
|
|
269
|
+
// Start monitoring for each account in the accountsToMonitor set
|
|
270
|
+
this.accountsToMonitor.forEach((accountIdString) => {
|
|
271
|
+
this.startMonitoringForAccount(accountIdString);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private startMonitoringForAccount(accountIdString: string): void {
|
|
276
|
+
// Clear existing timeout for this account
|
|
277
|
+
const existingTimeout = this.pollingTimeouts.get(accountIdString);
|
|
278
|
+
if (existingTimeout) {
|
|
279
|
+
clearTimeout(existingTimeout);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Set up monitoring timeout - only start polling if no WS notification in 30s
|
|
283
|
+
const timeoutId = setTimeout(async () => {
|
|
284
|
+
// Check if we've received a WS notification for this account recently
|
|
285
|
+
const lastNotificationTime =
|
|
286
|
+
this.lastWsNotificationTime.get(accountIdString);
|
|
287
|
+
const currentTime = Date.now();
|
|
288
|
+
|
|
289
|
+
if (
|
|
290
|
+
!lastNotificationTime ||
|
|
291
|
+
currentTime - lastNotificationTime >= this.pollingIntervalMs
|
|
292
|
+
) {
|
|
293
|
+
// No recent WS notification, start polling
|
|
294
|
+
await this.pollAccount(accountIdString);
|
|
295
|
+
// Schedule next poll
|
|
296
|
+
this.startPollingForAccount(accountIdString);
|
|
297
|
+
} else {
|
|
298
|
+
// We received a WS notification recently, continue monitoring
|
|
299
|
+
this.startMonitoringForAccount(accountIdString);
|
|
300
|
+
}
|
|
301
|
+
}, this.pollingIntervalMs);
|
|
302
|
+
|
|
303
|
+
this.pollingTimeouts.set(accountIdString, timeoutId);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private startPollingForAccount(accountIdString: string): void {
|
|
307
|
+
// Add account to polling set
|
|
308
|
+
this.accountsCurrentlyPolling.add(accountIdString);
|
|
309
|
+
|
|
310
|
+
// If this is the first account being polled, start batch polling
|
|
311
|
+
if (this.accountsCurrentlyPolling.size === 1) {
|
|
312
|
+
this.startBatchPolling();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private startBatchPolling(): void {
|
|
317
|
+
// Clear existing batch polling timeout
|
|
318
|
+
if (this.batchPollingTimeout) {
|
|
319
|
+
clearTimeout(this.batchPollingTimeout);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Set up batch polling interval
|
|
323
|
+
this.batchPollingTimeout = setTimeout(async () => {
|
|
324
|
+
await this.pollAllAccounts();
|
|
325
|
+
// Schedule next batch poll
|
|
326
|
+
this.startBatchPolling();
|
|
327
|
+
}, this.pollingIntervalMs);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private async pollAllAccounts(): Promise<void> {
|
|
331
|
+
try {
|
|
332
|
+
// Get all accounts currently being polled
|
|
333
|
+
const accountsToPoll = Array.from(this.accountsCurrentlyPolling);
|
|
334
|
+
if (accountsToPoll.length === 0) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Fetch all accounts in a single batch request
|
|
339
|
+
const accountAddresses = accountsToPoll.map(
|
|
340
|
+
(accountId) => accountId as Address
|
|
341
|
+
);
|
|
342
|
+
const rpcResponse = await this.rpc
|
|
343
|
+
.getMultipleAccounts(accountAddresses, {
|
|
344
|
+
commitment: this.options.commitment as GillCommitment,
|
|
345
|
+
encoding: 'base64',
|
|
346
|
+
})
|
|
347
|
+
.send();
|
|
348
|
+
|
|
349
|
+
const currentSlot = Number(rpcResponse.context.slot);
|
|
350
|
+
|
|
351
|
+
// Process each account response
|
|
352
|
+
for (let i = 0; i < accountsToPoll.length; i++) {
|
|
353
|
+
const accountIdString = accountsToPoll[i];
|
|
354
|
+
const accountInfo = rpcResponse.value[i];
|
|
355
|
+
|
|
356
|
+
if (!accountInfo) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const existingBufferAndSlot =
|
|
361
|
+
this.bufferAndSlotMap.get(accountIdString);
|
|
362
|
+
|
|
363
|
+
if (!existingBufferAndSlot) {
|
|
364
|
+
// Account not in our map yet, add it
|
|
365
|
+
let newBuffer: Buffer | undefined = undefined;
|
|
366
|
+
if (accountInfo.data) {
|
|
367
|
+
if (Array.isArray(accountInfo.data)) {
|
|
368
|
+
const [data, encoding] = accountInfo.data;
|
|
369
|
+
newBuffer = Buffer.from(data, encoding);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (newBuffer) {
|
|
374
|
+
this.bufferAndSlotMap.set(accountIdString, {
|
|
375
|
+
buffer: newBuffer,
|
|
376
|
+
slot: currentSlot,
|
|
377
|
+
});
|
|
378
|
+
const account = this.decodeBuffer(
|
|
379
|
+
this.accountDiscriminator,
|
|
380
|
+
newBuffer
|
|
381
|
+
);
|
|
382
|
+
const accountId = new PublicKey(accountIdString);
|
|
383
|
+
this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
|
|
384
|
+
}
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check if we missed an update
|
|
389
|
+
if (currentSlot > existingBufferAndSlot.slot) {
|
|
390
|
+
let newBuffer: Buffer | undefined = undefined;
|
|
391
|
+
if (accountInfo.data) {
|
|
392
|
+
if (Array.isArray(accountInfo.data)) {
|
|
393
|
+
const [data, encoding] = accountInfo.data;
|
|
394
|
+
if (encoding === ('base58' as any)) {
|
|
395
|
+
newBuffer = Buffer.from(bs58.decode(data));
|
|
396
|
+
} else {
|
|
397
|
+
newBuffer = Buffer.from(data, 'base64');
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Check if buffer has changed
|
|
403
|
+
if (
|
|
404
|
+
newBuffer &&
|
|
405
|
+
(!existingBufferAndSlot.buffer ||
|
|
406
|
+
!newBuffer.equals(existingBufferAndSlot.buffer))
|
|
407
|
+
) {
|
|
408
|
+
if (this.resubOpts?.logResubMessages) {
|
|
409
|
+
console.log(
|
|
410
|
+
`[${this.subscriptionName}] Batch polling detected missed update for account ${accountIdString}, resubscribing`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
// We missed an update, resubscribe
|
|
414
|
+
await this.unsubscribe(true);
|
|
415
|
+
this.receivingData = false;
|
|
416
|
+
await this.subscribe(this.onChange);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
if (this.resubOpts?.logResubMessages) {
|
|
423
|
+
console.log(
|
|
424
|
+
`[${this.subscriptionName}] Error batch polling accounts:`,
|
|
425
|
+
error
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private async pollAccount(accountIdString: string): Promise<void> {
|
|
432
|
+
try {
|
|
433
|
+
// Fetch current account data using gill's rpc
|
|
434
|
+
const accountAddress = accountIdString as Address;
|
|
435
|
+
const rpcResponse = await this.rpc
|
|
436
|
+
.getAccountInfo(accountAddress, {
|
|
437
|
+
commitment: this.options.commitment as GillCommitment,
|
|
438
|
+
encoding: 'base64',
|
|
439
|
+
})
|
|
440
|
+
.send();
|
|
441
|
+
|
|
442
|
+
const currentSlot = Number(rpcResponse.context.slot);
|
|
443
|
+
const existingBufferAndSlot = this.bufferAndSlotMap.get(accountIdString);
|
|
444
|
+
|
|
445
|
+
if (!existingBufferAndSlot) {
|
|
446
|
+
// Account not in our map yet, add it
|
|
447
|
+
if (rpcResponse.value) {
|
|
448
|
+
let newBuffer: Buffer | undefined = undefined;
|
|
449
|
+
if (rpcResponse.value.data) {
|
|
450
|
+
if (Array.isArray(rpcResponse.value.data)) {
|
|
451
|
+
const [data, encoding] = rpcResponse.value.data;
|
|
452
|
+
newBuffer = Buffer.from(data, encoding);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (newBuffer) {
|
|
457
|
+
this.bufferAndSlotMap.set(accountIdString, {
|
|
458
|
+
buffer: newBuffer,
|
|
459
|
+
slot: currentSlot,
|
|
460
|
+
});
|
|
461
|
+
const account = this.decodeBuffer(
|
|
462
|
+
this.accountDiscriminator,
|
|
463
|
+
newBuffer
|
|
464
|
+
);
|
|
465
|
+
const accountId = new PublicKey(accountIdString);
|
|
466
|
+
this.onChange(accountId, account, { slot: currentSlot }, newBuffer);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check if we missed an update
|
|
473
|
+
if (currentSlot > existingBufferAndSlot.slot) {
|
|
474
|
+
let newBuffer: Buffer | undefined = undefined;
|
|
475
|
+
if (rpcResponse.value) {
|
|
476
|
+
if (rpcResponse.value.data) {
|
|
477
|
+
if (Array.isArray(rpcResponse.value.data)) {
|
|
478
|
+
const [data, encoding] = rpcResponse.value.data;
|
|
479
|
+
if (encoding === ('base58' as any)) {
|
|
480
|
+
newBuffer = Buffer.from(bs58.decode(data));
|
|
481
|
+
} else {
|
|
482
|
+
newBuffer = Buffer.from(data, 'base64');
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check if buffer has changed
|
|
489
|
+
if (
|
|
490
|
+
newBuffer &&
|
|
491
|
+
(!existingBufferAndSlot.buffer ||
|
|
492
|
+
!newBuffer.equals(existingBufferAndSlot.buffer))
|
|
493
|
+
) {
|
|
494
|
+
if (this.resubOpts?.logResubMessages) {
|
|
495
|
+
console.log(
|
|
496
|
+
`[${this.subscriptionName}] Polling detected missed update for account ${accountIdString}, resubscribing`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
// We missed an update, resubscribe
|
|
500
|
+
await this.unsubscribe(true);
|
|
501
|
+
this.receivingData = false;
|
|
502
|
+
await this.subscribe(this.onChange);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
if (this.resubOpts?.logResubMessages) {
|
|
508
|
+
console.log(
|
|
509
|
+
`[${this.subscriptionName}] Error polling account ${accountIdString}:`,
|
|
510
|
+
error
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private clearPollingTimeouts(): void {
|
|
517
|
+
this.pollingTimeouts.forEach((timeoutId) => {
|
|
518
|
+
clearTimeout(timeoutId);
|
|
519
|
+
});
|
|
520
|
+
this.pollingTimeouts.clear();
|
|
521
|
+
|
|
522
|
+
// Clear batch polling timeout
|
|
523
|
+
if (this.batchPollingTimeout) {
|
|
524
|
+
clearTimeout(this.batchPollingTimeout);
|
|
525
|
+
this.batchPollingTimeout = undefined;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Clear accounts currently polling
|
|
529
|
+
this.accountsCurrentlyPolling.clear();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
unsubscribe(onResub = false): Promise<void> {
|
|
533
|
+
if (!onResub) {
|
|
534
|
+
this.resubOpts.resubTimeoutMs = undefined;
|
|
535
|
+
}
|
|
536
|
+
this.isUnsubscribing = true;
|
|
537
|
+
clearTimeout(this.timeoutId);
|
|
538
|
+
this.timeoutId = undefined;
|
|
539
|
+
|
|
540
|
+
// Clear polling timeouts
|
|
541
|
+
this.clearPollingTimeouts();
|
|
542
|
+
|
|
543
|
+
// Abort the WebSocket subscription
|
|
544
|
+
if (this.abortController) {
|
|
545
|
+
this.abortController.abort('unsubscribing');
|
|
546
|
+
this.abortController = undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.listenerId = undefined;
|
|
550
|
+
this.isUnsubscribing = false;
|
|
551
|
+
|
|
552
|
+
return Promise.resolve();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Method to add accounts to the polling list
|
|
556
|
+
addAccountToMonitor(accountId: PublicKey): void {
|
|
557
|
+
const accountIdString = accountId.toBase58();
|
|
558
|
+
this.accountsToMonitor.add(accountIdString);
|
|
559
|
+
|
|
560
|
+
// If already subscribed, start monitoring for this account
|
|
561
|
+
if (this.listenerId != null && !this.isUnsubscribing) {
|
|
562
|
+
this.startMonitoringForAccount(accountIdString);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Method to remove accounts from the polling list
|
|
567
|
+
removeAccountFromMonitor(accountId: PublicKey): void {
|
|
568
|
+
const accountIdString = accountId.toBase58();
|
|
569
|
+
this.accountsToMonitor.delete(accountIdString);
|
|
570
|
+
|
|
571
|
+
// Clear monitoring timeout for this account
|
|
572
|
+
const timeoutId = this.pollingTimeouts.get(accountIdString);
|
|
573
|
+
if (timeoutId) {
|
|
574
|
+
clearTimeout(timeoutId);
|
|
575
|
+
this.pollingTimeouts.delete(accountIdString);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Remove from currently polling set if it was being polled
|
|
579
|
+
this.accountsCurrentlyPolling.delete(accountIdString);
|
|
580
|
+
|
|
581
|
+
// If no more accounts are being polled, stop batch polling
|
|
582
|
+
if (this.accountsCurrentlyPolling.size === 0 && this.batchPollingTimeout) {
|
|
583
|
+
clearTimeout(this.batchPollingTimeout);
|
|
584
|
+
this.batchPollingTimeout = undefined;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Method to set polling interval
|
|
589
|
+
setPollingInterval(intervalMs: number): void {
|
|
590
|
+
this.pollingIntervalMs = intervalMs;
|
|
591
|
+
// Restart monitoring with new interval if already subscribed
|
|
592
|
+
if (this.listenerId != null && !this.isUnsubscribing) {
|
|
593
|
+
this.startMonitoringForAccounts();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
package/src/addresses/pda.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { PublicKey } from '@solana/web3.js';
|
|
2
2
|
import * as anchor from '@coral-xyz/anchor';
|
|
3
3
|
import { BN } from '@coral-xyz/anchor';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
getAssociatedTokenAddress,
|
|
6
|
+
TOKEN_2022_PROGRAM_ID,
|
|
7
|
+
TOKEN_PROGRAM_ID,
|
|
8
|
+
} from '@solana/spl-token';
|
|
5
9
|
import { SpotMarketAccount, TokenProgramFlag } from '../types';
|
|
6
10
|
|
|
7
11
|
export async function getDriftStateAccountPublicKeyAndNonce(
|
|
@@ -420,3 +424,113 @@ export function getRevenueShareEscrowAccountPublicKey(
|
|
|
420
424
|
programId
|
|
421
425
|
)[0];
|
|
422
426
|
}
|
|
427
|
+
|
|
428
|
+
export function getLpPoolPublicKey(
|
|
429
|
+
programId: PublicKey,
|
|
430
|
+
lpPoolId: number
|
|
431
|
+
): PublicKey {
|
|
432
|
+
return PublicKey.findProgramAddressSync(
|
|
433
|
+
[
|
|
434
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('lp_pool')),
|
|
435
|
+
new anchor.BN(lpPoolId).toArrayLike(Buffer, 'le', 1),
|
|
436
|
+
],
|
|
437
|
+
programId
|
|
438
|
+
)[0];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function getLpPoolTokenVaultPublicKey(
|
|
442
|
+
programId: PublicKey,
|
|
443
|
+
lpPool: PublicKey
|
|
444
|
+
): PublicKey {
|
|
445
|
+
return PublicKey.findProgramAddressSync(
|
|
446
|
+
[
|
|
447
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('LP_POOL_TOKEN_VAULT')),
|
|
448
|
+
lpPool.toBuffer(),
|
|
449
|
+
],
|
|
450
|
+
programId
|
|
451
|
+
)[0];
|
|
452
|
+
}
|
|
453
|
+
export function getAmmConstituentMappingPublicKey(
|
|
454
|
+
programId: PublicKey,
|
|
455
|
+
lpPoolPublicKey: PublicKey
|
|
456
|
+
): PublicKey {
|
|
457
|
+
return PublicKey.findProgramAddressSync(
|
|
458
|
+
[
|
|
459
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('AMM_MAP')),
|
|
460
|
+
lpPoolPublicKey.toBuffer(),
|
|
461
|
+
],
|
|
462
|
+
programId
|
|
463
|
+
)[0];
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function getConstituentTargetBasePublicKey(
|
|
467
|
+
programId: PublicKey,
|
|
468
|
+
lpPoolPublicKey: PublicKey
|
|
469
|
+
): PublicKey {
|
|
470
|
+
return PublicKey.findProgramAddressSync(
|
|
471
|
+
[
|
|
472
|
+
Buffer.from(
|
|
473
|
+
anchor.utils.bytes.utf8.encode('constituent_target_base_seed')
|
|
474
|
+
),
|
|
475
|
+
lpPoolPublicKey.toBuffer(),
|
|
476
|
+
],
|
|
477
|
+
programId
|
|
478
|
+
)[0];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function getConstituentPublicKey(
|
|
482
|
+
programId: PublicKey,
|
|
483
|
+
lpPoolPublicKey: PublicKey,
|
|
484
|
+
spotMarketIndex: number
|
|
485
|
+
): PublicKey {
|
|
486
|
+
return PublicKey.findProgramAddressSync(
|
|
487
|
+
[
|
|
488
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('CONSTITUENT')),
|
|
489
|
+
lpPoolPublicKey.toBuffer(),
|
|
490
|
+
new anchor.BN(spotMarketIndex).toArrayLike(Buffer, 'le', 2),
|
|
491
|
+
],
|
|
492
|
+
programId
|
|
493
|
+
)[0];
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
export function getConstituentVaultPublicKey(
|
|
497
|
+
programId: PublicKey,
|
|
498
|
+
lpPoolPublicKey: PublicKey,
|
|
499
|
+
spotMarketIndex: number
|
|
500
|
+
): PublicKey {
|
|
501
|
+
return PublicKey.findProgramAddressSync(
|
|
502
|
+
[
|
|
503
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('CONSTITUENT_VAULT')),
|
|
504
|
+
lpPoolPublicKey.toBuffer(),
|
|
505
|
+
new anchor.BN(spotMarketIndex).toArrayLike(Buffer, 'le', 2),
|
|
506
|
+
],
|
|
507
|
+
programId
|
|
508
|
+
)[0];
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function getAmmCachePublicKey(programId: PublicKey): PublicKey {
|
|
512
|
+
return PublicKey.findProgramAddressSync(
|
|
513
|
+
[Buffer.from(anchor.utils.bytes.utf8.encode('amm_cache_seed'))],
|
|
514
|
+
programId
|
|
515
|
+
)[0];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function getConstituentCorrelationsPublicKey(
|
|
519
|
+
programId: PublicKey,
|
|
520
|
+
lpPoolPublicKey: PublicKey
|
|
521
|
+
): PublicKey {
|
|
522
|
+
return PublicKey.findProgramAddressSync(
|
|
523
|
+
[
|
|
524
|
+
Buffer.from(anchor.utils.bytes.utf8.encode('constituent_correlations')),
|
|
525
|
+
lpPoolPublicKey.toBuffer(),
|
|
526
|
+
],
|
|
527
|
+
programId
|
|
528
|
+
)[0];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export async function getLpPoolTokenTokenAccountPublicKey(
|
|
532
|
+
lpPoolTokenMint: PublicKey,
|
|
533
|
+
authority: PublicKey
|
|
534
|
+
): Promise<PublicKey> {
|
|
535
|
+
return await getAssociatedTokenAddress(lpPoolTokenMint, authority, true);
|
|
536
|
+
}
|