Ethers.js 是面向以太坊及 EVM 兼容链的 JavaScript/TypeScript 客户端库,由 Richard Moore(ricmoo)发起并持续维护。它把节点通信(JSON-RPC/WebSocket)、账户与签名(钱包、助记词、EIP-712 结构化签名)、合约部署与调用(ABI 编解码、事件监听)统一封装为 Provider、Signer、Contract 三层抽象,开发者无需手写底层 RPC 请求与加密原语。当前主线为 v6(2023 年 3 月发布),以原生 bigint 取代 BigNumber、移除 utils 命名空间,API 更现代、打包体积更小;v5 仍处于维护期,存量项目众多。本篇从安装配置讲起,覆盖读取链上数据、钱包签名、合约交互的完整实操,生产部署要点(多 RPC 容灾、事件监控、回执确认),并与 web3.js、viem、wagmi 做选型对比,最后附常见误区与面试题。

岗位关联

掌握 Ethers.js 是 Web3 全栈与链上数据工程的基础技能:无论 DApp 前端、交易脚本还是索引服务,合约交互与钱包管理都离不开这套核心 API 抽象。

目录
  1. 为什么需要 Ethers.js:手写 JSON-RPC 与签名的成本太高
  2. 安装与环境配置:3 步就绪
  3. 快速上手:5 分钟读取余额并发送一笔交易
  4. 核心能力详解:5 大支柱
  5. 生产部署与监控:从上线到运维
  6. 版本迭代
  7. AI 应用场景
  8. 常见误区
  9. 面试题
  10. 参考来源
工具类型
以太坊 JavaScript/TypeScript 客户端库(开源)
开发商
Richard Moore(ricmoo)发起,社区维护
开源协议
MIT(可商用)
GitHub Stars
无
主要语言
TypeScript(编译输出 JS,支持 ESM/CJS 双格式)
当前版本
6.13.x(2024 年 10 月发布,持续维护)
安装方式
`npm install ethers`
下载地址
官方文档
上手难度
★★☆☆☆(较低)
适合人群
DApp 前端工程师、Web3 后端、合约测试与脚本开发者
同类工具
web3.js · viem · wagmi
托管服务
无

速览卡是工具类内容的固定首屏模块:30 秒判断「这工具是什么、能不能商用、适不适合我」。

为什么需要 Ethers.js:手写 JSON-RPC 与签名的成本太高

直接对接以太坊节点,开发者需要自己构造 JSON-RPC 请求(eth_call、eth_sendRawTransaction)、处理私钥签名(secp256k1 曲线、RLP 编码)、把函数签名转成 4 字节选择器并做 ABI 编解码。“把 1 ETH 转给某地址”这条最简单的路径,就要完成 gas 估算、nonce 管理、签名、序列化、广播、轮询回执六个步骤,每一步都容易出错,而且与业务逻辑无关。

Ethers.js 将上述过程收敛为三层抽象:Provider 负责与节点通信(读取链上数据、广播交易);Signer(通常由 Wallet 实现)负责账户与签名;Contract 负责合约的 ABI 编解码与调用。开发者只需 provider.getBalance(addr) 读取余额、wallet.sendTransaction({to, value}) 发送交易、contract.balanceOf(addr) 调用合约只读方法——底层细节全部由库处理,错误率与代码量同时下降。

与 web3.js 相比,Ethers.js 在 v6 后全面拥抱 TypeScript 与原生 bigint,类型推导更可靠、包体积更小;与 viem 相比,ethers 的面向对象风格更直观,历史教程与存量代码更多,至今仍是大多数课程、工具链(如 Hardhat 生态)的默认选择。

安装与环境配置:3 步就绪

Ethers.js 需要 Node.js 14+(建议 18+)。在项目目录内执行以下命令完成初始化、安装与验证。

