| name | hardhat-toolbox-viem |
| description | Use alongside the `hardhat` skill when the project depends on `@nomicfoundation/hardhat-toolbox-viem`. Covers the viem clients exposed on `network.create()`, contract interaction (`viem.deployContract`, `read`, `write`, `getContractAt`), and `viem.assertions` (revert / event / balance assertions). |
| metadata | {"package":"@nomicfoundation/hardhat-toolbox-viem"} |
Hardhat toolbox: viem
This skill builds on the core hardhat skill. Load that first for test organization, the network.create() shape, networkHelpers, fixtures, and the typechecking workflow. Everything below hangs off the connection returned by 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 and contract interaction
const publicClient = await viem.getPublicClient();
const [owner, alice, bob] = await viem.getWalletClients();
const testClient = await viem.getTestClient();
const counter = await viem.deployContract("Counter");
const value = await counter.read.x();
await counter.write.inc();
await counter.write.inc({ account: alice.account });
await counter.write.incBy([3n]);
await counter.write.deposit({ value: 10n ** 18n });
const existing = await viem.getContractAt("Counter", "0xabc...");
Avoid using walletClient.writeContract to interact with contracts — it has no ABI typing, so wrong args slip through. Prefer the typed instance returned by viem.deployContract or viem.getContractAt.
Inside a loadFixture setup function (see the hardhat skill for the surrounding pattern), viem.deployContract is the canonical deploy step:
async function deployCounter() {
const counter = await viem.deployContract("Counter");
return { counter };
}
const { counter } = await networkHelpers.loadFixture(deployCounter);
viem.assertions: Ethereum-specific assertions
Use viem.assertions for contract-specific checks. Pass the unawaited transaction promise as the first argument:
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],
);
await viem.assertions.emit(counter.write.inc(), counter, "Increment");
await viem.assertions.emitWithArgs(counter.write.inc(), counter, "Increment", [
1n,
]);
await viem.assertions.balancesHaveChanged(game.write.claim(), {
[winner]: PRIZE,
[loser]: -STAKE,
});
The *WithArgs matchers (revertWithCustomErrorWithArgs and emitWithArgs) accept a (value) => boolean predicate at any arg position, alongside concrete values. The plugin also ships an anyValue helper for positions you don't care about:
import { anyValue } from "@nomicfoundation/hardhat-toolbox-viem/predicates";
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],
);
await viem.assertions.revertWithCustomErrorWithArgs(
contract.write.failing(),
contract,
"BadValue",
[anyValue, "another error arg"],
);
For plain TypeScript assertions (equality, arrays, types), use node:assert/strict.