
hardhat-ethers 插件完全指南在 Hardhat 中集成 ethers.js 的部署、签名与网络交互【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat本篇技术指南围绕 Hardhat 官方插件nomicfoundation/hardhat-ethers当前仓库版本 4.0.15展开讲解如何把 ethers.jsv6无缝集成进 Hardhat 的每个网络连接中覆盖安装配置、waitForTransactionReceipt行为开关、ethers对象上的全套 Hardhat 增强 APIdeployContract、getContractFactory、getContractAt、getSigners、getImpersonatedSigner等以及库链接library linking的底层实现。读完本文你将掌握用 ethers.js 编写合约部署、读取链上状态、签名消息与 EIP-712 结构化数据、模拟任意账户的完整实战方案并理解这些 API 在 Hardhat 源码中的实现链路。插件定位把 ethers.js 注入每个网络连接hardhat-ethers的核心作用一句话概括将 ethers.js 集成到 Hardhat 中并为每一个网络连接network connection附加一个ethers对象。这意味着无论你连接的是 Hardhat 内置的内存网络EDR 模拟网络、配置的 HTTP 外部节点还是通过--network指定的其他网络都能以统一的 ethers.js API 进行交互同时获得若干 Hardhat 特有的增强能力。从源码看插件的注册非常轻量——src/index.ts 中通过definePlugin声明了id: hardhat-ethers与两个 hook handlerconfig与network并在HardhatEthersNetworkUserConfig等类型上做了模块扩展见 src/type-extensions.ts。其中networkhook 在每次建立新连接时执行先创建底层连接再调用initializeEthers把ethers附加到连接对象上见 src/internal/hook-handlers/network.ts。提示本插件是 Hardhat 官方 EthersMocha Toolboxnomicfoundation/hardhat-toolbox-mocha-ethers的组成部分。如果你已经使用了该 Toolbox则无需再单独安装本插件。安装与启用在项目根目录执行npm install --save-dev nomicfoundation/hardhat-ethers然后在hardhat.config.ts中导入插件并将其加入plugins数组这是当前 Hardhat 版本推荐的defineConfig写法import { defineConfig } from hardhat/config; import hardhatEthers from nomicfoundation/hardhat-ethers; export default defineConfig({ plugins: [hardhatEthers], });从 package.json 可以确认依赖关系插件运行时依赖ethers^6.14.0并以hardhatworkspace:^3.8.0为 peer 依赖因此在 Hardhat 3.x 项目中使用本插件时esbuild 无关的 ethers v6 API如ContractFactory、Signer、Contract可直接复用。配置waitForTransactionReceipt插件为每个网络的ethers配置项提供了一个布尔开关waitForTransactionReceipt。默认行为与 ethers.js 一致HardhatEthersSigner.sendTransaction在交易被网络接收可用时就 resolve不会等待交易上链出块。如果你希望某个网络连接在每次提交交易后都等到拿到收据receipt再 resolve可以按如下配置export default defineConfig({ networks: { externalNode: { type: http, url: http://127.0.0.1:8545, ethers: { waitForTransactionReceipt: true, }, }, }, });这个选项的典型应用场景是当你的测试是针对 Hardhat 内存网络编写的却要跑在外部节点上而该外部节点的eth_sendTransaction方法在挖矿前就提前返回。开启该选项后可以保证行为一致。配置的底层校验与解析逻辑在 src/internal/hook-handlers/config.ts 中validateUserConfig使用 zod 模式z.object({ ethers: z.object({ waitForTransactionReceipt: z.boolean().optional() }).optional() })校验每个网络的ethers配置非法类型会以network xxx - ...的形式报错resolveUserConfig在解析完用户配置后为每个网络补全ethers字段waitForTransactionReceipt的默认值为false见resolveHardhatEthersNetworkConfig类型扩展在 src/type-extensions.ts 中分别挂到HttpNetworkUserConfig与EdrNetworkUserConfig上因此 HTTP 节点和 EDR 内存网络都支持该开关。两个需要注意的行为细节即使交易回滚revertsendTransaction仍然会 resolve 为一个交易响应TransactionResponse不会直接抛错——这样调用方和 chai matcher 仍然可以检查到收据看到回滚原因。开启该选项会改变 pending 交易的时序且每次发送都会等待挖矿因此仅应在需要兼容外部节点时开启其他场景保持默认关闭。以上行为在 src/internal/signers/signers.ts 的sendTransaction中有明确实现发送后先轮询交易哈希拿到TransactionResponse若waitForTransactionReceipt为 true 则调用provider.waitForTransaction(hash)等待收据并刻意不用transactionResponse.wait()因为它对 status0 的收据会抛错而调用方和 matcher 仍需要这个响应。对应的测试用例见 test/transactions.ts 中 “should wait for a transaction receipt when configured” 用例——它在关闭 automine 的情况下验证sendTransaction会在收据产生后才 resolve。使用ethers对象与 Provider插件为每个网络连接添加ethers属性获取方式import { network } from hardhat; const { ethers } await network.create(); const counter await ethers.deployContract(Counter); await counter.inc(); console.log(await counter.x());这个对象的 API 与 ethers.js 完全一致即typeof ethers外加若干 Hardhat 特有的增强功能。从 src/internal/initialization.ts 可以看到其组装方式{ ...ethers, provider, getSigner, getSigners, getImpersonatedSigner, getContractFactory, getContractFactoryFromArtifact, getContractAt, getContractAtFromArtifact, deployContract }——也就是说ethers.js 的全部顶层工具函数isAddress、parseEther等原样可用增强方法被覆盖/补充进来。仓库中的最小验证脚本 packages/example-project/scripts/hardhat-ethers.ts 展示了组合用法const { ethers } await hre.network.create(); // ethers 顶层工具函数 ethers.isAddress(0x1234567890123456789012345678901234567890); // ethers.Provider await ethers.provider.getBlockNumber(); // Hardhat helper 方法 await ethers.getSigners();providerethers对象上的provider是一个 ethers.js Provider连接到network.create()所选中的网络const blockNumber await ethers.provider.getBlockNumber(); const balance await ethers.provider.getBalance(someAddress);它适合读取只读链上数据账户状态、区块数据、交易对象等。它的真实实现是HardhatEthersProvider见 src/internal/hardhat-ethers-provider/hardhat-ethers-provider.ts包装了 Hardhat 底层的EthereumProvider其send(method, params)直接转发为eth_*/hardhat_*等 RPC 请求同时实现了 ethersProvider接口所需的区块/日志格式化、事件监听block、transactionHash、event三类事件等能力默认等待 1 个确认DEFAULT_TRANSACTION_CONFIRMS。部署合约deployContractethers.deployContract让你从项目 artifact 中直接部署合约无需手动处理 ABI 与字节码const counter await ethers.deployContract(Counter); await counter.inc(); console.log(await counter.x());函数签名来自 src/types.ts 与 READMEfunction deployContract( name: string, constructorArgs?: any[], signer?: ethers.Signer, ): Promiseethers.Contract;按合约名查找多数情况下直接传合约名Counter即可插件通过ArtifactManager.readArtifact(name)读取编译产物见 src/internal/hardhat-helpers/hardhat-helpers.ts 的getContractFactory。同名字合约用全限定名如果不同文件里存在同名合约必须使用包含源文件名的全限定名const counter await ethers.deployContract(contracts/Counter.sol:Counter);构造函数参数作为第二个参数传入数组const counter await ethers.deployContract(Counter, [42]);指定签名者默认使用第一个可用 signer也可以通过第三个参数指定const [defaultSigner, deployer] await ethers.getSigners(); const counter await ethers.deployContract(Counter, [], deployer);在底层deployContract的实现HardhatHelpers.deployContract会先调用getContractFactory得到ContractFactory然后执行factory.deploy(...args, overrides)。它还接受一个DeployContractOptions类型的签名FactoryOptions ethers.Overrides即可以同时携带signer、libraries以及value、gasLimit等 ethers Overrides——源码中会先把signer/libraries从 options 中剥离再传给 ethers避免 ethers 拒绝未知属性。库链接Library linking某些合约在部署前需要与库合约链接。可以通过libraries选项把库名映射到地址const counter await ethers.deployContract(Counter, { libraries: { SafeMath: 0x..., }, });这会把Counter实例与部署在0x...地址的SafeMath库链接起来。若缺少必要的库插件会直接抛错拒绝部署或创建 factory。Libraries类型src/types.ts允许值为string或ethers.Addressable即地址字符串或可解析地址的对象。链接的完整逻辑在HardhatHelpers.#collectLibrariesAndLinksrc/internal/hardhat-helpers/hardhat-helpers.ts从 artifact 的linkReferences中解析出合约真正需要的库集合按sourceName:libName全限定名去重校验用户传入的每个库地址是否是合法地址isAddress否则报INVALID_ADDRESS_TO_LINK_CONTRACT_TO_LIBRARY检查用户传入的库是否属于合约所需的库不属于时报LIBRARY_NOT_AMONG_CONTRACT_LIBRARIES同名库有多个时如两个文件里都有SafeMath报AMBIGUOUS_LIBRARY_NAME此时应使用全限定名contracts/A.sol:SafeMath来消除歧义若提供库的集合少于所需集合报MISSING_LINK_FOR_LIBRARY并列出缺失的全限定名最后#linkBytecode根据linkReferences中的{start, length}偏移把占位符替换为地址十六进制完成字节码链接按偏移排序后分段拼接。进阶 APIContractFactory 与 Contract 实例getContractFactory返回一个 ethers.jsContractFactory支持三种调用形态function getContractFactory( name: string, signer?: ethers.Signer, ): Promiseethers.ContractFactory; function getContractFactory( name: string, factoryOptions: FactoryOptions, ): Promiseethers.ContractFactory; function getContractFactory( abi: any[], bytecode: ethers.utils.BytesLike, signer?: ethers.Signer, ): Promiseethers.ContractFactory;按合约名创建const Counter await ethers.getContractFactory(Counter); const counter await Counter.deploy();按 ABI 部署字节码创建const Counter await ethers.getContractFactory(counterAbi, counterBytecode); const counter await Counter.deploy();默认使用配置中第一个 signer指定其他 signer 时作为第二个参数传入const [defaultSigner, deployer] await ethers.getSigners(); const Counter await ethers.getContractFactory(Counter, deployer); const counter await Counter.deploy();从实现上看按名字调用时内部会先readArtifact再转调getContractFactoryFromArtifact而FactoryOptions{ signer?, libraries? }会被解构后参与库链接最终通过new ContractFactory(abi, bytecode, signer)创建实例。若传入的不是合法 artifact 对象会抛INVALID_ARTIFACT_FOR_FACTORY若 artifact 的bytecode 0x抽象合约会抛INVALID_ABSTRACT_CONTRACT_FOR_FACTORY并携带合约名。getContractAt返回连接了指定地址的 ethers.js 合约实例function getContractAt( name: string, address: string, signer?: ethers.Signer, ): Promiseethers.Contract; function getContractAt( abi: any[], address: string, signer?: ethers.Signer, ): Promiseethers.Contract;按合约名 地址const counter await ethers.getContractAt(Counter, 0x1234...abcd);按 ABI 地址const counter await ethers.getContractAt(counterAbi, 0x1234...abcd);默认连接到第一个 signer指定调用者时作为第三个参数const [defaultSigner, caller] await ethers.getSigners(); const counter await ethers.getContractAt(Counter, 0x1234...abcd, caller);实现细节上地址既支持字符串也支持AddressableisAddressable分支会先getAddress()解析当没有传入 signer 时插件会自动取getSigners()[0]若连 signer 都没有比如accounts: remote的节点则退化为用 provider 创建只读合约保证eth_call类只读操作可用。getContractFactoryFromArtifact / getContractAtFromArtifact与getContractFactory、getContractAt对应但接收的是 Hardhat artifact 对象而不是名字或 ABI/字节码function getContractFactoryFromArtifact( artifact: Artifact, signer?: ethers.Signer, ): Promiseethers.ContractFactory; function getContractFactoryFromArtifact( artifact: Artifact, factoryOptions: FactoryOptions, ): Promiseethers.ContractFactory; function getContractAtFromArtifact( artifact: Artifact, address: string, signer?: ethers.Signer, ): Promiseethers.Contract;这在需要复用已读取的 artifact如配合hre.artifacts.readArtifact或其他工具链时非常有用。getContractAtFromArtifact在创建合约后还会检查contract.runner是否为空为空则自动connect(provider)以保证可读性。签名者getSigners / getSigner / getImpersonatedSignergetSigners返回与 Hardhat 配置账户一一对应的 ethers.js signer 数组function getSigners(): Promiseethers.Signer[]; const signers await ethers.getSigners();实现上通过provider.send(eth_accounts, [])获取账户列表再逐账户创建HardhatEthersSigner如果节点返回 “the method has been deprecated: eth_accounts” 错误例如某些远程节点则静默返回空数组而不是中断流程。getSigner按地址返回特定 signerfunction getSigner(address: string): Promiseethers.Signer; const signer await ethers.getSigner(0x1234...abcd);getImpersonatedSigner与getSigner类似但会冒充给定地址——即使没有它的私钥也能使用function getImpersonatedSigner(address: string): Promiseethers.Signer; const impersonatedSigner await ethers.getImpersonatedSigner(0x1234...abcd);实现上它先向 provider 发送hardhat_impersonateAccount请求这是 Hardhat/EDR 网络特有的 RPC再返回该地址的 signersrc/internal/hardhat-helpers/hardhat-helpers.ts。这常用于模拟 DAO 金库、多签钱包等账户执行交易。HardhatEthersSigner 的能力边界所有 helper 返回的 signer 都是HardhatEthersSigner导出别名为SignerWithAddress便于迁移它实现了 ethersSigner接口并在 src/internal/signers/signers.ts 中有几点值得注意的实现sendTransaction先解析from/to支持 ENS 名与 Addressable通过eth_sendTransaction提交再轮询交易哈希返回TransactionResponse并受waitForTransactionReceipt开关控制signMessage走personal_signsignTypedData走eth_signTypedData_v4且会深度拷贝数据、解析 ENS 名称、把 bigint 序列化为字符串authorize/getPrivateKey用于 EIP-7702 授权等场景私钥来自配置账户HTTP 网络的 mnemonic/accounts 数组或 EDR 模拟网络的账户配置若账户是remote类型则无法取得私钥并会抛错signTransaction当前实现会抛出METHOD_NOT_IMPLEMENTED源码中有 TODO 注释说明未来可能为内存网络/持有私钥的 JSON-RPC 网络放开。类型系统TypeScript 全链路类型支持插件通过 src/type-extensions.ts 和 src/types.ts 提供完整的类型扩展HardhatEthers typeof ethers HardhatEthersHelpersethers对象既是 ethers.js 全量 API又叠加了 8 个 Hardhat 增强方法NetworkConnection接口被扩展出ethers: HardhatEthers属性因此network.create()解构出的ethers自带类型推断合约名参数类型为StringWithArtifactContractNamesAutocompletion支持基于 artifact 的自动补全HttpNetworkConfig/EdrNetworkConfig均带ethers: HardhatEthersNetworkConfig字段。在测试与脚本中的实战组织在脚本中使用参考 packages/example-project/scripts/hardhat-ethers.ts以npx hardhat run scripts/hardhat-ethers.ts运行import hre from hardhat; const { ethers } await hre.network.create(); // ethers 工具函数、provider 只读查询、Hardhat helpers 均可直接用 ethers.isAddress(0x1234567890123456789012345678901234567890); await ethers.provider.getBlockNumber(); await ethers.getSigners();在测试中使用官方初始化测试test/index.ts展示了标准流程hre await createHardhatRuntimeEnvironment({ plugins: [hardhatEthersPlugin], }); ({ ethers } await hre.network.create());随后即可用ethers.deployContract部署、用ethers.getContractAt连接已有合约、用ethers.provider读取链上数据与 ethers.js 的断言/matcher 生态无缝配合。小结nomicfoundation/hardhat-ethers是 Hardhat 与 ethers.js 之间的标准桥接层安装启用后每个网络连接都获得完整的 ethers APIdeployContract与库链接能力免去手工处理 ABI、字节码和 linkReferences 的繁琐getSigners/getSigner/getImpersonatedSigner覆盖了测试中绝大多数账户操作场景waitForTransactionReceipt则解决了外部节点测试时序不一致的痛点。理解其底层 hook 初始化链路config 校验 → network 连接挂载 →initializeEthers组装对象后无论是排查类型问题还是扩展自定义行为都能做到有的放矢。【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考