2、MetaMask集成:Web3.js库安装、Provider注入、连接账户、监听账户切换、获取链ID、处理连接错误

好,咱们直接进入正题。MetaMask 是目前最主流的浏览器钱包,没有之一。你想想看,市面上 90% 的 DApp 都在用它做入口。这一节,我会带你手把手把 MetaMask 集成到你的 NFT 平台里。

我个人习惯把集成过程拆成 6 个步骤:安装库、注入 Provider、连接账户、监听切换、获取链 ID、处理错误。咱们一个一个来。

2.1 安装 Web3.js 库

首先,你得装 Web3.js。这是和以太坊节点打交道的桥梁。

npm install web3

或者用 yarn:

yarn add web3

嗯,这里要注意版本。我建议用 ^4.x 版本,因为 4.x 对 TypeScript 支持更好,而且 API 更现代化。我在项目中遇到过有人装了 1.x 版本,结果很多新特性用不了,最后还得降级重装。

小提示:如果你用的是 Vue 或 React 框架,记得把 Web3 实例挂载到全局或通过 Context 传递,别在每个组件里都 new 一次。

2.2 Provider 注入检测

MetaMask 安装后,会在浏览器全局注入一个 window.ethereum 对象。这就是 Provider。

说白了,你的 DApp 得先确认这个对象存在,才能继续往下走。

// 检测 MetaMask 是否安装
if (typeof window.ethereum !== 'undefined') {
  console.log('MetaMask 已安装');
} else {
  console.error('请安装 MetaMask 浏览器扩展');
}

我曾经在一个项目里忘了做这个检测,结果用户没装 MetaMask 就直接报错,体验极差。后来加了个友好的提示弹窗,转化率反而提升了。

注意:不要直接用 window.web3,那个是旧版 MetaMask 注入的,新版已经废弃了。一律用 window.ethereum

2.3 连接账户

检测通过后,就该请求用户授权了。MetaMask 会弹出一个窗口,让用户选择要连接的账户。

async function connectWallet() {
  try {
    // 请求用户授权
    const accounts = await window.ethereum.request({
      method: 'eth_requestAccounts'
    });
    
    // 获取当前账户
    const currentAccount = accounts[0];
    console.log('已连接账户:', currentAccount);
    
    return currentAccount;
  } catch (error) {
    console.error('用户拒绝了连接请求:', error);
  }
}

这里有个坑:eth_requestAccounts 只能调用一次。如果用户第一次拒绝了,你再调用它不会再次弹出窗口。你得引导用户手动在 MetaMask 里切换账户。

核心要点:连接成功后,把账户地址存到状态管理里(比如 Redux 或 Vuex),后续所有交易都需要用它。

2.4 监听账户切换

用户可能在 MetaMask 里手动切换账户。你的 DApp 得实时感知到变化,否则会出现「显示的是 A 账户,实际用的是 B 账户」的尴尬情况。

// 监听账户切换
window.ethereum.on('accountsChanged', (accounts) => {
  if (accounts.length === 0) {
    // 用户断开了连接
    console.log('用户断开了钱包连接');
    // 重置状态,跳转到连接页面
  } else {
    // 用户切换了账户
    const newAccount = accounts[0];
    console.log('账户已切换至:', newAccount);
    // 更新全局状态
  }
});

我记得有一次线上事故,就是因为没监听 accountsChanged,用户切换账户后还在用旧地址发起交易,结果 gas 费白花了。嗯,从那以后我把这个监听写成了强制校验逻辑。

2.5 获取链 ID

你的 NFT 平台可能部署在以太坊主网,也可能在 Goerli 测试网。获取链 ID 是为了确认用户连接的是正确的网络。

async function getChainId() {
  const chainId = await window.ethereum.request({
    method: 'eth_chainId'
  });
  
  // 返回的是十六进制字符串,比如 '0x5' 代表 Goerli
  console.log('当前链 ID:', chainId);
  
  // 转换成十进制
  const decimalChainId = parseInt(chainId, 16);
  return decimalChainId;
}

常见的链 ID 对照表:

网络名称 链 ID(十六进制) 链 ID(十进制)
以太坊主网 0x1 1
Goerli 测试网 0x5 5
Sepolia 测试网 0xaa36a7 11155111
Polygon 主网 0x89 137
最佳实践:如果用户连错了网络,别直接报错。可以用 wallet_switchEthereumChain 方法自动帮用户切换到正确的网络。

2.6 处理连接错误

连接过程中可能遇到各种错误。我总结了几种常见情况:

  • 用户拒绝连接:错误码 4001,提示用户手动授权
  • MetaMask 未安装:引导用户安装扩展
  • 网络请求超时:可能是 RPC 节点挂了,建议重试
  • 链 ID 不匹配:提示用户切换网络
async function handleConnection() {
  try {
    await connectWallet();
    const chainId = await getChainId();
    
    // 检查是否在正确的网络
    if (chainId !== 5) { // 假设我们要用 Goerli
      await switchToCorrectNetwork();
    }
  } catch (error) {
    // 统一错误处理
    if (error.code === 4001) {
      // 用户拒绝
      showToast('请授权连接钱包');
    } else if (error.code === -32002) {
      // 请求已挂起
      showToast('请检查 MetaMask 中的待处理请求');
    } else {
      // 其他错误
      console.error('连接失败:', error);
      showToast('连接失败,请重试');
    }
  }
}

我曾经犯过一个低级错误:把所有错误都笼统地提示「连接失败」。结果用户根本不知道是没装 MetaMask 还是拒绝了授权。后来我针对每种错误码做了不同的提示文案,用户反馈好多了。

避坑指南:千万不要在 eth_requestAccounts 之前调用 eth_accounts。后者只返回已授权的账户,不会弹出授权窗口。很多新手搞混这两个方法,导致用户永远看不到授权弹窗。

小结

这一节的内容其实就一句话:先检测、再连接、后监听、最后兜底。你想想看,MetaMask 集成说白了就是和浏览器里的一个对象打交道,只要把它的生命周期管理好,基本不会出大问题。

下一节我们会讲如何用 Web3.js 发起交易,包括转账和调用合约。到时候你会看到,前面这些准备工作做得越扎实,后面的交易逻辑就越顺畅。