8 min read
Overview
Hello readers! To kick off Solana Summer and the current whitelist meta, we thought it would be helpful to dig into all of the token accounts you and your users have using the getParsedProgramAccounts method. This tool is convenient for querying different programs on Solana, including the Solana SPL Token Account library.
Prefer a video walkthrough? Follow along with Noah and learn how to get all tokens held by a Solana wallet in 13 minutes.
This guide will walk you through creating a simple script that will query Solana's mainnet and return all of the Token Accounts owned by that wallet and their account balances.
Here is a video if you prefer to watch:
What You Will Need
- Nodejs (version 16.15 or higher)
- Solana Web3
- Solana SPL Token Library
- Typescript experience and ts-node installed
- Basic Understanding of some Solana Principles:
Programs: smart contracts on Solana. In this example, we will use the SPL Token Program, which defines typical use cases for fungible and non-fungible tokens on Solana.
Program Filters: many on-chain program queries pull extensive data sets, so it is essential to narrow your search. We will use the GetProgramAccountsFilter type to help us narrow down our search.
Set Up Your Environment
Create a new project directory and file, index.ts, in your terminal with:
mkdir sol-get-accounts
cd sol-get-accounts
echo > index.ts
Initialize your project:
yarn init --yes
or
npm init --yes
Install Solana Web3 dependencies:
yarn add @solana/web3.js @solana/spl-token
or
npm install @solana/web3.js @solana/spl-token
Open index.ts in a code editor and add the following dependencies:
import { Connection, GetProgramAccountsFilter } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
Establish a Connection to Solana Mainnet
To build on Solana, you'll need an API endpoint to connect with the network. You're welcome to use public nodes or deploy and manage your own infrastructure, however, if you'd like 8x faster response times you can leave the heavy lifting to us. See why over 50% of projects on Solana choose QuickNode and sign up for free here.
Make sure to launch your node under the Solana Mainnet and copy the HTTP link:
const rpcEndpoint = 'https://example.solana-mainnet.quiknode.pro/000000/';
const solanaConnection = new Connection(rpcEndpoint);
Define the wallet that you want to query as a string:
const walletToQuery = 'YOUR_PUBLIC_KEY'; //example: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
Great! We are ready to build our token query.
Create your Token Account Query
Create a new async function, getTokenAccounts and require a parameter of wallet and solanaConnection:
async function getTokenAccounts(wallet: string, solanaConnection: Connection) {
}
Establish filters
First, let's define our filters. The filters we will be using are:
- dataSize is a filter used to look for accounts of a specific size. For token accounts, this is a known quantity, 165.
- memcmp, or "memory comparison" filter, is used to narrow our search within an account. Specifically, we will use offset to determine where in our account's 165 bytes to search (this is another known value for this Program: the owner's public key starts at 32) and bytes to set what we will be searching for: in this case, the users' wallet address.
- Source information about SPL token account data can be found here.
Inside getTokenAccounts, define a variable called filters.
const filters:GetProgramAccountsFilter[] = [
{
dataSize: 165, //size of account (bytes)
},
{
memcmp: {
offset: 32, //location of our query in the account (bytes)
bytes: wallet, //our search criteria, a base58 encoded string
}
}
];
This should reduce our query to look for only token accounts owned by our wallet.
Get Program Accounts
Now call the getParsedProgramAccounts method passing in the SPL Token Program ID and our filters.
const accounts = await solanaConnection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID, //SPL Token Program, new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{filters: filters}
);
The previous call returns an array of all matching token accounts with the following structure:
{
pubkey: PublicKey, //Token Account Public Key
account: AccountInfo //Object including information about our token account
}[]
You can log accounts.length to see how many token accounts the user has:
console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}.`);
Parse the Results
Build a simple forEach loop to iterate through our results and log our results. Inside your getTokenAccounts function add:
accounts.forEach((account, i) => {
//Parse the account data
const parsedAccountInfo:any = account.account.data;
const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
//Log results
console.log(`Token Account No. ${i + 1}: ${account.pubkey.toString()}`);
console.log(`--Token Mint: ${mintAddress}`);
console.log(`--Token Balance: ${tokenBalance}`);
});
Run Your Code
Finally, call getTokenAccounts(walletToQuery,solanaConnection). Your final script should look like this:
import { Connection, GetProgramAccountsFilter } from "@solana/web3.js";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
const rpcEndpoint = 'https://example.solana-mainnet.quiknode.pro/000000/';
const solanaConnection = new Connection(rpcEndpoint);
const walletToQuery = 'YOUR_PUBLIC_KEY'; //example: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
async function getTokenAccounts(wallet: string, solanaConnection: Connection) {
const filters:GetProgramAccountsFilter[] = [
{
dataSize: 165, //size of account (bytes)
},
{
memcmp: {
offset: 32, //location of our query in the account (bytes)
bytes: wallet, //our search criteria, a base58 encoded string
},
}];
const accounts = await solanaConnection.getParsedProgramAccounts(
TOKEN_PROGRAM_ID, //new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
{filters: filters}
);
console.log(`Found ${accounts.length} token account(s) for wallet ${wallet}.`);
accounts.forEach((account, i) => {
//Parse the account data
const parsedAccountInfo:any = account.account.data;
const mintAddress:string = parsedAccountInfo["parsed"]["info"]["mint"];
const tokenBalance: number = parsedAccountInfo["parsed"]["info"]["tokenAmount"]["uiAmount"];
//Log results
console.log(`Token Account No. ${i + 1}: ${account.pubkey.toString()}`);
console.log(`--Token Mint: ${mintAddress}`);
console.log(`--Token Balance: ${tokenBalance}`);
});
}
getTokenAccounts(walletToQuery,solanaConnection);
Run ts-node index.ts and you should see a log like this in your terminal:
Bonus: Add More Filters
Having fun? Want to try more filters? Say, for example, you want to see if a wallet contains tokens of a particular mint. You could include that filter in your forEach loop above, but you might consider adding it to your filters variable in your original query to reduce the search time. Here's how to do it.
In your definitions at the top of your application, add a mint address to search, for example:
const MINT_TO_SEARCH = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; //USDC Mint Address
Now back in our getTokenAccounts function, add an additional filter to filters looking for MINT_TO_SEARCH at byte position 0 (known location of the mint public key in this program):
const filters:GetProgramAccountsFilter[] = [
{
dataSize: 165, //size of account (bytes)
},
{
memcmp: {
offset: 32, //location of our query in the account (bytes)
bytes: wallet, //our search criteria, a base58 encoded string
},
},
//Add this search parameter
{
memcmp: {
offset: 0, //number of bytes
bytes: MINT_TO_SEARCH, //base58 encoded string
},
}];
Rerun your code, and you should see the results are now limited to just the mint you searched for:
Conclusion
And you're done! You should now understand how to query a wallet for all of its token accounts and have a solid foundation that will enable you to query other Solana Programs in the future.
Find this useful? Check out some of our other Solana tutorials here.Subscribe to our newsletter for more articles and guides on Solana. Feel free to reach out to us via Twitter if you have any feedback. You can always chat with us on our Discord community server, featuring some of the coolest developers you'll ever meet :)
We ❤️ Feedback!
Let us know if you have any feedback or requests for new topics. We'd love to hear from you.