Wagmi、AppKit、Viem 概念解析与示例

  • 曲弯
  • 发布于 13小时前
  • 阅读 24

Wagmi、AppKit、Viem概念解析与示例一、Viem:以太坊基础库概念Viem是一个类型安全、模块化的以太坊TypeScript/JavaScript库,提供与以太坊节点交互的低级和高级API。作用​与以太坊节点通信​:通过JSON-RPC与以太坊节点交互​数据

<!--StartFragment-->

Wagmi、AppKit、Viem 概念解析与示例

一、Viem:以太坊基础库

概念

Viem 是一个类型安全、模块化的以太坊 TypeScript/JavaScript 库,提供与以太坊节点交互的低级和高级 API。

作用

  1. 与以太坊节点通信​:通过 JSON-RPC 与以太坊节点交互
  2. 数据编码/解码​:处理 ABI 编码、函数选择器、数据解析
  3. 交易构建​:创建、签名、发送交易
  4. 类型安全​:完整的 TypeScript 支持
  5. 轻量级​:树摇优化,按需导入

示例代码

// 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:React Hooks 库

概念

Wagmi 是基于 Viem 构建的 React Hooks 集合,简化了在 React 应用中与以太坊交互的复杂度。

作用

  1. 提供 React Hooks​:useAccount, useBalance, useContractRead
  2. 状态管理​:自动管理连接状态、账户信息、网络切换
  3. 缓存与同步​:自动缓存链上数据,同步多个组件状态
  4. 类型安全​:完整的 TypeScript 支持
  5. 多链支持​:轻松切换不同网络

示例代码

// 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 (
    &lt;WagmiProvider config={config}>
      &lt;QueryClientProvider client={queryClient}>
        &lt;YourApp />
      &lt;/QueryClientProvider>
    &lt;/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 (
      &lt;div>
        {connectors.map((connector) => (
          &lt;button
            key={connector.uid}
            onClick={() => connect({ connector })}
          >
            连接 {connector.name}
          &lt;/button>
        ))}
      &lt;/div>
    )
  }

  return (
    &lt;div>
      &lt;p>地址: {address}&lt;/p>
      &lt;p>网络: {chain?.name}&lt;/p>
      &lt;p>余额: {balance?.formatted} {balance?.symbol}&lt;/p>

      &lt;select 
        value={chainId} 
        onChange={(e) => switchChain({ chainId: Number(e.target.value) })}
      >
        {chains.map((chain) => (
          &lt;option key={chain.id} value={chain.id}>
            {chain.name}
          &lt;/option>
        ))}
      &lt;/select>

      &lt;button onClick={() => disconnect()}>断开连接&lt;/button>
    &lt;/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 (
    &lt;div>
      &lt;button onClick={handleTransfer} disabled={isConfirming}>
        {isConfirming ? '发送中...' : '发送 1 USDC'}
      &lt;/button>
      {isConfirmed && &lt;p>交易已确认!&lt;/p>}
    &lt;/div>
  )
}

三、AppKit:钱包连接 UI 套件

概念

AppKit(原 Web3Modal)是 WalletConnect 团队开发的 React 组件库,提供开箱即用的钱包连接界面和状态管理。

作用

  1. 统一钱包连接界面​:支持 200+ 钱包的标准化连接界面
  2. WalletConnect 集成​:内置 WalletConnect v2 支持
  3. 状态管理​:管理钱包连接状态、网络切换
  4. 主题定制​:完全可定制的 UI 样式
  5. 移动端优化​:响应式设计,移动端友好

示例代码

// 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 (
    &lt;>
      &lt;YourAppContent />
      &lt;appKit.AppKit />
    &lt;/>
  )
}

// 3. 在组件中使用
import { useWeb3Modal } from '@reown/appkit/react'

function ConnectButton() {
  const { open, close } = useWeb3Modal()

  return (
    &lt;button onClick={() => open()}>
      连接钱包
    &lt;/button>
  )
}

// 4. 高级使用 - 自定义触发
function CustomConnectFlow() {
  const { open, close } = useWeb3Modal()

  const handleConnect = async () => {
    try {
      await open()

      // 连接成功后的逻辑
      console.log('钱包连接成功')

    } catch (error) {
      console.error('连接失败:', error)
    }
  }

  return (
    &lt;div className="custom-connect-card">
      &lt;h3>连接你的钱包&lt;/h3>
      &lt;p>选择以下方式连接&lt;/p>

      &lt;div className="wallet-options">
        &lt;button onClick={handleConnect}>
          &lt;WalletIcon /> 使用 AppKit
        &lt;/button>

        &lt;button onClick={() => open({ view: 'WalletConnect' })}>
          &lt;QRIcon /> WalletConnect
        &lt;/button>

        &lt;button onClick={() => open({ view: 'Connect' })}>
          &lt;ListIcon /> 选择钱包
        &lt;/button>
      &lt;/div>
    &lt;/div>
  )
}

// 5. 监听连接状态
import { useWeb3ModalState, useWeb3ModalEvents } from '@reown/appkit/react'

function ConnectionStatus() {
  const { open, selectedNetworkId } = useWeb3ModalState()
  const { data: events } = useWeb3ModalEvents()

  return (
    &lt;div>
      &lt;p>模态框状态: {open ? '打开' : '关闭'}&lt;/p>
      &lt;p>选择的网络: {selectedNetworkId}&lt;/p>
      &lt;p>最近事件: {events[events.length - 1]?.name}&lt;/p>
    &lt;/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 (
    &lt;WagmiProvider config={config}>
      &lt;QueryClientProvider client={queryClient}>
        {children}
        &lt;appKit.AppKit />
      &lt;/QueryClientProvider>
    &lt;/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 (
      &lt;button
        onClick={handleConnect}
        className="connect-button"
      >
        连接钱包
      &lt;/button>
    )
  }

  return (
    &lt;div className="wallet-info">
      &lt;div className="network-selector">
        &lt;select
          value={chain?.id}
          onChange={(e) => switchChain({ chainId: Number(e.target.value) })}
        >
          {chains.map((c) => (
            &lt;option key={c.id} value={c.id}>
              {c.name}
            &lt;/option>
          ))}
        &lt;/select>
      &lt;/div>

      &lt;div className="balance">
        {balance && (
          &lt;span>
            {formatEther(balance.value).slice(0, 6)} {balance.symbol}
          &lt;/span>
        )}
      &lt;/div>

      &lt;div className="address" onClick={copyAddress}>
        {`${address?.slice(0, 6)}...${address?.slice(-4)}`}
        {copied && &lt;span className="copied-tooltip">已复制&lt;/span>}
      &lt;/div>

      &lt;button onClick={handleDisconnect} className="disconnect-button">
        断开
      &lt;/button>
    &lt;/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 (
      &lt;div className="connect-prompt">
        &lt;h3>连接钱包铸造 NFT&lt;/h3>
        &lt;button onClick={() => open()}>连接钱包&lt;/button>
      &lt;/div>
    )
  }

  return (
    &lt;div className="nft-minter">
      &lt;h3>铸造 NFT&lt;/h3>

      &lt;div className="stats">
        &lt;p>你的 NFT: {nftBalance?.toString() || 0}&lt;/p>
        &lt;p>总供应量: {totalSupply?.toString() || 0}&lt;/p>
      &lt;/div>

      &lt;div className="mint-controls">
        &lt;input
          type="number"
          min="1"
          max="10"
          value={mintAmount}
          onChange={(e) => setMintAmount(Number(e.target.value))}
        />

        &lt;button 
          onClick={handleMint} 
          disabled={isMinting}
        >
          {isMinting ? '铸造中...' : `铸造 ${mintAmount} 个 NFT`}
        &lt;/button>

        &lt;p>价格: {0.01 * mintAmount} ETH&lt;/p>
      &lt;/div>

      {isConfirmed && (
        &lt;div className="success-message">
          ✅ 铸造成功!交易哈希: {hash?.slice(0, 20)}...
        &lt;/div>
      )}
    &lt;/div>
  )
}

五、关键区别总结

特性 Viem Wagmi AppKit
定位 底层以太坊库 React Hooks 库 钱包连接 UI 套件
使用场景 Node.js/浏览器基础交互 React 应用状态管理 钱包连接用户界面
核心功能 RPC 调用、交易构建 React Hooks、状态同步 模态框、多钱包支持
依赖关系 无依赖 依赖 Viem 可独立或与 Wagmi 集成
包大小 \~100KB \~50KB + Viem \~200KB
学习曲线 中等 简单 非常简单

六、最佳实践建议

  1. 开发环境​:使用测试网络(Sepolia、Goerli)
  2. 错误处理​:所有操作都要有错误边界
  3. 用户体验​:提供清晰的加载和反馈状态
  4. 移动端​:确保 WalletConnect 二维码清晰可扫描
  5. 类型安全​:充分利用 TypeScript 类型检查
  6. 性能​:合理使用缓存,避免重复请求

<!--EndFragment-->

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
曲弯
曲弯
0xb51E...CADb
江湖只有他的大名,没有他的介绍。