Solana - 如何构建一个Solana Explorer克隆(3/3) - 获取质押账户 - Quicknode

  • QuickNode
  • 发布于 2025-01-30 11:25
  • 阅读 16

本文介绍了如何构建一个 Solana Explorer 的 cloned 组件,来展示用户质押的 Solana 账户和委托的详细信息。通过提供详细的实现步骤和代码示例,读者可以学习到如何获取钱包的质押账户并在前端展示出来,适合有一定开发基础的读者。

概述

Solana Explorer 提供了关于 Solana 区块链的大量信息:交易历史和详情、代币余额、NFT 元数据等等。在我们看来,浏览 Solana Explorer 是了解 Solana 的最佳途径之一,但构建一个 Solana Explorer 可以让你的知识提升到一个新的层次!在本系列三部分中,我们将介绍创建 Solana Explorer 简单克隆所需的步骤!

在本指南中,我们将提取一个钱包的委托账户,以显示用户有多少 $SOL 被委托以及与哪些验证者进行委托。

你将要做什么

在本指南中,你将使用我们之前如何构建 Solana Explorer 克隆(第 2 部分:交易详情)中构建的 Solana Explorer 示例。你将利用这个框架制作一个新的组件,允许用户查看他们的 Staked Solana 账户和委托的表格。

预期输出 - 委托账户

你将需要什么

要按照本指南操作,你需要完成我们的如何构建 Solana Explorer 克隆(第 2 部分:交易详情)指南。为什么?因为我们会在那里的代码上进行构建。

“如何构建 Solana Explorer 克隆(第 2 部分:交易详情)”指南中的最终代码可以在这个 QuickNode Github 仓库中找到。那篇指南将作为这篇指南的起点。确保你已经遵循那篇指南中的说明并完成了所有步骤。

快速开始*: 如果你没有机会完成第二篇指南,这里有一个简单的方法可以直接开始。在你的终端中,创建一个项目目录并克隆样本:*

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

然后使用你的 QuickNode RPC 更新 .env

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

然后在终端中输入 yarn dev

你的 https://localhost:3000/explorer 应该呈现以下视图:

快速开始 - 起始点

好的!让我们开始吧。

创建一个委托详情组件

让我们基于我们的交易工具构建一个组件,以允许用户点击交易以获取更多关于该交易的详情。好消息是我们已经具备了实现此功能的工具。

在你的项目目录中,复制你的组件模板并命名为 StakingDetail.tsx

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

StakingDetail.tsx 现在应该存在于你的 src/components 文件夹中。用代码编辑器打开它。

更新依赖

首先将你的函数组件重命名为 StakingDetail。替换

export const Template: FC = () => {

export const StakingDetail: FC = () => {

对于这个组件,我们需要以下导入。用这些替换你模板中的现有导入:

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

这些导入应该是你在 TransactionLogTransactionDetail 中已经使用过的。由于这个组件的组成与我们之前的组件相似,我们将一次性分享完整代码并逐步讲解一些重要部分:

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, // 字节数\
                },\
                {\
                  memcmp: {\
                    offset: WALLET_OFFSET, // 字节数\
                    bytes: wallet, // base58 编码字符串\
                  },\
                },\
              ]
            }
        );
        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">余额 (SOL)</td>
                        <td className="px-6 py-3">年龄 (纪元)</td>
                        <td className="px-6 py-3">委托</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(<>未找到委托账户</>);
        }
    }
    const onClick = async () => {
        if (!publicKey) {
            console.log('error', '钱包未连接!');
            notify({ type: 'error', message: 'error', description: '钱包未连接!' });
            return;
        }
        try {
            // 示例:主网钱包用 publicKey.toString() 替换为 'AhbkRLfEuL5zV5gbooQzcDP7dZLBWK5En3mPVixs34yb'
            // 示例:开发网钱包用 publicKey.toString() 替换为 'Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE'
            await getStakeAccounts(publicKey.toString());

        } catch (error: any) {
            notify({ type: 'error', message: `无法找到委托账户!`, description: error?.message });
            console.log('error', `查找委托账户时出错! ${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>获取委托账户</span>
                </button>
            </div>
            <div>{stakeCard}</div>
        </div>
    )
}

下面是这个函数组件中发生的事情:

委托详情组件摘要

更新 Explorer 视图

干得好!现在我们需要在我们的 Explorer 视图中调用 StakeDetails。为此,导航到 src/views/explorer/index.tsx,这是我们当前在网站上显示的视图 /explorer/

StakeDetails 导入到你的视图中。在第 7 行,添加:

import { StakeDetail } from "components/StakingDetail";

在你的返回语句中调用 TransactionLogsGetTokens 组件(我们的在第 35 行):

        <div className="text-center">
          <TransactionLog/>
          <GetTokens/>
          <StakeDetail/>
        </div>

因为我们正在向我们的 ExplorerView 添加内容,而该视图已经在我们的 Explorer 页面中调用,所以我们不需要创建新的页面来显示这些结果。

干得不错!你应该可以开始了。继续运行你的代码。在你的终端中输入:

yarn dev

注意:如果你没有委托账户,你可以通过对你的 StakeDetail 组件进行小的修改来测试你的代码。

打开 explorer-clone-part-3/starter/src/components/StakingDetail.tsx 并替换第 92 行:

await getStakeAccounts(publicKey.toString());

await getStakeAccounts('AhbkRLfEuL5zV5gbooQzcDP7dZLBWK5En3mPVixs34yb'); // 对于主网标准版
// 或者
await getStakeAccounts('Cxcfw2GC1tfEPEuNABNwTujwr6nEtsV6Enzjxz2pDqoE'); // 对于开发网

这些是已知的委托账户,如果你在连接的钱包中没有委托账户,这将确保你的查询正常工作。

总结

恭喜你!如果你在这个系列中一路跟随,你应该已经拥有一个功能完整的 Solana Explorer,能够返回用户的代币账户、交易历史、交易详情和委托账户。

在本系列的此时,你应该对创建新的组件和视图展示有关 Solana 的任何类型信息感到自信。想继续构建你所学到的内容吗?尝试向网站添加一些你自己的自定义组件。以下是一些可能激发你创意的资源:

有想法、问题,还是想展示你的创作?在 Discord 上告诉我们或者通过 Twitter 与我们联系,共享你所创建的内容。

我们 ❤️ 反馈!

如果你对本指南有任何反馈或问题,请告诉我们。我们非常乐意听取你的意见!

  • 原文链接: quicknode.com/guides/sol...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。