QuickNode Raises $60M Series B!
Read the Letter from our CEO.

How to Deserialize Account Data on Solana

January 23, 2023

Overview

Solana uses a process called Binary Object Representation Serializer for Hashing (commonly referred to as Borsh) to serialize account data on chain. Borsh serialization is a way to convert data into a compact binary format that can be efficiently stored and transmitted on the Solana blockchain. It helps to save space on the blockchain and makes data transfer faster.

If you have ever made a getAccountInfo call using solanaWeb3.js, you have likely noticed that the returned data is not human-readable. That is because it has been serialized using Borsh. This guide will walk you through the steps necessary to make this data readable to humans.

What You Will Do

This guide will walk you through some basics of account structs and how to use struct schemas to deserialize Solana account data.

What You Will Need

To follow along with this guide, you will need the following:

Why Borsh?

Before we jump in, let's take a moment to understand the purpose of Borsh and why it is used for serialization of data. Generally, Borsh helps save space on the blockchain and makes data transfer faster. It does so by:

  • Borsch serialization uses a compact binary format that takes up less space than traditional text-based formats like JSON or XML.
  • Borsch uses a specific layout for structs, a pre-defined scheme for data organization. This allows for the efficient packing of data and reduces the size of the data stored on the blockchain.
  • Borsch also uses a type system to represent data, which allows for more precise encoding, reducing the size of the data.
Because Borsch serialization uses fewer bytes to represent the same data, less data needs to be transferred during each transaction, making the data transfer faster.

Let's jump in!

Set Up Your Project

Set Up Your Project

Create a new project directory in your terminal with the following:

set up your project

Copy
mkdir deserialize-solana
cd deserialize-solana

Create a file for your app, app.ts:

set up your project

Copy
echo > app.ts

Initialize your project with the "yes" flag to use default values for your new package:

set up your project

Copy
yarn init --yes
#or
npm init --yes

Install Solana Web3 Dependency

We will need to add the Solana Web3 and Buffer Layout libraries for this exercise. It is fine if you have not previously used the buffer-layout libraries. These will provide important elements for defining our structs (*Note: there are alternative tools for deserializing Borsch data--additional reference links are included at the end of this guide.) In your terminal, enter:

set up your project

Copy
yarn add @solana/web3.js @solana/buffer-layout @solana/buffer-layout-utils
#or
npm install @solana/web3.js @solana/buffer-layout @solana/buffer-layout-utils

We will need a few components from these libraries. Import them in app.ts at line 1 by adding:

set up your project

Copy
import { Connection, PublicKey } from "@solana/web3.js";
import { publicKey, u64, bool } from '@solana/buffer-layout-utils';
import { u32, u8, struct } from '@solana/buffer-layout';

Connect to a Solana Cluster with Your QuickNode Endpoint

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 a free account here.

We're going to use a Solana Mainnet node.

Copy the HTTP Provider link:


Inside app.ts under your import statements, declare your RPC and establish your Connection to Solana:

set up your project

Copy
const QUICKNODE_RPC = 'https://example.solana-mainnet.quiknode.pro/0123456/'; //replace with your HTTP Provider from https://www.quicknode.com/endpoints
const SOLANA_CONNECTION = new Connection(QUICKNODE_RPC);

Finally, let's define an account that we will deserialize:

set up your project

Copy
const MINT_ADDRESS = new PublicKey('7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU');

Though you can deserialize any account data, each type of account has different data schemas. To follow this guide, you should use a valid SPL Token Mint address (e.g., '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU').

Your environment should look like this.


Ready? Let's build!

Step 1 - Fetch Account Info

Inside app.ts, create a new async function, fetchAndParseMint that accepts a Connection and a mint's PublicKey:

step 1 fetch account info

Copy
export  const fetchAndParseMint = async (mint: PublicKey, solanaConnection: Connection) => {
    try {
        console.log(`Step - 1: Fetching Account Data for ${mint.toBase58()}`);
        let {data} = await solanaConnection.getAccountInfo(mint) || {};
        if (!data) return
        console.log(data) ;
    }
    catch {
        return null;
    }
}

fetchAndParseMint(MINT_ADDRESS, SOLANA_CONNECTION);

In the snippet above, we get our account's data a by destructuring the results of a getAccountInfo call. If results are found, we log them to the console. After declaring our function, we call it to see the results when we pass in MINT_ADDRESS and SOLANA_CONNECTION.

In your terminal, run your app:

step 1 fetch account info

Copy
ts-node app

You should see some not-very-useful buffered account data like this:



Let's deserialize that data so that we can find something more useful.

