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 库之一,提供
Contract、Wallet、Provider等对象,是脚本与测试中与链上合约交互的标准方式。
该插件所做的,就是让deploy的返回值从 Ignition 原生的“部署结果”自动映射为对应合约 ABI 的 ethers 合约实例,开发者拿到手即可直接调用合约方法。插件包描述也印证了这一职责定位:“The Ethers extension to Hardhat Ignition”,其peerDependencies同时声明了对@nomicfoundation/hardhat-ignition、@nomicfoundation/hardhat-ethers、@nomicfoundation/ignition-core与ethers的依赖(见 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-ignition与hardhat-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/network的NetworkConnection接口上声明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());拆解这段代码的执行语义:
network.create()创建一条网络连接,返回的ignition即插件注入的EthersIgnitionHelper;ignition.deploy(Counter)部署 Ignition 模块,并把模块return的每个合约 future 转成对应的 ethersContract实例,解构出counter;- 之后
counter.inc()、counter.x()就是标准的 ethers 合约调用——部署与交互共用同一套对象模型,无需再手动用地址getContractAt二次接线。
模块Counter用buildModule定义,例如:
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 / retryInterval:
getResolvedConfig会把构造时注入的全局配置(来自hardhatConfig.ignition)与本次config合并;若maxRetries、retryInterval未在本次配置中显式给出,还会回落到网络配置networkConfig.ignition上的同名选项(L193-L207)。gas 相关上限maxFeePerGasLimit、maxPriorityFeePerGas也取自网络配置。 - 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.isBar为undefined)。
并发安全:同一连接上一次只能有一个部署在途
实现中使用布尔互斥锁#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),仅供参考