- Abi Raja
- Follow me on Twitter @_abi_
When I was building my NFT showcase, I spent many hours trying to understand how to query data on the Solana blockchain. The actual method turned out to be very simple. But for someone new to Solana and its nomenclature and concepts, it can be hard to even know where to look. There isn't a ton of good information out there (see these unresolved Reddit and Github threads).
In this tutorial, my goal is to help you understand some basics of Solana and walk you through some code to do the following:
- What is the SOL balance of an address?
- What other tokens are owned by an address? And how much of each token?
- Which NFTs are owned by the address? What do these NFTs look like?
All the code below is runnable right here in the browser. Be sure to play around!
Solana's blockchain much like Bitcoin and Ethereum is stored on many decentralized nodes. These nodes are called validators in the Solana world since validation is one of the functions performed by these nodes. Like Bitcoin, there are multiple networks of nodes. There’s a test network for development purposes where the Solana is worthless and of course, there’s a main network. Unlike BTC and ETH, in the Solana world, networks are called "clusters". The main cluster, where the SOL is valuable, is the "mainnet-beta" cluster.
In fact, as you'll see many terms in Solana that differ from the more commonly used terms in other cryptocurrency communities. This is honestly pretty annoying. For instance, "ledger" = blockchain.
So, you have a ledger replicated across validators on the mainnet-beta cluster. That's the ledger against which we'll be running all our queries. Now, let's look at some code.
Connecting to a validator
In this example, we query for the current slot. Slots are essentially periods of time during which transactions are aggregated and a single block is produced. Typically each slot lasts less than a second. Confirm with the Solana Explorer that the output of the code above is the current slot or at least close to it since slots increment very quick.
For all our queries, we'll be using the solana/web3.js library, provided by Solana Labs, the creators of Solana. The library is actually a wrapper around a JSON RPC API that every Solana valudator supports. The JS library pretty much doesn't have any documentation for all its functions but you can figure out the functions you need by looking at the Typescript types and cross-referencing the documentation for the JSON RPC API.
Solana Labs maintains a list of RPC endpoints. These have rate limits since they are free but the limits are more than sufficient for a low-traffic website (100 requests per 10 seconds per IP). In line 2 of the code above, we create a connection to one of the Solana Labs validators in the mainnet-beta cluster. You also have the option of hosting your own validator if you need to go over the rate limits. In that case, you'd establish a connection to that node.
Exercise: Try changing to a different cluster that’s not mainnet-beta. You can confirm you did this correctly by going to Solana Explorer and updating the cluster there to check against your logged slot value.
Querying the SOL balance of an address
Now that we have a connection to a validator set up, we can start querying for data about a particular account. In Solana, all data stored on the ledger is stored in an account, regardless of whether it's the SOL balance or a smart contract (i.e. an executable program) or an NFT.
And an account is addressable by a key. I’ll stop talking about addresses henceforth and instead talk about accounts referenced by their public key.
6xsHVNZBcTCY2h4GBykpoWwY3wnTbwajYJTLQy8K4Ngg is a public key I found at random on Solscan. Let’s see how much SOL this account has.
getAccountInfo which gives us all the information about a particular account.
accountInfo is an object with the following properties:
(note the JS library will
camelCase all these properties that are underscored in the original documentation.)
lamportsis what we're looking for. At the time of writing, this account had 305804460 lamports. Lamports per SOL is 1000000000 (also found in
falsebecause smart contracts are also accounts in Solana but in this case, this is a "data account" (i.e. storing how many SOLs a particular public key owns and contains no code).
rent_epochcan be ignored (it should be set to the current epoch which you can find on Solana explorer. Read more about how rent works here.).
- We'll dive into
datain the next section.
datais used by smart contracts to store code (if I'm not mistaken) and they are also used by NFTs and other Solana-based tokens to store data such as the token balance.
- Let's take a look at the final property,
owner. Shouldn't the
ownerbe our public key
6xsHVNZBcTCY2h4GBykpoWwY3wnTbwajYJTLQy8K4Ngg? Let's check it. The owner is an instance of
PublicKeyso let's print the base58 representation of it like so:
What's the result? Surprisingly, it is not
6xsHVNZBcTCY2h4GBykpoWwY3wnTbwajYJTLQy8K4Ngg. Instead, it's a bunch of
1s. Why is that? This is the address of the System Program. My understanding is that applications like a wallet interacts with the system program to transfer SOLs and perform transactions. And you prove to the System Program that you're the owner of the account by signing with the private key for the corresponding public key
Here's the full code to print out the actual balance in SOL rather than in lamports.
Exercise: Check this against Solana Explorer and see that it matches.
Querying all the other accounts owned by this address
In the Solana ecosystem, most (all?) tokens (fungible and non-fungible-as-in-NFTs) are built on top of the Solana Program Library. SPL is a collection of programs/smart contracts written and maintained by Solana Labs. When you own some SPL tokens, your main account will own some other "token accounts". These token accounts are just the same as any other account as described in the previous section. But their
data property has additional information describing the type of token held and the amount of that token.
Let's look at some code now.
We're still using the
@solana/web3.js library here. We call
getParsedTokenAccountsByOwner passing a public key as a the first argument and a filter as the second argument: to filter for the program id of the SPL token program. Program ID is just the address of the executable account where the SPL program is stored. I'm honestly not sure why this second argument is necessary since this API only filters for SPL token programs anyway. But it is.
Looking at the output, we see that there are 5 SPL token accounts owned by this account. Here's where it gets a little complicated. We need to dig into the
data property of each of these accounts to understand what token they hold and how much of it. Within the
splTokensBalance object, this would be
splTokensBalance.value.account.data.parsed.info for the first token.
getParsedTokenAccountsByOwner thankfully parses the
data layout and makes it human readable hence, the
Below is a helpful diagram of the
data layout (source):
mint will be the address of the minting account. If you copy that address and search the token list, you can see more information about the token such as the symbol, name, Twitter handle, etc. As far as I know, this information isn't stored on chain and is instead added by token creators to the token-list repo. You can also look up the mint address on the Solana Explorer.
owner is another field that's interesting to us. And it should be the address we queried for:
EKZYBqisdcsn3shN1rQRuWTzH3iqMbj1dxFtDFmrBi8o. Note: this is different from the
owner field at the root of the account which as we saw earlier was the System Program.
amount will tell you the amount of the token this accounts has. In the JS object, this will get parsed as
...parsed.info.tokenAmount.uiAmount. Important to note that the
lamports property at the root of the account only ever counts SOL and not the amount of SPL token owned by the account.
Those are all the relevant fields for this exercise but you can read more about the other fields here.
How do these queries work on the validator given that it's not a simple key/value lookup? First, the validator filters on the size of the
data property to find all token accounts on the ledger. From the data layout diagram above, we can see that they are 165 bytes in length. The validator then has to query the
owner property inside the
data property for all these acounts to figure out which token accounts are owned by your public key. To do this efficiently, it does a memory comparison. Here's the implementation if you're interested. And here's a nice explanation of the code.
Querying all NFTs owned by an address
When you run the code above, you see that the address we're using owns 39 NFTs. We get an array of owned metadata which includes all sorts of information including the name of the NFT. To get the actual image, we'll have to fetch the
uri for each NFT. For example, here's the metadata of a particular NFT stored on Arweave. These formats are defined in the Metaplex standard.
Now, you should see a list of 39 images.
The API we used here is
Metadata.findDataByOwner. We pass it the
connection object and the public key we are querying for. Under the hood,
findDataByOwner simply calls the
getTokenAccountsByOwner which we know from the previous section (
getTokenAccountsByOwner is simply the non-parsed version of
getParsedTokenAccountsByOwner). On the list of token accounts returned, it filters out any tokens where the amount is not equal to 1. Of course, this might mean we're including non-NFTs (unlikely that token balance will exactly equal 1 but it's certainly possible) but this appears to be the best way to filter.
The rest of the code in
findDataByOwner queries and deserializes the data of the mint account associated with the token account to get you the human-readable information about the NFT.
So there you have it, a whirlwind tour through many concepts in Solana. Using these APIs, you should be able to build something like Solana Explorer on your own.
If you want to learn more, here are some good resources: