@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.
Files changed (73) hide show
  1. package/VERSION +1 -1
  2. package/lib/browser/accounts/types.d.ts +13 -1
  3. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +53 -0
  4. package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
  5. package/lib/browser/addresses/pda.d.ts +9 -0
  6. package/lib/browser/addresses/pda.js +60 -1
  7. package/lib/browser/adminClient.d.ts +160 -8
  8. package/lib/browser/adminClient.js +754 -18
  9. package/lib/browser/constituentMap/constituentMap.d.ts +64 -0
  10. package/lib/browser/constituentMap/constituentMap.js +170 -0
  11. package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.d.ts +24 -0
  12. package/lib/browser/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
  13. package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.d.ts +24 -0
  14. package/lib/browser/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
  15. package/lib/browser/driftClient.d.ts +89 -2
  16. package/lib/browser/driftClient.js +486 -27
  17. package/lib/browser/driftClientConfig.d.ts +2 -7
  18. package/lib/browser/idl/drift.json +4304 -1380
  19. package/lib/browser/index.d.ts +1 -4
  20. package/lib/browser/index.js +2 -9
  21. package/lib/browser/memcmp.d.ts +3 -1
  22. package/lib/browser/memcmp.js +19 -1
  23. package/lib/browser/types.d.ts +147 -0
  24. package/lib/browser/types.js +13 -1
  25. package/lib/node/accounts/types.d.ts +13 -1
  26. package/lib/node/accounts/types.d.ts.map +1 -1
  27. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +54 -0
  28. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -0
  29. package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +453 -0
  30. package/lib/node/addresses/pda.d.ts +9 -0
  31. package/lib/node/addresses/pda.d.ts.map +1 -1
  32. package/lib/node/addresses/pda.js +60 -1
  33. package/lib/node/adminClient.d.ts +160 -8
  34. package/lib/node/adminClient.d.ts.map +1 -1
  35. package/lib/node/adminClient.js +754 -18
  36. package/lib/node/constituentMap/constituentMap.d.ts +65 -0
  37. package/lib/node/constituentMap/constituentMap.d.ts.map +1 -0
  38. package/lib/node/constituentMap/constituentMap.js +170 -0
  39. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts +25 -0
  40. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.d.ts.map +1 -0
  41. package/lib/node/constituentMap/pollingConstituentAccountSubscriber.js +60 -0
  42. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts +25 -0
  43. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.d.ts.map +1 -0
  44. package/lib/node/constituentMap/webSocketConstituentAccountSubscriber.js +58 -0
  45. package/lib/node/driftClient.d.ts +89 -2
  46. package/lib/node/driftClient.d.ts.map +1 -1
  47. package/lib/node/driftClient.js +486 -27
  48. package/lib/node/driftClientConfig.d.ts +2 -7
  49. package/lib/node/driftClientConfig.d.ts.map +1 -1
  50. package/lib/node/idl/drift.json +4304 -1380
  51. package/lib/node/index.d.ts +1 -4
  52. package/lib/node/index.d.ts.map +1 -1
  53. package/lib/node/index.js +2 -9
  54. package/lib/node/memcmp.d.ts +3 -1
  55. package/lib/node/memcmp.d.ts.map +1 -1
  56. package/lib/node/memcmp.js +19 -1
  57. package/lib/node/types.d.ts +147 -0
  58. package/lib/node/types.d.ts.map +1 -1
  59. package/lib/node/types.js +13 -1
  60. package/package.json +1 -1
  61. package/src/accounts/types.ts +20 -0
  62. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +596 -0
  63. package/src/addresses/pda.ts +115 -1
  64. package/src/adminClient.ts +1612 -41
  65. package/src/constituentMap/constituentMap.ts +285 -0
  66. package/src/constituentMap/pollingConstituentAccountSubscriber.ts +97 -0
  67. package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +112 -0
  68. package/src/driftClient.ts +1097 -17
  69. package/src/driftClientConfig.ts +8 -15
  70. package/src/idl/drift.json +4304 -1380
  71. package/src/index.ts +1 -4
  72. package/src/memcmp.ts +23 -1
  73. 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
+ }