Hardhat + Viem
We’re happy to announce the newest addition to our official plugins — hardhat-viem. This plugin smoothly integrates the Viem library into…
Our newest addition to our official plugins
We’re happy to announce the newest addition to our official plugins — hardhat-viem
. This plugin smoothly integrates the Viem library into Hardhat projects for a nicer developer experience. Alongside this, we’re introducing a Viem-based alternative to Hardhat Toolbox.
Why Viem?
Viem is a lightweight alternative to ethers.js developed by the team behind Wagmi. It’s built around low-level, stateless functions, which results in an explicit and composable API. It also leverages advanced TypeScript features to offer a great developer experience. You can learn more about its philosophy here.
Why hardhat-viem?
The hardhat-viem
plugin provides you with helpers that simplify common use cases and seamlessly integrates with your project’s contracts, eliminating the need to duplicate ABIs. Viem's focus on composability is powerful, but it can lead to verbose code in certain scenarios. For example, with our new hardhat-viem
plugin one can deploy a contract like this:
const initialSupply = 1000n * 10n ** 18n;
const token = await hre.viem.deployContract("Token", [initialSupply]);
In contrast, without the plugin one would have to do this:
const tokenAbi = [{
inputs: [],
name: "totalSupply",
outputs: [{ type: "uint256" }],
stateMutability: "view",
type: "function",
}, /* ... */] as const;
const hash = await walletClient.deployContract({
abi: tokenAbi,
bytecode: "0x...",
args: [1000n * 10n ** 18n],
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const token = getContract({
address: receipt.contractAddress!,
publicClient,
walletClient,
abi: tokenAbi,
});
The hardhat-viem
plugin shields you from such complexities for common use cases.
Quick start
To create a new Hardhat project with Viem, initialize a project as you normally do, but select the “Create a TypeScript project (with Viem)” option.
You can also try hardhat-viem
in an existing project, even if it uses hardhat-ethers
, since both plugins are compatible. To do this, just install the @nomicfoundation/hardhat-viem
package and add it to your config.
Clients
Viem provides a set of interfaces to interact with the blockchain. hardhat-viem
wraps and auto-configures these based on your Hardhat project settings for a seamless experience.
These clients are tailored for specific interactions:
- Public Client fetches node information from the “public” JSON-RPC API.
- Wallet Client interacts with Ethereum Accounts for tasks like transactions and message signing.
- Test Client performs actions that are only available in development nodes.
You can access clients via hre.viem
. Read our documentation to learn more about the HRE. Find below an example of how to use the public and wallet clients:
- Create a
scripts/clients.ts
inside your project directory. - Add this code to
scripts/clients.ts
:
import { parseEther, formatEther } from "viem";
import hre from "hardhat";
async function main() {
const [bobWalletClient, aliceWalletClient] =
await hre.viem.getWalletClients();
const publicClient = await hre.viem.getPublicClient();
const bobBalance = await publicClient.getBalance({
address: bobWalletClient.account.address,
});
console.log(
`Balance of ${bobWalletClient.account.address}: ${formatEther(
bobBalance
)} ETH`
);
const hash = await bobWalletClient.sendTransaction({
to: aliceWalletClient.account.address,
value: parseEther("1"),
});
await publicClient.waitForTransactionReceipt({ hash });
}
main()
.then(() => process.exit())
.catch((error) => {
console.error(error);
process.exit(1);
});
3. Run npx hardhat run scripts/clients.ts
.
For more detailed documentation on clients, visit the hardhat-viem plugin site and Viem’s official site.
Contracts
Viem also provides functionality for interacting with contracts, and hardhat-viem
provides wrappers for the most useful methods. Plus, it generates types for your contracts, enhancing type-checking and IDE suggestions.
Use the hre.viem
object to get these helpers, similar to how clients are used. The next example shows how to get a contract instance and call one of its methods:
- Create a
MyToken.sol
file inside your project’scontracts
directory:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract MyToken {
uint256 public totalSupply;
constructor(uint256 _initialSupply) {
totalSupply = _initialSupply;
}
function increaseSupply(uint256 _amount) public {
require(_amount > 0, "Amount must be greater than 0");
totalSupply += _amount;
}
function getCurrentSupply() public view returns (uint256) {
return totalSupply;
}
}
2. Run npx hardhat compile
to compile your contracts and produce types in the artifacts
directory.
3. Create a contracts.ts
inside the scripts
directory:
import hre from "hardhat";
async function main() {
const myToken = await hre.viem.deployContract("MyToken", [1_000_000n]);
const initialSupply = await myToken.read.getCurrentSupply();
console.log(`Initial supply of MyToken: ${initialSupply}`);
await myToken.write.increaseSupply([500_000n]);
// increaseSupply sends a tx, so we need to wait for it to be mined
const publicClient = await hre.viem.getPublicClient();
await publicClient.waitForTransactionReceipt({ hash });
const newSupply = await myToken.read.getCurrentSupply();
console.log(`New supply of MyToken: ${newSupply}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
4. Open your terminal and run npx hardhat run scripts/contracts.ts
. This will deploy the MyToken
contract, call the increaseSupply()
function, and display the new supply in your terminal.
Testing
In this example, we’ll test the MyToken
contract, covering scenarios like supply increase and expected operation reverts.
- Create a
test/my-token.ts
file:
import hre from "hardhat";
import { assert, expect } from "chai";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers";
// A deployment function to set up the initial state
const deploy = async () => {
const myToken = await hre.viem.deployContract("MyToken", [1_000_000n]);
return { myToken };
};
describe("MyToken Contract Tests", function () {
it("should increase supply correctly", async function () {
// Load the contract instance using the deployment function
const { myToken } = await loadFixture(deploy);
// Get the initial supply
const initialSupply = await myToken.read.getCurrentSupply();
// Increase the supply
await myToken.write.increaseSupply([500_000n]);
// Get the new supply after the increase
const newSupply = await myToken.read.getCurrentSupply();
// Assert that the supply increased as expected
assert.equal(initialSupply + 500_000n, newSupply);
});
it("should revert when increasing supply by less than 1", async function () {
// Load the contract instance using the deployment function
const { myToken } = await loadFixture(deploy);
// Attempt to increase supply by 0 (which should fail)
await expect(myToken.write.increaseSupply([0n])).to.be.rejectedWith(
"Amount must be greater than 0"
);
});
});
2. Open your terminal and run npx hardhat test
to run these tests.
Learn more
For more details on how to use our new plugin, check its documentation. And don’t forget to star Hardhat on GitHub!