@drift-labs/sdk 2.96.0-beta.0 → 2.96.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.
Files changed (66) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/grpcAccountSubscriber.d.ts +16 -0
  3. package/lib/accounts/grpcAccountSubscriber.js +155 -0
  4. package/lib/accounts/grpcDriftClientAccountSubscriber.d.ts +13 -0
  5. package/lib/accounts/grpcDriftClientAccountSubscriber.js +96 -0
  6. package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.d.ts +10 -0
  7. package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.js +30 -0
  8. package/lib/accounts/grpcProgramAccountSubscriber.d.ts +19 -0
  9. package/lib/accounts/grpcProgramAccountSubscriber.js +161 -0
  10. package/lib/accounts/grpcUserAccountSubscriber.d.ts +10 -0
  11. package/lib/accounts/grpcUserAccountSubscriber.js +28 -0
  12. package/lib/accounts/grpcUserStatsAccountSubscriber.d.ts +10 -0
  13. package/lib/accounts/grpcUserStatsAccountSubscriber.js +28 -0
  14. package/lib/accounts/types.d.ts +8 -0
  15. package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
  16. package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +3 -3
  17. package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
  18. package/lib/driftClient.js +35 -14
  19. package/lib/driftClientConfig.d.ts +6 -0
  20. package/lib/events/eventSubscriber.d.ts +7 -0
  21. package/lib/events/eventSubscriber.js +69 -32
  22. package/lib/events/eventsServerLogProvider.d.ts +21 -0
  23. package/lib/events/eventsServerLogProvider.js +121 -0
  24. package/lib/events/pollingLogProvider.js +1 -1
  25. package/lib/events/types.d.ts +12 -5
  26. package/lib/events/types.js +5 -1
  27. package/lib/events/webSocketLogProvider.js +2 -2
  28. package/lib/orderSubscriber/OrderSubscriber.d.ts +2 -1
  29. package/lib/orderSubscriber/OrderSubscriber.js +19 -4
  30. package/lib/orderSubscriber/grpcSubscription.d.ts +25 -0
  31. package/lib/orderSubscriber/grpcSubscription.js +68 -0
  32. package/lib/orderSubscriber/types.d.ts +9 -0
  33. package/lib/user.js +11 -4
  34. package/lib/userConfig.d.ts +6 -1
  35. package/lib/userMap/grpcSubscription.d.ts +26 -0
  36. package/lib/userMap/grpcSubscription.js +42 -0
  37. package/lib/userMap/userMap.js +14 -0
  38. package/lib/userMap/userMapConfig.d.ts +7 -0
  39. package/lib/userStatsConfig.d.ts +6 -0
  40. package/package.json +3 -1
  41. package/src/accounts/grpcAccountSubscriber.ts +158 -0
  42. package/src/accounts/grpcDriftClientAccountSubscriber.ts +196 -0
  43. package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +62 -0
  44. package/src/accounts/grpcProgramAccountSubscriber.ts +181 -0
  45. package/src/accounts/grpcUserAccountSubscriber.ts +48 -0
  46. package/src/accounts/grpcUserStatsAccountSubscriber.ts +51 -0
  47. package/src/accounts/types.ts +9 -0
  48. package/src/accounts/webSocketAccountSubscriber.ts +1 -1
  49. package/src/accounts/webSocketDriftClientAccountSubscriber.ts +3 -3
  50. package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
  51. package/src/driftClient.ts +28 -0
  52. package/src/driftClientConfig.ts +7 -0
  53. package/src/events/eventSubscriber.ts +125 -54
  54. package/src/events/eventsServerLogProvider.ts +152 -0
  55. package/src/events/pollingLogProvider.ts +1 -1
  56. package/src/events/types.ts +29 -6
  57. package/src/events/webSocketLogProvider.ts +4 -4
  58. package/src/orderSubscriber/OrderSubscriber.ts +15 -1
  59. package/src/orderSubscriber/grpcSubscription.ts +126 -0
  60. package/src/orderSubscriber/types.ts +10 -0
  61. package/src/user.ts +11 -0
  62. package/src/userConfig.ts +7 -1
  63. package/src/userMap/grpcSubscription.ts +83 -0
  64. package/src/userMap/userMap.ts +17 -1
  65. package/src/userMap/userMapConfig.ts +8 -0
  66. package/src/userStatsConfig.ts +7 -0
