Skip to content

Commit 95af8ae

Browse files
authored
feat: customized cadence account loader (#1666)
* feat: customized cadence account loader bby * feat: method to read account cadence on custom cadence account loader * feat: PR feedback on customized loader cleaup code and better naming * fix: lint and prettify * feat: more efficient rpc polling on custom polling intervals * feat: custom cadence acct loader override load * chore: prettify
1 parent bb40afa commit 95af8ae

File tree

5 files changed

+384
-3
lines changed

5 files changed

+384
-3
lines changed

sdk/src/accounts/bulkAccountLoader.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@ import { v4 as uuidv4 } from 'uuid';
33
import { BufferAndSlot } from './types';
44
import { promiseTimeout } from '../util/promiseTimeout';
55
import { Connection } from '../bankrun/bankrunConnection';
6+
import { GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE } from '../constants/numericConstants';
67

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

12-
const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;
13-
1413
const oneMinute = 60 * 1000;
1514

1615
export class BulkAccountLoader {
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE } from '../constants/numericConstants';
2+
import { BulkAccountLoader } from './bulkAccountLoader';
3+
import { Commitment, Connection, PublicKey } from '@solana/web3.js';
4+
import { v4 as uuidv4 } from 'uuid';
5+
6+
export class CustomizedCadenceBulkAccountLoader extends BulkAccountLoader {
7+
private customIntervalId: NodeJS.Timeout | null;
8+
private accountFrequencies: Map<string, number>;
9+
private lastPollingTime: Map<string, number>;
10+
private defaultPollingFrequency: number;
11+
12+
constructor(
13+
connection: Connection,
14+
commitment: Commitment,
15+
defaultPollingFrequency: number
16+
) {
17+
super(connection, commitment, defaultPollingFrequency);
18+
this.customIntervalId = null;
19+
this.accountFrequencies = new Map();
20+
this.lastPollingTime = new Map();
21+
this.defaultPollingFrequency = defaultPollingFrequency;
22+
}
23+
24+
private getAccountsToLoad(): Array<{
25+
publicKey: PublicKey;
26+
callbacks: Map<string, (buffer: Buffer, slot: number) => void>;
27+
}> {
28+
const currentTime = Date.now();
29+
const accountsToLoad: Array<{
30+
publicKey: PublicKey;
31+
callbacks: Map<string, (buffer: Buffer, slot: number) => void>;
32+
}> = [];
33+
34+
for (const [key, frequency] of this.accountFrequencies.entries()) {
35+
const lastPollTime = this.lastPollingTime.get(key) || 0;
36+
if (currentTime - lastPollTime >= frequency) {
37+
const account = this.accountsToLoad.get(key);
38+
if (account) {
39+
accountsToLoad.push(account);
40+
this.lastPollingTime.set(key, currentTime);
41+
}
42+
}
43+
}
44+
45+
return accountsToLoad;
46+
}
47+
48+
public async load(): Promise<void> {
49+
return this.handleAccountLoading();
50+
}
51+
52+
private async handleAccountLoading(): Promise<void> {
53+
const accounts = this.getAccountsToLoad();
54+
55+
if (accounts.length > 0) {
56+
const chunks = this.chunks(
57+
this.chunks(accounts, GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE),
58+
10
59+
);
60+
61+
await Promise.all(
62+
chunks.map((chunk) => {
63+
return this.loadChunk(chunk);
64+
})
65+
);
66+
}
67+
}
68+
69+
public setCustomPollingFrequency(
70+
publicKey: PublicKey,
71+
newFrequency: number
72+
): void {
73+
const key = publicKey.toBase58();
74+
this.accountFrequencies.set(key, newFrequency);
75+
this.lastPollingTime.set(key, 0); // Reset last polling time to ensure immediate load
76+
this.restartPollingIfNeeded(newFrequency);
77+
}
78+
79+
private restartPollingIfNeeded(newFrequency: number): void {
80+
const currentMinFrequency = Math.min(
81+
...Array.from(this.accountFrequencies.values()),
82+
this.defaultPollingFrequency
83+
);
84+
85+
if (newFrequency < currentMinFrequency || !this.customIntervalId) {
86+
this.stopPolling();
87+
this.startPolling();
88+
}
89+
}
90+
91+
/**
92+
* Adds an account to be monitored by the bulk account loader
93+
* @param publicKey The public key of the account to monitor
94+
* @param callback Function to be called when account data is received
95+
* @param customPollingFrequency Optional custom polling frequency in ms for this specific account.
96+
* If not provided, will use the default polling frequency
97+
* @returns A unique callback ID that can be used to remove this specific callback later
98+
*
99+
* The method will:
100+
* 1. Create a new callback mapping for the account
101+
* 2. Set up polling frequency tracking for the account
102+
* 3. Reset last polling time to 0 to ensure immediate data fetch
103+
* 4. Automatically restart polling if this account needs a faster frequency than existing accounts
104+
*/
105+
public async addAccount(
106+
publicKey: PublicKey,
107+
callback: (buffer: Buffer, slot: number) => void,
108+
customPollingFrequency?: number
109+
): Promise<string> {
110+
const callbackId = uuidv4();
111+
const callbacks = new Map<string, (buffer: Buffer, slot: number) => void>();
112+
callbacks.set(callbackId, callback);
113+
const newAccountToLoad = {
114+
publicKey,
115+
callbacks,
116+
};
117+
this.accountsToLoad.set(publicKey.toString(), newAccountToLoad);
118+
119+
const key = publicKey.toBase58();
120+
const frequency = customPollingFrequency || this.defaultPollingFrequency;
121+
this.accountFrequencies.set(key, frequency);
122+
this.lastPollingTime.set(key, 0); // Reset last polling time to ensure immediate load
123+
124+
this.restartPollingIfNeeded(frequency);
125+
126+
return callbackId;
127+
}
128+
129+
public removeAccount(publicKey: PublicKey): void {
130+
const key = publicKey.toBase58();
131+
this.accountFrequencies.delete(key);
132+
this.lastPollingTime.delete(key);
133+
134+
if (this.accountsToLoad.size === 0) {
135+
this.stopPolling();
136+
} else {
137+
// Restart polling in case we removed the account with the smallest frequency
138+
this.restartPollingIfNeeded(this.defaultPollingFrequency);
139+
}
140+
}
141+
142+
public getAccountCadence(publicKey: PublicKey): number | null {
143+
const key = publicKey.toBase58();
144+
return this.accountFrequencies.get(key) || null;
145+
}
146+
147+
public startPolling(): void {
148+
if (this.customIntervalId) {
149+
return;
150+
}
151+
152+
const minFrequency = Math.min(
153+
...Array.from(this.accountFrequencies.values()),
154+
this.defaultPollingFrequency
155+
);
156+
157+
this.customIntervalId = setInterval(() => {
158+
this.handleAccountLoading().catch((error) => {
159+
console.error('Error in account loading:', error);
160+
});
161+
}, minFrequency);
162+
}
163+
164+
public stopPolling(): void {
165+
super.stopPolling();
166+
167+
if (this.customIntervalId) {
168+
clearInterval(this.customIntervalId);
169+
this.customIntervalId = null;
170+
}
171+
this.lastPollingTime.clear();
172+
}
173+
174+
public clearAccountFrequencies(): void {
175+
this.accountFrequencies.clear();
176+
}
177+
}

sdk/src/constants/numericConstants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { LAMPORTS_PER_SOL } from '@solana/web3.js';
2-
import { BN } from '../';
2+
import { BN } from '@coral-xyz/anchor';
33

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

116116
export const MAX_PREDICTION_PRICE = PRICE_PRECISION;
117+
118+
export const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;

sdk/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export * from './accounts/webSocketHighLeverageModeConfigAccountSubscriber';
1515
export * from './accounts/bulkAccountLoader';
1616
export * from './accounts/bulkUserSubscription';
1717
export * from './accounts/bulkUserStatsSubscription';
18+
export { CustomizedCadenceBulkAccountLoader } from './accounts/customizedCadenceBulkAccountLoader';
1819
export * from './accounts/pollingDriftClientAccountSubscriber';
1920
export * from './accounts/pollingOracleAccountSubscriber';
2021
export * from './accounts/pollingTokenAccountSubscriber';

0 commit comments

Comments
 (0)