在购买基于区块链的活动门票之前,了解 gas 价格对于成本透明度和有效的财务规划至关重要。在区块链交易中,"gas"是指在区块链上成功执行交易或合约所需支付的费用。
该费用会根据网络需求波动。提前了解 gas 价格,购票者可以对购买总成本做出更明智的决策。这一点尤为重要,因为 gas 费用可能会显著增加门票的总体成本,尤其是在网络拥堵严重的时期。
在本教程中,我们将在现有的活动售票 dapp 中集成 MetaMask Gas API,使用户在购买活动门票时能够查看 gas 价格。
前提条件与依赖项
有效的 Web3 API 密钥和 API 密钥 secret。
已安装 Node.js 以及 npm 或 yarn。
熟悉 Next.js 和 React。
前端框架将使用 Next.js,UI 将使用 ShadcnUI。让我们安装以下 ShadcnUI 组件:
npx shadcn-ui@latest add hover-card
npm i swr
上述代码将添加 hover-card 组件(用于显示 gas 价格)并安装 SWR React hooks 库。Stale-While-Revalidate(SWR)库有助于从网络获取数据。其工作原理是:先使用已缓存的旧数据,再请求新数据,最后用新数据替换旧数据。这使网站更快速、更及时。
接下来,我们将安装 react-circular-progressbar 组件,用于显示环形进度指示器。该组件适用于在 Web 应用中展示任务进度或加载状态,支持自定义配置,并使用 SVG 实现高质量图形渲染。
npm i react-circular-progressbar集成 Gas API
在 app/web 目录下,创建以下文件夹:app/api/gas。在其中创建 route.ts 文件,并添加以下代码:
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const chainId = searchParams.get("chainId");
const Auth = Buffer.from(
process.env.INFURA_API_KEY + ":" + process.env.INFURA_API_SECRET
).toString("base64");
const gasPricesResp = await fetch(
`https://gas.api.infura.io/networks/${chainId}/suggestedGasFees`,
{
headers: {
Authorization: `Basic ${Auth}`,
},
}
);
const baseFeePercentileRes = await fetch(
`https://gas.api.infura.io/networks/${chainId}/baseFeePercentile`,
{
headers: {
Authorization: `Basic ${Auth}`,
},
}
);
const gasPricesData = await gasPricesResp.json();
const baseFeePercentileData = await baseFeePercentileRes.json();
return NextResponse.json({
estimatedBaseFee: gasPricesData.estimatedBaseFee,
baseFeeTrend: gasPricesData.baseFeeTrend,
baseFeePercentile: baseFeePercentileData.baseFeePercentile,
});
}
以下是具体实现说明:
提取查询参数:- const { searchParams } = new URL(request.url);:从请求 URL 中提取查询参数。
const chainId = searchParams.get("chainId");:从 URL 中获取 chainId 参数,用于指定以太坊网络。
配置授权:- 将环境变量 INFURA_API_KEY 和 INFURA_API_SECRET 合并写入 Buffer,再编码为 Base64 字符串。该字符串用于向 Infura API 发起请求时的 HTTP 基本认证。
获取 Gas 价格和基础费用百分位数据:- 向 Infura API 发起两个异步 fetch 请求:- gasPricesResp 获取指定以太坊网络(chainId)的建议 gas 费用。
baseFeePercentileRes 获取同一网络的基础费用百分位。
Authorization 请求头设置为 Base64 编码的凭证。
解析并返回数据:- 将两个 fetch 请求的响应转换为 JSON。
NextResponse.json({...}) 创建并返回包含以下数据的 JSON 响应:- estimatedBaseFee:交易的预估基础费用。
baseFeeTrend:基础费用趋势(如上涨、下降)。
baseFeePercentile:基础费用的百分位分布。
创建用户界面
我们将创建一个名为 GasFeeCard.tsx 的组件,并添加以下代码:
import useSWR, { mutate } from "swr";
import {
CircularProgressbarWithChildren,
buildStyles,
} from "react-circular-progressbar";
import "react-circular-progressbar/dist/styles.css";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/hover-card";
import { useEffect, useState } from "react";
import { ArrowUp, ArrowDown, InfoIcon } from "lucide-react";
import { useSDK } from "@metamask/sdk-react";
const REFETCH_INTERVAL = 10000;
const STEP_INTERVAL = 1000;
const NETWORK_LATENCY = 2000;
const formatGweiValues = (value: string) => {
const gwei = parseFloat(value);
if (gwei < 0.01) {
return gwei.toExponential(2);
} else {
return gwei.toFixed(2);
}
};
export function GasFeeCard() {
const { chainId } = useSDK();
const [countdown, setCountdown] = useState(REFETCH_INTERVAL);
const [currentChainId, setCurrentChainId] = useState(chainId);
useEffect(() => {
if (chainId !== currentChainId) {
setCurrentChainId(chainId);
mutate("gas-data");
if (countdown < NETWORK_LATENCY) {
setCountdown(REFETCH_INTERVAL + NETWORK_LATENCY);
}
setCountdown(REFETCH_INTERVAL);
}
}, [countdown, chainId, currentChainId, mutate]);
useEffect(() => {
const interval = setInterval(() => {
setCountdown((oldCountdown) => {
if (oldCountdown <= 0) {
mutate("gas-data");
return REFETCH_INTERVAL;
} else {
return oldCountdown - STEP_INTERVAL;
}
});
}, STEP_INTERVAL);
return () => clearInterval(interval);
}, [chainId]);
const fetcher = () =>
fetch(`/api/gas?&chainId=${parseInt(chainId || "0xe704")}`).then((res) =>
res.json()
);
const {
data: gasData,
error: gasDataError,
isLoading: gasDataLoading,
} = useSWR("gas-data", fetcher);
if (gasDataLoading) {
return <p>loading</p>;
}
if (gasDataError) {
return <p className="text-red-500">error</p>;
}
const trendingUp = gasData?.baseFeeTrend === "up";
const trendingDown = gasData?.baseFeeTrend === "down";
return (
<HoverCard>
<HoverCardTrigger asChild>
<div className="cursor-pointer">
<div className="flex space-x-1">
<div className="w-6 h-6">
<CircularProgressbarWithChildren
strokeWidth={12}
styles={buildStyles({
pathColor: trendingUp ? "red" : "green",
})}
value={
((REFETCH_INTERVAL - countdown) / REFETCH_INTERVAL) * 100
}
>
<>
{trendingUp && (
<ArrowUp
className="animate-bounceUp"
color="red"
size={14}
/>
)}
{trendingDown && (
<ArrowDown
className="animate-bounceDown"
color="green "
size={14}
/>
)}
</>
</CircularProgressbarWithChildren>
</div>
<p>
~{formatGweiValues(gasData?.estimatedBaseFee)}
Gwei
</p>
<InfoIcon size={14} />
</div>
</div>
</HoverCardTrigger>
<HoverCardContent className="p-0 w-80">
<div className="p-2 space-y-4">
<p>
This is an <strong>estimated base gas fee.</strong>
</p>
<p>
50% of the historical base fees are less then or equal to{" "}
<strong>
~{formatGweiValues(gasData?.baseFeePercentile)} Gwei
</strong>
</p>
</div>
</HoverCardContent>
</HoverCard>
);
}
以下是具体实现说明:
常量:- REFETCH_INTERVAL、STEP_INTERVAL、NETWORK_LATENCY:用于管理数据获取间隔、倒计时更新步长以及预估网络延迟的常量。
工具函数:- formatGweiValues:将 gas 价格格式化为可读形式的函数,对较小的值使用科学计数法,对较大的值使用定点表示法。
GasFeeCard 组件:- 使用 useSDK 获取当前以太坊 chainId。
初始化状态变量 countdown 和 currentChainId。
第一个 useEffect 钩子在 chain ID 变化时更新当前 chain ID 并触发数据重新获取(mutate),同时重置倒计时。
第二个 useEffect 设置一个定时器,每次递减倒计时,当倒计时归零时触发数据重新获取,并在组件卸载时清除定时器。
fetcher:从服务端 API 端点获取 gas 数据的函数,将当前 chain ID 作为查询参数传入。
useSWR:使用 fetcher 函数获取 gas 数据的 hook,提供数据、加载和错误状态。
渲染逻辑:- 组件根据条件渲染加载和错误状态。
trendingUp 和 trendingDown 是根据获取的 gas 数据中 baseFeeTrend 确定的布尔值。
主 UI 由一个 HoverCard 组成,其中包含显示倒计时进度的 CircularProgressbarWithChildren 和表示 gas 价格趋势的箭头图标,并以 Gwei 为单位显示预估基础费用。
HoverCardContent 展示预估基础费用和历史基础费用百分位的详细信息。
样式:- 代码使用 Tailwind CSS 类进行样式设置(如 text-red-500、cursor-pointer)。
CircularProgressbarWithChildren 根据 gas 价格趋势动态改变颜色(上涨为红色,下降为绿色)。
我们还使用了 lucide-react 中的 ArrowUp、ArrowDown 和 InfoIcon 图标。现在,只需将此组件导入 GetTicket.tsx 组件,使其显示在门票总价下方即可。
继续使用 MetaMask Gas API 进行开发
了解 gas 价格有助于买家选择最具性价比的购买时机,通过在 gas 价格较低时进行交易来节省费用。对于活动售票平台而言,提供这些信息有助于提升透明度和用户满意度,让用户对自己的消费更有掌控感,同时避免产生意外费用。
我们已将 Gas API 集成到现有的活动售票 dapp 中。本教程的代码可在此处获取。
同时推荐参考以下资源:
Gas API 文档:https://docs.infura.io/infura-expansion-apis/gas-api
Gas API 模板:https://github.com/meowyx/GasAPI-Template
祝开发愉快 🚀