@@ -9,6 +9,10 @@ import {
9
9
  LogProvider,
10
10
  EventSubscriberEvents,
11
11
  WebSocketLogProviderConfig,
12
+ PollingLogProviderConfig,
13
+ EventsServerLogProviderConfig,
14
+ LogProviderType,
15
+ StreamingLogProviderConfig,
12
16
  } from './types';
13
17
  import { TxEventCache } from './txEventCache';
14
18
  import { EventList } from './eventList';
@@ -19,6 +23,7 @@ import { EventEmitter } from 'events';
19
23
  import StrictEventEmitter from 'strict-event-emitter-types';
20
24
  import { getSortFn } from './sort';
21
25
  import { parseLogs } from './parse';
26
+ import { EventsServerLogProvider } from './eventsServerLogProvider';
22
27
 
23
28
  export class EventSubscriber {
24
29
  private address: PublicKey;
@@ -27,6 +32,7 @@ export class EventSubscriber {
27
32
  private awaitTxPromises = new Map<string, Promise<void>>();
28
33
  private awaitTxResolver = new Map<string, () => void>();
29
34
  private logProvider: LogProvider;
35
+ private currentProviderType: LogProviderType;
30
36
  public eventEmitter: StrictEventEmitter<EventEmitter, EventSubscriberEvents>;
31
37
  private lastSeenSlot: number;
32
38
  private lastSeenBlockTime: number | undefined;
@@ -43,22 +49,57 @@ export class EventSubscriber {
43
49
  this.eventListMap = new Map<EventType, EventList<EventType>>();
44
50
  this.eventEmitter = new EventEmitter();
45
51
 
46
- if (this.options.logProviderConfig.type === 'websocket') {
52
+ this.currentProviderType = this.options.logProviderConfig.type;
53
+ this.initializeLogProvider();
54
+ }
55
+
56
+ private initializeLogProvider(subscribe = false) {
57
+ if (this.currentProviderType === 'websocket') {
58
+ const logProviderConfig = this.options
59
+ .logProviderConfig as WebSocketLogProviderConfig;
47
60
  this.logProvider = new WebSocketLogProvider(
48
61
  // @ts-ignore
49
62
  this.connection,
50
63
  this.address,
51
64
  this.options.commitment,
52
- this.options.logProviderConfig.resubTimeoutMs
65
+ logProviderConfig.resubTimeoutMs
53
66
  );
54
- } else {
67
+ } else if (this.currentProviderType === 'polling') {
68
+ const logProviderConfig = this.options
69
+ .logProviderConfig as PollingLogProviderConfig;
55
70
  this.logProvider = new PollingLogProvider(
56
71
  // @ts-ignore
57
72
  this.connection,
58
73
  this.address,
59
- options.commitment,
60
- this.options.logProviderConfig.frequency,
61
- this.options.logProviderConfig.batchSize
74
+ this.options.commitment,
75
+ logProviderConfig.frequency,
76
+ logProviderConfig.batchSize
77
+ );
78
+ } else if (this.currentProviderType === 'events-server') {
79
+ const logProviderConfig = this.options
80
+ .logProviderConfig as EventsServerLogProviderConfig;
81
+ this.logProvider = new EventsServerLogProvider(
82
+ logProviderConfig.url,
83
+ this.options.eventTypes,
84
+ this.options.address ? this.options.address.toString() : undefined
85
+ );
86
+ } else {
87
+ throw new Error(`Invalid log provider type: ${this.currentProviderType}`);
88
+ }
89
+
90
+ if (subscribe) {
91
+ this.logProvider.subscribe(
92
+ (txSig, slot, logs, mostRecentBlockTime, txSigIndex) => {
93
+ this.handleTxLogs(
94
+ txSig,
95
+ slot,
96
+ logs,
97
+ mostRecentBlockTime,
98
+ this.currentProviderType === 'events-server',
99
+ txSigIndex
100
+ );
101
+ },
102
+ true
62
103
  );
63
104
  }
64
105
  }
@@ -77,6 +118,33 @@ export class EventSubscriber {
77
118
  }
78
119
  }
79
120
 
121
+ /**
122
+ * Implements fallback logic for reconnecting to LogProvider. Currently terminates at polling,
123
+ * could be improved to try the original type again after some cooldown.
124
+ */
125
+ private updateFallbackProviderType(
126
+ reconnectAttempts: number,
127
+ maxReconnectAttempts: number
128
+ ) {
129
+ if (reconnectAttempts < maxReconnectAttempts) {
130
+ return;
131
+ }
132
+
133
+ let nextProviderType = this.currentProviderType;
134
+ if (this.currentProviderType === 'events-server') {
135
+ nextProviderType = 'websocket';
136
+ } else if (this.currentProviderType === 'websocket') {
137
+ nextProviderType = 'polling';
138
+ } else if (this.currentProviderType === 'polling') {
139
+ nextProviderType = 'polling';
140
+ }
141
+
142
+ console.log(
143
+ `EventSubscriber: Failing over providerType ${this.currentProviderType} to ${nextProviderType}`
144
+ );
145
+ this.currentProviderType = nextProviderType;
146
+ }
147
+
80
148
  public async subscribe(): Promise<boolean> {
81
149
  try {
82
150
  if (this.logProvider.isSubscribed()) {
@@ -85,52 +153,46 @@ export class EventSubscriber {
85
153
 
86
154
  this.populateInitialEventListMap();
87
155
 
88
- if (this.options.logProviderConfig.type === 'websocket') {
89
- if (this.options.logProviderConfig.resubTimeoutMs) {
90
- if (
91
- this.options.logProviderConfig.maxReconnectAttempts &&
92
- this.options.logProviderConfig.maxReconnectAttempts > 0
93
- ) {
94
- const logProviderConfig = this.options
95
- .logProviderConfig as WebSocketLogProviderConfig;
96
- this.logProvider.eventEmitter.on(
97
- 'reconnect',
98
- (reconnectAttempts) => {
99
- if (
100
- reconnectAttempts > logProviderConfig.maxReconnectAttempts
101
- ) {
102
- console.log('Failing over to polling');
103
- this.logProvider.eventEmitter.removeAllListeners('reconnect');
104
- this.unsubscribe().then(() => {
105
- this.logProvider = new PollingLogProvider(
106
- // @ts-ignore
107
- this.connection,
108
- this.address,
109
- this.options.commitment,
110
- logProviderConfig.fallbackFrequency,
111
- logProviderConfig.fallbackBatchSize
112
- );
113
- this.logProvider.subscribe(
114
- (txSig, slot, logs, mostRecentBlockTime) => {
115
- this.handleTxLogs(
116
- txSig,
117
- slot,
118
- logs,
119
- mostRecentBlockTime
120
- );
121
- },
122
- true
123
- );
124
- });
125
- }
156
+ if (
157
+ this.options.logProviderConfig.type === 'websocket' ||
158
+ this.options.logProviderConfig.type === 'events-server'
159
+ ) {
160
+ const logProviderConfig = this.options
161
+ .logProviderConfig as StreamingLogProviderConfig;
162
+
163
+ if (this.logProvider.eventEmitter) {
164
+ this.logProvider.eventEmitter.on(
165
+ 'reconnect',
166
+ async (reconnectAttempts) => {
167
+ if (reconnectAttempts > logProviderConfig.maxReconnectAttempts) {
168
+ console.log(
169
+ `EventSubscriber: Reconnect attempts ${reconnectAttempts}/${logProviderConfig.maxReconnectAttempts}, reconnecting...`
170
+ );
171
+ this.logProvider.eventEmitter.removeAllListeners('reconnect');
172
+ await this.unsubscribe();
173
+ this.updateFallbackProviderType(
174
+ reconnectAttempts,
175
+ logProviderConfig.maxReconnectAttempts
176
+ );
177
+ this.initializeLogProvider(true);
126
178
  }
127
- );
128
- }
179
+ }
180
+ );
129
181
  }
130
182
  }
131
- this.logProvider.subscribe((txSig, slot, logs, mostRecentBlockTime) => {
132
- this.handleTxLogs(txSig, slot, logs, mostRecentBlockTime);
133
- }, true);
183
+ this.logProvider.subscribe(
184
+ (txSig, slot, logs, mostRecentBlockTime, txSigIndex) => {
185
+ this.handleTxLogs(
186
+ txSig,
187
+ slot,
188
+ logs,
189
+ mostRecentBlockTime,
190
+ this.currentProviderType === 'events-server',
191
+ txSigIndex
192
+ );
193
+ },
194
+ true
195
+ );
134
196
 
135
197
  return true;
136
198
  } catch (e) {
@@ -144,13 +206,20 @@ export class EventSubscriber {
144
206
  txSig: TransactionSignature,
145
207
  slot: number,
146
208
  logs: string[],
147
- mostRecentBlockTime: number | undefined
209
+ mostRecentBlockTime: number | undefined,
210
+ fromEventsServer = false,
211
+ txSigIndex: number | undefined = undefined
148
212
  ): void {
149
- if (this.txEventCache.has(txSig)) {
213
+ if (!fromEventsServer && this.txEventCache.has(txSig)) {
150
214
  return;
151
215
  }
152
216
 
153
- const wrappedEvents = this.parseEventsFromLogs(txSig, slot, logs);
217
+ const wrappedEvents = this.parseEventsFromLogs(
218
+ txSig,
219
+ slot,
220
+ logs,
221
+ txSigIndex
222
+ );
154
223
 
155
224
  for (const wrappedEvent of wrappedEvents) {
156
225
  this.eventListMap.get(wrappedEvent.eventType).insert(wrappedEvent);
@@ -225,7 +294,8 @@ export class EventSubscriber {
225
294
  private parseEventsFromLogs(
226
295
  txSig: TransactionSignature,
227
296
  slot: number,
228
- logs: string[]
297
+ logs: string[],
298
+ txSigIndex: number | undefined
229
299
  ): WrappedEvents {
230
300
  const records = [];
231
301
  // @ts-ignore
@@ -238,7 +308,8 @@ export class EventSubscriber {
238
308
  event.data.txSig = txSig;
239
309
  event.data.slot = slot;
240
310
  event.data.eventType = event.name;
241
- event.data.txSigIndex = runningEventIndex;
311
+ event.data.txSigIndex =
312
+ txSigIndex !== undefined ? txSigIndex : runningEventIndex;
242
313
  records.push(event.data);
243
314
  }
244
315
  runningEventIndex++;
@@ -0,0 +1,152 @@
1
+ // import WebSocket from 'ws';
2
+ import { logProviderCallback, EventType, LogProvider } from './types';
3
+ import { EventEmitter } from 'events';
4
+
5
+ // browser support
6
+ let WebSocketImpl: typeof WebSocket;
7
+ if (typeof window !== 'undefined' && window.WebSocket) {
8
+ WebSocketImpl = window.WebSocket;
9
+ } else {
10
+ WebSocketImpl = require('ws');
11
+ }
12
+
13
+ const EVENT_SERVER_HEARTBEAT_INTERVAL_MS = 5000;
14
+ const ALLOWED_MISSED_HEARTBEATS = 3;
15
+
16
+ export class EventsServerLogProvider implements LogProvider {
17
+ private ws?: WebSocket;
18
+ private callback?: logProviderCallback;
19
+ private isUnsubscribing = false;
20
+ private externalUnsubscribe = false;
21
+ private lastHeartbeat = 0;
22
+ private timeoutId?: NodeJS.Timeout;
23
+ private reconnectAttempts = 0;
24
+ eventEmitter?: EventEmitter;
25
+
26
+ public constructor(
27
+ private readonly url: string,
28
+ private readonly eventTypes: EventType[],
29
+ private readonly userAccount?: string
30
+ ) {
31
+ this.eventEmitter = new EventEmitter();
32
+ }
33
+
34
+ public isSubscribed(): boolean {
35
+ return this.ws !== undefined;
36
+ }
37
+
38
+ public async subscribe(callback: logProviderCallback): Promise<boolean> {
39
+ if (this.ws !== undefined) {
40
+ return true;
41
+ }
42
+ this.ws = new WebSocketImpl(this.url);
43
+
44
+ this.callback = callback;
45
+ this.ws.addEventListener('open', () => {
46
+ for (const channel of this.eventTypes) {
47
+ const subscribeMessage = {
48
+ type: 'subscribe',
49
+ channel: channel,
50
+ };
51
+ if (this.userAccount) {
52
+ subscribeMessage['user'] = this.userAccount;
53
+ }
54
+ this.ws.send(JSON.stringify(subscribeMessage));
55
+ }
56
+ this.reconnectAttempts = 0;
57
+ });
58
+
59
+ this.ws.addEventListener('message', (data) => {
60
+ try {
61
+ if (!this.isUnsubscribing) {
62
+ clearTimeout(this.timeoutId);
63
+ this.setTimeout();
64
+ if (this.reconnectAttempts > 0) {
65
+ console.log(
66
+ 'eventsServerLogProvider: Resetting reconnect attempts to 0'
67
+ );
68
+ }
69
+ this.reconnectAttempts = 0;
70
+ }
71
+
72
+ const parsedData = JSON.parse(data.data.toString());
73
+ if (parsedData.channel === 'heartbeat') {
74
+ this.lastHeartbeat = Date.now();
75
+ return;
76
+ }
77
+ if (parsedData.message !== undefined) {
78
+ return;
79
+ }
80
+ const event = JSON.parse(parsedData.data);
81
+ this.callback(
82
+ event.txSig,
83
+ event.slot,
84
+ [
85
+ 'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH invoke [1]',
86
+ event.rawLog,
87
+ 'Program dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH success',
88
+ ],
89
+ undefined,
90
+ event.txSigIndex
91
+ );
92
+ } catch (error) {
93
+ console.error('Error parsing message:', error);
94
+ }
95
+ });
96
+
97
+ this.ws.addEventListener('close', () => {
98
+ console.log('eventsServerLogProvider: WebSocket closed');
99
+ });
100
+
101
+ this.ws.addEventListener('error', (error) => {
102
+ console.error('eventsServerLogProvider: WebSocket error:', error);
103
+ });
104
+
105
+ this.setTimeout();
106
+
107
+ return true;
108
+ }
109
+
110
+ public async unsubscribe(external = false): Promise<boolean> {
111
+ this.isUnsubscribing = true;
112
+ this.externalUnsubscribe = external;
113
+ if (this.timeoutId) {
114
+ clearInterval(this.timeoutId);
115
+ this.timeoutId = undefined;
116
+ }
117
+
118
+ if (this.ws !== undefined) {
119
+ this.ws.close();
120
+ this.ws = undefined;
121
+ return true;
122
+ } else {
123
+ this.isUnsubscribing = false;
124
+ return true;
125
+ }
126
+ }
127
+
128
+ private setTimeout(): void {
129
+ this.timeoutId = setTimeout(async () => {
130
+ if (this.isUnsubscribing || this.externalUnsubscribe) {
131
+ // If we are in the process of unsubscribing, do not attempt to resubscribe
132
+ return;
133
+ }
134
+
135
+ const timeSinceLastHeartbeat = Date.now() - this.lastHeartbeat;
136
+ if (
137
+ timeSinceLastHeartbeat >
138
+ EVENT_SERVER_HEARTBEAT_INTERVAL_MS * ALLOWED_MISSED_HEARTBEATS
139
+ ) {
140
+ console.log(
141
+ `eventServerLogProvider: No heartbeat in ${timeSinceLastHeartbeat}ms, resubscribing on attempt ${
142
+ this.reconnectAttempts + 1
143
+ }`
144
+ );
145
+ await this.unsubscribe();
146
+ this.reconnectAttempts++;
147
+ this.eventEmitter.emit('reconnect', this.reconnectAttempts);
148
+ this.subscribe(this.callback);
149
+ }
150
+ }, EVENT_SERVER_HEARTBEAT_INTERVAL_MS * 2);
151
+ }
152
+ }
@@ -60,7 +60,7 @@ export class PollingLogProvider implements LogProvider {
60
60
  const { mostRecentTx, transactionLogs } = response;
61
61
 
62
62
  for (const { txSig, slot, logs } of transactionLogs) {
63
- callback(txSig, slot, logs, response.mostRecentBlockTime);
63
+ callback(txSig, slot, logs, response.mostRecentBlockTime, undefined);
64
64
  }
65
65
 
66
66
  this.mostRecentSeenTx = mostRecentTx;
@@ -56,7 +56,11 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
56
56
  commitment: 'confirmed',
57
57
  maxTx: 4096,
58
58
  logProviderConfig: {
59
- type: 'websocket',
59
+ type: 'events-server',
60
+ url: 'wss://events.drift.trade/ws',
61
+ maxReconnectAttempts: 5,
62
+ fallbackFrequency: 1000,
63
+ fallbackBatchSize: 100,
60
64
  },
61
65
  };
62
66
 
@@ -126,7 +130,8 @@ export type logProviderCallback = (
126
130
  txSig: TransactionSignature,
127
131
  slot: number,
128
132
  logs: string[],
129
- mostRecentBlockTime: number | undefined
133
+ mostRecentBlockTime: number | undefined,
134
+ txSigIndex: number | undefined
130
135
  ) => void;
131
136
 
132
137
  export interface LogProvider {
@@ -139,20 +144,38 @@ export interface LogProvider {
139
144
  eventEmitter?: EventEmitter;
140
145
  }
141
146
 
142
- export type WebSocketLogProviderConfig = {
143
- type: 'websocket';
144
- resubTimeoutMs?: number;
147
+ export type LogProviderType = 'websocket' | 'polling' | 'events-server';
148
+
149
+ export type StreamingLogProviderConfig = {
150
+ /// Max number of times to try reconnecting before failing over to fallback provider
145
151
  maxReconnectAttempts?: number;
152
+ /// used for PollingLogProviderConfig on fallback
146
153
  fallbackFrequency?: number;
154
+ /// used for PollingLogProviderConfig on fallback
147
155
  fallbackBatchSize?: number;
148
156
  };
149
157
 
158
+ export type WebSocketLogProviderConfig = StreamingLogProviderConfig & {
159
+ type: 'websocket';
160
+ /// Max time to wait before resubscribing
161
+ resubTimeoutMs?: number;
162
+ };
163
+
150
164
  export type PollingLogProviderConfig = {
151
165
  type: 'polling';
166
+ /// frequency to poll for new events
152
167
  frequency: number;
168
+ /// max number of events to fetch per poll
153
169
  batchSize?: number;
154
170
  };
155
171
 
172
+ export type EventsServerLogProviderConfig = StreamingLogProviderConfig & {
173
+ type: 'events-server';
174
+ /// url of the events server
175
+ url: string;
176
+ };
177
+
156
178
  export type LogProviderConfig =
157
179
  | WebSocketLogProviderConfig
158
- | PollingLogProviderConfig;
180
+ | PollingLogProviderConfig
181
+ | EventsServerLogProviderConfig;
@@ -64,7 +64,7 @@ export class WebSocketLogProvider implements LogProvider {
64
64
  if (logs.err !== null) {
65
65
  return;
66
66
  }
67
- callback(logs.signature, ctx.slot, logs.logs, undefined);
67
+ callback(logs.signature, ctx.slot, logs.logs, undefined, undefined);
68
68
  },
69
69
  this.commitment
70
70
  );
@@ -106,9 +106,9 @@ export class WebSocketLogProvider implements LogProvider {
106
106
 
107
107
  if (this.receivingData) {
108
108
  console.log(
109
- `No log data in ${this.resubTimeoutMs}ms, resubscribing on attempt ${
110
- this.reconnectAttempts + 1
111
- }`
109
+ `webSocketLogProvider: No log data in ${
110
+ this.resubTimeoutMs
111
+ }ms, resubscribing on attempt ${this.reconnectAttempts + 1}`
112
112
  );
113
113
  await this.unsubscribe();
114
114
  this.receivingData = false;
@@ -11,11 +11,12 @@ import StrictEventEmitter from 'strict-event-emitter-types';
11
11
  import { EventEmitter } from 'events';
12
12
  import { BN } from '../index';
13
13
  import { decodeUser } from '../decode/user';
14
+ import { grpcSubscription } from './grpcSubscription';
14
15
 
15
16
  export class OrderSubscriber {
16
17
  driftClient: DriftClient;
17
18
  usersAccounts = new Map<string, { slot: number; userAccount: UserAccount }>();
18
- subscription: PollingSubscription | WebsocketSubscription;
19
+ subscription: PollingSubscription | WebsocketSubscription | grpcSubscription;
19
20
  commitment: Commitment;
20
21
  eventEmitter: StrictEventEmitter<EventEmitter, OrderSubscriberEvents>;
21
22
 
@@ -34,6 +35,19 @@ export class OrderSubscriber {
34
35
  orderSubscriber: this,
35
36
  frequency: config.subscriptionConfig.frequency,
36
37
  });
38
+ } else if (config.subscriptionConfig.type === 'grpc') {
39
+ this.subscription = new grpcSubscription({
40
+ grpcConfigs: config.subscriptionConfig.configs,
41
+ orderSubscriber: this,
42
+ commitment: this.commitment,
43
+ skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
44
+ resubOpts: {
45
+ resubTimeoutMs: config.subscriptionConfig?.resubTimeoutMs,
46
+ logResubMessages: config.subscriptionConfig?.logResubMessages,
47
+ },
48
+ resyncIntervalMs: config.subscriptionConfig.resyncIntervalMs,
49
+ decoded: config.decodeData,
50
+ });
37
51
  } else {
38
52
  this.subscription = new WebsocketSubscription({
39
53
  orderSubscriber: this,
@@ -0,0 +1,126 @@
1
+ import { Commitment, Context, PublicKey } from '@solana/web3.js';
2
+ import { Buffer } from 'buffer';
3
+ import { grpcProgramAccountSubscriber } from '../accounts/grpcProgramAccountSubscriber';
4
+ import { OrderSubscriber } from './OrderSubscriber';
5
+ import { GrpcConfigs, ResubOpts } from '../accounts/types';
6
+ import { UserAccount } from '../types';
7
+ import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
8
+
9
+ export class grpcSubscription {
10
+ private orderSubscriber: OrderSubscriber;
11
+ private commitment: Commitment;
12
+ private skipInitialLoad: boolean;
13
+ private resubOpts?: ResubOpts;
14
+ private resyncIntervalMs?: number;
15
+
16
+ private subscriber?: grpcProgramAccountSubscriber<UserAccount>;
17
+ private resyncTimeoutId?: NodeJS.Timeout;
18
+
19
+ private decoded?: boolean;
20
+
21
+ private grpcConfigs: GrpcConfigs;
22
+
23
+ constructor({
24
+ grpcConfigs,
25
+ orderSubscriber,
26
+ commitment,
27
+ skipInitialLoad = false,
28
+ resubOpts,
29
+ resyncIntervalMs,
30
+ decoded = true,
31
+ }: {
32
+ grpcConfigs: GrpcConfigs;
33
+ orderSubscriber: OrderSubscriber;
34
+ commitment: Commitment;
35
+ skipInitialLoad?: boolean;
36
+ resubOpts?: ResubOpts;
37
+ resyncIntervalMs?: number;
38
+ decoded?: boolean;
39
+ }) {
40
+ this.orderSubscriber = orderSubscriber;
41
+ this.commitment = commitment;
42
+ this.skipInitialLoad = skipInitialLoad;
43
+ this.resubOpts = resubOpts;
44
+ this.resyncIntervalMs = resyncIntervalMs;
45
+ this.decoded = decoded;
46
+ this.grpcConfigs = grpcConfigs;
47
+ }
48
+
49
+ public async subscribe(): Promise<void> {
50
+ if (this.subscriber) {
51
+ return;
52
+ }
53
+
54
+ this.subscriber = new grpcProgramAccountSubscriber<UserAccount>(
55
+ this.grpcConfigs,
56
+ 'OrderSubscriber',
57
+ 'User',
58
+ this.orderSubscriber.driftClient.program,
59
+ this.orderSubscriber.decodeFn,
60
+ {
61
+ filters: [getUserFilter(), getNonIdleUserFilter()],
62
+ commitment: this.commitment,
63
+ },
64
+ this.resubOpts
65
+ );
66
+
67
+ await this.subscriber.subscribe(
68
+ (
69
+ accountId: PublicKey,
70
+ account: UserAccount,
71
+ context: Context,
72
+ buffer: Buffer
73
+ ) => {
74
+ const userKey = accountId.toBase58();
75
+ if (this.decoded ?? true) {
76
+ this.orderSubscriber.tryUpdateUserAccount(
77
+ userKey,
78
+ 'decoded',
79
+ account,
80
+ context.slot
81
+ );
82
+ } else {
83
+ this.orderSubscriber.tryUpdateUserAccount(
84
+ userKey,
85
+ 'buffer',
86
+ buffer,
87
+ context.slot
88
+ );
89
+ }
90
+ }
91
+ );
92
+
93
+ if (!this.skipInitialLoad) {
94
+ await this.orderSubscriber.fetch();
95
+ }
96
+
97
+ if (this.resyncIntervalMs) {
98
+ const recursiveResync = () => {
99
+ this.resyncTimeoutId = setTimeout(() => {
100
+ this.orderSubscriber
101
+ .fetch()
102
+ .catch((e) => {
103
+ console.error('Failed to resync in OrderSubscriber');
104
+ console.log(e);
105
+ })
106
+ .finally(() => {
107
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
108
+ if (!this.resyncTimeoutId) return;
109
+ recursiveResync();
110
+ });
111
+ }, this.resyncIntervalMs);
112
+ };
113
+ recursiveResync();
114
+ }
115
+ }
116
+
117
+ public async unsubscribe(): Promise<void> {
118
+ if (!this.subscriber) return;
119
+ await this.subscriber.unsubscribe();
120
+ this.subscriber = undefined;
121
+ if (this.resyncTimeoutId !== undefined) {
122
+ clearTimeout(this.resyncTimeoutId);
123
+ this.resyncTimeoutId = undefined;
124
+ }
125
+ }
126
+ }
@@ -1,6 +1,7 @@
1
1
  import { Commitment, PublicKey } from '@solana/web3.js';
2
2
  import { Order, UserAccount } from '../types';
3
3
  import { DriftClient } from '../driftClient';
4
+ import { GrpcConfigs } from '../accounts/types';
4
5
 
5
6
  export type OrderSubscriberConfig = {
6
7
  driftClient: DriftClient;
@@ -10,6 +11,15 @@ export type OrderSubscriberConfig = {
10
11
  frequency: number;
11
12
  commitment?: Commitment;
12
13
  }
14
+ | {
15
+ type: 'grpc';
16
+ skipInitialLoad?: boolean;
17
+ resubTimeoutMs?: number;
18
+ logResubMessages?: boolean;
19
+ resyncIntervalMs?: number;
20
+ configs: GrpcConfigs;
21
+ commitment?: Commitment;
22
+ }
13
23
  | {
14
24
  type: 'websocket';
15
25
  skipInitialLoad?: boolean;