Published on

Querying information on the Solana blockchain

Authors
  • avatar
    Name
    Abi Raja
    Twitter

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?

At the end of it, you should be able to build something like Solscan or Solana Explorer yourself.

All the code below is runnable right here in the browser. Be sure to play around!

Basics

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.

We use getAccountInfo which gives us all the information about a particular account. accountInfo is an object with the following properties:

solana account properties

(note the JS library will camelCase all these properties that are underscored in the original documentation.)

  • lamports is what we're looking for. At the time of writing, this account had 305804460 lamports. Lamports per SOL is 1000000000 (also found in web3.LAMPORTS_PER_SOL).
  • executable is false because 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_epoch can 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 data in the next section. data is 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 owner be our public key 6xsHVNZBcTCY2h4GBykpoWwY3wnTbwajYJTLQy8K4Ngg? Let's check it. The owner is an instance of PublicKey so 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 6xsHVNZBcTCY2h4GBykpoWwY3wnTbwajYJTLQy8K4Ngg.

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[0].account.data.parsed.info for the first token. getParsedTokenAccountsByOwner thankfully parses the data layout and makes it human readable hence, the data.parsed.info.

Below is a helpful diagram of the data layout (source):

spl token account memory layout

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.

Finally, 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

To query NFTs, I ended up using one of the Metaplex libraries. Metaplex is the NFT standard on Solana.

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 @solana/web3 library's 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.

Diving Deeper

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: