Web3 钱包是什么?前端如何集成钱包功能?
Web3 钱包是用户与区块链交互的核心入口,负责管理私钥、签名交易和连接去中心化应用(dApp)。对前端开发者而言,钱包集成是构建 dApp 的第一步,也是最容易出现安全隐患的环节。本文从钱包原理出发,给出主流前端集成方案及安全实践。
Web3 钱包的本质
钱包并非"存储"资产——资产在链上,钱包管理的是访问链上资产的私钥。核心职责有三:
- 密钥管理:通过非对称加密生成公私钥对,派生链上地址(如以太坊
0x...) - 交易签名:用私钥对交易数据做数字签名,证明操作来自地址持有者
- 身份认证:通过签名消息(如 EIP-191 Personal Sign)实现链上登录,替代传统账号密码
钱包分类与前端集成选型
| 类型 | 代表 | 安全性 | 前端集成难度 | 适用场景 |
|---|---|---|---|---|
| 浏览器扩展 | MetaMask、Coinbase Wallet | 中 | 低 | 桌面端 dApp 首选 |
| 移动端钱包 | Trust Wallet、Rainbow | 中 | 中(需 WalletConnect) | 移动端适配 |
| 硬件钱包 | Ledger、Trezor | 高 | 高 | 高价值资产操作 |
| 嵌入式钱包 | Privy、Dynamic | 中 | 低 | 无插件的平滑接入 |
| 智能合约钱包 | Safe、Biconomy | 高 | 中 | 账户抽象场景 |
前端选型建议:桌面端优先支持浏览器扩展钱包(MetaMask 注入 window.ethereum),移动端通过 WalletConnect 协议桥接,追求无感接入可引入嵌入式钱包方案。
前端集成方案:Wagmi + Viem
2026 年前端集成的事实标准是 Wagmi v2 + Viem,替代已停维的 Ethers.js v5。Wagmi 提供 React Hooks 封装,Viem 作为轻量 RPC 客户端,bundle 体积仅为 Ethers.js 的 1/3。
1. 初始化配置
typescriptimport { createConfig, http } from "wagmi"; import { mainnet, sepolia } from "wagmi/chains"; import { injected, walletConnect, coinbaseWallet } from "wagmi/connectors"; const config = createConfig({ chains: [mainnet, sepolia], connectors: [ injected(), // MetaMask 等浏览器扩展 walletConnect({ projectId: "YOUR_WC_PROJECT_ID", }), coinbaseWallet({ appName: "My dApp" }), ], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, }); // 在 App 根组件包裹 Provider import { WagmiProvider } from "wagmi"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); function App() { return ( <WagmiProvider config={config}> <QueryClientProvider client={queryClient}> <YourDApp /> </QueryClientProvider> </WagmiProvider> ); }
2. 连接钱包与获取地址
typescriptimport { useAccount, useConnect, useDisconnect } from "wagmi"; function WalletConnect() { const { address, isConnected, chain } = useAccount(); const { connect, connectors, isPending } = useConnect(); const { disconnect } = useDisconnect(); if (isConnected) { return ( <div> <p>地址:{address}</p> <p>链:{chain?.name}</p> <button onClick={() => disconnect()}>断开连接</button> </div> ); } return ( <div> {connectors.map((connector) => ( <button key={connector.uid} onClick={() => connect({ connector })} disabled={isPending} > 连接 {connector.name} </button> ))} </div> ); }
3. 读取链上数据与发送交易
typescriptimport { useReadContract, useWriteContract, useWaitForTransactionReceipt } from "wagmi"; import { parseEther, formatEther } from "viem"; // 读取 ERC-20 余额 function TokenBalance({ tokenAddress, userAddress }: { tokenAddress: `0x${string}`; userAddress: `0x${string}`; }) { const { data: balance } = useReadContract({ address: tokenAddress, abi: [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }], }], functionName: "balanceOf", args: [userAddress], }); return <p>余额:{balance ? formatEther(balance as bigint) : "0"} ETH</p>; } // 发送交易 function SendTransaction() { const { writeContract, data: hash } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash }); return ( <div> <button onClick={() => writeContract({ address: "0xYourContractAddress", abi: [{ name: "transfer", type: "function", stateMutability: "nonpayable", inputs: [{ name: "to", type: "address" }, { name: "amount", type: "uint256" }], outputs: [{ name: "", type: "bool" }] }], functionName: "transfer", args: ["0xRecipientAddress", parseEther("0.01")], }) } > 转账 0.01 ETH </button> {isConfirming && <p>交易确认中...</p>} {isSuccess && <p>交易成功!哈希:{hash}</p>} </div> ); }
4. 监听账户与链切换
typescriptimport { useAccount, useSwitchChain } from "wagmi"; function ChainGuard() { const { chain } = useAccount(); const { switchChain } = useSwitchChain(); if (chain?.id !== mainnet.id) { return ( <div> <p>当前链:{chain?.name},需要切换到主网</p> <button onClick={() => switchChain({ chainId: mainnet.id })}> 切换到以太坊主网 </button> </div> ); } return null; }
账户抽象(ERC-4337):下一代钱包体验
传统钱包的痛点在于:用户必须保管私钥、手动支付 Gas、无法设置权限。ERC-4337 账户抽象通过智能合约钱包解决这些问题:
- 无 Gas 交易:由赞助方(Paymaster)代付 Gas,用户零成本交互
- 社交恢复:设置监护人,丢失设备可通过社交关系找回
- 批量操作:一笔交易内执行多个操作(approve + swap 一步完成)
- 权限管理:设置每日限额、白名单地址等细粒度控制
前端集成可使用 permissionless.js 或 Biconomy SDK:
typescriptimport { createSmartAccountClient } from "permissionless"; import { toSimpleSmartAccount } from "permissionless/accounts"; import { createPimlicoPaymasterClient } from "permissionless/clients/pimlico"; const smartAccount = await toSimpleSmartAccount(publicClient, { owner: signer, entryPoint: "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", }); const paymasterClient = createPimlicoPaymasterClient({ transport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), }); const smartAccountClient = createSmartAccountClient({ account: smartAccount, chain: sepolia, bundlerTransport: http("https://api.pimlico.io/v2/sepolia/rpc?apikey=YOUR_KEY"), paymaster: paymasterClient, }); // 发送无 Gas 交易 const hash = await smartAccountClient.sendUserOperation({ to: "0xRecipientAddress", value: parseEther("0.01"), data: "0x", });
安全实践:前端必须遵守的底线
钱包集成的安全事故多来自前端疏漏,以下是高频踩坑点及对策:
私钥与签名安全
- 绝不在前端存储私钥或助记词,所有签名操作通过
signer对象委托给钱包 - 验证请求来源:签名前展示完整待签数据,防止钓鱼合约诱导用户签署恶意数据
- 使用 EIP-712 类型化签名:结构化签名数据,用户可读且防篡改
typescriptimport { useSignTypedData } from "wagmi"; function SignOrder() { const { signTypedData } = useSignTypedData(); const sign = () => { signTypedData({ domain: { name: "MyDApp", version: "1", chainId: 1 }, types: { Order: [ { name: "recipient", type: "address" }, { name: "amount", type: "uint256" }, ], }, primaryType: "Order", message: { recipient: "0x...", amount: BigInt(100) }, }); }; return <button onClick={sign}>签名授权</button>; }
常见攻击与防御
| 攻击类型 | 原理 | 防御方式 |
|---|---|---|
| 钓鱼签名 | 诱导用户签署恶意 permit | 展示可读签名内容,EIP-712 类型化 |
| 前端注入 | XSS 篡改合约地址或金额 | Content-Security-Policy,地址白名单校验 |
| 交易替换 | 高 Gas 抢先提交恶意交易 | 设置合理 maxFeePerGas,使用 Flashbots Protect RPC |
| 链切换攻击 | 诱导切换到恶意链 | 校验 chainId,白名单限定支持链 |
生产环境检查清单
- 连接超时处理:钱包无响应时给出明确提示,而非无限等待
- 网络校验:操作前检查链 ID,不匹配时引导切换
- 交易状态轮询:
useWaitForTransactionReceipt确认上链,避免状态不一致 - 错误分类:区分用户拒绝(4001)、余额不足、网络错误等,给出针对性提示
- 多签验证:大额操作触发二次确认或硬件钱包签名
面试追问速答
Q:window.ethereum 和 Wagmi 的关系是什么?
window.ethereum 是钱包注入浏览器的 Provider 对象,Wagmi 在其上封装了 React Hooks、自动重连、多链切换等能力。Wagmi 是工具层,Provider 是数据层。
Q:WalletConnect 如何工作?
移动端钱包扫码建立 WebSocket 连接,通过中继服务器转发 JSON-RPC 请求,前端用 walletConnect connector 接入。关键配置是 projectId(需在 WalletConnect Cloud 注册)。
Q:账户抽象对前端架构有什么影响? 引入 Bundler 和 Paymaster 两个新角色。前端不再直接发送交易,而是构造 UserOperation 提交给 Bundler,Gas 可由 Paymaster 代付。状态管理需额外追踪 UserOperation 生命周期。
Q:如何处理多链场景下的钱包连接?
Wagmi v2 的 createConfig 支持多链声明,useAccount 返回当前连接链,useSwitchChain 主动切换。建议在 transports 中为每条链配置独立 RPC,避免单点故障。