Wagmi、AppKit、Viem概念解析与示例一、Viem:以太坊基础库概念Viem是一个类型安全、模块化的以太坊TypeScript/JavaScript库,提供与以太坊节点交互的低级和高级API。作用与以太坊节点通信:通过JSON-RPC与以太坊节点交互数据
<!--StartFragment-->
Viem 是一个类型安全、模块化的以太坊 TypeScript/JavaScript 库,提供与以太坊节点交互的低级和高级 API。
// 1. 创建公共客户端
import { createPublicClient, http, parseEther, formatEther } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http('https://eth-mainnet.g.alchemy.com/v2/your-key')
})
// 读取链上数据
const blockNumber = await client.getBlockNumber()
const balance = await client.getBalance({
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
})
console.log(`区块: ${blockNumber}`)
console.log(`余额: ${formatEther(balance)} ETH`)
// 2. 读取合约数据
import { parseAbi } from 'viem'
const abi = parseAbi([
'function balanceOf(address) view returns (uint256)',
'function totalSupply() view returns (uint256)'
])
const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
const totalSupply = await client.readContract({
address: usdcAddress,
abi,
functionName: 'totalSupply'
})
// 3. 钱包客户端(发送交易)
import { createWalletClient, custom } from 'viem'
// 假设在浏览器环境中
const walletClient = createWalletClient({
chain: mainnet,
transport: custom(window.ethereum!)
})
// 发送交易
const hash = await walletClient.sendTransaction({
account: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: parseEther('0.1')
})
Wagmi 是基于 Viem 构建的 React Hooks 集合,简化了在 React 应用中与以太坊交互的复杂度。
useAccount, useBalance, useContractRead等// 1. 基础配置
import { createConfig, http, WagmiProvider } from 'wagmi'
import { mainnet, sepolia, polygon } from 'wagmi/chains'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
})
const queryClient = new QueryClient()
function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</WagmiProvider>
)
}
// 2. 在组件中使用 Hooks
import {
useAccount,
useConnect,
useDisconnect,
useBalance,
useChainId,
useSwitchChain
} from 'wagmi'
import { injected, walletConnect } from 'wagmi/connectors'
function WalletProfile() {
// 账户信息
const { address, isConnected, chain } = useAccount()
// 余额查询
const { data: balance } = useBalance({ address })
// 网络切换
const chainId = useChainId()
const { chains, switchChain } = useSwitchChain()
// 连接钱包
const { connectors, connect } = useConnect()
const { disconnect } = useDisconnect()
// 合约读取示例
const { data: nftBalance } = useContractRead({
address: '0x...',
abi: nftAbi,
functionName: 'balanceOf',
args: [address],
})
if (!isConnected) {
return (
<div>
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => connect({ connector })}
>
连接 {connector.name}
</button>
))}
</div>
)
}
return (
<div>
<p>地址: {address}</p>
<p>网络: {chain?.name}</p>
<p>余额: {balance?.formatted} {balance?.symbol}</p>
<select
value={chainId}
onChange={(e) => switchChain({ chainId: Number(e.target.value) })}
>
{chains.map((chain) => (
<option key={chain.id} value={chain.id}>
{chain.name}
</option>
))}
</select>
<button onClick={() => disconnect()}>断开连接</button>
</div>
)
}
// 3. 合约交互 Hook
import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseAbi } from 'viem'
const erc20Abi = parseAbi([
'function balanceOf(address) view returns (uint256)',
'function transfer(address, uint256) returns (bool)',
'event Transfer(address indexed from, address indexed to, uint256 value)'
])
function TokenTransfer() {
const { data: hash, writeContract } = useWriteContract()
const { isLoading: isConfirming, isSuccess: isConfirmed } =
useWaitForTransactionReceipt({ hash })
const handleTransfer = () => {
writeContract({
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
abi: erc20Abi,
functionName: 'transfer',
args: ['0xRecipientAddress', 1000000n] // 1 USDC (6 decimals)
})
}
return (
<div>
<button onClick={handleTransfer} disabled={isConfirming}>
{isConfirming ? '发送中...' : '发送 1 USDC'}
</button>
{isConfirmed && <p>交易已确认!</p>}
</div>
)
}
AppKit(原 Web3Modal)是 WalletConnect 团队开发的 React 组件库,提供开箱即用的钱包连接界面和状态管理。
// 1. 基础配置
import { createAppKit } from '@reown/appkit/react'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
import { mainnet, sepolia } from '@reown/appkit/networks'
import { config } from './wagmi-config' // 你的 wagmi 配置
export const appKit = createAppKit({
adapters: [new WagmiAdapter(config)],
networks: [mainnet, sepolia],
projectId: 'your-project-id-from-cloud.reown.com',
metadata: {
name: 'My NFT Marketplace',
description: 'A decentralized NFT marketplace',
url: 'https://mynftmarket.xyz',
icons: ['https://mynftmarket.xyz/logo.png']
},
features: {
analytics: true, // 可选:启用分析
email: true, // 可选:启用邮箱登录
socials: ['google', 'x', 'github', 'discord', 'apple'] // 可选社交登录
},
themeMode: 'dark', // 或 'light'
themeVariables: {
'--w3m-font-family': 'Inter, sans-serif',
'--w3m-accent-color': '#6366f1',
'--w3m-border-radius-master': '12px',
}
})
// 2. 在应用中集成
import { appKit } from './appkit-config'
function App() {
return (
<>
<YourAppContent />
<appKit.AppKit />
</>
)
}
// 3. 在组件中使用
import { useWeb3Modal } from '@reown/appkit/react'
function ConnectButton() {
const { open, close } = useWeb3Modal()
return (
<button onClick={() => open()}>
连接钱包
</button>
)
}
// 4. 高级使用 - 自定义触发
function CustomConnectFlow() {
const { open, close } = useWeb3Modal()
const handleConnect = async () => {
try {
await open()
// 连接成功后的逻辑
console.log('钱包连接成功')
} catch (error) {
console.error('连接失败:', error)
}
}
return (
<div className="custom-connect-card">
<h3>连接你的钱包</h3>
<p>选择以下方式连接</p>
<div className="wallet-options">
<button onClick={handleConnect}>
<WalletIcon /> 使用 AppKit
</button>
<button onClick={() => open({ view: 'WalletConnect' })}>
<QRIcon /> WalletConnect
</button>
<button onClick={() => open({ view: 'Connect' })}>
<ListIcon /> 选择钱包
</button>
</div>
</div>
)
}
// 5. 监听连接状态
import { useWeb3ModalState, useWeb3ModalEvents } from '@reown/appkit/react'
function ConnectionStatus() {
const { open, selectedNetworkId } = useWeb3ModalState()
const { data: events } = useWeb3ModalEvents()
return (
<div>
<p>模态框状态: {open ? '打开' : '关闭'}</p>
<p>选择的网络: {selectedNetworkId}</p>
<p>最近事件: {events[events.length - 1]?.name}</p>
</div>
)
}
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AppKit │ │ Wagmi │ │ Viem │
│ (UI层/连接层) │ │ (React Hooks层)│ │ (基础通信层) │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • 钱包连接UI │ │ • useAccount │ │ • 创建客户端 │
│ • WalletConnect │ │ • useBalance │ │ • 交易构建 │
│ • 主题定制 │◄───┤ • useContractRead│◄───┤ • ABI编码 │
│ • 多链支持 │ │ • 状态管理 │ │ • RPC调用 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
// 1. 配置文件整合
// lib/web3-config.ts
import { createConfig, http } from 'wagmi'
import { mainnet, sepolia, polygon } from 'wagmi/chains'
import { injected, walletConnect } from 'wagmi/connectors'
import { createAppKit } from '@reown/appkit/react'
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'
// Wagmi 配置
export const config = createConfig({
chains: [mainnet, sepolia, polygon],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http('https://rpc.sepolia.org'),
[polygon.id]: http('https://polygon-rpc.com'),
},
connectors: [
injected(),
walletConnect({
projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
showQrModal: false, // 禁用 WalletConnect 自带模态框
}),
],
})
// AppKit 配置
export const appKit = createAppKit({
adapters: [new WagmiAdapter(config)],
networks: [mainnet, sepolia, polygon],
projectId: process.env.NEXT_PUBLIC_PROJECT_ID!,
metadata: {
name: 'NFTMarket',
description: 'NFT Marketplace',
url: 'http://localhost:3000',
icons: ['/logo.png']
},
themeVariables: {
'--w3m-color-mix': '#6366f1',
'--w3m-color-mix-strength': 40,
},
allWallets: 'SHOW',
featuredWalletIds: [
'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96', // MetaMask
'4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0', // Trust Wallet
]
})
// 2. 提供者组件
// components/Web3Providers.tsx
'use client'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config, appKit } from '@/lib/web3-config'
const queryClient = new QueryClient()
export function Web3Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
<appKit.AppKit />
</QueryClientProvider>
</WagmiProvider>
)
}
// 3. 自定义钱包连接组件
// components/WalletManager.tsx
'use client'
import { useAccount, useBalance, useChainId, useSwitchChain } from 'wagmi'
import { useWeb3Modal } from '@reown/appkit/react'
import { formatEther } from 'viem'
import { useEffect, useState } from 'react'
export function WalletManager() {
const { address, isConnected, chain } = useAccount()
const { data: balance } = useBalance({ address })
const { open, close } = useWeb3Modal()
const { chains, switchChain } = useSwitchChain()
const [copied, setCopied] = useState(false)
// 连接钱包
const handleConnect = async () => {
try {
await open()
} catch (error) {
console.error('连接失败:', error)
}
}
// 复制地址
const copyAddress = () => {
if (address) {
navigator.clipboard.writeText(address)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}
}
// 断开连接
const handleDisconnect = async () => {
close()
// Wagmi 会通过 AppKit 处理断开连接
}
if (!isConnected) {
return (
<button
onClick={handleConnect}
className="connect-button"
>
连接钱包
</button>
)
}
return (
<div className="wallet-info">
<div className="network-selector">
<select
value={chain?.id}
onChange={(e) => switchChain({ chainId: Number(e.target.value) })}
>
{chains.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
</div>
<div className="balance">
{balance && (
<span>
{formatEther(balance.value).slice(0, 6)} {balance.symbol}
</span>
)}
</div>
<div className="address" onClick={copyAddress}>
{`${address?.slice(0, 6)}...${address?.slice(-4)}`}
{copied && <span className="copied-tooltip">已复制</span>}
</div>
<button onClick={handleDisconnect} className="disconnect-button">
断开
</button>
</div>
)
}
// 4. NFT 合约交互示例
// components/NFTMinter.tsx
'use client'
import {
useAccount,
useWriteContract,
useWaitForTransactionReceipt,
useReadContract
} from 'wagmi'
import { useWeb3Modal } from '@reown/appkit/react'
import { parseEther, parseAbi } from 'viem'
import { useState } from 'react'
const NFT_CONTRACT = '0x...' // NFT 合约地址
const nftAbi = parseAbi([
'function mint(uint256 amount) payable returns (uint256)',
'function balanceOf(address owner) view returns (uint256)',
'function tokenURI(uint256 tokenId) view returns (string)',
'function totalSupply() view returns (uint256)'
])
export function NFTMinter() {
const { address, isConnected } = useAccount()
const { open } = useWeb3Modal()
const [mintAmount, setMintAmount] = useState(1)
// 读取合约数据
const { data: nftBalance } = useReadContract({
address: NFT_CONTRACT,
abi: nftAbi,
functionName: 'balanceOf',
args: [address!],
query: { enabled: !!address }
})
const { data: totalSupply } = useReadContract({
address: NFT_CONTRACT,
abi: nftAbi,
functionName: 'totalSupply'
})
// 写入合约(铸造)
const {
data: hash,
writeContract,
isPending: isMinting
} = useWriteContract()
const { isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash })
const handleMint = () => {
if (!isConnected) {
open()
return
}
writeContract({
address: NFT_CONTRACT,
abi: nftAbi,
functionName: 'mint',
args: [BigInt(mintAmount)],
value: parseEther((0.01 * mintAmount).toString()) // 每个 NFT 0.01 ETH
})
}
if (!isConnected) {
return (
<div className="connect-prompt">
<h3>连接钱包铸造 NFT</h3>
<button onClick={() => open()}>连接钱包</button>
</div>
)
}
return (
<div className="nft-minter">
<h3>铸造 NFT</h3>
<div className="stats">
<p>你的 NFT: {nftBalance?.toString() || 0}</p>
<p>总供应量: {totalSupply?.toString() || 0}</p>
</div>
<div className="mint-controls">
<input
type="number"
min="1"
max="10"
value={mintAmount}
onChange={(e) => setMintAmount(Number(e.target.value))}
/>
<button
onClick={handleMint}
disabled={isMinting}
>
{isMinting ? '铸造中...' : `铸造 ${mintAmount} 个 NFT`}
</button>
<p>价格: {0.01 * mintAmount} ETH</p>
</div>
{isConfirmed && (
<div className="success-message">
✅ 铸造成功!交易哈希: {hash?.slice(0, 20)}...
</div>
)}
</div>
)
}
| 特性 | Viem | Wagmi | AppKit |
|---|---|---|---|
| 定位 | 底层以太坊库 | React Hooks 库 | 钱包连接 UI 套件 |
| 使用场景 | Node.js/浏览器基础交互 | React 应用状态管理 | 钱包连接用户界面 |
| 核心功能 | RPC 调用、交易构建 | React Hooks、状态同步 | 模态框、多钱包支持 |
| 依赖关系 | 无依赖 | 依赖 Viem | 可独立或与 Wagmi 集成 |
| 包大小 | \~100KB | \~50KB + Viem | \~200KB |
| 学习曲线 | 中等 | 简单 | 非常简单 |
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!