Skip to main content
Polling subscribers use periodic RPC calls to fetch account data in batches. They are ideal for scenarios where WebSocket connections are unreliable or rate limits are a concern.

BulkAccountLoader

The BulkAccountLoader is the core mechanism for polling-based subscriptions. It efficiently batches multiple account requests into single RPC calls.

Constructor

new BulkAccountLoader(
  connection: Connection,
  commitment: Commitment,
  pollingFrequency: number
)
connection
Connection
required
Solana connection instance
commitment
Commitment
required
Commitment level for account fetches (e.g., ‘confirmed’, ‘finalized’)
pollingFrequency
number
required
Polling interval in milliseconds (e.g., 1000 for 1 second)

Methods

addAccount

Add an account to the polling set.
const callbackId = await accountLoader.addAccount(
  publicKey: PublicKey,
  callback: (buffer: Buffer, slot: number) => void
): Promise<string>
publicKey
PublicKey
required
Public key of the account to poll
callback
(buffer: Buffer, slot: number) => void
required
Callback function invoked when account data updates
Returns: Callback ID for later removal

removeAccount

Remove an account from the polling set.
accountLoader.removeAccount(publicKey: PublicKey, callbackId: string): void

load

Manually trigger a load of all accounts.
await accountLoader.load(): Promise<void>

updatePollingFrequency

Change the polling frequency.
accountLoader.updatePollingFrequency(pollingFrequency: number): void
pollingFrequency
number
required
New polling interval in milliseconds

Example Usage

import { BulkAccountLoader } from '@drift-labs/sdk';
import { Connection, PublicKey } from '@solana/web3.js';

const connection = new Connection('https://api.mainnet-beta.solana.com');

// Create bulk account loader with 1 second polling
const accountLoader = new BulkAccountLoader(
  connection,
  'confirmed',
  1000 // Poll every 1 second
);

// Add accounts to poll
const userPubkey = new PublicKey('...');
const callbackId = await accountLoader.addAccount(
  userPubkey,
  (buffer, slot) => {
    if (buffer) {
      console.log('Account updated at slot', slot);
      // Decode buffer as needed
    }
  }
);

// Manually trigger load
await accountLoader.load();

// Update polling frequency to 5 seconds
accountLoader.updatePollingFrequency(5000);

// Remove account
accountLoader.removeAccount(userPubkey, callbackId);

PollingUserAccountSubscriber

Poll-based subscriber for user accounts.

Constructor

new PollingUserAccountSubscriber(
  connection: Connection,
  userAccountPublicKey: PublicKey,
  accountLoader: BulkAccountLoader,
  decode: (name: string, buffer: Buffer) => UserAccount
)
connection
Connection
required
Solana connection instance
userAccountPublicKey
PublicKey
required
Public key of the user account to subscribe to
accountLoader
BulkAccountLoader
required
Shared bulk account loader instance
decode
(name: string, buffer: Buffer) => UserAccount
required
Function to decode account buffer into UserAccount

Methods

subscribe

Start polling the user account.
await subscriber.subscribe(userAccount?: UserAccount): Promise<boolean>
userAccount
UserAccount
Optional initial user account data

getUserAccountAndSlot

Get the current user account data.
const { data, slot } = subscriber.getUserAccountAndSlot();
Returns: DataAndSlot<UserAccount> Throws: NotSubscribedError if account hasn’t been fetched

updateData

Manually update the user account data.
subscriber.updateData(userAccount: UserAccount, slot: number): void

Example Usage

import { PollingUserAccountSubscriber, BulkAccountLoader } from '@drift-labs/sdk';
import { Connection, PublicKey } from '@solana/web3.js';
import { Program } from '@coral-xyz/anchor';

const connection = new Connection('https://api.mainnet-beta.solana.com');

// Create shared account loader
const accountLoader = new BulkAccountLoader(
  connection,
  'confirmed',
  1000
);

// Create decode function
const decode = (name: string, buffer: Buffer) => {
  return program.account[name.toLowerCase()].coder.accounts.decode(name, buffer);
};

// Create polling subscriber
const userPubkey = new PublicKey('...');
const subscriber = new PollingUserAccountSubscriber(
  connection,
  userPubkey,
  accountLoader,
  decode
);

// Listen for updates
subscriber.eventEmitter.on('userAccountUpdate', (userAccount) => {
  console.log('User account updated:', userAccount);
  console.log('Free collateral:', userAccount.freeCollateral);
});

subscriber.eventEmitter.on('error', (error) => {
  console.error('Polling error:', error);
});

// Subscribe
await subscriber.subscribe();

// Get current data
const { data: userAccount, slot } = subscriber.getUserAccountAndSlot();
console.log('Current user account at slot', slot);

// Cleanup
await subscriber.unsubscribe();

PollingDriftClientAccountSubscriber

Poll-based subscriber for Drift protocol accounts (state, markets, oracles).

Constructor

new PollingDriftClientAccountSubscriber(
  program: Program,
  accountLoader: BulkAccountLoader,
  perpMarketIndexes: number[],
  spotMarketIndexes: number[],
  oracleInfos: OracleInfo[],
  shouldFindAllMarketsAndOracles: boolean,
  delistedMarketSetting: DelistedMarketSetting
)
program
Program
required
Anchor program instance for the Drift protocol
accountLoader
BulkAccountLoader
required
Shared bulk account loader instance
perpMarketIndexes
number[]
required
Array of perpetual market indexes to poll
spotMarketIndexes
number[]
required
Array of spot market indexes to poll
oracleInfos
OracleInfo[]
required
Array of oracle information to poll
shouldFindAllMarketsAndOracles
boolean
required
Whether to automatically discover and poll all markets
delistedMarketSetting
DelistedMarketSetting
required
How to handle delisted markets: Unsubscribe, Subscribe, or Discard

