Web3钱包开发:使用Ethers.js集成MetaMask
Web3钱包开发使用Ethers.js集成MetaMask大家好我是欧阳瑞Rich Own。今天想和大家聊聊Web3钱包开发这个话题。作为一个Web3探索者我经常需要在DApp中集成钱包功能。今天就来分享一下如何使用Ethers.js与MetaMask进行集成。为什么需要钱包集成在Web3世界中钱包是用户与区块链交互的桥梁。通过钱包用户可以管理加密货币资产签署交易与智能合约交互验证身份什么是MetaMaskMetaMask是目前最流行的以太坊钱包浏览器扩展。它允许用户安全地管理以太坊账户并与DApp进行交互。Ethers.js简介Ethers.js是一个强大的以太坊JavaScript库提供了与以太坊区块链交互的所有功能。环境准备# 安装Ethers.js npm install ethers # 或使用yarn yarn add ethers基础集成1. 检测MetaMask是否安装async function checkMetaMask() { if (typeof window.ethereum ! undefined) { console.log(MetaMask is installed!); return true; } else { console.log(MetaMask is not installed); return false; } }2. 连接钱包async function connectWallet() { try { if (!await checkMetaMask()) { alert(请先安装MetaMask); return; } // 请求连接钱包 const accounts await window.ethereum.request({ method: eth_requestAccounts }); const account accounts[0]; console.log(Connected account:, account); return account; } catch (error) { console.error(连接失败:, error); throw error; } }3. 获取账户信息async function getAccountInfo() { const provider new ethers.providers.Web3Provider(window.ethereum); const signer provider.getSigner(); // 获取账户地址 const address await signer.getAddress(); console.log(地址:, address); // 获取余额 const balance await provider.getBalance(address); console.log(余额:, ethers.utils.formatEther(balance), ETH); // 获取链ID const chainId await provider.getNetwork(); console.log(链ID:, chainId.chainId); return { address, balance, chainId }; }监听账户和链变化// 监听账户变化 window.ethereum.on(accountsChanged, (accounts) { if (accounts.length 0) { console.log(用户已断开连接); } else { console.log(账户已切换:, accounts[0]); } }); // 监听链变化 window.ethereum.on(chainChanged, (chainId) { console.log(链已切换:, chainId); // 刷新页面以适配新链 window.location.reload(); }); // 监听连接状态 window.ethereum.on(connect, (info) { console.log(已连接:, info); }); window.ethereum.on(disconnect, (error) { console.log(连接已断开:, error); });与智能合约交互1. 读取合约数据async function readFromContract() { const provider new ethers.providers.Web3Provider(window.ethereum); // 合约地址和ABI const contractAddress 0x...; const contractABI [ function balanceOf(address owner) view returns (uint256), function name() view returns (string), function symbol() view returns (string) ]; // 创建合约实例 const contract new ethers.Contract(contractAddress, contractABI, provider); // 调用合约方法 const name await contract.name(); const symbol await contract.symbol(); const balance await contract.balanceOf(0x...); console.log(代币名称:, name); console.log(代币符号:, symbol); console.log(余额:, ethers.utils.formatUnits(balance, 18)); }2. 写入合约数据async function writeToContract() { const provider new ethers.providers.Web3Provider(window.ethereum); const signer provider.getSigner(); const contractAddress 0x...; const contractABI [ function transfer(address to, uint256 amount) returns (bool), function approve(address spender, uint256 amount) returns (bool) ]; // 使用signer创建合约实例 const contract new ethers.Contract(contractAddress, contractABI, signer); try { // 构建交易 const tx await contract.transfer( 0xRecipientAddress, ethers.utils.parseEther(1.0) ); console.log(交易哈希:, tx.hash); // 等待交易确认 const receipt await tx.wait(); console.log(交易已确认:, receipt); return receipt; } catch (error) { console.error(交易失败:, error); throw error; } }发送原生代币async function sendEther(to, amount) { const provider new ethers.providers.Web3Provider(window.ethereum); const signer provider.getSigner(); try { const tx await signer.sendTransaction({ to: to, value: ethers.utils.parseEther(amount) }); console.log(交易哈希:, tx.hash); const receipt await tx.wait(); console.log(交易已确认:, receipt); return receipt; } catch (error) { console.error(发送失败:, error); throw error; } }处理不同的网络const NETWORKS { 1: mainnet, 5: goerli, 11155111: sepolia, 137: polygon, 80001: polygon-mumbai }; async function getCurrentNetwork() { const provider new ethers.providers.Web3Provider(window.ethereum); const network await provider.getNetwork(); return NETWORKS[network.chainId] || unknown; } async function switchNetwork(chainId) { try { await window.ethereum.request({ method: wallet_switchEthereumChain, params: [{ chainId: ethers.utils.hexValue(chainId) }] }); } catch (error) { // 如果链不存在需要添加 if (error.code 4902) { await addNetwork(chainId); } else { throw error; } } } async function addNetwork(chainId) { const networkConfig { 137: { chainId: 0x89, chainName: Polygon Mainnet, rpcUrls: [https://polygon-rpc.com], nativeCurrency: { name: MATIC, symbol: MATIC, decimals: 18 }, blockExplorerUrls: [https://polygonscan.com] } // 添加更多网络配置... }; await window.ethereum.request({ method: wallet_addEthereumChain, params: [networkConfig[chainId]] }); }完整的钱包组件示例import { useState, useEffect } from react; import { ethers } from ethers; export default function WalletConnector() { const [account, setAccount] useStatestring | null(null); const [balance, setBalance] useStatestring(0); const [network, setNetwork] useStatestring(); useEffect(() { // 检查是否已连接 checkConnection(); // 设置监听 window.ethereum?.on(accountsChanged, handleAccountsChanged); window.ethereum?.on(chainChanged, handleChainChanged); return () { window.ethereum?.removeListener(accountsChanged, handleAccountsChanged); window.ethereum?.removeListener(chainChanged, handleChainChanged); }; }, []); const checkConnection async () { if (!window.ethereum) return; const provider new ethers.providers.Web3Provider(window.ethereum); const accounts await provider.listAccounts(); if (accounts.length 0) { setAccount(accounts[0]); await updateBalance(accounts[0]); await updateNetwork(); } }; const handleAccountsChanged async (accounts: string[]) { if (accounts.length 0) { setAccount(accounts[0]); await updateBalance(accounts[0]); } else { setAccount(null); setBalance(0); } }; const handleChainChanged async () { await updateNetwork(); }; const updateBalance async (addr: string) { const provider new ethers.providers.Web3Provider(window.ethereum); const bal await provider.getBalance(addr); setBalance(ethers.utils.formatEther(bal)); }; const updateNetwork async () { const provider new ethers.providers.Web3Provider(window.ethereum); const net await provider.getNetwork(); setNetwork(net.name || Unknown); }; const connect async () { try { const provider new ethers.providers.Web3Provider(window.ethereum); await provider.send(eth_requestAccounts, []); } catch (error) { console.error(连接失败:, error); } }; const disconnect async () { // MetaMask不支持直接断开连接 // 用户需要手动在MetaMask中断开 alert(请在MetaMask中手动断开连接); }; const switchToPolygon async () { await switchNetwork(137); }; if (!window.ethereum) { return div请安装MetaMask/div; } return ( div classNamewallet-connector {account ? ( div p账户: {account}/p p余额: {balance} ETH/p p网络: {network}/p button onClick{disconnect}断开连接/button button onClick{switchToPolygon}切换到Polygon/button /div ) : ( button onClick{connect}连接钱包/button )} /div ); }安全最佳实践1. 验证交易参数async function safeTransfer(to, amount) { // 验证地址格式 if (!ethers.utils.isAddress(to)) { throw new Error(无效的地址); } // 验证金额 if (ethers.utils.parseEther(amount).isZero()) { throw new Error(金额不能为0); } // 检查余额 const provider new ethers.providers.Web3Provider(window.ethereum); const signer provider.getSigner(); const balance await signer.getBalance(); if (balance.lt(ethers.utils.parseEther(amount))) { throw new Error(余额不足); } // 执行交易 const tx await signer.sendTransaction({ to, value: ethers.utils.parseEther(amount) }); return tx; }2. 使用硬件钱包// 使用Ledger或Trezor async function connectHardwareWallet() { const provider new ethers.providers.Web3Provider(window.ethereum); // 请求硬件钱包账户 const accounts await provider.send(eth_requestAccounts, []); return accounts[0]; }3. 显示交易确认async function confirmTransaction(txData) { const confirmation window.confirm( 确认交易:\n 目标地址: ${txData.to}\n 金额: ${ethers.utils.formatEther(txData.value)} ETH\n Gas费用: ${ethers.utils.formatEther(txData.gasLimit * txData.gasPrice)} ETH ); if (!confirmation) { throw new Error(用户取消交易); } const provider new ethers.providers.Web3Provider(window.ethereum); const signer provider.getSigner(); const tx await signer.sendTransaction(txData); return tx; }常见问题处理1. MetaMask未安装function handleNoMetaMask() { const installUrl https://metamask.io/download/; if (confirm(请安装MetaMask以继续使用此DApp)) { window.open(installUrl, _blank); } }2. 用户拒绝连接async function connectWithErrorHandling() { try { await connectWallet(); } catch (error) { if (error.code 4001) { console.log(用户拒绝连接); } else { console.error(连接错误:, error); } } }3. 交易失败async function handleTransactionError(error) { if (error.code 4001) { console.log(用户拒绝交易); } else if (error.code -32000) { console.log(交易失败:, error.message); } else { console.error(未知错误:, error); } }总结使用Ethers.js集成MetaMask是Web3开发的基础技能。掌握这些知识后你就可以开发出功能丰富的DApp了。我的鬃狮蜥Hash对Web3也很感兴趣——它每天看着我在MetaMask中管理资产似乎也想拥有自己的加密钱包。也许有一天我会为它创建一个专属的NFT收藏系统。如果你有Web3开发方面的问题欢迎留言交流我是欧阳瑞Web3探索之路我们一起前行技术栈Ethers.js · MetaMask · Ethereum · DApp