hardhat-zksync-ethers

A Hardhat plugin that integrates zksync-ethers into the Hardhat Runtime Environment, providing L1 and L2 providers, contract deployment helpers, and wallet management capabilities.

Description

The hardhat-zksync-ethers plugin integrates zksync-ethers into the Hardhat Runtime Environment, providing:

  • L1 and L2 providers for interacting with both networks
  • Contract deployment helpers with factory dependency support
  • Wallet management capabilities including rich accounts support
  • Contract interaction utilities

Resources:

Ensure you are using the correct version of the plugin with ethers:
  • For plugin version <1.0.0:
    • Compatible with ethers v5
  • For plugin version ≥1.0.0:
    • Compatible with ethers v6 (⭐ Recommended)
Examples in this documentation are for plugin version >=1.0.0

Installation

Prerequisites

  • Node.js version 18 or higher
  • Hardhat version 2.16.0 or higher

Setup

Install the plugin and its dependencies:

yarn add -D @matterlabs/hardhat-zksync-ethers zksync-ethers ethers

Import the package in your hardhat.config.ts file:

import "@matterlabs/hardhat-zksync-ethers";

Configuration

The plugin doesn't require any additional configuration options.

import "@matterlabs/hardhat-zksync-ethers";

import { HardhatUserConfig } from "hardhat/config";

const config: HardhatUserConfig = {
  // Your existing hardhat config
};

export default config;

Additions

Added Tasks

This plugin does not add any additional Hardhat tasks.

Added HRE Extensions

The plugin adds a zksyncEthers and ethers object to the Hardhat Runtime Environment with the following helpers:

HelperDescription
providerL2()Returns a zk.Provider for L2, automatically connected to the selected network
providerL1()Returns an ethers.Provider for L1, automatically connected to the selected network
getWallet(privateKeyOrIndex?: string | number)Returns zk.Wallet for the given private key or index
getWallets()Returns all wallets of type zk.Wallet
getContractFactory(name: string, wallet?: zk.Wallet, deploymentType?: DeploymentType)Returns a zk.ContractFactory for provided artifact name
getContractFactoryFromArtifact(artifact: ZkSyncArtifact, wallet?: zk.Wallet, deploymentType?: DeploymentType)Returns a zk.ContractFactory for provided artifact
getContractAt(nameOrAbi: string | any[], address: string | Address, wallet?: zk.Wallet)Returns zk.Contract for provided artifact name or abi and address
getContractAtFromArtifact(artifact: ZkSyncArtifact, address: string, wallet?: zk.Wallet)Returns zk.Contract for provided artifact and address
getImpersonatedSigner(address: string)Impersonates zk.Signer from address
loadArtifact(name: string)Loads ZkSyncArtifact from contract name
extractFactoryDeps(artifact: ZkSyncArtifact)Extracts factory deps from artifact
deployContract(artifact: ZkSyncArtifact, constructorArguments: any[], wallet?: zk.Wallet, overrides?: ethers.Overrides, additionalFactoryDeps?: ethers.BytesLike[])Deploys contract with factory dependencies support
  • When wallet?: zk.Wallet is omitted and the network is set to local, the default wallet is assigned to the first rich account.
  • If the Hardhat configuration specifies an accounts object for the selected network, the default wallet is chosen from that instead.
  • The default value for deploymentType?: DeploymentType is create unless explicitly set otherwise.

hre.ethers Object

The plugin also populates the hre.ethers object, which dynamically switches between zksync-ethers and regular ethers based on the selected network:

  • When a ZKSync network is selected, hre.ethers will use zksync-ethers and its helpers
  • When a regular Ethereum network is selected, hre.ethers will use standard ethers and hardhat-ethers helpers

This allows you to write network-agnostic code that automatically uses the appropriate implementation:

