🎥 信标链验证者排行榜

本文介绍了如何使用 Beacon REST API 和 Ethers.js 构建一个以太坊 2.0 的验证者排行榜应用。详细说明了如何配置项目,安装依赖,以及各个功能的实现,包括获取验证者余额、计算总余额和生成排行榜等。代码部分包含了重要的实现细节,适合有一定基础的开发者阅读。

概述

Beacon 链是 ETH 2 质押智能合约的所在链。该链需要被查询以获取与验证者和质押合约相关的任何数据。在这个视频中,我们将看到如何查询 Beacon 链以获取 ETH 2 质押合约中总质押的 ETH、获取验证者的余额,并根据他们持有的总 ETH 创建验证者排行榜。

我们将使用 QuickNode 的 Ethereum 端点的 Beacon REST APIEthers.js 进行区块链交互。我们将在 Next.jsTailwind CSS 中构建应用进行样式设计。

依赖项 版本
node.js latest
Next.js latest
ethers.js 5.7.2
daisyUI latest

视频

从零开始构建应用 - Ethereum 2 Beaconchain 验证者排行榜 - YouTube

QuickNode

131K 订阅者

从零开始构建应用 - Ethereum 2 Beaconchain 验证者排行榜

QuickNode

搜索

信息

购物

轻触以取消静音

如果播放没有开始,请尝试重新启动你的设备。

你已退出登录

你观看的视频可能会被添加到电视的观看历史中并影响电视推荐。为了避免这种情况,请在你的计算机上取消并登录 YouTube。

取消确认

分享

包含播放列表

检索共享信息时发生错误。请稍后再试。

稍后观看

分享

复制链接

观看于

0:00

/ •实时

在 YouTube 上观看

订阅我们的 YouTube 频道以获取更多视频! 订阅

代码

按照视频和以下步骤操作以运行该应用,或从此 GitHub 仓库 Git 克隆,进入目录并运行 npm install,创建 .env 文件并将 NEXT_PUBLIC_QUICKNODE_RPC 作为变量保留你的 QuickNode 端点 HTTPS URL,然后运行 npm run dev 启动项目。

git clone https://github.com/velvet-shark/beacon-validators.git

cd beacon-validators

按照以下步骤逐步创建应用:

获取 Ethereum 端点以访问 Beacon 链数据

创建帐户后,点击 创建端点按钮。然后,选择 Ethereum 主网。

接着,复制你的 HTTP 提供程序 URL QuickNode 端点

安装依赖项并设置项目

npm i create-next-app

现在,通过运行以下命令初始化 Next.js 应用:

npx create-next-app@latest beacon-validators

beacon-validators 是项目名称,你可以将其更改为你选择的任何名称。

Tailwind 已包含在较新版本的 create-next-app 中。

询问选项时,使用以下配置: Next 应用安装配置

进入新创建的目录并通过运行安装 daisyUI 和其他依赖项:

npm i -D daisyui postcss autoprefixer

我们需要一个特定版本的 Ethers.js 以与 Next.js 配合使用,因此我们将通过以下命令安装:

npm i ethers@5.7.2

/tailwind.config.js

找到 tailwind.config.js 文件,并将文件内容替换为以下内容:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./pages/**/*.{js,ts,jsx,tsx}"],
  plugins: [require("daisyui")]
};

/styles/global.css

现在,找到 styles 目录中的 global.css 文件,删除所有内容,保留以下内容:

@tailwind base;
@tailwind components;
@tailwind utilities;

/pages/index.js

现在,找到 pages 目录中的 index.js 文件,并将代码替换为以下代码:

import Head from "next/head";
import Image from "next/image";
import { Inter } from "@next/font/google";
import styles from "@/styles/Home.module.css";

