hardhat-ignition-ethers:用 ethers.js 结果类型优雅落地 Hardhat Ignition 部署
2026/9/16 19:40:01 网站建设 项目流程

hardhat-ignition-ethers:用 ethers.js 结果类型优雅落地 Hardhat Ignition 部署

【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat

导读

本文围绕开源仓库 hardhat 中@nomicfoundation/hardhat-ignition-ethers插件展开,它把声明式部署框架 Hardhat Ignition 与 ethers.js 无缝集成:你只需写一份 Ignition 模块,deploy之后拿到的就是可直接调用的 ethersContract实例。读完本文,你将掌握该插件的安装、配置与在脚本/测试中使用connection.ignition.deploy()的完整流程,并能理解它从模块定义到返回合约实例的底层实现链路。

插件是什么:Ignition 与 ethers.js 之间的胶水层

hardhat-ignition-ethers是 Hardhat 官方生态中把两个能力缝合起来的插件:

  • Hardhat Ignition:声明式智能合约部署系统。你定义要部署的合约实例、要执行的调用(m.call)以及合约间的依赖关系,Ignition 负责解析依赖顺序、保存部署状态、支持失败重试与按部署 ID 复用。
  • ethers.js:当前最主流的以太坊 JavaScript 库之一,提供ContractWalletProvider等对象,是脚本与测试中与链上合约交互的标准方式。

该插件所做的,就是让deploy的返回值从 Ignition 原生的“部署结果”自动映射为对应合约 ABI 的 ethers 合约实例,开发者拿到手即可直接调用合约方法。插件包描述也印证了这一职责定位:“The Ethers extension to Hardhat Ignition”,其peerDependencies同时声明了对@nomicfoundation/hardhat-ignition@nomicfoundation/hardhat-ethers@nomicfoundation/ignition-coreethers的依赖(见 package.json)。

从源码入口看,插件通过definePlugin声明自身依赖并注册网络级 hook(src/index.ts):

const hardhatIgnitionEthersPlugin: HardhatPlugin = definePlugin({ id: "hardhat-ignition-ethers", dependencies: () => [ import("@nomicfoundation/hardhat-ignition"), import("@nomicfoundation/hardhat-ethers"), ], hookHandlers: { network: () => import("./internal/hook-handlers/network.js"), }, npmPackage: "@nomicfoundation/hardhat-ignition-ethers", });

也就是说,它不是一个独立运行的工具,而是建立在hardhat-ignitionhardhat-ethers之上的扩展层——这正是它能直接复用两边的部署引擎与合约实例化能力的原因。

安装与配置

安装命令

在项目根目录执行:

npm install --save-dev @nomicfoundation/hardhat-ignition-ethers

提示:该插件是 Ethers+Mocha Hardhat Toolbox(@nomicfoundation/hardhat-toolbox-mocha-ethers)的组成部分。如果你已经使用了这个 Toolbox,则无需再单独安装与配置本插件,Toolbox 已替你完成这一切。

在 hardhat.config.ts 中注册

以 Hardhat 3 的配置风格为例,在hardhat.config.ts中导入插件并加入plugins数组:

import { defineConfig } from "hardhat/config"; import hardhatIgnitionEthers from "@nomicfoundation/hardhat-ignition-ethers"; export default defineConfig({ plugins: [hardhatIgnitionEthers], });

配置完成后,插件会在每次创建网络连接(network connection)时,通过newConnectionhook 将ignition属性注入到连接对象上(src/internal/hook-handlers/network.ts)。类型层面则通过模块增强在hardhat/types/networkNetworkConnection接口上声明ignition: EthersIgnitionHelper(src/type-extensions.ts),因此 TypeScript 用户在脚本与测试中都能获得完整的类型提示。

一个值得注意的约束:若同一连接上已有其他 Ignition 扩展插件注入过ignition属性(例如 viem 版本),hook 会抛出ONLY_ONE_IGNITION_EXTENSION_PLUGIN_ALLOWED错误,保证同一网络连接上只能存在一种 Ignition 结果类型扩展,避免 ethers 与 viem 两种结果模型相互冲突。

核心用法:connection.ignition.deploy()

插件为每个网络连接添加ignition属性,其核心方法为deploy。官方 README 给出的最小用法如下:

import { network } from "hardhat"; import Counter from "../ignition/modules/Counter.js"; const { ignition } = await network.create(); const { counter } = await ignition.deploy(Counter); await counter.inc(); console.log(await counter.x());

拆解这段代码的执行语义:

  1. network.create()创建一条网络连接,返回的ignition即插件注入的EthersIgnitionHelper
  2. ignition.deploy(Counter)部署 Ignition 模块,并把模块return的每个合约 future 转成对应的 ethersContract实例,解构出counter
  3. 之后counter.inc()counter.x()就是标准的 ethers 合约调用——部署与交互共用同一套对象模型,无需再手动用地址getContractAt二次接线。

模块CounterbuildModule定义,例如:

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("Counter", (m) => { const counter = m.contract("Counter"); return { counter }; });

仓库示例项目中的 Apollo.ts 展示了带构造参数与链上调用的完整模块形态:

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("Apollo", (m) => { const apollo = m.contract("Rocket", ["Saturn V"]); m.call(apollo, "launch", []); return { apollo }; });

deploy 方法的完整签名与选项

EthersIgnitionHelper.deploy的完整签名定义在 src/types.ts:

