Deploy a contract with Hardhat

Hardhat is one of the popular smart contract development frameworks. It is the ZKFair’s preferred framework, and therefore used in the ZKFair as a default for deploying and automatically verifying smart contracts.

This document is a guide on how to deploy a smart contract on the ZKFair network using Hardhat.

Hardhat smart contract

  • mkdir <project-name>;cd <project-name>

  • Initialize a project with Hardhat: npx hardhat.

  • Next, (… To avoid failure … please go slow with this cli dialogue…),

    So then,

  • Press <ENTER> to set the project root.

  • Press <ENTER> again to accept addition of .gitignore.

  • Type n to reject installing sample project's dependencies.

    The idea here is to postpone installing dependencies to later steps due to a possible version-related bug.

  • Open the hardhat.config.js file and paste the below code:

    require("dotenv").config();
    require("@nomicfoundation/hardhat-toolbox");
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
    solidity: "0.8.9",
    paths: {
        artifacts: "./src",
    },
    networks: {
        zkEVM: {
        url: `https://testnet-rpc.zkfair.io`,
        accounts: [process.env.ACCOUNT_PRIVATE_KEY],
        },
    },
    };

    Note that a different path to artifacts is added so that the React app will be able to read the contract ABI within the src folder.

Add scripts

  • Create a new file, in the contracts folder, named Counter.sol: touch contracts/Counter.sol.

  • Copy the below code and paste it in the Counter contract code:

    //SPDX-License-Identifier: MIT
    pragma solidity ^0.8.9;
    
    contract Counter {
    uint256 currentCount = 0;
    
        function increment() public {
            currentCount = currentCount + 1;
        }
    
        function retrieve() public view returns (uint256){
            return currentCount;
        }
    }
  • Create a new file in the scripts folder deploy-counter.js: touch scripts/deploy-counter.js.

  • Add the code below to the deploy-counter.js file:

    const hre = require("hardhat");
    
    async function main() {
        const deployedContract = await hre.ethers.deployContract("Counter");
        await deployedContract.waitForDeployment();
        console.log(
            `Counter contract deployed to https://testnet-scan.zkfair.io/address/${deployedContract.target}`
        );
    }
    
    main().catch((error) => {
        console.error(error);
        process.exitCode = 1;
    });
  • Before compiling the contract, you need to install the toolbox. You may need to change directory to install outside the project. Use this command:

    npm install --save-dev @nomicfoundation/hardhat-toolbox
  • Compile your contract code (i.e., go back to the project root in the CLI):

    npx hardhat compile
  • Now run the scripts:

    npx hardhat run scripts/deploy-counter.js --network zkEVM

    ​Here’s an output example:

    Counter contract deployed to https://explorer.public.zkevm-test.net/address/0x5FbDB2315678afecb367f032d93F642f64180aa3

Update frontend

The next step is to turn Counter.sol into a dApp by importing the ethers and the Counter file, as well as logging the contract’s ABI.

  • Include the below code in the App.js file:

    import { ethers } from "ethers";
    import Counter from "./contracts/Counter.sol/Counter.json";
    const counterAddress = "your-contract-address"
    console.log(counterAddress, "Counter ABI: ", Counter.abi);
  • Update the counterAddress to your deployed address.

    • It is the hexadecimal number found at the tail-end of the output of the last npx hardhat run ... command and looks like this 0x5FbDB2315678afecb367f032d93F642f64180aa3.

    • It must be pasted in the App.js to replace your-contract-address. Be sure to use the deployed address from your own implementation!

  • Update frontend counter to read from blockchain. Include the below code in the App.js file:

    useEffect(() => {
        // declare the data fetching function
        const fetchCount = async () => {
        const data = await readCounterValue();
        return data;
        };
    
        fetchCount().catch(console.error);
    }, []);
    
    async function readCounterValue() {
        if (typeof window.ethereum !== "undefined") {
            const provider = new ethers.providers.Web3Provider(window.ethereum);
    
            console.log("provider", provider);
    
            const contract = new ethers.Contract(
                counterAddress,
                Counter.abi,
                provider
            );
    
            console.log("contract", contract);
    
            try {
                const data = await contract.retrieve();
                console.log(data);
                console.log("data: ", parseInt(data.toString()));
                setCount(parseInt(data.toString()));
            } catch (err) {
                console.log("Error: ", err);
                alert(
                    "Switch your MetaMask network to ZKFair Testnet and refresh this page!"
                );
            }
        }
    }
  • Also, to import useEffect, insert it like this:

    import { useState, useEffect } from "react";
  • To be able to track a loader, add this to your state:

    const [isLoading, setIsLoading] = useState(false);
    • This is within the App() function.

  • Let frontend counter write to the blockchain by adding the below requestAccount and updateCounter functions:

    async function requestAccount() {
    await window.ethereum.request({ method: "eth_requestAccounts" });
    }
    
    async function updateCounter() {
    if (typeof window.ethereum !== "undefined") {
        await requestAccount();
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        console.log({ provider });
        const signer = provider.getSigner();
        const contract = new ethers.Contract(counterAddress, Counter.abi, signer);
        const transaction = await contract.increment();
        setIsLoading(true);
        await transaction.wait();
        setIsLoading(false);
        readCounterValue();
    }
    }

    Place these two functions above the readCounterValue() function in the App.js file.

  • Replace the incrementCounter function with this one:

    const incrementCounter = async () => {
    await updateCounter();
    };
  • Update the increment button code to:

    <Button
    onClick={incrementCounter}
    variant="outlined"
    disabled={isLoading}
    >
    {isLoading ? "loading..." : "+1"}
    </Button>

Now, run the Counter dApp by simply using npm start in CLI at the project root.

Congratulations for reaching this far. You have successfully deployed a dApp on the ZKFair testnet.

Last updated