Web3钱包开发:使用Ethers.js集成MetaMask

Web3钱包开发:使用Ethers.js集成MetaMask

大家好,我是欧阳瑞(Rich Own)。今天想和大家聊聊Web3钱包开发这个话题。作为一个Web3探索者,我经常需要在DApp中集成钱包功能。今天就来分享一下如何使用Ethers.js与MetaMask进行集成。

为什么需要钱包集成?

在Web3世界中,钱包是用户与区块链交互的桥梁。通过钱包,用户可以:

  • 管理加密货币资产
  • 签署交易
  • 与智能合约交互
  • 验证身份

什么是MetaMask?

MetaMask是目前最流行的以太坊钱包浏览器扩展。它允许用户安全地管理以太坊账户,并与DApp进行交互。

Ethers.js简介

Ethers.js是一个强大的以太坊JavaScript库,提供了与以太坊区块链交互的所有功能。

环境准备

bash 复制代码
# 安装Ethers.js
npm install ethers
# 或使用yarn
yarn add ethers

基础集成

1. 检测MetaMask是否安装

javascript 复制代码
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. 连接钱包

javascript 复制代码
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. 获取账户信息

javascript 复制代码
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 };
}

监听账户和链变化

javascript 复制代码
// 监听账户变化
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. 读取合约数据

javascript 复制代码
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. 写入合约数据

javascript 复制代码
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;
  }
}

发送原生代币

javascript 复制代码
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;
  }
}

处理不同的网络

javascript 复制代码
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]]
  });
}

完整的钱包组件示例

tsx 复制代码
import { useState, useEffect } from 'react';
import { ethers } from 'ethers';

export default function WalletConnector() {
  const [account, setAccount] = useState<string | null>(null);
  const [balance, setBalance] = useState<string>('0');
  const [network, setNetwork] = useState<string>('');

  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 className="wallet-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. 验证交易参数

javascript 复制代码
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. 使用硬件钱包

javascript 复制代码
// 使用Ledger或Trezor
async function connectHardwareWallet() {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  
  // 请求硬件钱包账户
  const accounts = await provider.send('eth_requestAccounts', []);
  return accounts[0];
}

3. 显示交易确认

javascript 复制代码
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未安装

javascript 复制代码
function handleNoMetaMask() {
  const installUrl = 'https://metamask.io/download/';
  if (confirm('请安装MetaMask以继续使用此DApp')) {
    window.open(installUrl, '_blank');
  }
}

2. 用户拒绝连接

javascript 复制代码
async function connectWithErrorHandling() {
  try {
    await connectWallet();
  } catch (error) {
    if (error.code === 4001) {
      console.log('用户拒绝连接');
    } else {
      console.error('连接错误:', error);
    }
  }
}

3. 交易失败

javascript 复制代码
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

相关推荐
Richown11 小时前
机器学习入门:TensorFlow.js实战
区块链·react
mutourend1 天前
Zcash 与量子计算机
区块链·量子计算·后量子密码学
TechubNews1 天前
稳定币下一战:不是谁发币,而是谁掌握结算通道
人工智能·web3·区块链
mutourend1 天前
量子计算与区块链:让紧迫性与真实威胁相匹配
区块链·量子计算·后量子密码学
多年小白1 天前
A股算力租赁板块 深度分析
大数据·人工智能·ai·金融·区块链
架构源启1 天前
Spring AI 进阶系列- Agent 智能体开发:ReAct模式、多步推理与自主Agent实战
人工智能·spring·react·ai agent·智能体·springai
小牛itbull1 天前
ReactPress 3.0 :一分钟拥有自己的 CMS & 博客
开源·cms·react·博客系统·reactpress
黄焖鸡能干四碗2 天前
固定资产管理系统建设方案和源码(Java源码)
大数据·数据库·人工智能·物联网·区块链
master-dragon2 天前
DeFi 基础: 流动性、池子、AMM、滑点
区块链