Marketplace has launched, further enabling blockchain developers! Learn more

How to Build a Solana Explorer Clone (3 of 3): Get Staking Accounts

September 23, 2022

Overview

Solana Explorer provides access to a wealth of great information on the Solana blockchain: transaction history and details, token balances, NFT metadata, and much more. In our opinion, browsing Solana Explorer is one of the best ways to understand Solana, but building a Solana Explorer takes your knowledge to the next level! In this 3 part series, we are covering the steps needed to create a simple clone of Solana Explorer!

In this guide, we will fetch a wallet's staking accounts to display how much $SOL a user has staked and which validator(s) it's staked with.

What You Will Do

In this guide, you will use the Solana Explorer Example we built in our previous How to Build a Solana Explorer Clone (Part 2: Transaction Detail) guide. You'll use that framework to make a new component that will allow users to see a table of their Staked Solana accounts and delegation.

Expected Output - Staking Accounts


What You Will Need


To follow along with this guide, you will need to have completed our guide on How to Build a Solana Explorer Clone (Part 2: Transaction Detail). Why? We will be building on top of the code that is there.

The final code from the How to Build a Solana Explorer Clone (Part 2: Transaction Detail) guide can be found in this QuickNode Github repo. That guide will serve as a starting point for this one. Make sure you have followed the instructions in that guide and completed all the steps.

Quick Start: If you didn't have a chance to complete the 2nd guide, here's a simple way to jump right in. In your terminal, create a project directory and clone the sample:

overview

Copy
mkdir solana-explorer-demo
cd solana-explorer-demo
git clone https://github.com/quiknode-labs/technical-content.git .
cd solana/explorer-clone-part-3/starter    
yarn install 
echo > .env

Then update your .env with your QuickNode RPC:

overview

Copy
REACT_APP_SOLANA_RPC_HOST=https://example.solana-devnet.quiknode.pro/000000/
REACT_APP_NETWORK=devnet

And enter yarn dev into your terminal.

Your https://localhost:3000/explorer should render the following view:

Quick Start - Starting Point

Alright! Let's get started.

Create a Staking Detail Component

Let's build on our transactions tool to allow a user to click on a transaction to get more details about that transaction. The great news is that we already have the tools to do this.

From your project directory, duplicate your Component Template and name it StakingDetail.tsx:

create a staking detail component

Copy
cp ./src/components/template.tsx ./src/components/StakingDetail.tsx

StakingDetail.tsx should now exist in your src/components folder. Open it in a code editor.

Update Dependencies

Start by renaming your Function Component to StakingDetail. Replace

create a staking detail component