Step 2 - Deserialize Account Data

To deserialize our data, we need to know the account schema from our on-chain program struct, and we will need a TypeScript interface corresponding to that struct.

Define TypeScript Interface

Let's start by defining our TypeScript interface for our account data. Below your fetchAndParseMint function, declare a new interface, RawMint:

step 2 deserialize account data

Copy
export interface RawMint {
    mintAuthorityOption: 1 | 0;
    mintAuthority: PublicKey;
    supply: bigint;
    decimals: number;
    isInitialized: boolean;
    freezeAuthorityOption: 1 | 0;
    freezeAuthority: PublicKey;
}

We are defining for our app how we expect our deserialized data to be structured. The structure we use here must match the on-chain program. For core Solana programs, like the SPL Token program, the program structs are open source and well documented. We were able to get this one from the SPL Program Library GitHub. Sometimes these interfaces are not so accessible, and you may need to go to the original program source code. You should notice that the Mint struct in the program is very similar to the TypeScript interface, but it is missing two fields (mintAuthorityOption and freezeAuthorityOption). These are established because those authorities are optional fields (denoted by COption<Pubkey> in the source code and are generated to allow us to easily query whether or not a value has been passed into that field.

For account data that you do not have access to a GitHub repository, you will need to check a public registry or explorer (often published as an Interface Description Language, IDL) for the struct or contact the developer directly. We have included some resources for finding IDLs at the end of this guide.

Note: you may notice the use of a non-native type, bigint, here. bigint stands for Big Integer, a length integer library for JavaScript to support large numbers. Though this type should work in your environment, if you have an older version of JavaScript, you may need to add Big Integer as a dependency in your project.

Define Buffer layout

Next, we must define our buffer layout, allowing us to find where each element of our data exists in the data struct.

step 2 deserialize account data

Copy
export const MintLayout = struct<RawMint>([
    u32('mintAuthorityOption'),
    publicKey('mintAuthority'),
    u64('supply'),
    u8('decimals'),
    bool('isInitialized'),
    u32('freezeAuthorityOption'),
    publicKey('freezeAuthority'),
]);

We use the struct function, a generic function that takes in an array of fields, to return a Structure object with type RawMint (which we defined in the previous section). By using struct<RawMint>, the fields that are passed to the function are expected to match the properties defined in the RawMint interface, and the function will return a Structure<RawMint> object. This allows for more robust type checking and better documentation of the expected structure of the mint object.

The Structure class has several methods, like decode and encode, which can be used to serialize and deserialize data.

Each field passed to the struct function defines a specific aspect of the RawMint object's structure. These fields, defined in @solana/buffer-layout-utils and @solana/buffer-layout, are typically functions that take a single argument, which is a string that specifies the name of the property in the RawMint object that the field corresponds to.

For example, u32('mintAuthorityOption') is used to define a field for the mintAuthorityOption property in the RawMint object, and it tells the struct function that this property should be interpreted as a 32-bit unsigned integer. Similarly, publicKey('mintAuthority') is used to define a field for the mintAuthority property in the RawMint object, and it tells the struct function that this property should be interpreted as a PublicKey. In short, these fields define how each property of the RawMint object should be interpreted and read or written when encoding or decoding the RawMint object.

Decode the Account Data

Jump back to your fetchAndParseMint function and update your try statement by passing our data into .decode on our newly defined MintLayout:

step 2 deserialize account data

Copy
    try {
        console.log(`Step - 1: Fetching Account Data for ${mint.toBase58()}`);
        let {data} = await solanaConnection.getAccountInfo(mint) || {};
        if (!data) return;

        console.log(`Step - 2: Deserializing Found Account Data`);
        const deserialized = MintLayout.decode(data);
        console.log(deserialized);
    }

We should expect this to decode our data into a type of RawMint. Go ahead and run your updated code--in your terminal type:

step 2 deserialize account data

Copy
ts-node app

Hopefully, you see something like this:



We have a little cleanup to do, but you should see that the new object is in the form of our RawMint class. Nice job!

Clean Results

Let's clean up our console.log a little to improve the readability of our results.

Inside your fetchAndParseMint, replace console.log(deserialized) with a series of logs to destructure our data: .

step 2 deserialize account data

Copy
        console.log(`Step - 3: Clean and Log Deserialized Data`);
        console.log('   Mint Authority Option:',deserialized.mintAuthorityOption);
        console.log('   Mint Authority:',deserialized.mintAuthority.toString());
        console.log('   Supply:',(Number(deserialized.supply)/10**deserialized.decimals).toLocaleString(undefined, { maximumFractionDigits: 0 })); // Necessary to convert bigint
        console.log('   Decimals:',deserialized.decimals);
        console.log('   Initialized:',deserialized.isInitialized);
        console.log('   Freeze Authority Option:',deserialized.freezeAuthorityOption);
        console.log('   Freeze Authority:',deserialized.freezeAuthority.toString());     

There are a couple of things to note here:

  1. To make our PublicKeys readable, we convert them to strings by using toString().
  2. Supply is a bigint, so we need to convert it to a number by passing it into Number(value:bigint). Because we are using the SPL Token program, we must consider our token's decimals. To do this, we can divide by 10^numDecimals. Finally, since our number is large, we use toLocaleString to format the number to the local language format (maximumFractionDigits will remove any decimals).

Run your code one last time, and see what you get. In your terminal, type:

step 2 deserialize account data

Copy
ts-node app



🤯 WHOA! We went from <Buffer 00 00... to a polished set of helpful information in just a few minutes. Great job.

Deserializing on the Fly

Before heading off, we want to introduce you to a helpful tool for deserializing accounts without needing to write any code. The SOL/Borsh Decoder by M2 is a user-friendly UI for doing what we just did. Check it out--they've already preloaded structs for SPL programs!



You should see the same results we just got from this exercise. Play around with this tool and a few other accounts--it can be a handy tool for figuring out an account's struct or getting a quick answer for a struct you already know!

We have created an account on devnet for you to test this out:


deserializing on the fly

Copy
pub struct Message {
    // discriminator: u64
    secret_one: String,
    secret_two: String,
    value: u8,
    completed: bool,
}

Your input should look like this:



Do you see the message? 🙌

Wrap Up

Deserializing data is a critical component of Solana development. Great job for getting here. What are you deserializing, and how is it helping you on your next build? Share what you are working on in our Discord, or give us a follow on Twitter to stay up to date on all the latest information!

We <3 Feedback!

If you have any feedback on this guide, let us know. We’d love to hear from you.

Additional Resources

Related articles 41

Solana NFT Metadata Deep Dive
Published: Dec 16, 2022
Updated: Dec 16, 2022

Even in the 2022 bear market, Solana NFTs are showing no signs of slowing down. If you are building with Solana NFTs, understanding your NFTs' metadata will make it easier for you to deploy...

Continue reading
How to Transfer SPL Tokens on Solana
Published: Sep 23, 2022
Updated: Sep 23, 2022

Sending Solana Program Library (SPL) Tokens is a critical mechanism for Solana development. Whether you are airdropping whitelist tokens to your community, bulk sending NFTs to another wallet,...

Continue reading
Como crear un NFT en SOLANA
Published: Dec 27, 2021
Updated: Sep 23, 2022

¡Hola querido lector! Bienvenidos a una nueva guía de Solana.Solana es una blockchain que promete mucho a la hora de intentar resolver los problemas de escalabilidad que...

Continue reading
How to Mint an NFT on Solana
Published: Aug 27, 2021
Updated: Sep 23, 2022

Updated at: April 10, 2022Welcome to another QuickNode guide on Solana - the up-and-coming blockchain that seeks to solve the scalability issues of Ethereum. We will be walking through...

Continue reading
How to Send Bulk Transactions on Solana
Published: Aug 31, 2022
Updated: Oct 3, 2022

Are you running a batch process that has many transactions? Perhaps an airdrop to your community's NFT holders or a token distribution to early users of your dApp. Solana transaction...

Continue reading
How to Burn SPL Tokens on Solana
Published: Jan 13, 2023
Updated: Jan 13, 2023

🔥🔥🔥Building a deflationary token protocol? Want to destroy a rugged NFT? Just want to have some fun with your community? The Solana SPL Token Program's Burn feature is what...

Continue reading
How to Get Transaction Logs on Solana
Published: Jun 24, 2022
Updated: Oct 27, 2022

Ever need to pull all the transactions associated with a Wallet? Want to see all of the mint transactions associated with a Candy Machine? Or maybe see transaction history of an NFT? Solana's...

Continue reading
How to Use Priority Fees on Solana
Published: Jan 13, 2023
Updated: Jan 17, 2023

Are you looking to get your transactions confirmed as quickly as possible on Solana? This guide will show you how to use priority fees to bid for priority in the leader's queue and confirm...

Continue reading
Solana Fundamentals Reference Guide
Published: Oct 27, 2022
Updated: Oct 27, 2022

The Solana blockchain is a powerful tool, delivering thousands of transactions per second with almost no-cost transaction fees. If you are new to Web3 or have developed on EVM-based...

Continue reading