UNPKG

@drift-labs/sdk

Version:
110 lines (109 loc) 5.31 kB
/// <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>; }