// This will use zksync-ethers on ZKSync networks and regular ethers on other networks
const provider = hre.ethers.provider;
const wallet = hre.ethers.getWallet();
const contract = await hre.ethers.getContractFactory("MyContract");
Make sure to only import hardhat-zksync-ethers and not hardhat-ethers to avoid conflicts in the hre.ethers object population.

Interfaces

interface FactoryDeps {
  [contractHash: string]: string;
}

interface ZkSyncArtifact extends Artifact {
  factoryDeps: FactoryDeps;
  sourceMapping: string;
}

interface FactoryOptions {
  wallet?: zk.Wallet;
}

Usage

The plugin can be used in tasks, scripts, and tests. Here are some examples:

Tasks

task("getFeeData", "Returns a fee data.").setAction(async (hre) => {
  const feeDataL2 = await hre.zksyncEthers.providerL2.getFeeData();
  const feeDataL1 = await this.env.zksyncEthers.providerL1.getFeeData();

  return { feeDataL2, feeDataL1 };
});

Scripts

export default async function (hre: HardhatRuntimeEnvironment) {
  console.info(chalk.yellow(`Running deploy`));

  // automatically connected to the selected network
  const gasPrice = await hre.zksyncEthers.providerL2.send("eth_gasPrice", []);

  // getContractFactory with default wallet, deploy contract and set new greeting message
  const greeterFactory = await hre.zksyncEthers.getContractFactory("Greeter");
  const greeter = await greeterFactory.deploy("Hello, world!");

  console.info(chalk.green(`Greeter deployed to: ${await greeter.getAddress()}`));

  console.info(chalk.green(`Greeter greeting set to: ${await greeter.greet()}`));

  const tx = await greeter.setGreeting("Hello, world again!");
  await tx.wait();
  console.info(chalk.green(`Greeter greeting set to: ${await greeter.greet()}`));

  // deploy with provided wallet using loadArtifact and deployContract
  const wallet = await hre.zksyncEthers.getWallet("0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110");

  // deposit ETH from L1 to L2 to cover costs of deployment
  const depositHandle = await wallet.deposit({
    to: wallet.address,
    token: utils.ETH_ADDRESS,
    amount: ethers.parseEther("0.001"),
  });
  await depositHandle.wait();

  const artifact = await hre.zksyncEthers.loadArtifact("Greeter");
  const greets = await hre.zksyncEthers.deployContract(artifact, ["Hello, world!"], wallet);
  console.info(chalk.green(`Greeter deployed to: ${await greets.getAddress()}`));
  console.info(chalk.green(`Greeter greeting set to: ${await greets.greet()}`));
}

Tests

    describe('Deploy Greeter', async function () {
        it('deploy greeter and validate greet method ', async function () {
              const greets = await hre.ethers.deployContract('Greeter', ["Hello, world!"]);
              expect(await greets.greet()).to.equal("Hello, world!");
        });
    });

Troubleshooting

Connection Issues

Problem: Provider Connection Issues Solution:

  • Ensure your network configuration in hardhat.config.ts is correct
  • Check if you have the correct RPC URLs set up
  • Verify network connectivity

Contract Deployment Failures

Problem: Deployment of contract fails Solution:

  • Make sure you have sufficient funds in your wallet
  • Verify contract bytecode and ABI are correct
  • Check if all required factory dependencies are included

TypeScript Configuration

Problem: TypeScript compilation errors or type issues Solution:

  • Ensure you're using the correct version of ethers (v6 for plugin version ≥1.0.0)
  • Check if all type definitions are properly imported
  • Update tsconfig.json with required compiler options

Plugin Conflicts

Problem: Multiple ethers implementations or conflicting providers Solution:

  • Make sure that only hardhat-zksync-ethers are imported in the hardhat config
  • Use the appropriate provider based on network:
    // For L2 operations
    const providerL2 = hre.zksyncEthers.providerL2();
    
    // For L1 operations
    const providerL1 = hre.zksyncEthers.providerL1();
    

For more help, please refer to the GitHub issues.


Made with ❤️ by the ZKsync Community