步骤一:初始化项目并安装依赖。步骤二:打印版本号确认安装成功,v6 应输出 6.x.x。步骤三:连接公共 RPC 读取主网最新区块高度,验证网络通路可用。浏览器端也可用官方 CDN 的 UMD 包(https://cdn.ethers.io/lib/ethers-6.13.umd.min.js,版本号按需替换)直接引入。

bash12 行
npm init -ynpm install ethers # 验证版本(ESM 方式)node --input-type=module -e "import { ethers } from 'ethers'; console.log(ethers.version);" # 连接公共节点读取区块高度,验证链上通路node --input-type=module -e "import { ethers } from 'ethers';const provider = new ethers.JsonRpcProvider('https://cloudflare-eth.com');console.log('最新区块:', await provider.getBlockNumber());"

快速上手:5 分钟读取余额并发送一笔交易

最小可用流程:连接网络(Provider)→ 读取链上数据 → 用钱包(Wallet)签名广播交易 → 等待回执(wait)。

1. 在项目根目录创建 demo.mjs(ESM 模块),引入 ethers。

2. 创建 Provider:new ethers.JsonRpcProvider('https://cloudflare-eth.com'),也可替换为 Infura/Alchemy 的带 key 地址。

3. 用 provider.getBalance(地址) 读取余额,ethers.formatEther 把 wei(ETH 最小单位,1 ETH = 10^18 wei)显示为 ETH 字符串。

4. 用私钥创建 Wallet 并指定网络:new ethers.Wallet(process.env.PRIVATE_KEY, provider)。

5. 调用 wallet.sendTransaction({to, value}),value 用 ethers.parseEther('0.001') 转为 wei。

6. await tx.wait() 等待区块确认,从回执读取 status 与 gasUsed。

进阶技巧:主网发送会消耗真实 ETH,建议先在 Sepolia 测试网联调(RPC 地址与测试币 faucet 向 RPC 服务商索取),流程验证通过后再切主网。

javascript24 行
import { ethers } from 'ethers'; // 1. 连接以太坊主网(也可替换为 Infura/Alchemy 私有地址)const provider = new ethers.JsonRpcProvider('https://cloudflare-eth.com');console.log('最新区块高度:', await provider.getBlockNumber()); // 2. 读取余额。formatEther 把 wei(bigint)转为 ETH 字符串const vitalik = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045';const wei = await provider.getBalance(vitalik);console.log('Vitalik 余额:', ethers.formatEther(wei), 'ETH'); // 3. 用私钥创建钱包(务必使用环境变量,勿硬编码)const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); // 4. 发送交易。parseEther 把 ETH 字符串转为 wei(bigint)const tx = await wallet.sendTransaction({  to: '0xRecipientAddressHere',  value: ethers.parseEther('0.001'),});console.log('交易广播成功,hash:', tx.hash); // 5. 等待确认。receipt.status === 1 表示执行成功const receipt = await tx.wait();console.log('已确认,区块:', receipt.blockNumber, 'gas 消耗:', receipt.gasUsed.toString());

核心能力详解:5 大支柱

支柱一·Provider 网络接入:Provider 抽象对节点的全部读写操作。v6 提供 JsonRpcProvider(HTTP 轮询)、WebSocketProvider(实时推送)、FallbackProvider(多路容灾,支持权重与超时配置)、BrowserProvider(包装浏览器注入的 EIP-1193 钱包)。常用读接口有 getBlockNumber、getBalance、getTransactionReceipt;写接口 broadcastTransaction 负责将签名后的交易广播上链。

支柱二·Wallet 账户与签名:Wallet 实现 Signer 接口,持有私钥并负责签名。创建方式:new Wallet(privateKey)、Wallet.createRandom()、HDNodeWallet.fromPhrase(mnemonic, path)(BIP-39/BIP-44 助记词派生,一条助记词可派生无限多账户)。签名能力包括 signMessage(消息签名)与 signTypedData(EIP-712 结构化签名,常用于登录授权与 Permit 免 gas 授权)。

支柱三·Contract 合约交互:new ethers.Contract(address, abi, runner),runner 传 Provider 则只读、传 Signer 则可写。view 函数直接返回结果值;写函数返回 TransactionResponse,必须 .wait() 拿回执;payable 函数可在调用参数中附带 { value }。v6 支持人类可读 ABI(如 ['function transfer(address to, uint256 amount) returns (bool)']),无需手抄 JSON ABI。

支柱四·事件监听与日志解析:contract.on('Transfer', cb) 订阅实时事件;provider.getLogs(filter) 按区块范围回溯历史日志;事件参数按 ABI 自动解码(如 from、to、value)。数据中心型应用常用 WebSocketProvider 配合 checkpoint 记录已处理区块,断线后回扫补漏。

支柱五·数据编解码与工具函数:formatUnits/parseUnits 完成任意 ERC-20 代币的小数与最小单位换算;getAddress 校验并统一地址校验和;Interface 负责函数选择器与事件 topic 的编解码;keccak256、hexlify 等用于文件哈希上链、构造原始数据等场景。

生产部署与监控:从上线到运维

多 RPC 容灾:单一路 RPC 在限流或宕机时直接导致服务不可用。生产建议用 FallbackProvider 组合多家供应商,按权重分配并设置单次请求的 stallTimeout(超时阈值):

事件监听服务:使用 WebSocketProvider 订阅合约事件,进程内维护 lastSyncedBlock,每处理一批日志更新 checkpoint 并持久化(Redis/数据库);断线重连后从 checkpoint 区块高度调用 getLogs 回扫,避免漏数据。服务保持无状态,把进度放到外部存储以便水平扩容。

可观测性:所有交易操作记录 hash、nonce、gas 上限、实际 gasUsed 与确认耗时;对 wait() 超时和 status !== 1 的交易上报告警;采集 Provider 层的请求失败率、429 限流次数等指标接入 Prometheus/Sentry。私钥只存于后端环境变量或 KMS 签名服务,前端永不接触。

<p class="para-label">避坑清单:</p>

<ul class="dash-list"><li>v5 代码直接迁移 v6 会大量报错:BigNumber 与 ethers.utils 命名空间已移除,wei 值变为原生 bigint,务必先读官方迁移指南再改业务代码。</li><li>只 await sendTransaction 不调 wait():交易仅代表广播成功,未确认不算成功,必须 await tx.wait() 并校验 receipt.status === 1。</li><li>私钥写死在前端或提交到 Git:任何拿到代码者都能转走资产,私钥应只放后端环境变量或托管签名服务。</li><li>生产裸用免费公共 RPC:限流频繁导致请求失败,建议 FallbackProvider 多路容灾或自建节点。</li><li>WebSocket 断线后事件会静默丢失:必须记录 lastSyncedBlock 检查点,重连后调用 getLogs 回扫补漏。</li></ul>

javascript5 行
const provider = new ethers.FallbackProvider([  { provider: process.env.INFURA_URL, weight: 2, stallTimeout: 5000 },  { provider: process.env.ALCHEMY_URL, weight: 2, stallTimeout: 5000 },  { provider: 'http://localhost:8545', weight: 1, stallTimeout: 10000 },]);
维度Ethers.jsweb3.jsviemwagmi
定位通用以太坊 JS 库,DApp/脚本/后端通用老牌通用以太坊 JS 库类型安全的以太坊 JS 库(函数式)基于 viem 的 React Hooks 封装
编排范式面向对象:Provider/Signer/Contract面向对象:web3.eth.*函数式 actions + 强类型推导声明式 Hooks,组件内调用
类型约束TypeScript 编写,v6 类型较完善弱类型,依赖社区类型补丁TypeScript 优先,类型最严格继承 viem 的完整类型链
学习曲线较低,教程与资料最多低,历史教程丰富中,需理解函数式风格中低,React 体验好
生态集成Hardhat 默认依赖之一,插件多老生态,课程引用多wagmi/viem 系新工具链RainbowKit/ConnectKit 主流
生产部署Provider 容灾/重试/事件监听能力完备功能完备,维护节奏慢活跃维护,工具链友好专注前端,后端仍需 viem
LicenseMITLGPL-3.0MITMIT
适合谁全栈 Web3、脚本与后端、课程学习维护老项目、历史代码新项目、类型敏感团队React DApp 前端

选型建议:后端脚本、索引服务与课程学习选 Ethers.js,资料最多、心智模型最直观;新起的 React DApp 前端优先 wagmi + viem,类型体验更好;纯 Node 服务且追求最强类型推导选 viem;仅在维护已有 web3.js 项目时保留它,新模块可逐步迁移。

版本迭代

Ethers.js 自 2016 年发布 v1,经历 2019 年 v4 的 ABI/事件系统重写、2020 年 v5 全面 TypeScript 化与模块化、2023 年 v6 以 bigint 取代 BigNumber 并扁平化 API 的三次大版本演进。v5 进入长期维护,v6 自 2023 年起经多次小版本打磨,截至训练数据最新为 6.13.x(2024 年 10 月发布,后续持续有补丁更新,本文撰写时可能已有更新版本)。

版本时间变化提升
4.02019 年重写 ABI 解码器与 Contract 事件 API合约交互稳定性显著提升,事件订阅体验改善
5.02020-07全面重写:TypeScript 源码、ESM 模块化类型安全、包体积缩小,成为 DApp 与工具链事实标准
5.72022-01v5 系列收尾,修复安全与兼容问题v5 进入长期维护,存量项目可继续安全使用
6.02023-03推翻式重构:BigNumber→原生 bigint、移除 utils 命名空间、Provider 架构重设计API 更现代,打包体积与解析性能明显优化
6.132024-106.x 系列持续维护:合约调用、钱包与 Provider 稳定性修复当前主线推荐版本(截至训练数据,可能已有更新版本)
结论

Ethers.js 无官方跑分榜单,v6 相对 v5 的实测差异集中在工程层面:包体积显著下降(移除 utils 冗余代码与依赖)、wei 值使用原生 bigint 减少类型转换开销、Provider 层支持 stallTimeout 与优先级调度。选型建议:新项目直接用 v6;存量 v5 项目若无强需求不必急于迁移,但新功能与安全修复以 v6 为优先。

AI 应用场景

场景 1:DApp 前端连接钱包与合约交互

阶段:链上交互 / 合约调用层

解决的问题:浏览器接入 MetaMask 等钱包需要处理 EIP-1193(浏览器扩展注入钱包的标准接口)的窗口注入、账号切换、链切换等协议细节。用 new ethers.BrowserProvider(window.ethereum) 一步包装,getSigner() 后用 Signer 发起转账与合约调用,账号切换通过监听 EIP-1193 的 accountsChanged 事件刷新界面。

具体案例:某 DeFi 前端用 BrowserProvider 实现代币授权与交易流程,交易通过 tx.wait() 更新确认状态,典型落地中交互代码量较手写 RPC 封装缩减约 80%,用户侧交易失败率明显下降。

场景 2:批量空投与运营脚本

阶段:链上批量操作 / 运营工具化

解决的问题:人工在钱包逐个转账效率低且易错。用 Node 脚本加 Wallet 批量构造交易,结合 EIP-1559 参数与 nonce 管理,用 parseUnits 精确处理代币小数位,getAddress 统一校验收件地址。

具体案例:项目方发放 1000 份代币奖励:脚本读取 CSV 地址清单、逐笔构造 transfer 调用并等待回执,异常交易自动记录并重试,整体耗时从人工操作数天压缩到脚本数十分钟。

场景 3:链上事件监控与数据索引

阶段:实时事件流 / 数据管道

解决的问题:轮询区块获取事件延迟高且浪费 RPC 配额。用 WebSocketProvider 加 contract.on 做推送订阅,用 getLogs 做历史回扫,以 lastSyncedBlock 为断点实现增量索引。

具体案例:某 NFT 数据分析平台监控巨鲸转账与合约大额异动:事件推送相比轮询将监控延迟从分钟级降至秒级(P95 从约 60 秒降到 5 秒内),断线重连后从未处理区块回扫,未出现数据缺口。

常见误区

误区我项目里用的 ethers 5,升级到 6 只要把包版本改一下就行,API 差不多。

正解v6 是破坏性大版本:BigNumber 类被原生 bigint 取代(余额、交易值等从对象变为 bigint、不能用 .toString() 之外的旧方法),ethers.utils 命名空间被移除(parseEther 等变为顶层函数),providers 等子命名空间扁平化,Contract 创建方式也变了。v5 代码不迁移直接跑 v6 会大面积报错,需对照官方迁移指南逐项修改。

为何常见:v5 长期是 DApp 事实标准,教程与存量代码大多基于 v5,升级 v6 后教程没跟上,很多人误以为只是小版本更新。

误区反正是测试网或小金额,把私钥放在前端或仓库里没关系,方便调试。

正解私钥一旦出现在前端代码、Git 历史、日志等任何可被他人获取的位置,该地址资产就处于风险中。生产环境私钥应只存放于后端环境变量或托管签名服务(如 KMS、云钱包),前端通过 BrowserProvider 连接用户钱包、由用户授权签名。

为何常见:教程示例为了方便常把私钥写进代码,初学者照抄后形成了坏习惯。

误区sendTransaction 返回了 hash,交易就成功了,可以继续做下一步。

正解sendTransaction 只代表交易被节点接收并广播,尚未打包进区块;pending 状态可能持续,也可能因低估 gas 或 nonce 冲突被替换、丢弃。必须 await tx.wait() 拿到回执,并检查 receipt.status === 1(0 表示执行回滚)才能认为交易成功。

为何常见:返回的 hash 与回执长得很像,很多示例为了简洁略去 wait(),导致新手把两阶段过程当成一阶段。

误区直接用免费的公共 RPC 地址,不花钱也能跑生产服务。

正解免费公共 RPC 有严格速率限制,高峰期会频繁返回 429 或超时;部分公共节点还禁用写接口。生产环境应使用带配额的商业 RPC(Infura/Alchemy/QuickNode),并用 FallbackProvider 组合多路节点,自建节点可进一步降低成本与延迟。

为何常见:免费 RPC 在开发联调时体验尚可,容易让人低估生产流量的限流风险。

误区contract.on 注册之后就会一直收到事件,不用操心断线和遗漏。

正解WebSocket 连接会因网络抖动、节点重启等原因断开,断线期间的事件全部丢失。生产环境必须监听断线事件自动重连,并用 checkpoint(lastSyncedBlock)记录处理进度,重连后调用 getLogs 回扫补偿,防止漏数据。

为何常见:本地短时间测试看不到断线,容易让人误以为订阅是长期稳定可靠的。

面试题

Q1. ethers v6 中,ethers.formatEther(balance) 的返回值类型是什么?

单选题 · easy

  • A.number 浮点数类型
  • B.string 字符串类型← 正确答案
  • C.bigint 大整数类型
  • D.BigNumber 对象类型

答案:B。formatEther 把最小单位 wei(bigint)格式化为 ETH 字符串,便于界面精确展示、避免浮点精度丢失。number 会损失精度,bigint 是输入类型而非输出,BigNumber 类在 v6 中已移除。

Q2. ethers v5 中的 BigNumber 类,在 v6 中被什么替代?

单选题 · easy

  • A.原生 number 类型
  • B.原生 bigint 类型← 正确答案
  • C.Uint8Array 字节数组
  • D.Decimal 高精度小数

答案:B。v6 全面改用 JavaScript 原生 bigint 表示 wei 等大整数,移除了 BigNumber 类及 .mul()、.toString() 等方法,运算符改为原生的 +、*、/。bigint 与 number 直接运算会报错,需要显式转换。

Q3. 前端浏览器中,连接 MetaMask 等注入钱包应使用 ethers v6 的哪个类?

单选题 · easy

  • A.JsonRpcProvider 类
  • B.BrowserProvider 类← 正确答案
  • C.WebSocketProvider 类
  • D.FallbackProvider 类

答案:B。BrowserProvider 包装 window.ethereum(EIP-1193 注入对象),复用钱包内置的 RPC 连接与账户;JsonRpcProvider 直连固定 RPC 地址,WebSocketProvider 用于实时推送,FallbackProvider 用于多路容灾。

Q4. 以下哪些方式可在 ethers v6 中正确导入并使用库?

多选题 · hard

  • A.import { ethers } from "ethers" 具名导入← 正确答案
  • B.import { JsonRpcProvider } from "ethers" 具名导入← 正确答案
  • C.import ethers from "ethers" 默认导入
  • D.const { ethers } = require("ethers") CJS 方式← 正确答案

答案:ABD。v6 只提供具名导出,默认导出已移除,因此 C 的默认导入会拿到 undefined;A、B 正确;D 在 CommonJS 环境中可用,require 返回的模块对象含 ethers 具名属性。

Q5. EIP-1559 类型交易中,用于控制用户最高支付费用的字段组合是?

单选题 · mid

  • A.gasPrice 与 gas 旧式字段
  • B.maxFeePerGas 与 maxPriorityFeePerGas← 正确答案
  • C.nonce 与 gasLimit 组合字段
  • D.value 与 data 交易字段

答案:B。EIP-1559(以太坊的矿工小费费率机制)用 maxFeePerGas(单价上限)与 maxPriorityFeePerGas(验证者小费上限)取代单一 gasPrice。gasLimit 控制用量上限,nonce 是交易序号,均与费用定价无关。

Q6. 在 ethers v6 中,监听合约的 Transfer 事件可以怎么做?

多选题 · mid

  • A.contract.on("Transfer", cb) 持续监听← 正确答案
  • B.contract.once("Transfer", cb) 只监听一次← 正确答案
  • C.provider.getLogs(filter) 拉取历史日志← 正确答案
  • D.contract.Transfer(cb) 注册回调(事件)

答案:ABC。on/once 是实时订阅事件,getLogs 按过滤条件回溯历史日志。D 会把 Transfer 当作合约函数名的普通调用(若 ABI 中存在同名函数会触发交易),不是事件注册方式。

Q7. 在 ethers v6 中,调用 sendTransaction 后确认交易成功的正确做法是?

单选题 · mid

  • A.直接 await tx 即拿到回执
  • B.必须 await tx.wait() 等待← 正确答案
  • C.同步读取 tx.receipt 属性
  • D.provider.getTransaction 轮询

答案:B。sendTransaction 返回 TransactionResponse,仅代表广播成功;wait() 会等待打包并返回 TransactionReceipt,status 为 1 表示成功、0 表示回滚。tx 本身没有 receipt 属性,getTransaction 只是按 hash 查交易。

Q8. 使用助记词(BIP-39/BIP-44)创建并派生 HD 钱包,应使用 ethers v6 的哪个类?

单选题 · mid

  • A.普通 Wallet 类实例
  • B.HDNodeWallet 类实例← 正确答案
  • C.Vault 保险库类实例
  • D.SigningKey 密钥类实例

答案:B。HDNodeWallet 支持 fromPhrase(mnemonic, path) 按派生路径生成账户,是 HD 钱包(分层确定性钱包,一条助记词派生无限多账户)的实现;Wallet 仅持有单个私钥,SigningKey 只负责底层签名原语。

Q9. 在 ethers v6 中调用合约 view 函数(如 balanceOf)时,返回值是?

单选题 · hard

  • A.Promise<TransactionResponse>,需等待确认回执
  • B.直接返回结果值(等待 Promise 解包)← 正确答案
  • C.需要手动调用 .call() 获取结果
  • D.返回合约事件的原始日志字节数组

答案:B。view 函数只读不写链,v6 直接返回解码后的结果,async/await 即可拿到实际值,无需 wait()。A 描述的是写函数的行为,C 的 .call() 属于旧版调用方式,D 与事件日志无关。

Q10. 关于 ethers v5 迁移到 v6,以下说法正确的是?

多选题 · hard

  • A.ethers.utils.parseEther 改为调用 ethers.parseEther← 正确答案
  • B.Contract 仍使用 getContract 工厂方式创建实例
  • C.providers 和 utils 命名空间均已移除并改为顶层导出← 正确答案
  • D.原生 BigNumber 类型仍需要在 v6 中显式手动使用

答案:AC。v6 将 utils、providers 等命名空间扁平化为顶层导出,A、C 正确;v6 用 new ethers.Contract(address, abi, runner) 创建实例,B 的 getContract 工厂已移除;BigNumber 被原生 bigint 取代,D 错误。

参考来源