@drift-labs/sdk
Version:
SDK for Drift Protocol
146 lines • 7.09 kB
TypeScript
/// <reference types="node" />
/// <reference types="node" />
import { BufferAndSlot, ProgramAccountSubscriber, ResubOpts } from './types';
import { Program } from '@coral-xyz/anchor';
import { Commitment, Context, MemcmpFilter, PublicKey } from '@solana/web3.js';
import { AccountInfoBase, AccountInfoWithBase58EncodedData, AccountInfoWithBase64EncodedData, Address } from 'gill';
/**
* WebSocketProgramAccountsSubscriberV2
*
* High-level overview
* - WebSocket-first subscriber for Solana program accounts that also layers in
* targeted polling to detect missed updates reliably.
* - Emits decoded account updates via the provided `onChange` callback.
* - Designed to focus extra work on the specific accounts the consumer cares
* about ("monitored accounts") while keeping baseline WS behavior for the
* full program subscription.
*
* Why polling if this is a WebSocket subscriber?
* - WS infra can stall, drop, or reorder notifications under network stress or
* provider hiccups. When that happens, critical account changes can be missed.
* - To mitigate this, the class accepts a set of accounts (provided via constructor) to monitor
* and uses light polling to verify whether a WS change was missed.
* - If polling detects a newer slot with different data than the last seen
* buffer, a centralized resubscription is triggered to restore a clean stream.
*
* Initial fetch (on subscribe)
* - On `subscribe()`, we first perform a single batched fetch of all monitored
* accounts ("initial monitor fetch").
* - Purpose: seed the internal `bufferAndSlotMap` and emit the latest state so
* consumers have up-to-date data immediately, even before WS events arrive.
* - This step does not decide resubscription; it only establishes ground truth.
*
* Continuous polling (only for monitored accounts)
* - After seeding, each monitored account is put into a monitoring cycle:
* 1) If no WS notification for an account is observed for `pollingIntervalMs`,
* we enqueue it for a batched fetch (buffered for a short window).
* 2) Once an account enters the "currently polling" set, a shared batch poll
* runs every `pollingIntervalMs` across all such accounts.
* 3) If WS notifications resume for an account, that account is removed from
* the polling set and returns to passive monitoring.
* - Polling compares the newly fetched buffer with the last stored buffer at a
* later slot. A difference indicates a missed update; we schedule a single
* resubscription (coalesced across accounts) to re-sync.
*
* Accounts the consumer cares about
* - Provide accounts up-front via the constructor `accountsToMonitor`, or add
* them dynamically with `addAccountToMonitor()` and remove with
* `removeAccountFromMonitor()`.
* - Only these accounts incur additional polling safeguards; other accounts are
* still processed from the WS stream normally.
*
* Resubscription strategy
* - Missed updates from any monitored account are coalesced and trigger a single
* resubscription after a short delay. This avoids rapid churn.
* - If `resubOpts.resubTimeoutMs` is set, an inactivity timer also performs a
* batch check of monitored accounts. If a missed update is found, the same
* centralized resubscription flow is used.
*
* Tuning knobs
* - `setPollingInterval(ms)`: adjust how often monitoring/polling runs
* (default 30s). Shorter = faster detection, higher RPC load.
* - Debounced immediate poll (~100ms): batches accounts added to polling right after inactivity.
* - Batch size for `getMultipleAccounts` is limited to 100, requests are chunked
* and processed concurrently.
*/
export declare class WebSocketProgramAccountsSubscriberV2<T> implements ProgramAccountSubscriber<T> {
subscriptionName: string;
accountDiscriminator: string;
bufferAndSlotMap: Map<string, BufferAndSlot>;
program: Program;
decodeBuffer: (accountName: string, ix: Buffer) => T;
onChange: (accountId: PublicKey, data: T, context: Context, buffer: Buffer) => void;
listenerId?: number;
resubOpts: ResubOpts;
isUnsubscribing: boolean;
timeoutId?: ReturnType<typeof setTimeout>;
options: {
filters: MemcmpFilter[];
commitment?: Commitment;
};
receivingData: boolean;
private rpc;
private rpcSubscriptions;
private abortController?;
private accountsToMonitor;
private pollingIntervalMs;
private pollingTimeouts;
private lastWsNotificationTime;
private accountsCurrentlyPolling;
private batchPollingTimeout?;
private debouncedImmediatePollTimeout?;
private debouncedImmediatePollMs;
private missedChangeDetected;
private resubscriptionTimeout?;
private accountsWithMissedUpdates;
constructor(subscriptionName: string, accountDiscriminator: string, program: Program, decodeBufferFn: (accountName: string, ix: Buffer) => T, options?: {
filters: MemcmpFilter[];
commitment?: Commitment;
}, resubOpts?: ResubOpts, accountsToMonitor?: PublicKey[]);
private handleNotificationLoop;
subscribe(onChange: (accountId: PublicKey, data: T, context: Context, buffer: Buffer) => void): Promise<void>;
protected setTimeout(): void;
handleRpcResponse(context: {
slot: bigint;
}, accountId: Address, accountInfo?: AccountInfoBase & (AccountInfoWithBase58EncodedData | AccountInfoWithBase64EncodedData)['data']): void;
private startMonitoringForAccounts;
private startMonitoringForAccount;
private scheduleDebouncedImmediatePoll;
private startBatchPolling;
private pollAllAccounts;
/**
* Fetches and populates all monitored accounts data without checking for missed changes
* This is used during initial subscription to populate data
*/
private fetchAndPopulateAllMonitoredAccounts;
/**
* Fetches all monitored accounts and checks for missed changes
* Returns true if a missed change was detected and resubscription is needed
*/
private fetchAllMonitoredAccounts;
private fetchAccountsBatch;
private clearPollingTimeouts;
/**
* Centralized resubscription handler that only resubscribes once after checking all accounts
*/
private handleResubscription;
/**
* Signal that a missed change was detected and schedule resubscription
*/
private signalMissedChange;
unsubscribe(onResub?: boolean): Promise<void>;
/**
* Add an account to the monitored set.
* - Monitored accounts are subject to initial fetch and periodic batch polls
* if WS notifications are not observed within `pollingIntervalMs`.
*/
addAccountToMonitor(accountId: PublicKey): void;
removeAccountFromMonitor(accountId: PublicKey): void;
/**
* Set the monitoring/polling interval for monitored accounts.
* Shorter intervals detect missed updates sooner but increase RPC load.
*/
setPollingInterval(intervalMs: number): void;
private updateBufferAndHandleChange;
}
//# sourceMappingURL=webSocketProgramAccountsSubscriberV2.d.ts.map