Hardhat + Viem

We’re happy to announce the newest addition to our official plugins — hardhat-viem. This plugin smoothly integrates the Viem library into…

Hardhat + Viem

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:

  1. Create a scripts/clients.ts inside your project directory.
  2. 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:

  1. Create a MyToken.sol file inside your project’s contracts 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.

  1. 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!