import { ethers, utils } from "ethers";
import { useState } from "react";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
    const [totalBalance, setTotalBalance] = useState(0);
    const [validators, setValidators] = useState([]);
    const [validatorBalance, setValidatorBalance] = useState(0);
    const [leaderboard, setLeaderboard] = useState([]);

    const [pendingInitializedBalance, setPendingInitializedBalance] = useState();
    const [pendingQueuedBalance, setPendingQueuedBalance] = useState();
    const [activeOngoingBalance, setActiveOngoingBalance] = useState();
    const [activeExitingBalance, setActiveExitingBalance] = useState();
    const [activeSlashedBalance, setActiveSlashedBalance] = useState();
    const [exitedUnslashedBalance, setExitedUnslashedBalance] = useState();
    const [exitedSlashedBalance, setExitedSlashedBalance] = useState();
    const [withdrawalPossibleBalance, setWithdrawalPossibleBalance] = useState();
    const [withdrawalDoneBalance, setWithdrawalDoneBalance] = useState();

    // 获取 Beacon 存款合约余额
    const fetchBeaconContractBalance = async () => {
        const provider = new ethers.providers.JsonRpcProvider(process.env.NEXT_PUBLIC_QUICKNODE_RPC);
        const balance = await provider.getBalance("0x00000000219ab540356cBB839Cbe05303d7705Fa");
        setTotalBalance(balance);
    };

    // 从 REST API 获取验证者数据
    const fetchValidators = async () => {
        try {
            console.log("正在获取验证者...");
            const validators = await fetch(
                `${process.env.NEXT_PUBLIC_QUICKNODE_RPC}eth/v1/beacon/states/head/validators`
            ).then((res) => res.json());

            // 取消注释以在控制台中查看所有验证者数据
            // console.log(validators.data);
            setValidators(validators.data);
        } catch (error) {
            console.error(error);
            setValidators([]);
        }
    };

    const sumValidatorBalances = (validators) => {
        const validatorBalance = validators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);
        console.log(validatorBalance);
        setValidatorBalance(validatorBalance);
    };

    // 汇总每个验证者状态的余额
    const sumValidatorStatuses = (validators) => {
        const pendingInitializedValidators = validators.filter(
            (validator) => validator.status === "pending_initialized"
        );
        const pendingQueuedValidators = validators.filter((validator) => validator.status === "pending_queued");
        const activeOngoingValidators = validators.filter((validator) => validator.status === "active_ongoing");
        const activeExitingValidators = validators.filter((validator) => validator.status === "active_exiting");
        const activeSlashedValidators = validators.filter((validator) => validator.status === "active_slashed");
        const exitedUnslashedValidators = validators.filter((validator) => validator.status === "exited_unslashed");
        const exitedSlashedValidators = validators.filter((validator) => validator.status === "exited_slashed");
        const withdrawalPossibleValidators = validators.filter(
            (validator) => validator.status === "withdrawal_possible"
        );
        const withdrawalDoneValidators = validators.filter((validator) => validator.status === "withdrawal_done");

        const pendingInitializedBalance = pendingInitializedValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const pendingQueuedBalance = pendingQueuedValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const activeOngoingBalance = activeOngoingValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const activeExitingBalance = activeExitingValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const activeSlashedBalance = activeSlashedValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const exitedUnslashedBalance = exitedUnslashedValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const exitedSlashedBalance = exitedSlashedValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const withdrawalPossibleBalance = withdrawalPossibleValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        const withdrawalDoneBalance = withdrawalDoneValidators.reduce((acc, validator) => {
            return acc + parseInt(validator.balance);
        }, 0);

        setPendingInitializedBalance(pendingInitializedBalance);
        setPendingQueuedBalance(pendingQueuedBalance);
        setActiveOngoingBalance(activeOngoingBalance);
        setActiveExitingBalance(activeExitingBalance);
        setActiveSlashedBalance(activeSlashedBalance);
        setExitedUnslashedBalance(exitedUnslashedBalance);
        setExitedSlashedBalance(exitedSlashedBalance);
        setWithdrawalPossibleBalance(withdrawalPossibleBalance);
        setWithdrawalDoneBalance(withdrawalDoneBalance);

        console.log("pendingInitializedBalance", pendingInitializedBalance);
        console.log("pendingQueuedBalance", pendingQueuedBalance);
        console.log("activeOngoingBalance", activeOngoingBalance);
        console.log("activeExitingBalance", activeExitingBalance);
        console.log("activeSlashedBalance", activeSlashedBalance);
        console.log("exitedUnslashedBalance", exitedUnslashedBalance);
        console.log("exitedSlashedBalance", exitedSlashedBalance);
        console.log("withdrawalPossibleBalance", withdrawalPossibleBalance);
        console.log("withdrawalDoneBalance", withdrawalDoneBalance);
    };

    // 处理数据获取
    const fetchData = (e) => {
        fetchBeaconContractBalance();
    };

    const calculateData = (e) => {
        sumValidatorBalances(validators);
        sumValidatorStatuses(validators);
    };

    const calculateLeaderboard = (e) => {
        const leaderboad = validators.sort((a, b) => b.balance - a.balance);
        console.log(leaderboad.slice(0, 300));
        setLeaderboard(leaderboad.slice(0, 300));
    };

    return (
        <>
            <Head>
                <title>Ethereum 验证者数据</title>
                <meta name="description" content="Ethereum 验证者数据" />
                <meta name="viewport" content="width=device-width, initial-scale=1" />
                <link rel="icon" href="/favicon.ico" />
            </Head>
            <div className="container xl mx-auto px-4">
                <div className="hero bg-base-200 py-10">
                    <div className="hero-content text-center">
                        <div className="max-w-3xl">
                            <h1 className="text-5xl font-bold text-primary-content">Ethereum 验证者</h1>
                            <p className="py-6 text-primary-content">
                                总质押 ETH + 按质押状态(活动、被削减、退出等)汇总 + 前 300 名
                                质押排行榜
                            </p>
                            <button type="submit" onClick={fetchData} className="btn btn-primary m-2 normal-case">
                                获取质押余额
                            </button>
                            <button type="submit" onClick={fetchValidators} className="btn btn-primary m-2 normal-case">
                                获取验证者数据
                            </button>
                            <button
                                type="submit"
                                onClick={calculateData}
                                className="btn btn-secondary m-2 normal-case"
                                disabled={validators.length > 0 ? false : true}
                            >
                                计算验证者数据
                            </button>
                            <button
                                type="submit"
                                onClick={calculateLeaderboard}
                                className="btn btn-accent m-2 normal-case"
                                disabled={validators.length > 0 ? false : true}
                            >
                                显示排行榜
                            </button>
                        </div>
                    </div>
                </div>

                {totalBalance > 0 && (
                    <div className="stats bg-primary text-primary-content m-2">
                        <div className="stat">
                            <div className="stat-title">总 Beacon 合约余额</div>
                            <div className="stat-value">
                                {(parseInt(totalBalance) / 1e18).toLocaleString(undefined, {
                                    minimumFractionDigits: 0
                                })}{" "}
                                ETH
                            </div>
                        </div>
                    </div>
                )}

                {validators.length > 0 && (
                    <div className="stats bg-primary text-primary-content m-2">
                        <div className="stat">
                            <div className="stat-title">总验证者条目</div>
                            <div className="stat-value">
                                {validators.length.toLocaleString(undefined, { minimumFractionDigits: 0 })}
                            </div>
                        </div>
                    </div>
                )}
                <br />
                {validatorBalance > 0 && (
                    <div className="stats bg-secondary text-primary-content m-2">
                        <div className="stat">
                            <div className="stat-title">所有验证者余额总和</div>
                            <div className="stat-value">
                                {(validatorBalance / 1e9).toLocaleString(undefined, { minimumFractionDigits: 0 })} ETH
                            </div>
                        </div>
                    </div>
                )}

                {validatorBalance > 0 && (
                    <>
                        <h2 className="text-4xl text-primary-content font-bold pt-5 pb-3">按状态汇总</h2>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>pending_initialized</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(pendingInitializedBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>pending_queued</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(pendingQueuedBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>active_ongoing</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(activeOngoingBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>active_exiting</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(activeExitingBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>active_slashed</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(activeSlashedBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>exited_unslashed</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(exitedUnslashedBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>exited_slashed</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(exitedSlashedBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>withdrawal_possible</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(withdrawalPossibleBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>

                        <div className="stats bg-secondary text-primary-content m-2">
                            <div className="stat">
                                <div className="stat-title">
                                    所有 <strong>withdrawal_done</strong> 验证者的余额总和
                                </div>
                                <div className="stat-value">
                                    {(withdrawalDoneBalance / 1e9).toLocaleString(undefined, {
                                        minimumFractionDigits: 0
                                    })}{" "}
                                    ETH
                                </div>
                            </div>
                        </div>
                    </>
                )}

                {leaderboard.length > 0 && (
                    <>
                        <h2 className="text-4xl text-primary-content font-bold pt-5 pb-3">排行榜</h2>

                        <div className="overflow-x-auto">
                            <table className="table table-compact w-full">
                                <thead>
                                    <tr>
                                        <th className="bg-accent text-primary-content">排名</th>
                                        <th className="bg-accent text-primary-content">索引</th>
                                        <th className="bg-accent text-primary-content">余额</th>
                                        <th className="bg-accent text-primary-content">公钥</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {leaderboard.map((validator, index) => (
                                        <tr key={validator.publicKey}>
                                            <th>{index + 1}</th>
                                            <td>{validator.index}</td>
                                            <td>
                                                <strong>
                                                    {(validator.balance / 1e9).toLocaleString(undefined, {
                                                        minimumFractionDigits: 4
                                                    })}{" "}
                                                    ETH
                                                </strong>
                                            </td>
                                            <td>
                                                <a
                                                    href={`https://beaconscan.com/validator/${validator.validator.pubkey}`}
                                                    target="_blank"
                                                    rel="noreferrer"
                                                    className="text-primary-content"
                                                >
                                                    {validator.validator.pubkey}
                                                </a>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                                <tfoot>
                                    <tr>
                                        <th>排名</th>
                                        <th>索引</th>
                                        <th>余额</th>
                                        <th>公钥</th>
                                    </tr>
                                </tfoot>
                            </table>
                        </div>
                    </>
                )}
            </div>
            <footer className="footer footer-center p-4 bg-base-300 text-base-content">
                <div>
                    <p>
                        <a
                            href="https://www.quicknode.com"
                            target="_blank"
                            rel="noreferrer"
                            className="text-primary-content"
                        >
                            <img src="powered-by-quicknode-blue.png" alt="QuickNode" width="150" />
                        </a>
                    </p>
                    <div>
                        <div className="card inline-block my-2 mx-4 w-69 bg-primary text-primary-content">
                            <div className="card-body">
                                <h2 className="card-title">代码</h2>
                                <p>网站的完整代码。</p>
                                <div className="card-actions justify-end">
                                    <button className="btn normal-case">
                                        <a
                                            href="https://github.com/velvet-shark/beacon-validators"
                                            target="_blank"
                                            rel="noreferrer"
                                            className="text-primary-content inline"
                                        >
                                            在 GitHub 上获取
                                        </a>
                                    </button>
                                </div>
                            </div>
                        </div>
                        <div className="card inline-block m-2 w-69 bg-primary text-primary-content">
                            <div className="card-body">
                                <h2 className="card-title">视频</h2>
                                <p>想看看现场构建吗?</p>
                                <div className="card-actions justify-end">
                                    <button className="btn normal-case">
                                        <a
                                            href="https://youtu.be/DpQqXv8Tq5A"
                                            target="_blank"
                                            rel="noreferrer"
                                            className="text-primary-content inline"
                                        >
                                            在 YouTube 上观看
                                        </a>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </footer>
        </>
    );
}

在父目录 beacon-validators 中创建一个 .env 文件,并粘贴以下内容:

NEXT_PUBLIC_QUICKNODE_RPC = QUICKNODE_HTTPS_URL

用你的 QuickNode Ethereum 主网 HTTPS 端点替换 QUICKNODE_HTTPS_URL

运行应用

npm run dev

最终的应用应该是这样的: 最终应用

我们 ❤️ 反馈!

让我们知道如果你有任何反馈或新主题的请求。我们期待听到你的意见。

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

0 条评论

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