学习目标: 连接钱包、读取合约,并发送打赏交易。
上一课
本课目录
- 1. Wagmi 简介
- 2. 为什么选择 Wagmi?
- 3. 安装与配置
- 4. 开发实战:Web3 打赏箱
- 5. 组装与测试
- 6. 常见问题与最佳实践
1. Wagmi 简介
Wagmi 是一个基于 React Hooks 构建的 Web3 前端工具集。如果说 Viem 是与区块链通信的底层引擎(类似于 Web2 的 fetch 或 axios),那么 Wagmi 就是为 React 量身定制的业务层封装(类似于 SWR 或 React Query)。
主要特点
- 开箱即用:内置数十个常用的 React Hooks(连接钱包、查余额、调用合约等)。
- 原生缓存:底层由 @tanstack/react-query 驱动,自动处理请求去重、缓存和轮询。
- 类型安全:深度集成 TypeScript,提供 ABI 级别的强类型推导。
- 极简架构:告别过去 Web3.js / Ethers.js 时代繁琐的 Provider 状态管理。
2. 为什么选择 Wagmi?
对于 Web2 开发者来说,处理区块链交互最头疼的是状态同步。区块链的数据是异步且需要等待区块确认的。
2.1 核心优势对比

3. 安装与配置
我们将使用 Vite 快速搭建一个现代化的 React 项目。
3.1 初始化项目
npm create vite@latest web3-tip-jar -- --template react-ts
cd web3-tip-jar
npm install
3.2 安装核心依赖
安装 Wagmi、Viem 以及 React Query:
npm install wagmi viem @tanstack/react-query
3.3 配置全局 Provider
在 src 目录下创建 config.ts,配置支持的区块链网络(这里以以太坊主网和 Sepolia 测试网为例):
// src/config.ts
import { http, createConfig } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'
export const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
})
修改 src/main.tsx,用 Provider 包裹你的应用:
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from './config'
import App from './App'
const queryClient = new QueryClient()
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
)
4. 开发实战:Web3 打赏箱
我们的项目分为三个核心步骤:连接身份、展示数据、发起打赏。
4.1 第一步:连接用户钱包 (Login)
在 Web3 中,连接钱包就是登录。我们创建一个 WalletConnect.tsx 组件。
// src/components/WalletConnect.tsx
import { useAccount, useConnect, useDisconnect } from 'wagmi'
import { injected } from 'wagmi/connectors'
export function WalletConnect() {
const { address, isConnected } = useAccount()
const { connect } = useConnect()
const { disconnect } = useDisconnect()
if (isConnected) {
return (
<div>
<p>已连接: {address?.slice(0, 6)}...{address?.slice(-4)}</p>
<button onClick={() => disconnect()}>断开连接</button>
</div>
)
}
return (
<button onClick={() => connect({ connector: injected() })}>
连接 MetaMask
</button>
)
}
4.2 第二步:读取链上数据 (Read)
用户连接后,我们要展示他们的 ETH 余额,并尝试解析他们的 ENS 域名(类似 Web2 的获取 User Profile)。
// src/components/UserProfile.tsx
import { useAccount, useBalance, useEnsName } from 'wagmi'
export function UserProfile() {
const { address } = useAccount()
// 读取余额
const { data: balance } = useBalance({ address })
// 读取 ENS 域名 (例如 alice.eth)
const { data: ensName } = useEnsName({ address })
if (!address) return null
return (
<div style={{ padding: '20px', border: '1px solid #ccc', marginTop: '20px' }}>
<h3>👋 欢迎, {ensName ? ensName : '神秘黑客'}</h3>
<p>你的钱包余额: {balance?.formatted} {balance?.symbol}</p>
</div>
)
}
4.3 第三步:发起打赏交易 (Write / Transaction)
这是最激动人心的一步。我们将调用 useSendTransaction 向指定的地址发送 ETH,并处理交易的等待状态。
// src/components/TipJar.tsx
import { useState } from 'react'
import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
export function TipJar() {
const [amount, setAmount] = useState('0.01')
// 收款人地址(你可以换成你自己的地址)
const RECEIVER_ADDRESS = '0xYourAddressHere...'
const { data: hash, sendTransaction, isPending } = useSendTransaction()
// 监听交易上链状态
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash })
const handleTip = () => {
sendTransaction({
to: RECEIVER_ADDRESS,
value: parseEther(amount), // 将 0.01 转换为 Wei 单位
})
}
return (
<div style={{ marginTop: '20px', padding: '20px', background: '#f5f5f5' }}>
<h3>☕ 请我喝杯咖啡</h3>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
step="0.01"
/> ETH
<button
onClick={handleTip}
disabled={isPending || isConfirming}
style={{ marginLeft: '10px' }}
>
{isPending ? '钱包确认中...' : isConfirming ? '区块打包中...' : '打赏'}
</button>
{/* 状态反馈 */}
{hash && <p style={{ fontSize: '12px' }}>交易哈希: {hash}</p>}
{isConfirmed && <p style={{ color: 'green' }}>🎉 打赏成功!非常感谢!</p>}
</div>
)
}
5. 组装与测试
将所有组件组装到 App.tsx 中:
// src/App.tsx
import { WalletConnect } from './components/WalletConnect'
import { UserProfile } from './components/UserProfile'
import { TipJar } from './components/TipJar'
import { useAccount } from 'wagmi'
function App() {
const { isConnected } = useAccount()
return (
<div style={{ maxWidth: '500px', margin: '50px auto', fontFamily: 'sans-serif' }}>
<h1>💎 Web3 打赏箱</h1>
<WalletConnect />
{isConnected && (
<>
<UserProfile />
<TipJar />
</>
)}
</div>
)
}
export default App
5.1 本地运行与调试
运行 npm run dev。确保你的 MetaMask 切换到了 Sepolia 测试网,并拥有一些测试币(可以从水龙头 Faucet 领取)。你可以尝试自己给自己打赏,观察按钮状态从“钱包确认中”到“区块打包中”再到“打赏成功”的丝滑转变。
6. 常见问题与最佳实践
6.1 大数处理 (BigInt)
Web2 开发者常犯的错误是用普通的 Number 处理金额。以太坊的精度是 18 位,极易导致 JS 精度丢失。
- 最佳实践:永远使用 Viem 提供的 parseEther (输入时) 和 formatEther (展示时),并在底层逻辑中保持 BigInt 类型。
6.2 错误捕获 (Error Handling)
用户随时可能在钱包弹窗中点击“拒绝”。
- 最佳实践:在 sendTransaction 的 onError 回调中捕获 UserRejectedRequestError,并给用户友好的 Toast 提示,而不是让控制台报错。
6.3 引入组件库 (RainbowKit)
在真实生产环境中,我们通常不会手写基础的连接按钮。
- 最佳实践:使用 RainbowKit 或 Web3Modal。它们底层完全基于 Wagmi,但为你提供了极其精美的多钱包适配 UI(支持扫码、移动端跳转等)。
