name: web3-frontend
description: 用于dApp开发的Web3前端模式。适用于构建钱包连接、交易用户体验、代币显示或任何区块链连接的前端。涵盖wagmi/viem、ethers.js、Solana钱包适配器、BigNumber处理和错误翻译。
Web3前端模式
钱包连接
EVM (wagmi + viem)
import { WagmiProvider, createConfig, http } from "wagmi"
import { mainnet, base, arbitrum } from "wagmi/chains"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import { ConnectKitProvider, getDefaultConfig } from "connectkit"
const config = createConfig(
getDefaultConfig({
chains: [mainnet, base, arbitrum],
transports: {
[mainnet.id]: http(),
[base.id]: http(),
[arbitrum.id]: http(),
},
walletConnectProjectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
appName: "My dApp",
})
)
// 在组件中
import { useAccount, useConnect, useDisconnect } from "wagmi"
function ConnectButton() {
const { address, isConnected, chain } = useAccount()
const { disconnect } = useDisconnect()
// ...
}
Solana (@solana/wallet-adapter)
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base"
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui"
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react"
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets"
const network = WalletAdapterNetwork.Mainnet
const endpoint = clusterApiUrl(network)
const wallets = [new PhantomWalletAdapter()]
交易状态管理
type TxState =
| { status: "idle" }
| { status: "confirming" } // 钱包弹窗打开
| { status: "pending"; hash: `0x${string}` } // 已提交,等待中
| { status: "success"; receipt: TransactionReceipt }
| { status: "error"; error: Error }
// 始终向用户显示当前步骤
// 优雅处理钱包拒绝(用户关闭弹窗)
交易流程
async function sendTransaction() {
setTxState({ status: "confirming" })
try {
const hash = await walletClient.writeContract({
address: contractAddress,
abi: contractAbi,
functionName: "mint",
args: [amount],
})
setTxState({ status: "pending", hash })
const receipt = await publicClient.waitForTransactionReceipt({ hash })
if (receipt.status === "reverted") {
throw new Error("交易回滚")
}
setTxState({ status: "success", receipt })
} catch (error) {
if (isUserRejection(error)) {
setTxState({ status: "idle" }) // 用户取消时不显示错误
return
}
setTxState({ status: "error", error: error as Error })
}
}
BigNumber显示
import { formatEther, formatUnits } from "viem"
// ETH (18位小数)
const display = formatEther(balanceWei) // "1.234567890123456789"
// 代币(可变小数位)
const display = formatUnits(amount, decimals) // 例如:USDC有6位小数
// 显示格式化
function formatTokenAmount(amount: bigint, decimals: number, maxDecimals = 4): string {
const formatted = formatUnits(amount, decimals)
const num = parseFloat(formatted)
if (num === 0) return "0"
if (num < 0.0001) return "<0.0001"
return num.toLocaleString(undefined, { maximumFractionDigits: maxDecimals })
}
错误翻译
function getHumanError(error: unknown): string {
const message = error instanceof Error ? error.message : String(error)
// 用户拒绝
if (message.includes("User rejected") || message.includes("user rejected"))
return "交易已取消"
// 资金不足
if (message.includes("insufficient funds"))
return "Gas费用不足"
// 合约回滚 — 尝试解码
if (message.includes("execution reverted")) {
const reason = extractRevertReason(message)
return reason || "交易将失败 — 请检查输入"
}
// 网络错误
if (message.includes("network") || message.includes("timeout"))
return "网络错误 — 请重试"
return "出现错误 — 请重试"
}
链切换用户体验
import { useSwitchChain } from "wagmi"
function ChainSwitcher() {
const { switchChain } = useSwitchChain()
const { chain } = useAccount()
// 如果链错误,提示用户切换
if (chain?.id !== expectedChainId) {
return (
<button onClick={() => switchChain({ chainId: expectedChainId })}>
切换到 {expectedChainName}
</button>
)
}
}
ENS / SNS解析
// ENS (以太坊域名服务)
import { normalize } from "viem/ens"
const address = await publicClient.getEnsAddress({ name: normalize("vitalik.eth") })
const name = await publicClient.getEnsName({ address })
// 显示:如果可用则显示ENS名称,否则显示截断地址
function formatAddress(address: string, ensName?: string): string {
if (ensName) return ensName
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
详细参考
- 钱包模式 — 多钱包、WalletConnect、断开连接