Getting started

Learn how to use Hardhat with ZKsync.
If you are using Windows, we strongly recommend you use Windows Subsystem for Linux (also known as WSL 2). You can use Hardhat and Hardhat ZKsync plugins without it, but it will work better if you use it.To install Node.js using WSL 2, please read this guide.

Hardhat is an Ethereum development environment, designed for easy smart contract development. One of its most prominent features is extendability: you can easily add new plugins to your hardhat project.

Along with the official plugins, there are other plugins from the community that you can use with ZKsync Era.

To learn more about Hardhat itself, check out the official documentation.

This tutorial shows you how to setup a ZKsync Era Solidity project with Hardhat using the ZKsync CLI.

If you are using Vyper, check out the Vyper plugin documentation or the vyper-example in GitHub!

Project setup

To create a new project run the zksync-cli create command, passing a project name:

Solidity project

npx zksync-cli create demo --template hardhat_solidity

Vyper project

npx zksync-cli create demo --template hardhat_vyper

This command creates a demo folder and clones a Hardhat template project inside it. The downloaded project is already configured and contains all the required plugins.

If you want to migrate an existing project, please check the project migration guide.

Hardhat configuration

The hardhat.config.ts file contains some ZKsync Era specific configurations:

Solidity project

import '@matterlabs/hardhat-zksync';

The zksolc block contains the minimal configuration for the compiler.

  zksolc: {
    version: 'latest', // Uses latest available in https://github.com/matter-labs/zksolc-bin
    settings: {},
  },

Vyper project

import '@nomiclabs/hardhat-vyper';
import '@matterlabs/hardhat-zksync-vyper';
import '@matterlabs/hardhat-zksync-node';
import '@matterlabs/hardhat-zksync-ethers';

The zkvyper block contains the minimal configuration for the compiler.

  zkvyper: {
    version: 'latest', // Uses latest available in https://github.com/matter-labs/zkvyper-bin
    settings: {},
  },

Network

The default network is set to ZKsyncEraSepolia, but mainnet and local test node networks are also configured.

  defaultNetwork: 'ZKsyncEraSepolia',
  networks: {
    ZKsyncEraSepolia: {
      url: 'https://sepolia.era.zksync.dev',
      ethNetwork: 'sepolia',
      zksync: true,
      verifyURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification',
    },
    ZKsyncEraMainnet: {
      url: 'https://mainnet.era.zksync.io',
      ethNetwork: 'mainnet',
      zksync: true,
      verifyURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification',
    },
    dockerizedNode: {
      url: 'http://localhost:3050',
      ethNetwork: 'http://localhost:8545',
      zksync: true,
    },
    anvilZKsync: {
      url: 'http://127.0.0.1:8011',
      ethNetwork: 'localhost', // in-memory node doesn't support eth node; removing this line will cause an error
      zksync: true,
    },
    hardhat: {
      zksync: true,
    },
  },
For local ZKsync testing, modify the default network name.
This template project includes a basic unit test in the /test folder that runs with the local-setup and can be executed with yarn test.

Set your Private Key

Rename .env.example to .env and set your private key:

WALLET_PRIVATE_KEY=YourPrivateKeyHere

Your private key will be used for paying the costs of deploying the smart contract.

Compile and deploy a contract

Smart contracts belong in the contracts folder. Both the Solidity and Vyper templates contain a simple Greeter contract.

1. To compile the contract, run

npm run compile

You'll see the following output:

Compiling contracts for ZKsync Era with zksolc v1.5.10 and zkvm-solc v0.8.28-1.0.1
Successfully compiled 1 Solidity file
// Successfully compiled 1 Vyper file - Vyper project
✨  Done in 1.09s.

The artifacts-zk and cache-zk folders appear in the root directory (instead of the regular Hardhat's artifacts and cache). These folders contain the compilation artifacts (including contract's ABIs) and compiler cache files.

The artifacts-zk and cache-zk folders are included in the .gitignore file.

In the scripts folder, the deploy.ts script shows an example of how to deploy the Greeter.sol/Greeter.vy contract.

// Script that deploys a given contract to a network
async function main() {
  const CONTRACT_NAME = 'Greeter';
  const ARGS = ['Hi there!'];
  console.log(`Deploying ${CONTRACT_NAME} contract to ${hre.network.name}`);
  const contract = await hre.ethers.deployContract(CONTRACT_NAME, ARGS, {});
  await contract.waitForDeployment();
  const contractAddress = await contract.getAddress();
  console.log(`${CONTRACT_NAME} deployed to ${contractAddress}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

2. To execute the deployment script run

npm run deploy

This script deploys the Greeting contract with the message "Hi there!" to ZKsync Sepolia Testnet.

You should see something like this:

Deploying Greeter contract to ZKsyncEraSepolia
Greeter was deployed to 0x46f1d2d8A16DBD8b47e9D61175a826ac667288Be4D1293a22E8

  Done in 12.69s.

Congratulations! You have deployed a smart contract project to ZKsync Sepolia Testnet with Hardhat 🎉

The Solidity template additionally contains a token and NFT contracts and deployment scripts.

Interact with the contract

The template project contains another script to interact with the contract.

  1. Enter the address of the deployed Greeter contract in the CONTRACT_ADDRESS variable of the interact.ts script:
    interact.ts
    // Script that interacts with a Greeter contract
    
    // Address of the contract to interact with
    const CONTRACT_ADDRESS = '';
    if (!CONTRACT_ADDRESS) throw '⛔️ Provide address of the contract to interact with!';
    
    async function main() {
      console.log(`Running script to interact with contract ${CONTRACT_ADDRESS}`);
    
      // Get the first signer
      const [signer] = await hre.ethers.getSigners();
    
      // Get the contract factory and deploy
      const Greeter = await hre.ethers.getContractFactory('Greeter');
      const greeterContract = await Greeter.connect(signer).attach(CONTRACT_ADDRESS);
    
      // Run contract read function
      const response = await greeterContract.greet();
      console.log(`Current message is: ${response}`);
    
      // Run contract write function
      const transaction = await greeterContract.setGreeting('Hello people!');
      console.log(`Transaction hash of setting new message: ${transaction.hash}`);
    
      // Wait until transaction is processed
      await transaction.wait();
    
      // Read message after transaction
      console.log(`The message now is: ${await greeterContract.greet()}`);
    }
    
    main()
      .then(() => process.exit(0))
      .catch((error) => {
        console.error(error);
        process.exit(1);
      });
    
  2. To execute the script, run:
    npm run interact
    

    The script will:
    • Retrieve the message from the contract by calling the greet() method.
    • Update the greeting message in the contract with the setGreeting() method.
    • Retrieve the message from the contract again.

    You should see something like this:
    Running script to interact with contract 0xaB387e2bf9068Ee39C76fdd16F80b2078Ff73F3D
    Current message is: Hi there!
    Transaction hash of setting new message: 0xe7ea1fa4041ff0d5a117d0d53c8d8934c4a9e1f4730f58ca2bae7389f428fbf2
    The message now is: Hello people!
    
      Done in 14.32s.
    

Learn more

Check the installation guide for instructions!

Made with ❤️ by the ZKsync Community