关于 hardhat-toolbox-viem
面向使用 viem 的 Hardhat 项目的技能,适用于编写 TypeScript 合约测试的开发者,涵盖类型化合约交互和以太坊专用断言。
主要功能
- 01类型化 viem 客户端
- 02类型化合约部署与查找
- 03基于 fixture 的部署
- 04以太坊回滚与事件断言
适用场景
- 01在 Hardhat 测试中部署合约
- 02检查合约回滚和事件
- 03编写类型安全的合约交互
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ nomicfoundation/ hardhat-skills --skill hardhat-toolbox-viem安装后在 Claude Code 中输入/hardhat-toolbox-viem确认已加载。
详细介绍
Hardhat 工具箱:viem
此技能基于核心 hardhat 技能。请先加载该技能,了解测试组织方式、network.create() 的结构、networkHelpers、fixture 以及类型检查工作流。以下内容都依赖于 network.create() 返回的连接:
import { network } from "hardhat";
import { describe, it } from "node:test";
import assert from "node:assert/strict";
describe("Counter", async function () {
const { viem, networkHelpers } = await network.create();
// ...
});
viem:客户端和合约交互
// Clients
const publicClient = await viem.getPublicClient();
const [owner, alice, bob] = await viem.getWalletClients(); // default accounts
const testClient = await viem.getTestClient(); // dev-only operations
// Deploy a contract, returns a fully typed instance
const counter = await viem.deployContract("Counter");
// Read state (return type inferred from ABI)
const value = await counter.read.x();
// Write transactions. Args and options are type-checked against the ABI at
// compile time; passing wrong types, wrong argument counts, or `value` on a
// non-payable function is a TypeScript error
await counter.write.inc();
await counter.write.inc({ account: alice.account }); // different sender
await counter.write.incBy([3n]); // with args
await counter.write.deposit({ value: 10n ** 18n }); // no parameters and payable
// Attach to an already-deployed contract
const existing = await viem.getContractAt("Counter", "0xabc...");
避免使用 walletClient.writeContract 与合约交互——它没有 ABI 类型支持,因此错误的参数也能通过。优先使用 viem.deployContract 或 viem.getContractAt 返回的类型化实例。
在 loadFixture 设置函数中(外围模式请参见 hardhat 技能),viem.deployContract 是标准的部署步骤:
async function deployCounter() {
const counter = await viem.deployContract("Counter");
return { counter };
}
const { counter } = await networkHelpers.loadFixture(deployCounter);
viem.assertions:Ethereum 专用断言
使用 viem.assertions 进行合约专用检查。将未等待的交易 Promise 作为第一个参数传入:
// Reverts
await viem.assertions.revert(counter.write.inc({ account: banned }));
await viem.assertions.revertWith(
counter.write.inc({ account: banned }),
"Not authorized",
);
await viem.assertions.revertWithCustomError(
counter.write.inc({ account: banned }),
counter,
"Unauthorized",
);
await viem.assertions.revertWithCustomErrorWithArgs(
counter.write.inc({ account: banned }),
counter,
"Unauthorized",
[banned],
);
// Events
await viem.assertions.emit(counter.write.inc(), counter, "Increment");
await viem.assertions.emitWithArgs(counter.write.inc(), counter, "Increment", [
1n,
]);
// ETH balance changes (positive = received, negative = spent, before gas)
await viem.assertions.balancesHaveChanged(game.write.claim(), {
[winner]: PRIZE,
[loser]: -STAKE,
});
*WithArgs 匹配器(revertWithCustomErrorWithArgs 和 emitWithArgs)允许在任意参数位置传入 (value) => boolean 谓词,也可以与具体值搭配使用。该插件还提供了 anyValue 助手,用于不关心的参数位置:
import { anyValue } from "@nomicfoundation/hardhat-toolbox-viem/predicates";
// Inline predicate at any arg position. Useful for ranges or computed conditions.
await viem.assertions.revertWithCustomErrorWithArgs(
contract.write.failing(),
contract,
"BadValue",
[(n: bigint) => n > 100n, "another error arg"],
);
await viem.assertions.emitWithArgs(
counter.write.incBy([3n]),
counter,
"Increment",
[(by: bigint) => by >= 1n],
);
// `anyValue` matches anything — handy for fields you don't care about.
await viem.assertions.revertWithCustomErrorWithArgs(
contract.write.failing(),
contract,
"BadValue",
[anyValue, "another error arg"],
);
对于普通的 TypeScript 断言(相等性、数组、类型),请使用 node:assert/strict。
评论
支持 Markdown