#Using Viem
# Overview
Most of this documentation assumes that you are using ethers as your connection library, but you can also use Hardhat with Viem, a more lightweight and type-safe alternative. This guide explains how to setup a project that uses the Viem-based Toolbox instead of the main one.
# 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); });
-
Run
npx hardhat run scripts/clients.ts
.
For more detailed documentation on clients, you can 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.23; 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; } }
-
Run
npx hardhat compile
to compile your contracts and produce types in theartifacts
directory. -
Create a
contracts.ts
inside thescripts
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}`); const hash = 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); });
-
Open your terminal and run
npx hardhat run scripts/contracts.ts
. This will deploy theMyToken
contract, call theincreaseSupply()
function, and display the new supply in your terminal.
#Contract Type Generation
The proper types for each contract are generated during compilation. These types are used to overload the hardhat-viem
types and improve type checking and suggestions. For example, if you copy and paste the following code at the end of the main()
function of scripts/contracts.ts
, TypeScript would highlight it as an error:
// The amount is required as a parameter
// TS Error: Expected 1-2 arguments, but got 0.
await myToken.write.increaseSupply();
// There is no setSupply function in the MyToken contract
// TS Error: Property 'setSupply' does not exist on type...
const tokenPrice = await myToken.write.setSupply([5000000n]);
// The first argument of the constructor arguments is expected to be an bigint
// TS Error: No overload matches this call.
const myToken2 = await hre.viem.deployContract("MyToken", ["1000000"]);
If you want to learn more about working with contracts, you can visit the hardhat-viem
plugin site and Viem's official site.
#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" ); }); });
-
Open your terminal and run
npx hardhat test
to run these tests.
#Managing Types and Version Stability
Viem adopts a particular approach to handling changes in types within their codebase. They consider these changes as non-breaking and typically release them as patch version updates. This approach has implications for users of both hardhat-viem
and hardhat-toolbox-viem
.
Option 1: Pinning Versions (Recommended for Stability)
Viem recommends pinning their package version in your project. However, it's important to note that if you choose to follow this recommendation, you should also pin the versions of hardhat-viem
and hardhat-toolbox-viem
. This ensures version compatibility and stability for your project. However, it's worth mentioning that by pinning versions, you may miss out on potential improvements and updates shipped with our plugins.
To pin the versions, follow these steps:
-
Explicitly install
hardhat-viem
,hardhat-toolbox-viem
, andviem
. This will add these dependencies to yourpackage.json
file:npm i @nomicfoundation/hardhat-toolbox-viem @nomicfoundation/hardhat-viem viem
-
Open your
package.json
file and remove the caret character (^
) from the versions of the three packages:{ "dependencies": { "@nomicfoundation/hardhat-toolbox-viem": "X.Y.Z", "@nomicfoundation/hardhat-viem": "X.Y.Z", "viem": "X.Y.Z" } }
Option 2: Stay Updated (Recommended for Features)
Alternatively, you can choose not to pin versions and remain aware that your project's types may break if a newer version of viem
is installed. By opting for this approach, you won't miss out on important upgrades and features, but you might need to address type errors occasionally.
Both options have their merits, and your choice depends on whether you prioritize stability or staying up-to-date with the latest features and improvements.