Methods

subscribe

Start polling all configured accounts.
await subscriber.subscribe(): Promise<boolean>

addPerpMarket

Dynamically add a perpetual market to poll.
await subscriber.addPerpMarket(marketIndex: number): Promise<boolean>

addSpotMarket

Dynamically add a spot market to poll.
await subscriber.addSpotMarket(marketIndex: number): Promise<boolean>

addOracle

Dynamically add an oracle to poll.
await subscriber.addOracle(oracleInfo: OracleInfo): Promise<boolean>

updateAccountLoaderPollingFrequency

Update the polling frequency.
subscriber.updateAccountLoaderPollingFrequency(pollingFrequency: number): void
pollingFrequency
number
required
New polling interval in milliseconds

Example Usage

import { 
  PollingDriftClientAccountSubscriber,
  BulkAccountLoader,
  DelistedMarketSetting 
} from '@drift-labs/sdk';

const connection = new Connection('https://api.mainnet-beta.solana.com');

// Create shared account loader
const accountLoader = new BulkAccountLoader(
  connection,
  'confirmed',
  2000 // Poll every 2 seconds
);

// Create polling subscriber for all markets
const driftSubscriber = new PollingDriftClientAccountSubscriber(
  program,
  accountLoader,
  [], // perpMarketIndexes - will be auto-discovered
  [], // spotMarketIndexes - will be auto-discovered  
  [], // oracleInfos - will be auto-discovered
  true, // shouldFindAllMarketsAndOracles
  DelistedMarketSetting.Discard
);

// Listen for events
driftSubscriber.eventEmitter.on('stateAccountUpdate', (state) => {
  console.log('State updated:', state);
});

driftSubscriber.eventEmitter.on('perpMarketAccountUpdate', (market) => {
  console.log('Perp market updated:', market.marketIndex);
});

driftSubscriber.eventEmitter.on('oraclePriceUpdate', (pubkey, source, data) => {
  console.log('Oracle price:', data.price.toString());
});

// Subscribe to all accounts
await driftSubscriber.subscribe();

// Get state account
const { data: state } = driftSubscriber.getStateAccountAndSlot();
console.log('Min perp auction duration:', state.minPerpAuctionDuration);

// Get specific market
const btcPerpMarket = driftSubscriber.getMarketAccountAndSlot(1);
if (btcPerpMarket) {
  console.log('BTC-PERP market:', btcPerpMarket.data);
}

// Adjust polling frequency to 5 seconds
driftSubscriber.updateAccountLoaderPollingFrequency(5000);

// Cleanup
await driftSubscriber.unsubscribe();

Other Polling Subscribers

The SDK provides polling subscribers for other account types:

PollingTokenAccountSubscriber

Poll SPL token account updates:
import { PollingTokenAccountSubscriber } from '@drift-labs/sdk';

const tokenSubscriber = new PollingTokenAccountSubscriber(
  connection,
  tokenAccountPublicKey,
  accountLoader
);

await tokenSubscriber.subscribe();
const { data: tokenAccount } = tokenSubscriber.getTokenAccountAndSlot();

PollingOracleAccountSubscriber

Poll oracle account updates:
import { PollingOracleAccountSubscriber } from '@drift-labs/sdk';

const oracleSubscriber = new PollingOracleAccountSubscriber(
  connection,
  oraclePublicKey,
  oracleSource,
  accountLoader,
  program
);

await oracleSubscriber.subscribe();
const { data: oracleData } = oracleSubscriber.getOraclePriceData();

Performance Considerations

Polling Frequency

Choose polling frequency based on your needs:
  • High frequency (500-1000ms): Real-time trading, liquidations
  • Medium frequency (2000-5000ms): Portfolio monitoring, dashboards
  • Low frequency (10000+ms): Background tasks, analytics

Batch Efficiency

The BulkAccountLoader automatically batches requests:
  • Uses getMultipleAccounts for efficiency
  • Chunks requests to avoid RPC limits (100 accounts per request)
  • Processes chunks in parallel (10 chunks at a time)
  • Shares a single loader across multiple subscribers

Rate Limiting

To avoid rate limits:
// Use a shared account loader
const accountLoader = new BulkAccountLoader(connection, 'confirmed', 2000);

// Share across multiple subscribers
const userSubscriber = new PollingUserAccountSubscriber(
  connection,
  userPubkey,
  accountLoader,
  decode
);

const driftSubscriber = new PollingDriftClientAccountSubscriber(
  program,
  accountLoader,
  [], [], [], true,
  DelistedMarketSetting.Discard
);

// Both subscribers share the same polling cycle

Best Practices

  1. Share account loaders - Use a single BulkAccountLoader instance across multiple subscribers
  2. Choose appropriate polling frequency - Balance between latency and RPC usage
  3. Handle errors - Listen to error events and implement retry logic
  4. Monitor slot numbers - Check slot numbers to detect stale data
  5. Clean up properly - Unsubscribe to stop polling and free resources

Advantages Over WebSocket

  • More reliable - Less affected by network issues
  • Rate limit friendly - Batches multiple accounts in single requests
  • Predictable resource usage - Fixed polling interval
  • Easier to debug - Simple request/response model
  • Better for batch operations - Efficient when monitoring many accounts

Disadvantages

  • Higher latency - Updates only arrive at polling intervals
  • More RPC calls - Regular polling even when data unchanged
  • Resource usage - Continuous polling consumes RPC quota

Build docs developers (and LLMs) love