@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,285 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Commitment,
|
|
3
|
+
Connection,
|
|
4
|
+
MemcmpFilter,
|
|
5
|
+
PublicKey,
|
|
6
|
+
RpcResponseAndContext,
|
|
7
|
+
} from '@solana/web3.js';
|
|
8
|
+
import { ConstituentAccountSubscriber, DataAndSlot } from '../accounts/types';
|
|
9
|
+
import { ConstituentAccount } from '../types';
|
|
10
|
+
import { PollingConstituentAccountSubscriber } from './pollingConstituentAccountSubscriber';
|
|
11
|
+
import { WebSocketConstituentAccountSubscriber } from './webSocketConstituentAccountSubscriber';
|
|
12
|
+
import { DriftClient } from '../driftClient';
|
|
13
|
+
import { getConstituentFilter, getConstituentLpPoolFilter } from '../memcmp';
|
|
14
|
+
import { ZSTDDecoder } from 'zstddec';
|
|
15
|
+
import { getLpPoolPublicKey } from '../addresses/pda';
|
|
16
|
+
|
|
17
|
+
const MAX_CONSTITUENT_SIZE_BYTES = 480; // TODO: update this when account is finalized
|
|
18
|
+
|
|
19
|
+
export type ConstituentMapConfig = {
|
|
20
|
+
driftClient: DriftClient;
|
|
21
|
+
connection?: Connection;
|
|
22
|
+
subscriptionConfig:
|
|
23
|
+
| {
|
|
24
|
+
type: 'polling';
|
|
25
|
+
frequency: number;
|
|
26
|
+
commitment?: Commitment;
|
|
27
|
+
}
|
|
28
|
+
| {
|
|
29
|
+
type: 'websocket';
|
|
30
|
+
resubTimeoutMs?: number;
|
|
31
|
+
logResubMessages?: boolean;
|
|
32
|
+
commitment?: Commitment;
|
|
33
|
+
};
|
|
34
|
+
lpPoolId?: number;
|
|
35
|
+
// potentially use these to filter Constituent accounts
|
|
36
|
+
additionalFilters?: MemcmpFilter[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export interface ConstituentMapInterface {
|
|
40
|
+
subscribe(): Promise<void>;
|
|
41
|
+
unsubscribe(): Promise<void>;
|
|
42
|
+
has(key: string): boolean;
|
|
43
|
+
get(key: string): ConstituentAccount | undefined;
|
|
44
|
+
getFromSpotMarketIndex(
|
|
45
|
+
spotMarketIndex: number
|
|
46
|
+
): ConstituentAccount | undefined;
|
|
47
|
+
getFromConstituentIndex(
|
|
48
|
+
constituentIndex: number
|
|
49
|
+
): ConstituentAccount | undefined;
|
|
50
|
+
|
|
51
|
+
getWithSlot(key: string): DataAndSlot<ConstituentAccount> | undefined;
|
|
52
|
+
mustGet(key: string): Promise<ConstituentAccount>;
|
|
53
|
+
mustGetWithSlot(key: string): Promise<DataAndSlot<ConstituentAccount>>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ConstituentMap implements ConstituentMapInterface {
|
|
57
|
+
private driftClient: DriftClient;
|
|
58
|
+
private constituentMap = new Map<string, DataAndSlot<ConstituentAccount>>();
|
|
59
|
+
private constituentAccountSubscriber: ConstituentAccountSubscriber;
|
|
60
|
+
private additionalFilters?: MemcmpFilter[];
|
|
61
|
+
private commitment?: Commitment;
|
|
62
|
+
private connection?: Connection;
|
|
63
|
+
|
|
64
|
+
private constituentIndexToKeyMap = new Map<number, string>();
|
|
65
|
+
private spotMarketIndexToKeyMap = new Map<number, string>();
|
|
66
|
+
|
|
67
|
+
private lpPoolId: number;
|
|
68
|
+
|
|
69
|
+
constructor(config: ConstituentMapConfig) {
|
|
70
|
+
this.driftClient = config.driftClient;
|
|
71
|
+
this.additionalFilters = config.additionalFilters;
|
|
72
|
+
this.commitment = config.subscriptionConfig.commitment;
|
|
73
|
+
this.connection = config.connection || this.driftClient.connection;
|
|
74
|
+
this.lpPoolId = config.lpPoolId ?? 0;
|
|
75
|
+
|
|
76
|
+
if (config.subscriptionConfig.type === 'polling') {
|
|
77
|
+
this.constituentAccountSubscriber =
|
|
78
|
+
new PollingConstituentAccountSubscriber(
|
|
79
|
+
this,
|
|
80
|
+
this.driftClient.program,
|
|
81
|
+
config.subscriptionConfig.frequency,
|
|
82
|
+
config.subscriptionConfig.commitment,
|
|
83
|
+
this.getFilters()
|
|
84
|
+
);
|
|
85
|
+
} else if (config.subscriptionConfig.type === 'websocket') {
|
|
86
|
+
this.constituentAccountSubscriber =
|
|
87
|
+
new WebSocketConstituentAccountSubscriber(
|
|
88
|
+
this,
|
|
89
|
+
this.driftClient.program,
|
|
90
|
+
config.subscriptionConfig.resubTimeoutMs,
|
|
91
|
+
config.subscriptionConfig.commitment,
|
|
92
|
+
this.getFilters()
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Listen for account updates from the subscriber
|
|
97
|
+
this.constituentAccountSubscriber.eventEmitter.on(
|
|
98
|
+
'onAccountUpdate',
|
|
99
|
+
(account: ConstituentAccount, pubkey: PublicKey, slot: number) => {
|
|
100
|
+
this.updateConstituentAccount(pubkey.toString(), account, slot);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private getFilters(): MemcmpFilter[] {
|
|
106
|
+
const filters = [
|
|
107
|
+
getConstituentFilter(),
|
|
108
|
+
getConstituentLpPoolFilter(
|
|
109
|
+
getLpPoolPublicKey(this.driftClient.program.programId, this.lpPoolId)
|
|
110
|
+
),
|
|
111
|
+
];
|
|
112
|
+
if (this.additionalFilters) {
|
|
113
|
+
filters.push(...this.additionalFilters);
|
|
114
|
+
}
|
|
115
|
+
return filters;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private decode(name: string, buffer: Buffer): ConstituentAccount {
|
|
119
|
+
return this.driftClient.program.account.constituent.coder.accounts.decodeUnchecked(
|
|
120
|
+
name,
|
|
121
|
+
buffer
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public async sync(): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
const rpcRequestArgs = [
|
|
128
|
+
this.driftClient.program.programId.toBase58(),
|
|
129
|
+
{
|
|
130
|
+
commitment: this.commitment,
|
|
131
|
+
filters: this.getFilters(),
|
|
132
|
+
encoding: 'base64+zstd',
|
|
133
|
+
withContext: true,
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// @ts-ignore
|
|
138
|
+
const rpcJSONResponse: any = await this.connection._rpcRequest(
|
|
139
|
+
'getProgramAccounts',
|
|
140
|
+
rpcRequestArgs
|
|
141
|
+
);
|
|
142
|
+
const rpcResponseAndContext: RpcResponseAndContext<
|
|
143
|
+
Array<{ pubkey: PublicKey; account: { data: [string, string] } }>
|
|
144
|
+
> = rpcJSONResponse.result;
|
|
145
|
+
const slot = rpcResponseAndContext.context.slot;
|
|
146
|
+
|
|
147
|
+
const promises = rpcResponseAndContext.value.map(
|
|
148
|
+
async (programAccount) => {
|
|
149
|
+
const compressedUserData = Buffer.from(
|
|
150
|
+
programAccount.account.data[0],
|
|
151
|
+
'base64'
|
|
152
|
+
);
|
|
153
|
+
const decoder = new ZSTDDecoder();
|
|
154
|
+
await decoder.init();
|
|
155
|
+
const buffer = Buffer.from(
|
|
156
|
+
decoder.decode(compressedUserData, MAX_CONSTITUENT_SIZE_BYTES)
|
|
157
|
+
);
|
|
158
|
+
const key = programAccount.pubkey.toString();
|
|
159
|
+
const currAccountWithSlot = this.getWithSlot(key);
|
|
160
|
+
|
|
161
|
+
if (currAccountWithSlot) {
|
|
162
|
+
if (slot >= currAccountWithSlot.slot) {
|
|
163
|
+
const constituentAcc = this.decode('Constituent', buffer);
|
|
164
|
+
this.updateConstituentAccount(key, constituentAcc, slot);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
const constituentAcc = this.decode('Constituent', buffer);
|
|
168
|
+
this.updateConstituentAccount(key, constituentAcc, slot);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
await Promise.all(promises);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.log(`ConstituentMap.sync() error: ${error.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public async subscribe(): Promise<void> {
|
|
179
|
+
await this.constituentAccountSubscriber.subscribe();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public async unsubscribe(): Promise<void> {
|
|
183
|
+
await this.constituentAccountSubscriber.unsubscribe();
|
|
184
|
+
this.constituentMap.clear();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public has(key: string): boolean {
|
|
188
|
+
return this.constituentMap.has(key);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public get(key: string): ConstituentAccount | undefined {
|
|
192
|
+
return this.constituentMap.get(key)?.data;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public getFromConstituentIndex(
|
|
196
|
+
constituentIndex: number
|
|
197
|
+
): ConstituentAccount | undefined {
|
|
198
|
+
const key = this.constituentIndexToKeyMap.get(constituentIndex);
|
|
199
|
+
return key ? this.get(key) : undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public getFromSpotMarketIndex(
|
|
203
|
+
spotMarketIndex: number
|
|
204
|
+
): ConstituentAccount | undefined {
|
|
205
|
+
const key = this.spotMarketIndexToKeyMap.get(spotMarketIndex);
|
|
206
|
+
return key ? this.get(key) : undefined;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public getWithSlot(key: string): DataAndSlot<ConstituentAccount> | undefined {
|
|
210
|
+
return this.constituentMap.get(key);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public async mustGet(key: string): Promise<ConstituentAccount> {
|
|
214
|
+
if (!this.has(key)) {
|
|
215
|
+
await this.sync();
|
|
216
|
+
}
|
|
217
|
+
const result = this.constituentMap.get(key);
|
|
218
|
+
if (!result) {
|
|
219
|
+
throw new Error(`ConstituentAccount not found for key: ${key}`);
|
|
220
|
+
}
|
|
221
|
+
return result.data;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public async mustGetWithSlot(
|
|
225
|
+
key: string
|
|
226
|
+
): Promise<DataAndSlot<ConstituentAccount>> {
|
|
227
|
+
if (!this.has(key)) {
|
|
228
|
+
await this.sync();
|
|
229
|
+
}
|
|
230
|
+
const result = this.constituentMap.get(key);
|
|
231
|
+
if (!result) {
|
|
232
|
+
throw new Error(`ConstituentAccount not found for key: ${key}`);
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public size(): number {
|
|
238
|
+
return this.constituentMap.size;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public *values(): IterableIterator<ConstituentAccount> {
|
|
242
|
+
for (const dataAndSlot of this.constituentMap.values()) {
|
|
243
|
+
yield dataAndSlot.data;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public valuesWithSlot(): IterableIterator<DataAndSlot<ConstituentAccount>> {
|
|
248
|
+
return this.constituentMap.values();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public *entries(): IterableIterator<[string, ConstituentAccount]> {
|
|
252
|
+
for (const [key, dataAndSlot] of this.constituentMap.entries()) {
|
|
253
|
+
yield [key, dataAndSlot.data];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public entriesWithSlot(): IterableIterator<
|
|
258
|
+
[string, DataAndSlot<ConstituentAccount>]
|
|
259
|
+
> {
|
|
260
|
+
return this.constituentMap.entries();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public updateConstituentAccount(
|
|
264
|
+
key: string,
|
|
265
|
+
constituentAccount: ConstituentAccount,
|
|
266
|
+
slot: number
|
|
267
|
+
): void {
|
|
268
|
+
const existingData = this.getWithSlot(key);
|
|
269
|
+
if (existingData) {
|
|
270
|
+
if (slot >= existingData.slot) {
|
|
271
|
+
this.constituentMap.set(key, {
|
|
272
|
+
data: constituentAccount,
|
|
273
|
+
slot,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
this.constituentMap.set(key, {
|
|
278
|
+
data: constituentAccount,
|
|
279
|
+
slot,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
this.constituentIndexToKeyMap.set(constituentAccount.constituentIndex, key);
|
|
283
|
+
this.spotMarketIndexToKeyMap.set(constituentAccount.spotMarketIndex, key);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NotSubscribedError,
|
|
3
|
+
ConstituentAccountEvents,
|
|
4
|
+
ConstituentAccountSubscriber,
|
|
5
|
+
} from '../accounts/types';
|
|
6
|
+
import { Program } from '@coral-xyz/anchor';
|
|
7
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import { Commitment, MemcmpFilter } from '@solana/web3.js';
|
|
10
|
+
import { ConstituentMap } from './constituentMap';
|
|
11
|
+
|
|
12
|
+
export class PollingConstituentAccountSubscriber
|
|
13
|
+
implements ConstituentAccountSubscriber
|
|
14
|
+
{
|
|
15
|
+
isSubscribed: boolean;
|
|
16
|
+
program: Program;
|
|
17
|
+
frequency: number;
|
|
18
|
+
commitment?: Commitment;
|
|
19
|
+
additionalFilters?: MemcmpFilter[];
|
|
20
|
+
eventEmitter: StrictEventEmitter<EventEmitter, ConstituentAccountEvents>;
|
|
21
|
+
|
|
22
|
+
intervalId?: NodeJS.Timeout;
|
|
23
|
+
constituentMap: ConstituentMap;
|
|
24
|
+
|
|
25
|
+
public constructor(
|
|
26
|
+
constituentMap: ConstituentMap,
|
|
27
|
+
program: Program,
|
|
28
|
+
frequency: number,
|
|
29
|
+
commitment?: Commitment,
|
|
30
|
+
additionalFilters?: MemcmpFilter[]
|
|
31
|
+
) {
|
|
32
|
+
this.constituentMap = constituentMap;
|
|
33
|
+
this.isSubscribed = false;
|
|
34
|
+
this.program = program;
|
|
35
|
+
this.frequency = frequency;
|
|
36
|
+
this.commitment = commitment;
|
|
37
|
+
this.additionalFilters = additionalFilters;
|
|
38
|
+
this.eventEmitter = new EventEmitter();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async subscribe(): Promise<boolean> {
|
|
42
|
+
if (this.isSubscribed || this.frequency <= 0) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const executeSync = async () => {
|
|
47
|
+
await this.sync();
|
|
48
|
+
this.intervalId = setTimeout(executeSync, this.frequency);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Initial sync
|
|
52
|
+
await this.sync();
|
|
53
|
+
|
|
54
|
+
// Start polling
|
|
55
|
+
this.intervalId = setTimeout(executeSync, this.frequency);
|
|
56
|
+
|
|
57
|
+
this.isSubscribed = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async sync(): Promise<void> {
|
|
62
|
+
try {
|
|
63
|
+
await this.constituentMap.sync();
|
|
64
|
+
this.eventEmitter.emit('update');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.log(
|
|
67
|
+
`PollingConstituentAccountSubscriber.sync() error: ${error.message}`
|
|
68
|
+
);
|
|
69
|
+
this.eventEmitter.emit('error', error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async unsubscribe(): Promise<void> {
|
|
74
|
+
if (!this.isSubscribed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.intervalId) {
|
|
79
|
+
clearTimeout(this.intervalId);
|
|
80
|
+
this.intervalId = undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.isSubscribed = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
assertIsSubscribed(): void {
|
|
87
|
+
if (!this.isSubscribed) {
|
|
88
|
+
throw new NotSubscribedError(
|
|
89
|
+
'You must call `subscribe` before using this function'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
didSubscriptionSucceed(): boolean {
|
|
95
|
+
return this.isSubscribed;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NotSubscribedError,
|
|
3
|
+
ConstituentAccountEvents,
|
|
4
|
+
ConstituentAccountSubscriber,
|
|
5
|
+
} from '../accounts/types';
|
|
6
|
+
import { Program } from '@coral-xyz/anchor';
|
|
7
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import { Commitment, Context, MemcmpFilter, PublicKey } from '@solana/web3.js';
|
|
10
|
+
import { ConstituentAccount } from '../types';
|
|
11
|
+
import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
|
|
12
|
+
import { getConstituentFilter } from '../memcmp';
|
|
13
|
+
import { ConstituentMap } from './constituentMap';
|
|
14
|
+
|
|
15
|
+
export class WebSocketConstituentAccountSubscriber
|
|
16
|
+
implements ConstituentAccountSubscriber
|
|
17
|
+
{
|
|
18
|
+
isSubscribed: boolean;
|
|
19
|
+
resubTimeoutMs?: number;
|
|
20
|
+
commitment?: Commitment;
|
|
21
|
+
program: Program;
|
|
22
|
+
eventEmitter: StrictEventEmitter<EventEmitter, ConstituentAccountEvents>;
|
|
23
|
+
|
|
24
|
+
constituentDataAccountSubscriber: WebSocketProgramAccountSubscriber<ConstituentAccount>;
|
|
25
|
+
constituentMap: ConstituentMap;
|
|
26
|
+
private additionalFilters?: MemcmpFilter[];
|
|
27
|
+
|
|
28
|
+
public constructor(
|
|
29
|
+
constituentMap: ConstituentMap,
|
|
30
|
+
program: Program,
|
|
31
|
+
resubTimeoutMs?: number,
|
|
32
|
+
commitment?: Commitment,
|
|
33
|
+
additionalFilters?: MemcmpFilter[]
|
|
34
|
+
) {
|
|
35
|
+
this.constituentMap = constituentMap;
|
|
36
|
+
this.isSubscribed = false;
|
|
37
|
+
this.program = program;
|
|
38
|
+
this.eventEmitter = new EventEmitter();
|
|
39
|
+
this.resubTimeoutMs = resubTimeoutMs;
|
|
40
|
+
this.commitment = commitment;
|
|
41
|
+
this.additionalFilters = additionalFilters;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async subscribe(): Promise<boolean> {
|
|
45
|
+
if (this.isSubscribed) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
this.constituentDataAccountSubscriber =
|
|
49
|
+
new WebSocketProgramAccountSubscriber<ConstituentAccount>(
|
|
50
|
+
'LpPoolConstituent',
|
|
51
|
+
'Constituent',
|
|
52
|
+
this.program,
|
|
53
|
+
this.program.account.constituent.coder.accounts.decode.bind(
|
|
54
|
+
this.program.account.constituent.coder.accounts
|
|
55
|
+
),
|
|
56
|
+
{
|
|
57
|
+
filters: [getConstituentFilter(), ...(this.additionalFilters || [])],
|
|
58
|
+
commitment: this.commitment,
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await this.constituentDataAccountSubscriber.subscribe(
|
|
63
|
+
(accountId: PublicKey, account: ConstituentAccount, context: Context) => {
|
|
64
|
+
this.constituentMap.updateConstituentAccount(
|
|
65
|
+
accountId.toBase58(),
|
|
66
|
+
account,
|
|
67
|
+
context.slot
|
|
68
|
+
);
|
|
69
|
+
this.eventEmitter.emit(
|
|
70
|
+
'onAccountUpdate',
|
|
71
|
+
account,
|
|
72
|
+
accountId,
|
|
73
|
+
context.slot
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this.eventEmitter.emit('update');
|
|
79
|
+
this.isSubscribed = true;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async sync(): Promise<void> {
|
|
84
|
+
try {
|
|
85
|
+
await this.constituentMap.sync();
|
|
86
|
+
this.eventEmitter.emit('update');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.log(
|
|
89
|
+
`WebSocketConstituentAccountSubscriber.sync() error: ${error.message}`
|
|
90
|
+
);
|
|
91
|
+
this.eventEmitter.emit('error', error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async unsubscribe(): Promise<void> {
|
|
96
|
+
if (!this.isSubscribed) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await Promise.all([this.constituentDataAccountSubscriber.unsubscribe()]);
|
|
101
|
+
|
|
102
|
+
this.isSubscribed = false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
assertIsSubscribed(): void {
|
|
106
|
+
if (!this.isSubscribed) {
|
|
107
|
+
throw new NotSubscribedError(
|
|
108
|
+
'You must call `subscribe` before using this function'
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|