@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.
- package/VERSION +1 -1
- package/lib/accounts/grpcAccountSubscriber.d.ts +16 -0
- package/lib/accounts/grpcAccountSubscriber.js +155 -0
- package/lib/accounts/grpcDriftClientAccountSubscriber.d.ts +13 -0
- package/lib/accounts/grpcDriftClientAccountSubscriber.js +96 -0
- package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.d.ts +10 -0
- package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.js +30 -0
- package/lib/accounts/grpcProgramAccountSubscriber.d.ts +19 -0
- package/lib/accounts/grpcProgramAccountSubscriber.js +161 -0
- package/lib/accounts/grpcUserAccountSubscriber.d.ts +10 -0
- package/lib/accounts/grpcUserAccountSubscriber.js +28 -0
- package/lib/accounts/grpcUserStatsAccountSubscriber.d.ts +10 -0
- package/lib/accounts/grpcUserStatsAccountSubscriber.js +28 -0
- package/lib/accounts/types.d.ts +8 -0
- package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +3 -3
- package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
- package/lib/driftClient.js +35 -14
- package/lib/driftClientConfig.d.ts +6 -0
- package/lib/events/eventSubscriber.d.ts +7 -0
- package/lib/events/eventSubscriber.js +69 -32
- package/lib/events/eventsServerLogProvider.d.ts +21 -0
- package/lib/events/eventsServerLogProvider.js +121 -0
- package/lib/events/pollingLogProvider.js +1 -1
- package/lib/events/types.d.ts +12 -5
- package/lib/events/types.js +5 -1
- package/lib/events/webSocketLogProvider.js +2 -2
- package/lib/orderSubscriber/OrderSubscriber.d.ts +2 -1
- package/lib/orderSubscriber/OrderSubscriber.js +19 -4
- package/lib/orderSubscriber/grpcSubscription.d.ts +25 -0
- package/lib/orderSubscriber/grpcSubscription.js +68 -0
- package/lib/orderSubscriber/types.d.ts +9 -0
- package/lib/user.js +11 -4
- package/lib/userConfig.d.ts +6 -1
- package/lib/userMap/grpcSubscription.d.ts +26 -0
- package/lib/userMap/grpcSubscription.js +42 -0
- package/lib/userMap/userMap.js +14 -0
- package/lib/userMap/userMapConfig.d.ts +7 -0
- package/lib/userStatsConfig.d.ts +6 -0
- package/package.json +3 -1
- package/src/accounts/grpcAccountSubscriber.ts +158 -0
- package/src/accounts/grpcDriftClientAccountSubscriber.ts +196 -0
- package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +62 -0
- package/src/accounts/grpcProgramAccountSubscriber.ts +181 -0
- package/src/accounts/grpcUserAccountSubscriber.ts +48 -0
- package/src/accounts/grpcUserStatsAccountSubscriber.ts +51 -0
- package/src/accounts/types.ts +9 -0
- package/src/accounts/webSocketAccountSubscriber.ts +1 -1
- package/src/accounts/webSocketDriftClientAccountSubscriber.ts +3 -3
- package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
- package/src/driftClient.ts +28 -0
- package/src/driftClientConfig.ts +7 -0
- package/src/events/eventSubscriber.ts +125 -54
- package/src/events/eventsServerLogProvider.ts +152 -0
- package/src/events/pollingLogProvider.ts +1 -1
- package/src/events/types.ts +29 -6
- package/src/events/webSocketLogProvider.ts +4 -4
- package/src/orderSubscriber/OrderSubscriber.ts +15 -1
- package/src/orderSubscriber/grpcSubscription.ts +126 -0
- package/src/orderSubscriber/types.ts +10 -0
- package/src/user.ts +11 -0
- package/src/userConfig.ts +7 -1
- package/src/userMap/grpcSubscription.ts +83 -0
- package/src/userMap/userMap.ts +17 -1
- package/src/userMap/userMapConfig.ts +8 -0
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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 (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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(
|
|
132
|
-
|
|
133
|
-
|
|
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(
|
|
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 =
|
|
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;
|
package/src/events/types.ts
CHANGED
|
@@ -56,7 +56,11 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
|
|
|
56
56
|
commitment: 'confirmed',
|
|
57
57
|
maxTx: 4096,
|
|
58
58
|
logProviderConfig: {
|
|
59
|
-
type: '
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
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 ${
|
|
110
|
-
this.
|
|
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;
|