Create a Fullstack Counter Dapp on the Polygon zkEVM Testnet

General Setup

Star this repo and clone it locally

git clone https://github.com/oceans404/fullstack-zkevm

Install dependencies and start react app

cd fullstack-zkevm
npm i
npm start

Install dependencies

npm install ethers hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers dotenv
cp .env.sample .env;

Update .env to set your ACCOUNT_PRIVATE_KEY environment variable. Here's an article on how to get your private key from MetaMask.

Hardhat Smart Contract

Before running npx hardhat, rename your README.md file temporarily. (README.md -> README-tutorial.md) Hardhat can't initialize a sample project if there's an existing README file.

npx hardhat

What do you want to do? … ❯ Create a JavaScript project

Hardhat project root: default

Do you want to add a .gitignore? y

Open the hardhat.config.js and paste in this 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://rpc.public.zkevm-test.net`,
      accounts: [process.env.ACCOUNT_PRIVATE_KEY],
    },
  },
};

Notice that we've added a different path to artifacts so that the React app will be able to read the contract ABI within the src folder

Create a new file in the contracts folder Counter.sol

touch contracts/Counter.sol

Copy paste 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

and add the following code to the deploy-counter.js file

const hre = require("hardhat");

async function main() {
  const CounterContractFactory = await hre.ethers.getContractFactory("Counter");
  const counterContract = await CounterContractFactory.deploy();

  await counterContract.deployed();

  console.log(
    `Counter contract deployed to https://explorer.public.zkevm-test.net/address/${counterContract.address}`
  );
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Compile your contract code

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

Verify the contract by following my verification instructions

Update the Frontend to turn it into a dapp

In App.js, import the ethers, the Counter file and log the contract's abi. Update the counterAddress to your deployed address.

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 frontend counter to read from blockchain

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 Polygon zkEVM testnet and refresh this page!"
        );
      }
    }
  }

Let's track a loader. Add this to your state

const [isLoading, setIsLoading] = useState(false);

Let frontend counter write to the blockchain by adding the following 2 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();
  }
}

Update the incrementCounter function to

const incrementCounter = async () => {
  await updateCounter();
};

Update the increment button code to

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

You did it! 🚀 Want to deploy your dapp to Fleek for decentralized hosting? Follow my instructions here