@drift-labs/sdk 2.136.0-beta.0 → 2.136.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/browser/accounts/types.d.ts +2 -0
- package/lib/browser/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
- package/lib/browser/accounts/webSocketAccountSubscriberV2.js +211 -39
- package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +87 -0
- package/lib/browser/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
- package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.d.ts +145 -0
- package/lib/browser/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
- package/lib/browser/accounts/websocketProgramUserAccountSubscriber.d.ts +22 -0
- package/lib/browser/accounts/websocketProgramUserAccountSubscriber.js +54 -0
- package/lib/browser/driftClient.js +22 -18
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/factory/bigNum.d.ts +2 -2
- package/lib/browser/factory/bigNum.js +20 -5
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/memcmp.d.ts +2 -0
- package/lib/browser/memcmp.js +19 -1
- package/lib/browser/oracles/oracleId.d.ts +5 -0
- package/lib/browser/oracles/oracleId.js +46 -1
- package/lib/browser/user.js +12 -5
- package/lib/browser/userConfig.d.ts +3 -0
- package/lib/node/accounts/types.d.ts +2 -0
- package/lib/node/accounts/types.d.ts.map +1 -1
- package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts +76 -3
- package/lib/node/accounts/webSocketAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketAccountSubscriberV2.js +211 -39
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts +88 -0
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/webSocketDriftClientAccountSubscriberV2.js +444 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts +146 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.d.ts.map +1 -0
- package/lib/node/accounts/webSocketProgramAccountsSubscriberV2.js +744 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts +23 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.d.ts.map +1 -0
- package/lib/node/accounts/websocketProgramUserAccountSubscriber.js +54 -0
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +22 -18
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/factory/bigNum.d.ts +2 -2
- package/lib/node/factory/bigNum.d.ts.map +1 -1
- package/lib/node/factory/bigNum.js +20 -5
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/memcmp.d.ts +2 -0
- package/lib/node/memcmp.d.ts.map +1 -1
- package/lib/node/memcmp.js +19 -1
- package/lib/node/oracles/oracleId.d.ts +5 -0
- package/lib/node/oracles/oracleId.d.ts.map +1 -1
- package/lib/node/oracles/oracleId.js +46 -1
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +12 -5
- package/lib/node/userConfig.d.ts +3 -0
- package/lib/node/userConfig.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/accounts/README_WebSocketAccountSubscriberV2.md +41 -0
- package/src/accounts/types.ts +3 -0
- package/src/accounts/webSocketAccountSubscriberV2.ts +243 -42
- package/src/accounts/webSocketDriftClientAccountSubscriberV2.ts +745 -0
- package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +995 -0
- package/src/accounts/websocketProgramUserAccountSubscriber.ts +94 -0
- package/src/driftClient.ts +13 -7
- package/src/driftClientConfig.ts +15 -8
- package/src/factory/bigNum.ts +22 -5
- package/src/index.ts +4 -0
- package/src/memcmp.ts +17 -0
- package/src/oracles/oracleId.ts +34 -0
- package/src/user.ts +21 -9
- package/src/userConfig.ts +3 -0
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -53
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +0 -54
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +0 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +0 -453
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -596
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.136.0-beta.
|
|
1
|
+
2.136.0-beta.2
|
|
@@ -139,6 +139,8 @@ export type DataAndSlot<T> = {
|
|
|
139
139
|
export type ResubOpts = {
|
|
140
140
|
resubTimeoutMs?: number;
|
|
141
141
|
logResubMessages?: boolean;
|
|
142
|
+
usePollingInsteadOfResub?: boolean;
|
|
143
|
+
pollingIntervalMs?: number;
|
|
142
144
|
};
|
|
143
145
|
export interface UserStatsAccountEvents {
|
|
144
146
|
userStatsAccountUpdate: (payload: UserStatsAccount) => void;
|
|
@@ -2,8 +2,56 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { DataAndSlot, AccountSubscriber, ResubOpts, BufferAndSlot } from './types';
|
|
4
4
|
import { Program } from '@coral-xyz/anchor';
|
|
5
|
-
import { AccountInfoBase, AccountInfoWithBase58EncodedData,
|
|
5
|
+
import { AccountInfoBase, AccountInfoWithBase64EncodedData, AccountInfoWithBase58EncodedData, Rpc, RpcSubscriptions, SolanaRpcSubscriptionsApi, type Commitment } from 'gill';
|
|
6
6
|
import { PublicKey } from '@solana/web3.js';
|
|
7
|
+
/**
|
|
8
|
+
* WebSocketAccountSubscriberV2
|
|
9
|
+
*
|
|
10
|
+
* High-level overview
|
|
11
|
+
* - WebSocket-first subscriber for a single Solana account with optional
|
|
12
|
+
* polling safeguards when the WS feed goes quiet.
|
|
13
|
+
* - Emits decoded updates via `onChange` and maintains the latest
|
|
14
|
+
* `{buffer, slot}` and decoded `{data, slot}` internally.
|
|
15
|
+
*
|
|
16
|
+
* Why polling if this is a WebSocket subscriber?
|
|
17
|
+
* - Under real-world conditions, WS notifications can stall or get dropped.
|
|
18
|
+
* - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
|
|
19
|
+
* - resubscribe to the WS stream (default), or
|
|
20
|
+
* - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
|
|
21
|
+
* account via RPC to check for missed changes.
|
|
22
|
+
* - Polling compares the fetched buffer to the last known buffer. If different
|
|
23
|
+
* at an equal-or-later slot, it indicates a missed update and we resubscribe
|
|
24
|
+
* to WS to restore a clean stream.
|
|
25
|
+
*
|
|
26
|
+
* Initial fetch (on subscribe)
|
|
27
|
+
* - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
|
|
28
|
+
* emit the latest account state, ensuring consumers start from ground truth
|
|
29
|
+
* even before WS events arrive.
|
|
30
|
+
*
|
|
31
|
+
* Continuous polling (opt-in)
|
|
32
|
+
* - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
|
|
33
|
+
* polling loop that periodically `fetch()`es the account and checks for
|
|
34
|
+
* changes. On change, polling stops and we resubscribe to WS.
|
|
35
|
+
* - If not set (default), the inactivity timeout immediately triggers a WS
|
|
36
|
+
* resubscription (no polling loop).
|
|
37
|
+
*
|
|
38
|
+
* Account focus
|
|
39
|
+
* - This class tracks exactly one account — the one passed to the constructor —
|
|
40
|
+
* which is by definition the account the consumer cares about. The extra
|
|
41
|
+
* logic is narrowly scoped to this account to minimize overhead.
|
|
42
|
+
*
|
|
43
|
+
* Tuning knobs
|
|
44
|
+
* - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
|
|
45
|
+
* - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
|
|
46
|
+
* - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
|
|
47
|
+
* - `resubOpts.logResubMessages`: verbose logs for diagnostics.
|
|
48
|
+
* - `commitment`: WS/RPC commitment used for reads and notifications.
|
|
49
|
+
* - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
|
|
50
|
+
*
|
|
51
|
+
* Implementation notes
|
|
52
|
+
* - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
|
|
53
|
+
* program provider’s RPC endpoint. Handles base58/base64 encoded data.
|
|
54
|
+
*/
|
|
7
55
|
export declare class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
|
|
8
56
|
dataAndSlot?: DataAndSlot<T>;
|
|
9
57
|
bufferAndSlot?: BufferAndSlot;
|
|
@@ -14,19 +62,44 @@ export declare class WebSocketAccountSubscriberV2<T> implements AccountSubscribe
|
|
|
14
62
|
decodeBufferFn: (buffer: Buffer) => T;
|
|
15
63
|
onChange: (data: T) => void;
|
|
16
64
|
listenerId?: number;
|
|
17
|
-
resubOpts
|
|
65
|
+
resubOpts: ResubOpts;
|
|
18
66
|
commitment?: Commitment;
|
|
19
67
|
isUnsubscribing: boolean;
|
|
20
68
|
timeoutId?: ReturnType<typeof setTimeout>;
|
|
69
|
+
pollingTimeoutId?: ReturnType<typeof setTimeout>;
|
|
21
70
|
receivingData: boolean;
|
|
22
71
|
private rpc;
|
|
23
72
|
private rpcSubscriptions;
|
|
24
73
|
private abortController?;
|
|
25
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Create a single-account WebSocket subscriber with optional polling fallback.
|
|
76
|
+
*
|
|
77
|
+
* @param accountName Name of the Anchor account type (used for default decode).
|
|
78
|
+
* @param program Anchor `Program` used for decoding and provider access.
|
|
79
|
+
* @param accountPublicKey Public key of the account to track.
|
|
80
|
+
* @param decodeBuffer Optional custom decode function; if omitted, uses
|
|
81
|
+
* program coder to decode `accountName`.
|
|
82
|
+
* @param resubOpts Resubscription/polling options. See class docs.
|
|
83
|
+
* @param commitment Commitment for WS and RPC operations.
|
|
84
|
+
* @param rpcSubscriptions Optional override/injection for testing.
|
|
85
|
+
* @param rpc Optional override/injection for testing.
|
|
86
|
+
*/
|
|
87
|
+
constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T, resubOpts?: ResubOpts, commitment?: Commitment, rpcSubscriptions?: RpcSubscriptions<SolanaRpcSubscriptionsApi> & string, rpc?: Rpc<any>);
|
|
26
88
|
private handleNotificationLoop;
|
|
27
89
|
subscribe(onChange: (data: T) => void): Promise<void>;
|
|
28
90
|
setData(data: T, slot?: number): void;
|
|
29
91
|
protected setTimeout(): void;
|
|
92
|
+
/**
|
|
93
|
+
* Start the polling loop (single-account).
|
|
94
|
+
* - Periodically calls `fetch()` and compares buffers to detect changes.
|
|
95
|
+
* - On detected change, stops polling and resubscribes to WS.
|
|
96
|
+
*/
|
|
97
|
+
private startPolling;
|
|
98
|
+
private stopPolling;
|
|
99
|
+
/**
|
|
100
|
+
* Fetch the current account state via RPC and process it through the same
|
|
101
|
+
* decoding and update pathway as WS notifications.
|
|
102
|
+
*/
|
|
30
103
|
fetch(): Promise<void>;
|
|
31
104
|
handleRpcResponse(context: {
|
|
32
105
|
slot: bigint;
|
|
@@ -7,17 +7,81 @@ exports.WebSocketAccountSubscriberV2 = void 0;
|
|
|
7
7
|
const utils_1 = require("./utils");
|
|
8
8
|
const gill_1 = require("gill");
|
|
9
9
|
const bs58_1 = __importDefault(require("bs58"));
|
|
10
|
+
/**
|
|
11
|
+
* WebSocketAccountSubscriberV2
|
|
12
|
+
*
|
|
13
|
+
* High-level overview
|
|
14
|
+
* - WebSocket-first subscriber for a single Solana account with optional
|
|
15
|
+
* polling safeguards when the WS feed goes quiet.
|
|
16
|
+
* - Emits decoded updates via `onChange` and maintains the latest
|
|
17
|
+
* `{buffer, slot}` and decoded `{data, slot}` internally.
|
|
18
|
+
*
|
|
19
|
+
* Why polling if this is a WebSocket subscriber?
|
|
20
|
+
* - Under real-world conditions, WS notifications can stall or get dropped.
|
|
21
|
+
* - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
|
|
22
|
+
* - resubscribe to the WS stream (default), or
|
|
23
|
+
* - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
|
|
24
|
+
* account via RPC to check for missed changes.
|
|
25
|
+
* - Polling compares the fetched buffer to the last known buffer. If different
|
|
26
|
+
* at an equal-or-later slot, it indicates a missed update and we resubscribe
|
|
27
|
+
* to WS to restore a clean stream.
|
|
28
|
+
*
|
|
29
|
+
* Initial fetch (on subscribe)
|
|
30
|
+
* - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
|
|
31
|
+
* emit the latest account state, ensuring consumers start from ground truth
|
|
32
|
+
* even before WS events arrive.
|
|
33
|
+
*
|
|
34
|
+
* Continuous polling (opt-in)
|
|
35
|
+
* - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
|
|
36
|
+
* polling loop that periodically `fetch()`es the account and checks for
|
|
37
|
+
* changes. On change, polling stops and we resubscribe to WS.
|
|
38
|
+
* - If not set (default), the inactivity timeout immediately triggers a WS
|
|
39
|
+
* resubscription (no polling loop).
|
|
40
|
+
*
|
|
41
|
+
* Account focus
|
|
42
|
+
* - This class tracks exactly one account — the one passed to the constructor —
|
|
43
|
+
* which is by definition the account the consumer cares about. The extra
|
|
44
|
+
* logic is narrowly scoped to this account to minimize overhead.
|
|
45
|
+
*
|
|
46
|
+
* Tuning knobs
|
|
47
|
+
* - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
|
|
48
|
+
* - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
|
|
49
|
+
* - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
|
|
50
|
+
* - `resubOpts.logResubMessages`: verbose logs for diagnostics.
|
|
51
|
+
* - `commitment`: WS/RPC commitment used for reads and notifications.
|
|
52
|
+
* - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
|
|
53
|
+
*
|
|
54
|
+
* Implementation notes
|
|
55
|
+
* - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
|
|
56
|
+
* program provider’s RPC endpoint. Handles base58/base64 encoded data.
|
|
57
|
+
*/
|
|
10
58
|
class WebSocketAccountSubscriberV2 {
|
|
11
|
-
|
|
12
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Create a single-account WebSocket subscriber with optional polling fallback.
|
|
61
|
+
*
|
|
62
|
+
* @param accountName Name of the Anchor account type (used for default decode).
|
|
63
|
+
* @param program Anchor `Program` used for decoding and provider access.
|
|
64
|
+
* @param accountPublicKey Public key of the account to track.
|
|
65
|
+
* @param decodeBuffer Optional custom decode function; if omitted, uses
|
|
66
|
+
* program coder to decode `accountName`.
|
|
67
|
+
* @param resubOpts Resubscription/polling options. See class docs.
|
|
68
|
+
* @param commitment Commitment for WS and RPC operations.
|
|
69
|
+
* @param rpcSubscriptions Optional override/injection for testing.
|
|
70
|
+
* @param rpc Optional override/injection for testing.
|
|
71
|
+
*/
|
|
72
|
+
constructor(accountName, program, accountPublicKey, decodeBuffer, resubOpts, commitment, rpcSubscriptions, rpc) {
|
|
13
73
|
this.isUnsubscribing = false;
|
|
14
74
|
this.accountName = accountName;
|
|
15
75
|
this.logAccountName = `${accountName}-${accountPublicKey.toBase58()}-ws-acct-subscriber-v2`;
|
|
16
76
|
this.program = program;
|
|
17
77
|
this.accountPublicKey = accountPublicKey;
|
|
18
78
|
this.decodeBufferFn = decodeBuffer;
|
|
19
|
-
this.resubOpts = resubOpts
|
|
20
|
-
|
|
79
|
+
this.resubOpts = resubOpts !== null && resubOpts !== void 0 ? resubOpts : {
|
|
80
|
+
resubTimeoutMs: 30000,
|
|
81
|
+
usePollingInsteadOfResub: true,
|
|
82
|
+
logResubMessages: false,
|
|
83
|
+
};
|
|
84
|
+
if (this.resubOpts.resubTimeoutMs < 1000) {
|
|
21
85
|
console.log(`resubTimeoutMs should be at least 1000ms to avoid spamming resub ${this.logAccountName}`);
|
|
22
86
|
}
|
|
23
87
|
this.receivingData = false;
|
|
@@ -27,32 +91,59 @@ class WebSocketAccountSubscriberV2 {
|
|
|
27
91
|
this.commitment =
|
|
28
92
|
commitment !== null && commitment !== void 0 ? commitment : this.program.provider.opts.commitment;
|
|
29
93
|
// Initialize gill client using the same RPC URL as the program provider
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
94
|
+
this.rpc = rpc
|
|
95
|
+
? rpc
|
|
96
|
+
: (() => {
|
|
97
|
+
const rpcUrl = this.program.provider.connection
|
|
98
|
+
.rpcEndpoint;
|
|
99
|
+
const { rpc } = (0, gill_1.createSolanaClient)({
|
|
100
|
+
urlOrMoniker: rpcUrl,
|
|
101
|
+
});
|
|
102
|
+
return rpc;
|
|
103
|
+
})();
|
|
104
|
+
this.rpcSubscriptions = rpcSubscriptions
|
|
105
|
+
? rpcSubscriptions
|
|
106
|
+
: (() => {
|
|
107
|
+
const rpcUrl = this.program.provider.connection
|
|
108
|
+
.rpcEndpoint;
|
|
109
|
+
const { rpcSubscriptions } = (0, gill_1.createSolanaClient)({
|
|
110
|
+
urlOrMoniker: rpcUrl,
|
|
111
|
+
});
|
|
112
|
+
return rpcSubscriptions;
|
|
113
|
+
})();
|
|
37
114
|
}
|
|
38
|
-
async handleNotificationLoop(
|
|
39
|
-
|
|
115
|
+
async handleNotificationLoop(subscriptionPromise) {
|
|
116
|
+
const subscription = await subscriptionPromise;
|
|
40
117
|
for await (const notification of subscription) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else {
|
|
48
|
-
this.handleRpcResponse(notification.context, notification.value);
|
|
118
|
+
// If we're currently polling and receive a WebSocket event, stop polling
|
|
119
|
+
if (this.pollingTimeoutId) {
|
|
120
|
+
if (this.resubOpts.logResubMessages) {
|
|
121
|
+
console.log(`[${this.logAccountName}] Received WebSocket event while polling, stopping polling`);
|
|
122
|
+
}
|
|
123
|
+
this.stopPolling();
|
|
49
124
|
}
|
|
125
|
+
this.receivingData = true;
|
|
126
|
+
clearTimeout(this.timeoutId);
|
|
127
|
+
this.handleRpcResponse(notification.context, notification.value);
|
|
128
|
+
this.setTimeout();
|
|
50
129
|
}
|
|
51
130
|
}
|
|
52
131
|
async subscribe(onChange) {
|
|
53
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Start the WebSocket subscription and (optionally) setup inactivity
|
|
134
|
+
* fallback.
|
|
135
|
+
*
|
|
136
|
+
* Flow
|
|
137
|
+
* - If we do not have initial state, perform a one-time `fetch()` to seed
|
|
138
|
+
* internal buffers and emit current data.
|
|
139
|
+
* - Subscribe to account notifications via WS.
|
|
140
|
+
* - If `resubOpts.resubTimeoutMs` is set, schedule an inactivity timeout.
|
|
141
|
+
* When it fires:
|
|
142
|
+
* - if `usePollingInsteadOfResub` is true, start polling loop;
|
|
143
|
+
* - otherwise, resubscribe to WS immediately.
|
|
144
|
+
*/
|
|
54
145
|
if (this.listenerId != null || this.isUnsubscribing) {
|
|
55
|
-
if (
|
|
146
|
+
if (this.resubOpts.logResubMessages) {
|
|
56
147
|
console.log(`[${this.logAccountName}] Subscribe returning early - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
|
|
57
148
|
}
|
|
58
149
|
return;
|
|
@@ -65,14 +156,14 @@ class WebSocketAccountSubscriberV2 {
|
|
|
65
156
|
const abortController = new AbortController();
|
|
66
157
|
this.abortController = abortController;
|
|
67
158
|
this.listenerId = Math.random(); // Unique ID for logging purposes
|
|
68
|
-
if (
|
|
159
|
+
if (this.resubOpts.resubTimeoutMs) {
|
|
69
160
|
this.receivingData = true;
|
|
70
161
|
this.setTimeout();
|
|
71
162
|
}
|
|
72
163
|
// Subscribe to account changes using gill's rpcSubscriptions
|
|
73
164
|
const pubkey = this.accountPublicKey.toBase58();
|
|
74
165
|
if ((0, gill_1.isAddress)(pubkey)) {
|
|
75
|
-
const
|
|
166
|
+
const subscriptionPromise = this.rpcSubscriptions
|
|
76
167
|
.accountNotifications(pubkey, {
|
|
77
168
|
commitment: this.commitment,
|
|
78
169
|
encoding: 'base64',
|
|
@@ -80,8 +171,11 @@ class WebSocketAccountSubscriberV2 {
|
|
|
80
171
|
.subscribe({
|
|
81
172
|
abortSignal: abortController.signal,
|
|
82
173
|
});
|
|
83
|
-
// Start notification loop
|
|
84
|
-
this.handleNotificationLoop(
|
|
174
|
+
// Start notification loop with the subscription promise
|
|
175
|
+
this.handleNotificationLoop(subscriptionPromise);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
throw new Error('Invalid account public key');
|
|
85
179
|
}
|
|
86
180
|
}
|
|
87
181
|
setData(data, slot) {
|
|
@@ -95,37 +189,108 @@ class WebSocketAccountSubscriberV2 {
|
|
|
95
189
|
};
|
|
96
190
|
}
|
|
97
191
|
setTimeout() {
|
|
98
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Schedule inactivity handling. If WS is quiet for
|
|
194
|
+
* `resubOpts.resubTimeoutMs` and `receivingData` is true, trigger either
|
|
195
|
+
* a polling loop or a resubscribe depending on options.
|
|
196
|
+
*/
|
|
99
197
|
if (!this.onChange) {
|
|
100
198
|
throw new Error('onChange callback function must be set');
|
|
101
199
|
}
|
|
102
200
|
this.timeoutId = setTimeout(async () => {
|
|
103
|
-
var _a, _b, _c, _d;
|
|
104
201
|
if (this.isUnsubscribing) {
|
|
105
202
|
// If we are in the process of unsubscribing, do not attempt to resubscribe
|
|
106
|
-
if (
|
|
203
|
+
if (this.resubOpts.logResubMessages) {
|
|
107
204
|
console.log(`[${this.logAccountName}] Timeout fired but isUnsubscribing=true, skipping resubscribe`);
|
|
108
205
|
}
|
|
109
206
|
return;
|
|
110
207
|
}
|
|
111
208
|
if (this.receivingData) {
|
|
112
|
-
if (
|
|
113
|
-
|
|
209
|
+
if (this.resubOpts.usePollingInsteadOfResub) {
|
|
210
|
+
// Use polling instead of resubscribing
|
|
211
|
+
if (this.resubOpts.logResubMessages) {
|
|
212
|
+
console.log(`[${this.logAccountName}] No ws data in ${this.resubOpts.resubTimeoutMs}ms, starting polling - listenerId=${this.listenerId}`);
|
|
213
|
+
}
|
|
214
|
+
this.startPolling();
|
|
114
215
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
216
|
+
else {
|
|
217
|
+
// Original resubscribe behavior
|
|
218
|
+
if (this.resubOpts.logResubMessages) {
|
|
219
|
+
console.log(`No ws data from ${this.logAccountName} in ${this.resubOpts.resubTimeoutMs}ms, resubscribing - listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
|
|
220
|
+
}
|
|
221
|
+
await this.unsubscribe(true);
|
|
222
|
+
this.receivingData = false;
|
|
223
|
+
await this.subscribe(this.onChange);
|
|
224
|
+
if (this.resubOpts.logResubMessages) {
|
|
225
|
+
console.log(`[${this.logAccountName}] Resubscribe completed - receivingData=${this.receivingData}, listenerId=${this.listenerId}, isUnsubscribing=${this.isUnsubscribing}`);
|
|
226
|
+
}
|
|
120
227
|
}
|
|
121
228
|
}
|
|
122
229
|
else {
|
|
123
|
-
if (
|
|
230
|
+
if (this.resubOpts.logResubMessages) {
|
|
124
231
|
console.log(`[${this.logAccountName}] Timeout fired but receivingData=false, skipping resubscribe`);
|
|
125
232
|
}
|
|
126
233
|
}
|
|
127
|
-
},
|
|
234
|
+
}, this.resubOpts.resubTimeoutMs);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Start the polling loop (single-account).
|
|
238
|
+
* - Periodically calls `fetch()` and compares buffers to detect changes.
|
|
239
|
+
* - On detected change, stops polling and resubscribes to WS.
|
|
240
|
+
*/
|
|
241
|
+
startPolling() {
|
|
242
|
+
const pollingInterval = this.resubOpts.pollingIntervalMs || 30000; // Default to 30s
|
|
243
|
+
const poll = async () => {
|
|
244
|
+
var _a, _b;
|
|
245
|
+
if (this.isUnsubscribing) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
// Store current data and buffer before polling
|
|
250
|
+
const currentBuffer = (_a = this.bufferAndSlot) === null || _a === void 0 ? void 0 : _a.buffer;
|
|
251
|
+
// Fetch latest account data
|
|
252
|
+
await this.fetch();
|
|
253
|
+
// Check if we got new data by comparing buffers
|
|
254
|
+
const newBuffer = (_b = this.bufferAndSlot) === null || _b === void 0 ? void 0 : _b.buffer;
|
|
255
|
+
const hasNewData = newBuffer && (!currentBuffer || !newBuffer.equals(currentBuffer));
|
|
256
|
+
if (hasNewData) {
|
|
257
|
+
// New data received, stop polling and resubscribe to websocket
|
|
258
|
+
if (this.resubOpts.logResubMessages) {
|
|
259
|
+
console.log(`[${this.logAccountName}] Polling detected account data change, resubscribing to websocket`);
|
|
260
|
+
}
|
|
261
|
+
await this.unsubscribe(true);
|
|
262
|
+
this.receivingData = false;
|
|
263
|
+
await this.subscribe(this.onChange);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
// No new data, continue polling
|
|
267
|
+
if (this.resubOpts.logResubMessages) {
|
|
268
|
+
console.log(`[${this.logAccountName}] Polling found no account changes, continuing to poll every ${pollingInterval}ms`);
|
|
269
|
+
}
|
|
270
|
+
this.pollingTimeoutId = setTimeout(poll, pollingInterval);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
if (this.resubOpts.logResubMessages) {
|
|
275
|
+
console.error(`[${this.logAccountName}] Error during polling:`, error);
|
|
276
|
+
}
|
|
277
|
+
// On error, continue polling
|
|
278
|
+
this.pollingTimeoutId = setTimeout(poll, pollingInterval);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
// Start polling immediately
|
|
282
|
+
poll();
|
|
283
|
+
}
|
|
284
|
+
stopPolling() {
|
|
285
|
+
if (this.pollingTimeoutId) {
|
|
286
|
+
clearTimeout(this.pollingTimeoutId);
|
|
287
|
+
this.pollingTimeoutId = undefined;
|
|
288
|
+
}
|
|
128
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Fetch the current account state via RPC and process it through the same
|
|
292
|
+
* decoding and update pathway as WS notifications.
|
|
293
|
+
*/
|
|
129
294
|
async fetch() {
|
|
130
295
|
// Use gill's rpc for fetching account info
|
|
131
296
|
const accountAddress = this.accountPublicKey.toBase58();
|
|
@@ -204,12 +369,19 @@ class WebSocketAccountSubscriberV2 {
|
|
|
204
369
|
}
|
|
205
370
|
}
|
|
206
371
|
unsubscribe(onResub = false) {
|
|
372
|
+
/**
|
|
373
|
+
* Stop timers, polling, and WS subscription.
|
|
374
|
+
* - When called during a resubscribe (`onResub=true`), we preserve
|
|
375
|
+
* `resubOpts.resubTimeoutMs` for the restarted subscription.
|
|
376
|
+
*/
|
|
207
377
|
if (!onResub && this.resubOpts) {
|
|
208
378
|
this.resubOpts.resubTimeoutMs = undefined;
|
|
209
379
|
}
|
|
210
380
|
this.isUnsubscribing = true;
|
|
211
381
|
clearTimeout(this.timeoutId);
|
|
212
382
|
this.timeoutId = undefined;
|
|
383
|
+
// Stop polling if active
|
|
384
|
+
this.stopPolling();
|
|
213
385
|
// Abort the WebSocket subscription
|
|
214
386
|
if (this.abortController) {
|
|
215
387
|
this.abortController.abort('unsubscribing');
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { AccountSubscriber, DataAndSlot, DelistedMarketSetting, DriftClientAccountEvents, DriftClientAccountSubscriber, ResubOpts } from './types';
|
|
3
|
+
import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
|
|
4
|
+
import { Program } from '@coral-xyz/anchor';
|
|
5
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { PublicKey } from '@solana/web3.js';
|
|
8
|
+
import { Commitment } from 'gill';
|
|
9
|
+
import { OracleInfo, OraclePriceData } from '../oracles/types';
|
|
10
|
+
import { OracleClientCache } from '../oracles/oracleClientCache';
|
|
11
|
+
import { WebSocketProgramAccountsSubscriberV2 } from './webSocketProgramAccountsSubscriberV2';
|
|
12
|
+
import { WebSocketAccountSubscriberV2 } from './webSocketAccountSubscriberV2';
|
|
13
|
+
export declare class WebSocketDriftClientAccountSubscriberV2 implements DriftClientAccountSubscriber {
|
|
14
|
+
isSubscribed: boolean;
|
|
15
|
+
program: Program;
|
|
16
|
+
commitment?: Commitment;
|
|
17
|
+
perpMarketIndexes: number[];
|
|
18
|
+
spotMarketIndexes: number[];
|
|
19
|
+
oracleInfos: OracleInfo[];
|
|
20
|
+
oracleClientCache: OracleClientCache;
|
|
21
|
+
resubOpts?: ResubOpts;
|
|
22
|
+
shouldFindAllMarketsAndOracles: boolean;
|
|
23
|
+
skipInitialData: boolean;
|
|
24
|
+
eventEmitter: StrictEventEmitter<EventEmitter, DriftClientAccountEvents>;
|
|
25
|
+
stateAccountSubscriber?: WebSocketAccountSubscriberV2<StateAccount>;
|
|
26
|
+
perpMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<PerpMarketAccount>;
|
|
27
|
+
perpMarketAccountLatestData: Map<number, DataAndSlot<PerpMarketAccount>>;
|
|
28
|
+
spotMarketAllAccountsSubscriber: WebSocketProgramAccountsSubscriberV2<SpotMarketAccount>;
|
|
29
|
+
spotMarketAccountLatestData: Map<number, DataAndSlot<SpotMarketAccount>>;
|
|
30
|
+
perpOracleMap: Map<number, PublicKey>;
|
|
31
|
+
perpOracleStringMap: Map<number, string>;
|
|
32
|
+
spotOracleMap: Map<number, PublicKey>;
|
|
33
|
+
spotOracleStringMap: Map<number, string>;
|
|
34
|
+
oracleSubscribers: Map<string, AccountSubscriber<OraclePriceData>>;
|
|
35
|
+
delistedMarketSetting: DelistedMarketSetting;
|
|
36
|
+
initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
|
|
37
|
+
initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
|
|
38
|
+
initialOraclePriceData: Map<string, OraclePriceData>;
|
|
39
|
+
protected isSubscribing: boolean;
|
|
40
|
+
protected subscriptionPromise: Promise<boolean>;
|
|
41
|
+
protected subscriptionPromiseResolver: (val: boolean) => void;
|
|
42
|
+
private rpc;
|
|
43
|
+
private rpcSubscriptions;
|
|
44
|
+
constructor(program: Program, perpMarketIndexes: number[], spotMarketIndexes: number[], oracleInfos: OracleInfo[], shouldFindAllMarketsAndOracles: boolean, delistedMarketSetting: DelistedMarketSetting, resubOpts?: ResubOpts, commitment?: Commitment, skipInitialData?: boolean);
|
|
45
|
+
subscribe(): Promise<boolean>;
|
|
46
|
+
chunks: <T>(array: readonly T[], size: number) => T[][];
|
|
47
|
+
fetch(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* This is a no-op method that always returns true.
|
|
50
|
+
* Unlike the previous implementation, we don't need to manually subscribe to individual perp markets
|
|
51
|
+
* because we automatically receive updates for all program account changes via a single websocket subscription.
|
|
52
|
+
* This means any new perp markets will automatically be included without explicit subscription.
|
|
53
|
+
* @param marketIndex The perp market index to add (unused)
|
|
54
|
+
* @returns Promise that resolves to true
|
|
55
|
+
*/
|
|
56
|
+
addPerpMarket(_marketIndex: number): Promise<boolean>;
|
|
57
|
+
/**
|
|
58
|
+
* This is a no-op method that always returns true.
|
|
59
|
+
* Unlike the previous implementation, we don't need to manually subscribe to individual spot markets
|
|
60
|
+
* because we automatically receive updates for all program account changes via a single websocket subscription.
|
|
61
|
+
* This means any new spot markets will automatically be included without explicit subscription.
|
|
62
|
+
* @param marketIndex The spot market index to add (unused)
|
|
63
|
+
* @returns Promise that resolves to true
|
|
64
|
+
*/
|
|
65
|
+
addSpotMarket(_marketIndex: number): Promise<boolean>;
|
|
66
|
+
setInitialData(): Promise<void>;
|
|
67
|
+
removeInitialData(): void;
|
|
68
|
+
subscribeToOracles(): Promise<boolean>;
|
|
69
|
+
subscribeToOracle(oracleInfo: OracleInfo): Promise<boolean>;
|
|
70
|
+
unsubscribeFromMarketAccounts(): Promise<void>;
|
|
71
|
+
unsubscribeFromSpotMarketAccounts(): Promise<void>;
|
|
72
|
+
unsubscribeFromOracles(): Promise<void>;
|
|
73
|
+
unsubscribe(): Promise<void>;
|
|
74
|
+
addOracle(oracleInfo: OracleInfo): Promise<boolean>;
|
|
75
|
+
setPerpOracleMap(): Promise<void>;
|
|
76
|
+
setSpotOracleMap(): Promise<void>;
|
|
77
|
+
handleDelistedMarketOracles(): Promise<void>;
|
|
78
|
+
assertIsSubscribed(): void;
|
|
79
|
+
getStateAccountAndSlot(): DataAndSlot<StateAccount>;
|
|
80
|
+
getMarketAccountAndSlot(marketIndex: number): DataAndSlot<PerpMarketAccount> | undefined;
|
|
81
|
+
getMarketAccountsAndSlots(): DataAndSlot<PerpMarketAccount>[];
|
|
82
|
+
getSpotMarketAccountAndSlot(marketIndex: number): DataAndSlot<SpotMarketAccount> | undefined;
|
|
83
|
+
getSpotMarketAccountsAndSlots(): DataAndSlot<SpotMarketAccount>[];
|
|
84
|
+
getOraclePriceDataAndSlot(oracleId: string): DataAndSlot<OraclePriceData> | undefined;
|
|
85
|
+
getOraclePriceDataAndSlotForPerpMarket(marketIndex: number): DataAndSlot<OraclePriceData> | undefined;
|
|
86
|
+
getOraclePriceDataAndSlotForSpotMarket(marketIndex: number): DataAndSlot<OraclePriceData> | undefined;
|
|
87
|
+
}
|