Copy
export const Template: FC = () => {

with

create a staking detail component

Copy
export const StakingDetail: FC = () => {

For this component, we need the following imports. Replace the existing imports of your template with these:

create a staking detail component

Copy
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { AccountInfo, LAMPORTS_PER_SOL, ParsedAccountData, PublicKey } from '@solana/web3.js';
import { FC, useEffect, useState } from 'react';
import { notify } from "../utils/notifications";

These imports should look familiar as we're using a lot of what we have done in TransactionLog and TransactionDetail. Since the assembly of this component is similar to our previous ones, we're going to share the complete code up front and walk through a few essential pieces:

create a staking detail component

Copy
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { AccountInfo, LAMPORTS_PER_SOL, ParsedAccountData, PublicKey } from '@solana/web3.js';
import { FC, useEffect, useState } from 'react';
import { notify } from "../utils/notifications";

const STAKE_PROGRAM_PK = new PublicKey('Stake11111111111111111111111111111111111111');
const WALLET_OFFSET = 44;
const DATA_SIZE = 200;

export const StakeDetail: FC = () => {
    const { connection } = useConnection();
    const { publicKey } = useWallet();
    const [stakeAccounts, setStakeAccounts] = useState<
        Array<{
            pubkey: PublicKey;
            account: AccountInfo<ParsedAccountData|Buffer>;
        }>    
    >();
    const [stakeCard, setStakeCard] = useState<JSX.Element>();

    useEffect(() => {
        if (stakeAccounts) {
            buildView();
        }
      }, [stakeAccounts]);
    
    async function getStakeAccounts(wallet: string) {
        const stakeAccounts = await connection.getParsedProgramAccounts(
            STAKE_PROGRAM_PK, {
            filters: [
                {
                  dataSize: DATA_SIZE, // number of bytes
                },
                {
                  memcmp: {
                    offset: WALLET_OFFSET, // number of bytes
                    bytes: wallet, // base58 encoded string
                  },
                },
              ]
            }
        );
        setStakeAccounts(stakeAccounts);        
    }

    function buildView() {
        if(stakeAccounts.length > 0) {
            let header = 
                <thead className="text-xs text-gray-700 uppercase bg-zinc-50 dark:bg-gray-700 dark:text-gray-400">
                    <tr>
                        <td className="px-6 py-3">#</td>
                        <td className="px-6 py-3">Stake Wallet</td>
                        <td className="px-6 py-3">Balance (SOL)</td>
                        <td className="px-6 py-3">Age (Epochs)</td>
                        <td className="px-6 py-3">Delegation</td>
                    </tr>
                </thead>;
                
            let rows = (stakeAccounts.map((account,i)=>{
                const epcohStart = Number(account.account.data.parsed?.info.stake.delegation.activationEpoch);
                const epochCurrent = Number(account.account.rentEpoch);
                const epochAge = epochCurrent - epcohStart;
                return (
                    <tr key={i+1} className="bg-white border-b bg-zinc-800 dark:border-zinc-700">
                        <td className="px-6 py-3">{i+1}</td>
                        <td className="px-6 py-3">{account.pubkey.toString()}</td>                
                        <td className="px-6 py-3">{'◎ ' + (account.account.lamports/LAMPORTS_PER_SOL).toFixed(2)}</td>       
                        <td className="px-6 py-3">{epochAge}</td>       
                        <td className="px-6 py-3">{account.account.data.parsed?.info.stake.delegation.voter}</td>               
                    </tr>)
                }
            ));
            let table = (
            <table className="w-full text-sm text-left text-gray-500 dark:text-gray-400">
                {header}
                <tbody>{rows}</tbody>
            </table>);
            setStakeCard(table)
        } 
        else {
            setStakeCard(<>No Stake Accounts Found</>);
        }
    }
    const onClick = async () => {
        if (!publicKey) {
            console.log('error', 'Wallet not connected!');
            notify({ type: 'error', message: 'error', description: 'Wallet not connected!' });
            return;
        }
        try { 
            //Example Mainnet Wallet Replace publicKey.toString() with 'AhbkRLfEuL5zV5gbooQzcDP7dZLBWK5En3mPVixs34yb'
            //Example Devnet Wallet Replace publicKey.toString() with 'Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE'
            await getStakeAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `Couldn't Staking Accounts!`, description: error?.message });
            console.log('error', `Error finding Staking Accounts! ${error?.message}`);
        }
    };

    return(
        <div>
            <div className="text-center">
                <button
                        className="px-8 m-2 btn animate-pulse bg-gradient-to-r from-[#9945FF] to-[#14F195] center hover:from-pink-500 hover:to-yellow-500 ..."
                        onClick={onClick}
                    >
                        <span>Get Staked Accounts</span>
                </button>
            </div>
            <div>{stakeCard}</div>
        </div>
    )    
}

Here's what's happening in this Function Component:

Summary of Staking Detail Component

Update Explorer View

Nice work! Now we need to call StakeDetails in our Explorer View. To do that, navigate to src/views/explorer/index.tsx, the view we currently have displayed on our site at /explorer/.

Import StakeDetails into your view. On line 7, add:

update explorer view

Copy
import { StakeDetail } from "components/StakingDetail";

Call in your return statement with your TransactionLogs and GetTokens components (ours is on line 35):

update explorer view

Copy
        <div className="text-center">          
          <TransactionLog/>
          <GetTokens/> 
          <StakeDetail/>
        </div>
Since we're adding to our ExplorerView and that view is already called in our Explorer page, we do not need to create any new pages to display these results.

Great job! You should be good to go. Go ahead and run your code. In your terminal type:

update explorer view

Copy
yarn dev
NOTE: If you do not have a staking account, you can test your code by making a small change to your StakeDetail component.

Open explorer-clone-part-3/starter/src/components/StakingDetail.tsx and replace line 92:

update explorer view

Copy
await getStakeAccounts(publicKey.toString());

with

update explorer view

Copy
await getStakeAccounts('AhbkRLfEuL5zV5gbooQzcDP7dZLBWK5En3mPVixs34yb'); // for mainnet-beta
// OR
await getStakeAccounts('Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE'); // for devnet

These are known staking accounts that will allow you to make sure your query is working correctly if you don't have a staked account in your connected wallet.

Wrap Up

Congrats! If you have followed along throughout this series, you should have a functioning Solana Explorer that returns a user's token accounts, transaction histories, transaction details, and staking accounts.

At this point in the series, you should feel confident in creating new components and views for displaying any kind of information on Solana. Want to keep building on what you have learned? Try adding some of your own custom components to the site. Here are some resources that might get your creative juices flowing:


Got ideas, questions, or want to show off what you've created? Show us on Discord or reach out to us via Twitter to share what you come up with.

We <3 Feedback!

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

Related articles 33

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