0

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:

  1. Attempting to do this with a local Javascript Node application.
  2. Using Quicknode as my RPC provider.
  3. 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:

  1. What is the best way to get all token holders and their balances by mint address?
  2. 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.

2 Answers 2

1

I personally use the getTokenAccount method from helius like so

let page = 1;
let allOwners = new Set<TokenHolder>();

while (true) {
    const response = await fetch(
    `https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`,
    {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      method: "getTokenAccounts",
      id: "421",
      params: {
        page: page,
        limit: 1000,
        mint: token,
      },
    }),
  }
  );
  const data = await response.json();

  if (!data.result || data.result.token_accounts.length === 0) {
    break;
  }

  data.result.token_accounts.forEach((account: any) => {
    allOwners.add({
      amount: account.amount,
      address: account.owner,
    } as TokenHolder);
  });
  page++;
}

return Array.from(allOwners);
1

The error occurs because getProgramAccounts no longer supports querying the Token Program (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA) due to Solana's updates. Instead, you can use Helius RPC, which provides the getTokenAccounts endpoint specifically for fetching token holders.

Helpful Links:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.