5月28日 05:32

Hardhat Ignition 是什么?声明式部署智能合约实战指南

部署智能合约是 Web3 开发里最让人头大的环节之一——手动跑脚本、记地址、处理依赖、一旦中断就得从头来。Hardhat Ignition 就是来解决这些问题的:它用声明式的方式定义部署逻辑,自动管理状态和依赖,部署中断了能接着跑,不用推倒重来。

为什么需要 Hardhat Ignition

传统的部署方式是写一个 JavaScript 脚本,按顺序调用合约的 deploy 方法。问题很明显:

  • 不可恢复:脚本跑到一半失败,已部署的合约地址可能丢,重来一遍又浪费 gas
  • 依赖混乱:合约 B 依赖合约 A 的地址,手动传递容易出错
  • 无法并行:多个互不依赖的合约只能串行部署,浪费时间

Hardhat Ignition 的思路完全不同——你只声明"我要部署什么",执行顺序、并行优化、状态管理全部交给 Ignition 处理。这跟 Terraform 管理基础设施的理念很像:描述期望状态,而不是写操作步骤。

和社区插件 hardhat-deploy 相比,Ignition 是 Nomic Foundation(Hardhat 团队)的官方方案,在 Hardhat 3 中已默认集成。hardhat-deploy 已经进入维护模式,官方也提供了迁移指南,新项目建议直接用 Ignition。

安装和配置

在已有的 Hardhat 项目中安装 Ignition 插件:

bash
# 使用 viem(推荐,Hardhat 3 默认) npm add --save-dev @nomicfoundation/hardhat-ignition-viem # 或使用 ethers npm add --save-dev @nomicfoundation/hardhat-ignition-ethers

然后在 hardhat.config.ts 中引入:

typescript
import { defineConfig } from "hardhat/config"; import hardhatIgnitionViemPlugin from "@nomicfoundation/hardhat-ignition-viem"; export default defineConfig({ plugins: [hardhatIgnitionViemPlugin], solidity: "0.8.28", });

注意一个常见坑:ethers 和 viem 插件不能同时安装,否则会报 HHE10119 错误。选一个就行。

创建第一个 Ignition 模块

Ignition 的核心概念是模块(Module)——一个模块定义一组合约实例和操作,类似一个部署蓝图。

先建好目录结构:

bash
mkdir -p ignition/modules

写一个最简单的模块,部署一个 ERC20 代币:

javascript
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("TokenModule", (m) => { const token = m.contract("MyToken", ["MyToken", "MTK", 18]); return { token }; });

buildModule 接收模块名和一个回调函数,回调里的 m 就是模块上下文。m.contract() 声明要部署的合约,第二个参数是构造函数参数。return 出去的对象可以在其他模块中引用。

参数化部署

硬编码参数不灵活,用 m.getParameter() 让部署时可配置:

javascript
export default buildModule("TokenModule", (m) => { const name = m.getParameter("name", "MyToken"); const symbol = m.getParameter("symbol", "MTK"); const token = m.contract("MyToken", [name, symbol, 18]); return { token }; });

第二个参数是默认值。部署时通过 --parameters 覆盖:

bash
npx hardhat ignition deploy ignition/modules/TokenModule.js --parameters name:CustomToken,symbol:CTK

多合约依赖和交互

真实项目中合约之间往往有依赖关系。比如一个代币销售合约需要引用代币合约的地址:

javascript
export default buildModule("DAppModule", (m) => { const token = m.contract("MyToken", ["MyToken", "MTK", 18]); const sale = m.contract("TokenSale", [token]); // 部署后调用 token 合约的方法 m.call(token, "transferOwnership", [sale]); return { token, sale }; });

Ignition 会自动分析依赖关系——TokenSale 的构造函数需要 token,所以 Ignition 保证先部署 MyToken,拿到地址后再部署 TokenSale。m.call() 会在两个合约都部署完成后执行。

如果多个合约互不依赖,Ignition 会并行部署,省时间。

引用已部署的合约

有时你需要跟链上已经存在的合约交互,不需要重新部署:

javascript
export default buildModule("InteractModule", (m) => { const existingToken = m.contractAt("MyToken", "0x1234..."); const sale = m.contract("TokenSale", [existingToken]); return { sale }; });

m.contractAt() 创建一个指向已有合约的引用,后续可以传给其他合约的构造函数或 m.call()

执行部署

bash
# 部署到本地 Hardhat 网络 npx hardhat ignition deploy ignition/modules/TokenModule.js # 部署到测试网 npx hardhat ignition deploy ignition/modules/TokenModule.js --network sepolia # 部署并验证合约源码 npx hardhat ignition deploy ignition/modules/TokenModule.js --verify

部署前可以先预览执行计划:

bash
npx hardhat ignition plan ignition/modules/TokenModule.js

Ignition 会列出所有将要执行的步骤及其依赖关系,确认无误再部署。

部署结果默认保存在 ignition/deployments/ 目录下,包含合约地址、ABI 和交易哈希。

增量部署和错误恢复

这是 Ignition 最实用的特性。假设你有一个包含 5 个合约的模块,部署到第 3 个时网络超时了:

  • 传统脚本:要么从头跑(前两个合约重新部署,浪费 gas),要么手动记录已部署的地址然后改脚本跳过
  • Ignition:直接重新运行同一条命令,Ignition 检测到前两个合约已部署,跳过它们,从第 3 个继续

这个特性基于 Ignition 的状态追踪机制——每次成功执行一个 Future(部署合约、调用方法都是一个 Future),状态就会被持久化。中断后重启,Ignition 读取状态跳过已完成的部分。

注意:部署 ID 只能包含字母数字、短横线和下划线,否则会报 HHE10108 错误。

常见踩坑和排错

"Unrecognized task 'ignition'"

说明 Ignition 插件没正确加载。检查 hardhat.config.ts 是否 import 了插件,node_modules 里是否安装了对应包。

验证失败

部署时加 --verify 需要配置 Etherscan API key。在 hardhat.config.ts 中添加:

javascript
etherscan: { apiKey: "YOUR_ETHERSCAN_API_KEY" }

合约找不到

先确认合约已编译(npx hardhat compile),如果修改了合约内容但部署结果没变,运行 npx hardhat clean 清除缓存后重试。

链 ID 变更

Ignition 记录了每个部署对应的链 ID。如果你把同一个部署目录指向了不同的链,会报 HHE10900 错误。解决方案是删除 ignition/deployments/ 下对应的部署记录,或者指定不同的部署 ID。

从 hardhat-deploy 迁移

如果你之前用的是 hardhat-deploy,迁移步骤:

  1. 卸载 hardhat-deploy
  2. 安装 @nomicfoundation/hardhat-ignition-ethersviem 版本
  3. deploy/ 目录下的部署脚本改写为 Ignition 模块格式(buildModule + m.contract
  4. 更新 hardhat.config.ts 中的插件引用

核心改动是把命令式的部署脚本(deploy() 函数)改成声明式的模块定义。逻辑上等价,但 Ignition 版本由框架管理执行顺序和状态。

什么时候该用 Ignition

  • 多合约系统,合约间有依赖关系 → 用 Ignition,自动管理部署顺序
  • 需要在多个网络(本地/测试网/主网)反复部署 → 用 Ignition,增量部署省 gas
  • 只部署一个简单合约 → 传统脚本也够用,但 Ignition 也不复杂,用起来一样简单
  • 团队协作 → Ignition 的声明式模块比脚本更好维护,部署逻辑不会因为谁改了脚本就出问题
标签:Hardhat