deploy<ModuleIdT, ContractNameT, IgnitionModuleResultsT, StrategyT = "basic">( ignitionModule: IgnitionModule<ModuleIdT, ContractNameT, IgnitionModuleResultsT>, options?: { parameters?: DeploymentParameters | string; // 部署参数对象,或指向参数 JSON 文件的路径字符串 config?: Partial<DeployConfig>; // 本次部署的配置覆盖 defaultSender?: string; // 部署交易的默认发送者地址 strategy?: StrategyT; // 部署策略名,默认 "basic" strategyConfig?: StrategyConfig[StrategyT]; // 策略专属配置 deploymentId?: string; // 手动指定部署 ID,覆盖自动生成值 displayUi?: boolean; // 是否显示 Pretty 部署进度 UI }, ): Promise<IgnitionModuleResultsTToEthersContracts<ContractNameT, IgnitionModuleResultsT>>;

各选项的实际处理逻辑,可以在核心实现 src/internal/ethers-ignition-helper.ts 中逐一对号入座:

  • parameters:传入字符串时,插件会调用readDeploymentParameters从 JSON 文件加载参数(L185-L191);传入对象则直接使用,缺省为{}
  • config / maxRetries / retryIntervalgetResolvedConfig会把构造时注入的全局配置(来自hardhatConfig.ignition)与本次config合并;若maxRetriesretryInterval未在本次配置中显式给出,还会回落到网络配置networkConfig.ignition上的同名选项(L193-L207)。gas 相关上限maxFeePerGasLimitmaxPriorityFeePerGas也取自网络配置。
  • strategy / strategyConfig:策略名缺省为"basic";若未提供strategyConfig,会从hardhatConfig.ignition?.strategyConfig?.[strategyName]读取全局策略配置(L323-L340)。
  • deploymentId:缺省时由resolveDeploymentId(givenDeploymentId, chainId)结合当前链 ID 生成。只有在网络类型为edr-simulated(模拟网络)时部署目录为undefined(即不做持久化),其余情况部署状态会写入<paths.ignition>/deployments/<deploymentId>目录(L161-L170),这正是 Ignition 失败续跑与状态复用能力的基础。
  • displayUi:为true时创建PrettyEventHandler并临时注册用户中断(userInterruptions)处理钩子,部署结束后自动注销(L172-L183)。

返回值:从部署结果到 ethers 合约实例

deploy的类型定义保证了返回结构的精确性(src/types.ts):模块results中每个合约部署 future(NamedArtifactContractDeploymentFuture)或合约地址 future(NamedArtifactContractAtFuture,都会被映射为对应合约名的 ethers 合约类型;其他类型的 future 则退化为通用的Contract

运行时转换发生在#toEthersContracts#getContract两个私有方法中(ethers-ignition-helper.ts#L264-L321):

  • 遍历ignitionModule.results中的每个 future,从部署结果result.contracts[future.id]取实际地址;
  • 若 future 携带内联 artifact(例如外部加载的合约 artifact),则用其abi直接getContractAt(abi, address)
  • 否则按合约名getContractAt(contractName, address),由hardhat-ethers负责从 artifacts 解析 ABI 并实例化。

测试文件 deploy-with-ethers-result.ts 用真实 fixture 验证了这些行为:m.contract("Foo")部署、m.contractAt("Foo", foo)地址引用、外部 artifact 加载等场景,返回的实例都能直接调用x()isFoo()isExternallyLoaded()等合约方法,且类型系统能区分不同合约的方法集(result.foo.isBarundefined)。

并发安全:同一连接上一次只能有一个部署在途

实现中使用布尔互斥锁#mutex保护deploy:若并发调用,直接抛出IGNITION.DEPLOY.ALREADY_IN_PROGRESS错误;若首次部署失败(如合约 artifact 不存在),finally中会释放锁,允许随后再次部署(ethers-ignition-helper.ts#L126-L132)。对应测试见 deploy-with-ethers-result.ts#L107-L161。

惰性加载:LazyEthersIgnitionHelper

hook 注入的连接属性是LazyEthersIgnitionHelper(hook-handlers/network.ts#L27-L124),它把真正的实现EthersIgnitionHelperImpl推迟到首次deploy时才await import并缓存实例。这样做的收益是:创建网络连接时不触发额外模块加载,同时所有调用共享同一个实现实例,避免并发调用各持一份状态导致的竞争问题。

与 Toolbox 的关系及版本前提

根据 README,本插件已被@nomicfoundation/hardhat-toolbox-mocha-ethers打包收录。该 Toolbox 的src/index.ts(packages/hardhat-toolbox-mocha-ethers/src/index.ts)聚合了 mocha 测试、ethers、Ignition 等常用组件,使用 Toolbox 的项目开箱即得connection.ignition

总结

hardhat-ignition-ethers的价值在于消除了部署与交互之间的类型鸿沟:用 Ignition 声明部署拓扑、用 ethers 的结果对象直接操作合约,二者通过一个network连接上的ignition属性统一起来。本文覆盖了它的安装配置、deploy方法全参数、返回值类型映射、并发保护与惰性加载机制,并给出了 fixture 模块 与 测试用例 作为可运行的参考起点。若你的脚本与测试已基于 ethers.js,那么在 Hardhat 3 项目中它就是衔接 Ignition 部署引擎的最直接方案。

【免费下载链接】hardhatHardhat is a development environment to compile, deploy, test, and debug your Ethereum software.项目地址: https://gitcode.com/GitHub_Trending/ha/hardhat

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询