@drift-labs/sdk
Version:
SDK for Drift Protocol
110 lines (109 loc) • 5.31 kB
TypeScript
/// <reference types="node" />
/// <reference types="node" />
import { DataAndSlot, AccountSubscriber, ResubOpts, BufferAndSlot } from './types';
import { Program } from '@coral-xyz/anchor';
import { AccountInfoBase, AccountInfoWithBase64EncodedData, AccountInfoWithBase58EncodedData, Rpc, RpcSubscriptions, SolanaRpcSubscriptionsApi, Commitment } from 'gill';
import { PublicKey } from '@solana/web3.js';
/**
* WebSocketAccountSubscriberV2
*
* High-level overview
* - WebSocket-first subscriber for a single Solana account with optional
* polling safeguards when the WS feed goes quiet.
* - Emits decoded updates via `onChange` and maintains the latest
* `{buffer, slot}` and decoded `{data, slot}` internally.
*
* Why polling if this is a WebSocket subscriber?
* - Under real-world conditions, WS notifications can stall or get dropped.
* - When `resubOpts.resubTimeoutMs` elapses without WS data, you can either:
* - resubscribe to the WS stream (default), or
* - enable `resubOpts.usePollingInsteadOfResub` to start polling this single
* account via RPC to check for missed changes.
* - Polling compares the fetched buffer to the last known buffer. If different
* at an equal-or-later slot, it indicates a missed update and we resubscribe
* to WS to restore a clean stream.
*
* Initial fetch (on subscribe)
* - On `subscribe()`, we do a one-time RPC `fetch()` to seed internal state and
* emit the latest account state, ensuring consumers start from ground truth
* even before WS events arrive.
*
* Continuous polling (opt-in)
* - If `usePollingInsteadOfResub` is set, the inactivity timeout triggers a
* polling loop that periodically `fetch()`es the account and checks for
* changes. On change, polling stops and we resubscribe to WS.
* - If not set (default), the inactivity timeout immediately triggers a WS
* resubscription (no polling loop).
*
* Account focus
* - This class tracks exactly one account — the one passed to the constructor —
* which is by definition the account the consumer cares about. The extra
* logic is narrowly scoped to this account to minimize overhead.
*
* Tuning knobs
* - `resubOpts.resubTimeoutMs`: WS inactivity threshold before fallback.
* - `resubOpts.usePollingInsteadOfResub`: toggle polling vs immediate resub.
* - `resubOpts.pollingIntervalMs`: polling cadence (default 30s).
* - `resubOpts.logResubMessages`: verbose logs for diagnostics.
* - `commitment`: WS/RPC commitment used for reads and notifications.
* - `decodeBufferFn`: optional custom decode; defaults to Anchor coder.
*
* Implementation notes
* - Uses `gill` for both WS (`rpcSubscriptions`) and RPC (`rpc`) to match the
* program provider’s RPC endpoint. Handles base58/base64 encoded data.
*/
export declare class WebSocketAccountSubscriberV2<T> implements AccountSubscriber<T> {
dataAndSlot?: DataAndSlot<T>;
bufferAndSlot?: BufferAndSlot;
accountName: string;
logAccountName: string;
program: Program;
accountPublicKey: PublicKey;
decodeBufferFn: (buffer: Buffer) => T;
onChange: (data: T) => void;
listenerId?: number;
resubOpts: ResubOpts;
commitment?: Commitment;
isUnsubscribing: boolean;
timeoutId?: ReturnType<typeof setTimeout>;
pollingTimeoutId?: ReturnType<typeof setTimeout>;
receivingData: boolean;
private rpc;
private rpcSubscriptions;
private abortController?;
/**
* Create a single-account WebSocket subscriber with optional polling fallback.
*
* @param accountName Name of the Anchor account type (used for default decode).
* @param program Anchor `Program` used for decoding and provider access.
* @param accountPublicKey Public key of the account to track.
* @param decodeBuffer Optional custom decode function; if omitted, uses
* program coder to decode `accountName`.
* @param resubOpts Resubscription/polling options. See class docs.
* @param commitment Commitment for WS and RPC operations.
* @param rpcSubscriptions Optional override/injection for testing.
* @param rpc Optional override/injection for testing.
*/
constructor(accountName: string, program: Program, accountPublicKey: PublicKey, decodeBuffer?: (buffer: Buffer) => T, resubOpts?: ResubOpts, commitment?: Commitment, rpcSubscriptions?: RpcSubscriptions<SolanaRpcSubscriptionsApi> & string, rpc?: Rpc<any>);
private handleNotificationLoop;
subscribe(onChange: (data: T) => void): Promise<void>;
setData(data: T, slot?: number): void;
protected setTimeout(): void;
/**
* Start the polling loop (single-account).
* - Periodically calls `fetch()` and compares buffers to detect changes.
* - On detected change, stops polling and resubscribes to WS.
*/
private startPolling;
private stopPolling;
/**
* Fetch the current account state via RPC and process it through the same
* decoding and update pathway as WS notifications.
*/
fetch(): Promise<void>;
handleRpcResponse(context: {
slot: bigint;
}, accountInfo?: AccountInfoBase & (AccountInfoWithBase58EncodedData | AccountInfoWithBase64EncodedData)): void;
decodeBuffer(buffer: Buffer): T;
unsubscribe(onResub?: boolean): Promise<void>;
}