Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions sdk/src/accounts/bulkAccountLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { v4 as uuidv4 } from 'uuid';
import { BufferAndSlot } from './types';
import { promiseTimeout } from '../util/promiseTimeout';
import { Connection } from '../bankrun/bankrunConnection';
import { GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE } from '../constants/numericConstants';

export type AccountToLoad = {
publicKey: PublicKey;
callbacks: Map<string, (buffer: Buffer, slot: number) => void>;
};

const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;

const oneMinute = 60 * 1000;

export class BulkAccountLoader {
Expand Down
177 changes: 177 additions & 0 deletions sdk/src/accounts/customizedCadenceBulkAccountLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE } from '../constants/numericConstants';
import { BulkAccountLoader } from './bulkAccountLoader';
import { Commitment, Connection, PublicKey } from '@solana/web3.js';
import { v4 as uuidv4 } from 'uuid';

export class CustomizedCadenceBulkAccountLoader extends BulkAccountLoader {
private customIntervalId: NodeJS.Timeout | null;
private accountFrequencies: Map<string, number>;
private lastPollingTime: Map<string, number>;
private defaultPollingFrequency: number;

constructor(
connection: Connection,
commitment: Commitment,
defaultPollingFrequency: number
) {
super(connection, commitment, defaultPollingFrequency);
this.customIntervalId = null;
this.accountFrequencies = new Map();
this.lastPollingTime = new Map();
this.defaultPollingFrequency = defaultPollingFrequency;
}

private getAccountsToLoad(): Array<{
publicKey: PublicKey;
callbacks: Map<string, (buffer: Buffer, slot: number) => void>;
}> {
const currentTime = Date.now();
const accountsToLoad: Array<{
publicKey: PublicKey;
callbacks: Map<string, (buffer: Buffer, slot: number) => void>;
}> = [];

for (const [key, frequency] of this.accountFrequencies.entries()) {
const lastPollTime = this.lastPollingTime.get(key) || 0;
if (currentTime - lastPollTime >= frequency) {
const account = this.accountsToLoad.get(key);
if (account) {
accountsToLoad.push(account);
this.lastPollingTime.set(key, currentTime);
}
}
}

return accountsToLoad;
}

public async load(): Promise<void> {
return this.handleAccountLoading();
}

private async handleAccountLoading(): Promise<void> {
const accounts = this.getAccountsToLoad();

if (accounts.length > 0) {
const chunks = this.chunks(
this.chunks(accounts, GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE),
10
);

await Promise.all(
chunks.map((chunk) => {
return this.loadChunk(chunk);
})
);
}
}

public setCustomPollingFrequency(
publicKey: PublicKey,
newFrequency: number
): void {
const key = publicKey.toBase58();
this.accountFrequencies.set(key, newFrequency);
this.lastPollingTime.set(key, 0); // Reset last polling time to ensure immediate load
this.restartPollingIfNeeded(newFrequency);
}

private restartPollingIfNeeded(newFrequency: number): void {
const currentMinFrequency = Math.min(
...Array.from(this.accountFrequencies.values()),
this.defaultPollingFrequency
);

if (newFrequency < currentMinFrequency || !this.customIntervalId) {
this.stopPolling();
this.startPolling();
}
}

/**
* Adds an account to be monitored by the bulk account loader
* @param publicKey The public key of the account to monitor
* @param callback Function to be called when account data is received
* @param customPollingFrequency Optional custom polling frequency in ms for this specific account.
* If not provided, will use the default polling frequency
* @returns A unique callback ID that can be used to remove this specific callback later
*
* The method will:
* 1. Create a new callback mapping for the account
* 2. Set up polling frequency tracking for the account
* 3. Reset last polling time to 0 to ensure immediate data fetch
* 4. Automatically restart polling if this account needs a faster frequency than existing accounts
*/
public async addAccount(
publicKey: PublicKey,
callback: (buffer: Buffer, slot: number) => void,
customPollingFrequency?: number
): Promise<string> {
const callbackId = uuidv4();
const callbacks = new Map<string, (buffer: Buffer, slot: number) => void>();
callbacks.set(callbackId, callback);
const newAccountToLoad = {
publicKey,
callbacks,
};
this.accountsToLoad.set(publicKey.toString(), newAccountToLoad);

const key = publicKey.toBase58();
const frequency = customPollingFrequency || this.defaultPollingFrequency;
this.accountFrequencies.set(key, frequency);
this.lastPollingTime.set(key, 0); // Reset last polling time to ensure immediate load

this.restartPollingIfNeeded(frequency);

return callbackId;
}

public removeAccount(publicKey: PublicKey): void {
const key = publicKey.toBase58();
this.accountFrequencies.delete(key);
this.lastPollingTime.delete(key);

if (this.accountsToLoad.size === 0) {
this.stopPolling();
} else {
// Restart polling in case we removed the account with the smallest frequency
this.restartPollingIfNeeded(this.defaultPollingFrequency);
}
}

public getAccountCadence(publicKey: PublicKey): number | null {
const key = publicKey.toBase58();
return this.accountFrequencies.get(key) || null;
}

public startPolling(): void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the idea you use this instead of a normal BulkAccountLoader? when does the default frequency polling get triggered?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to replace the startPolling method to make sure things were not polled incorrectly(only using one interval) but maybe I can adjust that and maybe it seems odd the way it is...

Copy link
Collaborator Author

@LukasDeco LukasDeco Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, default frequency gets set when you add an account to this class and you don't specify a frequency, then it just uses the default(on line 96).

if (this.customIntervalId) {
return;
}

const minFrequency = Math.min(
...Array.from(this.accountFrequencies.values()),
this.defaultPollingFrequency
);

this.customIntervalId = setInterval(() => {
this.handleAccountLoading().catch((error) => {
console.error('Error in account loading:', error);
});
}, minFrequency);
}

public stopPolling(): void {
super.stopPolling();

if (this.customIntervalId) {
clearInterval(this.customIntervalId);
this.customIntervalId = null;
}
this.lastPollingTime.clear();
}

public clearAccountFrequencies(): void {
this.accountFrequencies.clear();
}
}
4 changes: 3 additions & 1 deletion sdk/src/constants/numericConstants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
import { BN } from '../';
import { BN } from '@coral-xyz/anchor';

export const ZERO = new BN(0);
export const ONE = new BN(1);
Expand Down Expand Up @@ -114,3 +114,5 @@ export const FUEL_WINDOW = new BN(60 * 60 * 24 * 28); // 28 days
export const FUEL_START_TS = new BN(1723147200); // unix timestamp

export const MAX_PREDICTION_PRICE = PRICE_PRECISION;

export const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;
1 change: 1 addition & 0 deletions sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './accounts/webSocketHighLeverageModeConfigAccountSubscriber';
export * from './accounts/bulkAccountLoader';
export * from './accounts/bulkUserSubscription';
export * from './accounts/bulkUserStatsSubscription';
export { CustomizedCadenceBulkAccountLoader } from './accounts/customizedCadenceBulkAccountLoader';
export * from './accounts/pollingDriftClientAccountSubscriber';
export * from './accounts/pollingOracleAccountSubscriber';
export * from './accounts/pollingTokenAccountSubscriber';
Expand Down
Loading
Loading