As the title states I would like to get all wallets that hold a particular token by providing the token's address. I believe in more accurate terms I would like to get all token accounts with a non-zero balance related to a particular mint account via mint address. These token accounts should provide me the wallet addresses and the balances in each wallet which is what I'm ultimately after. I'll be using it to take a snapshot of all token holders at a given datetime.
Base setup:
- Attempting to do this with a local Javascript Node application.
- Using Quicknode as my RPC provider.
- Using the solana-web3.js V2.0.0 library to interact with the RPC provider.
Current code:
interface TokenAccountInfo {
account: {
data: {
parsed: {
info: {
owner: string;
tokenAmount: {
uiAmount: number;
};
};
};
};
};
}
export class SolanaService {
private rpc: ReturnType<typeof createSolanaRpc>;
constructor(endpoint: string) {
this.rpc = createSolanaRpc(endpoint);
}
async getTokenHolders(tokenMintAddress: string): Promise<Map<string, number>> {
const tokenAddress = address(tokenMintAddress);
const tokenHolders = new Map<string, number>();
const tokenProgramId = address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const TOKEN_ACC_SIZE = 165;
const datasizeFilter: GetProgramAccountsDatasizeFilter = {
// Filter to only accounts with length 165, which is the fixed length that token accounts have. This way we get rid of accounts that are owned by the token program but aren't token accounts
dataSize: TOKEN_ACC_SIZE as unknown as bigint
}
const memoryComparisonFilter: GetProgramAccountsMemcmpFilter = {
memcmp: {
offset: 0 as unknown as bigint, // Filter by mint address
bytes: tokenMintAddress,
encoding: "base64"
}
}
// Fetch all token accounts by mint address
const tokenAccounts = await this.rpc.getProgramAccounts(
tokenProgramId,
{ filters: [ datasizeFilter, memoryComparisonFilter] }
).send();
console.log('Token accounts response:', tokenAccounts);
// Iterate over each token account and sum the balances
(tokenAccounts.values as unknown as TokenAccountInfo[]).forEach((accountInfo) => {
const accountData = accountInfo.account.data.parsed.info;
const walletAddress = accountData.owner;
const tokenAmount = accountData.tokenAmount.uiAmount;
console.log(`Processing account: ${walletAddress}, amount: ${tokenAmount}`);
if (tokenHolders.has(walletAddress)) {
tokenHolders.set(walletAddress, tokenHolders.get(walletAddress)! + tokenAmount);
} else {
tokenHolders.set(walletAddress, tokenAmount);
}
});
console.log('Final token holders:', tokenHolders);
return tokenHolders;
}
}
I am currently attempting to do this using the getProgramAccounts endpoint and applying the appropriate filters to get back the token accounts. This idea was derived from the following posts:
- What is the best way to get all token holders and their balances by mint address?
- Get all token holders of a token (mint)
However it looks like this functionality may have been deprecated and when doing so I get the following error:
SolanaError: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA excluded from account secondary indexes; this RPC method unavailable for key
It looks as if the token program ID (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) is no longer an accepted value for this endpoint.
Could someone please provide me the correct means of achieving this query?
Edit* I am aware that Helius has an endpoint specifically for this and a SDK to call it but I am using QuickNode as my RPC provider. I would like to implement this using the solana-web3.js library and Solana standard endpoints.