Web3前端开发模式 web3-frontend

Web3前端开发模式是用于构建去中心化应用(dApp)前端界面的最佳实践集合。该技能涵盖钱包连接集成(EVM和Solana生态)、交易状态管理、大数字显示格式化、区块链错误信息翻译、链切换用户体验优化以及域名解析等功能实现。关键词:Web3前端、dApp开发、钱包连接、区块链交易、用户体验优化、Ethereum、Solana、React、TypeScript、智能合约交互。

链开发 0 次安装 0 次浏览 更新于 2/23/2026

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)}`
}

详细参考