Solana区块浏览器开发实战
区块链浏览器是一种允许用户搜索、查看和验证区块链内容的工具。本文介绍如何使用 Next.js 开发Solana区块链浏览器。
一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | AVAX | FTM | OK
区块链浏览器是一种允许用户搜索、查看和验证区块链内容的工具。 许多区块链浏览器都可用,例如比特币、以太坊和 Solana。
Solana 区块链是一个支持大规模去中心化应用的高性能区块链平台。 Solana 区块链的一些用例包括去中心化金融 (DeFi)、不可替代代币 (NFT)、游戏和社交媒体。
本文将着眼于使用 Next.js 为 Solana 区块链构建区块链浏览器。
1、设置 Next.js 项目
我们将构建一个 Next.js 应用程序来与 Solana 区块链交互。 Next.js 是一个用于构建 React 应用程序的框架。 它是构建 React 应用程序的流行选择,因为它具有许多开箱即用的功能。 这包括:
- 基于文件的路由
- 服务端渲染
- 静态站点生成
- 自动代码分割
要创建 Next.js 应用程序,请确保你的计算机上安装了 Node.js v14.16.0 或更高版本。 确认后,打开终端并运行下面的代码。
npx create-next-app@latest
上面的命令引导 Next.js 应用程序。 你将被要求
- 提供应用程序的名称
- 选择 Typescript 和 Javascript 来引导应用程序
- 安装 Eslint
安装后,导航到新创建的应用程序的目录并运行以下代码来启动应用程序。
npm run dev
打开浏览器并导航到 http://localhost:3000 以查看该应用程序:
为了设计这个项目,我们将使用 Tailwind CSS,一个实用的 CSS 框架。 运行以下代码将 Tailwind CSS 添加到项目中。
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
上面的命令将安装 Tailwind CSS 库并创建 tailwind.config.js
文件。 在代码编辑器中打开文件并替换内容属性。 你的配置应该类似于下面的代码。
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
};
content
块帮助告诉 tailwind 文件目录以查找 tailwind 样式。 接下来,导航到 styles 目录并打开 global.css
文件。 在文件顶部添加以下导入。
@tailwind base;
@tailwind components;
@tailwind utilities;
我们现在可以在项目中使用 Tailwind。 导航到页面目录中的index.js 文件并将代码替换为以下代码。
import Head from "next/head";
export default function Home() {
return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="w-full h-full max-w-2xl p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
<h1 className="text-2xl">Solana Blockchain Explorer</h1>
</main>
</>
);
}
2、构建组件
在获取交易历史记录之前,让我们花一点时间构建将用于显示交易信息的组件。
运行下面的代码来安装我们需要的库
npm i axios date-fns
这将安装 Axios(一个基于 Promise 的 JavaScript 数据获取库)和 date-fns(一个用于操作 JavaScript 日期的库)。
安装后,导航到根目录并创建一个 components
目录,在其中创建一个 TransactionList.js
文件并粘贴以下代码。
import React from "react";
import { fromUnixTime, format, formatDistanceToNow } from "date-fns";
import Link from "next/link";
const TransactionList = ({ transactionList, balance }) => {
return (
<div className="first-line:overflow-hidden transition-all duration-300 max-h-fit w-full h-full">
{balance && (
<h2 className="flex justify-between text-lg mb-4">
Balance: <span>◎{balance}</span>
</h2>
)}
{transactionList?.length > 0 && (
<div className="overflow-x-auto">
<table className="w-full border-spacing-x-4 -ml-4 border-separate">
<thead className="text-left">
<tr>
<th className="font-medium">Signature</th>
<th className="font-medium">Block</th>
<th className="font-medium">Age</th>
<th className="font-medium">Status</th>
</tr>
</thead>
<tbody>
{transactionList.map((transaction) => (
<tr key={transaction?.signature}>
<td className="truncate max-w-[230px] text-blue-600 hover:underline">
<Link href={`/transaction/${transaction?.signature}`}>
{transaction?.signature}
</Link>
</td>
<td>{transaction?.slot}</td>
<td
className="whitespace-nowrap"
title={format(
fromUnixTime(transaction?.blockTime),
"MMMM d, yyyy 'at' HH:mm:ss OOOO"
)}
>
{formatDistanceToNow(fromUnixTime(transaction?.blockTime), {
includeSeconds: true,
})}
</td>
<td>
<span
className={`inline-block px-2 py-1 rounded-full text-xs font-bold leading-none text-white ${
transaction?.confirmationStatus === "finalized"
? "bg-green-500"
: "bg-yellow-400"
}`}
>
{transaction?.confirmationStatus}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{transactionList?.length <= 0 && (
<div className="text-center">No transaction to display</div>
)}
</div>
);
};
export default TransactionList;
该组件将用于显示从 Solana 区块链获取的交易列表。 它将 transactionList
和 balance
作为 props 并将它们显示在 UI 上。
在 components
目录中,创建另一个名为 SearchTransactionForm.js
的文件并粘贴以下代码。
import React from "react";
const SearchTransactionForm = ({
handleFormSubmit,
address,
loading,
setAddress,
errorMessage,
}) => {
return (
<form onSubmit={handleFormSubmit} className="flex flex-wrap w-full">
<label htmlFor="address" className="w-full shrink-0 text-lg mb-2">
Transaction address
</label>
<input
type="text"
name="address"
value={address}
onChange={(event) => setAddress(event.target.value)}
className="w-3/4 border-2 border-r-0 border-gray-500 h-12 rounded-l-lg px-4 focus:outline-none focus:border-blue-600 disabled:bg-gray-500 transition-colors duration-150"
placeholder="CHrNmjoRzaGCL..."
disabled={loading}
required
/>
<button
type="submit"
disabled={loading}
className="flex-grow bg-blue-600 flex items-center justify-center rounded-r-lg text-white text-sm hover:bg-blue-900 disabled:bg-gray-500 transition-colors duration-150"
>
Search
</button>
{errorMessage && (
<p className="text-red-600 text-base my-1">{errorMessage}</p>
)}
</form>
);
};
export default SearchTransactionForm;
该组件显示搜索输入表单。 它允许用户输入地址并提交,预计会返回交易列表。
最后,粘贴以下代码以创建另一个名为 TransactionListDetail.js
的文件。
import React from "react";
import { fromUnixTime, format } from "date-fns";
const TransactionListDetail = ({ loading, transactionData }) => {
return (
<div className="w-full">
{!loading && transactionData && (
<div className="rounded-lg border max-w-xl overflow-x-auto mx-auto">
<table className="table-auto w-full border-collapse p-4">
<tbody className="overflow-x-scroll">
<tr className="border-b">
<td className="font-medium text-sm p-4">Signature</td>
<td className="p-4">
{transactionData.transaction.signatures[0]}
</td>
</tr>
<tr className="border-b">
<td className="font-medium text-sm p-4">Timestamp</td>
<td className="p-4">
{format(
fromUnixTime(transactionData?.blockTime),
"MMMM d, yyyy 'at' HH:mm:ss OOOO"
)}
</td>
</tr>
<tr className="border-b">
<td className="font-medium text-sm p-4">Recent Blockhash</td>
<td className="p-4">
{transactionData.transaction.message.recentBlockhash}
</td>
</tr>
<tr className="border-b">
<td className="font-medium text-sm p-4">Slot</td>
<td className="p-4">
{Intl.NumberFormat().format(transactionData.slot)}
</td>
</tr>
<tr className="border-b">
<td className="font-medium text-sm p-4">Fee</td>
<td className="p-4">
◎{transactionData.meta.fee / 1_000_000_000}
</td>
</tr>
<tr className="border-b">
<td className="font-medium text-sm p-4">Amount</td>
<td className="p-4">
◎
{transactionData.transaction.message.instructions[0].parsed
.info.lamports / 1_000_000_000}
</td>
</tr>
</tbody>
</table>
</div>
)}
{!loading && !transactionData && (
<p className="text-center">No transaction to display</p>
)}
</div>
);
};
export default TransactionListDetail;
我们将使用此组件来显示特定交易的详细信息。 它将接受 transactionData
作为 props 并使用其详细信息显示在 UI 上。
3、从 Solana 获取交易历史记录
现在我们已经启动并运行了 Next.js 应用程序,下一步是将 Solana 添加到我们的应用程序中。 幸运的是,Solana 提供了一个维护良好的 JavaScript 库,用于与 Solana 区块链交互,称为 @solana/web3.js
。 运行下面的代码来安装该库。
npm install @solana/web3.js
安装后,转到 pages/api
并创建一个 transactions.js
文件。 我们将使用 Next.js API 路由来获取用户交易。 这使我们能够将 Solana 配置和业务逻辑与客户端分开。 打开 transactions.js
文件并粘贴以下代码。
import * as solanaWeb3 from "@solana/web3.js";
const DEV_NET = solanaWeb3.clusterApiUrl("devnet");
const solanaConnection = new solanaWeb3.Connection(DEV_NET);
const getAddressInfo = async (address, numTx = 3) => {
const pubKey = new solanaWeb3.PublicKey(address);
const transactionList = await solanaConnection.getSignaturesForAddress(
pubKey,
{ limit: numTx }
);
const accountBalance = await solanaConnection.getBalance(pubKey);
return { transactionList, accountBalance };
};
const handler = async (req, res) => {
const queryAddress = req.query?.address;
if (!queryAddress) {
return res.status(401).json({
message: "Invalid address",
});
}
try {
const { accountBalance, transactionList } = await getAddressInfo(
queryAddress
);
return res.status(200).json({ transactionList, accountBalance });
} catch (error) {
console.log(error);
return res.status(500).json({
message: "Something went wrong. Please try again later",
});
}
};
export default handler;
我们必须导入该库才能在 transactions.js
文件中使用 Solana。 之后,我们创建与 Solana RPC 节点的连接。
const DEV_NET = solanaWeb3.clusterApiUrl('devnet');
const solanaConnection = new solanaWeb3.Connection(DEV_NET);
Solana RPC(远程过程调用)节点是响应网络请求并允许用户提交交易的节点。 Solana 维护一些公开可用的节点,其中包括 DEV_NET
。 我们将创建与 DEV_NET RPC 节点的连接,使我们能够获取节点上交易的地址的交易历史记录和余额。
下一步是创建一个函数 getAddressInfo
以从 Solana RPC 节点获取我们需要的信息。 该函数接受一个地址和要获取的交易数量,默认数量为 3。要获取交易并使用 @solana/web3.js
执行大多数操作,我们需要一个公钥,即Solana上的通用标识符。 公钥可以从 base58 编码的字符串、缓冲区、Uint8Array、数字和数字数组生成。 我们从用户地址(一个 base58 编码的字符串)生成公钥。
const pubKey = new solanaWeb3.PublicKey(address);
为了获取交易列表,我们使用 getSignaturesForAddress
方法,该方法返回交易列表。 该方法需要一个 publicKey
和一个可选的分页对象。
const transactionList = await solanaConnection.getSignaturesForAddress(pubKey, { limit: numTx });
getBalance
方法返回用户余额,需要传入公钥参数。
const accountBalance = await solanaConnection.getBalance(pubKey);
handler
函数将所有内容联系在一起,并以可向客户端显示的可呈现方式返回详细信息。
在终端中运行下面的代码来安装库,这将帮助我们正确获取和格式化交易。
现在返回到 index.js
文件并粘贴下面的代码。
import Head from "next/head";
import { useState } from "react";
import axios from "axios";
import TransactionList from "../components/TransactionList";
import SearchTransactionForm from "../components/SearchTransactionForm";
export default function Home() {
const [loading, setLoading] = useState(false);
const [transactionList, setTransactionList] = useState([]);
const [balance, setBalance] = useState(null);
const [address, setAddress] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const handleFormSubmit = async (event) => {
try {
event.preventDefault();
setLoading(true);
setErrorMessage("");
const response = await axios.get(`/api/transactions/?address=${address}`);
if (response.status === 200) {
setTransactionList(response.data.transactionList);
const accountBalanceText = response.data.accountBalance;
const accountBalance = parseInt(accountBalanceText) / 1_000_000_000;
accountBalance && setBalance(accountBalance);
}
} catch (error) {
console.log("client", error);
setErrorMessage(
error?.response.data?.message ||
"Unable to fetch transactions. Please try again later."
);
} finally {
}
setLoading(false);
};
return (
<>
<Head>
<title>Solana Blockchain Explorer</title>
</Head>
<main className="w-full h-full max-w-2xl p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
<h1 className="text-2xl">Solana Blockchain Explorer</h1>
<SearchTransactionForm
handleFormSubmit={handleFormSubmit}
address={address}
setAddress={setAddress}
loading={loading}
errorMessage={errorMessage}
/>
<TransactionList transactionList={transactionList} balance={balance} />
{loading && (
<div className="absolute inset-0 bg-white/70 flex items-center justify-center">
Loading
</div>
)}
</main>
</>
);
}
我们在此页面上所做的就是将所有内容联系在一起。 我们显示之前创建的 SearchTransactionForm
组件来收集用户的地址。 当用户提交表单时,将调用 handleFormSubmit
函数,该函数调用我们之前创建的交易API,并将 address
作为参数传递。 如果搜索成功,API 请求将返回 transactionData
和 balance
,它们将作为 props
传递给要显示的 TransactionList
组件。
保存并重新加载你的浏览器。 现在可以输入 Solana 地址并单击搜索按钮来获取交易历史记录。 应该得到类似于下面的屏幕截图的结果。
4、获取单笔交易
我们研究了如何从 Solana web3 库获取交易列表并显示它。 在本节中,我们将了解如何获取单个交易的详细信息。 导航到 api 目录并创建 transaction.js
文件。 打开文件并粘贴下面的代码。
import * as solanaWeb3 from "@solana/web3.js";
const DEV_NET = solanaWeb3.clusterApiUrl("devnet");
const solanaConnection = new solanaWeb3.Connection(DEV_NET);
const handler = async (req, res) => {
const transactionHash = req.body.transactionHash;
if (!transactionHash) {
return res.status(401).json({
error: "Invalid transaction hash",
});
}
try {
const transaction = await solanaConnection.getParsedTransaction(
transactionHash
);
return res.status(200).json(transaction);
} catch (error) {
console.log("Error:", error);
return res.status(500).json({
error: "Server error",
});
}
};
export default handler;
为了获取单个交易的详细信息,我们使用 getParsedTransaction
方法,该方法需要交易哈希。 交易哈希是从请求正文中获取的,该正文由 Next.js 提供给处理函数。 根据结果,我们向客户端返回响应。
下一步是构建一个页面来显示从 API 获取的交易详细信息。 在 pages
目录中创建一个 transaction
目录。 导航到事务目录并创建一个名为 [id].js
的文件。 该页面为动态路由; 每当用户访问 /transaction/gm12
或 transaction/12gm
时,该页面将在浏览器中呈现。 在代码编辑器中打开该文件并粘贴下面的代码。
import Head from "next/head";
import { useState, useEffect } from "react";
import axios from "axios";
import { useRouter } from "next/router";
import TransactionListDetail from "../../components/TransactionListDetail";
export default function TransactionDetail() {
const [loading, setLoading] = useState(false);
const [transactionData, setTransactionData] = useState();
const [errorMessage, setErrorMessage] = useState("");
const router = useRouter();
useEffect(() => {
const getTransaction = async () => {
try {
setLoading(true);
setErrorMessage("");
const response = await axios.post("/api/transaction", {
transactionHash: router.query?.id,
});
if (response.status === 200) {
setTransactionData(response.data.transaction);
}
} catch (error) {
setErrorMessage(
error?.response.data?.message ||
"Unable to fetch transaction. Please try again later."
);
} finally {
setLoading(false);
}
};
getTransaction();
}, [router.query?.id]);
return (
<>
<Head>
<title>Solana Blockchain Explorer: Transaction</title>
</Head>
<main className="w-full h-full p-6 flex flex-col items-center justify-between gap-6 mx-auto relative">
<h1 className="text-2xl">Transaction</h1>
{errorMessage && (
<p className="text-red-600 text-base text-center my-1">
{errorMessage}
</p>
)}
<TransactionListDetail
loading={loading}
transactionData={transactionData}
/>
{loading && (
<div className="absolute inset-0 bg-white/70 flex items-center justify-center">
Loading
</div>
)}
</main>
</>
);
}
我们所做的事情与主页上所做的类似,但我们不是从用户那里获取输入并将其传递给调用 API 的函数,而是从 URL 中获取所需的输入。 当用户访问 /transaction/[id]
路由时,会调用 getTransaction
函数。 该函数使用从路由获取的事务哈希请求 /api/transaction
端点。 如果请求成功,则返回数据,并显示在页面上。 如果获取交易详细信息时出现错误,页面上还会显示相应的错误消息。
现在在浏览器中访问该应用程序并搜索地址。 当结果显示时,单击交易以打开交易页面上的详细信息。
5、结束语
在本文中,我们研究了在 Solana 区块链上创建一个区块链浏览器。 区块链浏览器有很多用途,它可以是允许用户查看其交易历史记录的 web3 应用程序的一部分,也可以是像我们上面构建的那样的独立应用程序。
原文链接:Build A Blockchain Explorer With Solana And Next.Js
DefiPlot翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。