How To Deploy A Contract To Polygon zkEVM Testnet

How To Deploy An ERC20 To Polygon Hermes zkEVM Testnest

Manny
10 min readDec 3, 2022
Learn To Deploy Contracts To Polygon zkEVM Testnet

What Is zkEVM?

You might be asking “What is zkEVM?”

Polygon zkEVM is the first zk-Rollup with source code available, providing complete EVM opcode equivalence for a frictionless user experience and the security of Ethereum.
- https://polygon.technology/solutions/polygon-zkevm

Is it a different network than Polygon Matic POS?

Yes it is a completely different network with its own token and wallet configurations.

Does that mean it uses its own tokens?

Yes it uses it’s own native tokens and not Matic or Mumbai Testnet Tokens.

Ok, but what does EVM-equivalence mean?

Equivalence refers to a Type 2 ZK-EVM that is defined a bit better by Vitalik’s blog on ZK-EVMs.

Type 2 ZK-EVMs strive to be exactly EVM-equivalent, but not quite Ethereum-equivalent. That is, they look exactly like Ethereum “from within”, but they have some differences on the outside, particularly in data structures like the block structure and state tree.
- https://vitalik.eth.limo/general/2022/08/04/zkevm.html

For Developers

What that means for developers is you can deploy you existing solidity code without going through any extra steps to compile your code to get it to work on this network. Compared to other ZK-EVM solutions out there, Type 2 offers an easier way to work with that ZK-EVM solution compared to Type 3 and Type 4 solutions.

The goal is to be fully compatible with existing applications, but make some minor modifications to Ethereum to make development easier and to make proof generation faster.

- https://vitalik.eth.limo/general/2022/08/04/zkevm.html

TL;DR

Jump to the Full Code section for the GitHub repository.

Wallet Configuration

It should be noted that a lot of this information already exists on the official Polygon Wiki For zkEVM and that these settings may change when the zkEVM Mainnet is available.

Is the zkEVM wallet configuration on Chainlist.org? Unfortunately not yet because the network configurations and port number might change.

Our configurations setting for zkEVM Testnet are as followed:

⚠️ NOTE: March 17 — Details Have Changed

ℹ️ You can also quickly add the Polygon zkEVM Testnet by searching for “zkevm” on https://chainid.network.

You can add this to your current MetaMask wallet by going to your networks and adding a network manually.

MetaMask Add A Network Manually
MetaMask Configured With Polygon zkEVM Testnet

Let’s take a look at our wallet on the zkEVM Block Explorer.

https://explorer.public.zkevm-test.net/address/0xB3f03B93F0bd65B960EE950d9aFC6867D461C33f

Getting Testnet Tokens From A Faucet

It’s a bit different to get Testnet tokens for zkEVM compared to other Testnet networks. In order to get tokens, you need to get Goerli Testnet tokens and then bridge them over to zkEVM.

We’ll be using the QuickNode Goerli Faucet, but you can also use anything of the following (please let me know if I missed one):

NOTE: If a faucet isn’t currently working please refer to the respective RPC providers for network issues.

QuickNode Goerli Testnet Faucet

Once we have Goerli Testnet Tokens, we need to bridge them to the zkEVM Testnet by using https://public.zkevm-test.net/.

Connect your preferred wallet, make sure the network is set to Ethereum Goerli, enter the desired amount to bridge, and click Continue.

Configuring Polygon zkEVM Bridge From Goerli

Confirm the bridge transaction.

Confirming Bridge Transaction For zkEVM

Finalise the transaction by switching to zkEVM and then confirming the transaction.

zkEVM Finalise Bridge Transaction

If the transaction was successful, you should a confirmation screen and see the result on the Block Explorer.

Successful Bridge From Goerli Testnet To zkEVM Testnet

Deploying An ERC20 Contract To zkEVM Testnet

This next part we’re going to setup and deploy and ERC20 contract to zkEVM with Hardhat.

Requirements

Make sure to have the following installed on your computer before hand.

  • nvm or node v18.12.1
  • pnpm v7.15.0

Setting Up Hardhat

The first thing we’re going to do is create a new project folder, initiate pnpm, install Hardhat, and configure it.

mkdir zkevm-erc20;
cd zkevm-erc20;
git init;
pnpx hardhat;

# Expected Prompts
# 888 888 888 888 888
# 888 888 888 888 888
# 888 888 888 888 888
# 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
# 888 888 "88b 888P" d88" 888 888 "88b "88b 888
# 888 888 .d888888 888 888 888 888 888 .d888888 888
# 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
# 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
#
# 👷 Welcome to Hardhat v2.12.3 👷‍
#
# ? What do you want to do? …
# Create a JavaScript project
# ❯ Create a TypeScript project
# Create an empty hardhat.config.js
# Quit

# ? Hardhat project root: › /path/to/zkevm-erc20

# ? Do you want to add a .gitignore? (Y/n) › y

# ? Do you want to install this sample project's dependencies with npm (@nomicfoundation/hardhat-toolbox)? (Y/n) › y

pnpm install;

Let’s double check that our Hardhat setup is working as expected by running a node, deploying the default contract, and then testing that contract.

Terminal 1

# FROM: ./zkevm-erc20

./node_modules/.bin/hardhat node;

# Expected Output:
# Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
#
# Accounts
# ========
#
# WARNING: These accounts, and their private keys, are publicly known.
# Any funds sent to them on Mainnet or any other live network WILL BE LOST.
# ...

Terminal 2

Deploy the Lock.sol contract to the local node we’re running.

# FROM: ./zkevm-erc20

./node_modules/.bin/hardhat run scripts/deploy.ts

# Expected Output:
# Compiled 1 Solidity file successfully
# Lock with 1 ETH and unlock timestamp 1701595951 deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3

Run the tests that were generated with the original scaffolded project.

# FROM: ./zkevm-erc20

./node_modules/.bin/hardhat test;

# Expected Output:
# Lock
# Deployment
# ✔ Should set the right unlockTime (894ms)
# ✔ Should set the right owner
# ✔ Should receive and store the funds to lock
# ✔ Should fail if the unlockTime is not in the future
# Withdrawals
# Validations
# ✔ Should revert with the right error if called too soon
# ✔ Should revert with the right error if called from another account
# ✔ Shouldn't fail if the unlockTime has arrived and the owner calls it
# Events
# ✔ Should emit an event on withdrawals
# Transfers
# ✔ Should transfer the funds to the owner
#
# 9 passing (1s)

Creating An ERC20 Contract

We’re going to base our contract from OpenZeppelin’s ERC20 Solidity contract, which will create token with an initial 10,000 tokens and allows the owner to mint more tokens.

Add Dependencies

# FROM: ./zkevm-erc20

pnpm add -D @openzeppelin/contracts;

Configure ERC20
Use The OpenZepplin Wizard to configure an ERC20 token.

Make New Contract

We’re going to rename our existing Lock.sol for zkerc20.sol and replace it with the code we generated from the OpenZeppeling wizard.

# FROM: ./zkevm-erc20

mv ./contracts/Lock.sol ./contracts/zkERC20.sol;

File: ./contracts/zkERC20.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract ZkERC20 is ERC20, Ownable {
constructor() ERC20("zkERC20", "ZK20") {
_mint(msg.sender, 10000 * 10 ** decimals());
}

function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}

Testing Our ERC20 Contract

First we’re going to make a single test for our ERC20 contract and then verify that it’s working correctly.

Deploy Script
First we need to modify our deploy script to account for the new contract name.

File: ./scripts/deploy.ts

// Imports
// ========================================================
import { ethers } from "hardhat";

// Main Deployment Script
// ========================================================
async function main() {
// Make sure in the contract factory that it mateches the contract name in the solidity file
// Ex: contract ZkERC20
const zkERC20Contract = await ethers.getContractFactory("ZkERC20");
const contract = await zkERC20Contract.deploy();

await contract.deployed();

console.log(`ZkERC20 deployed to ${contract.address}`);
};

// Init
// ========================================================
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Creating Our Test

Next we’re going to rename our Lock.ts test file to zkERC20.test.ts and add a test that confirms the total balance of the minted erc20 token.

# FROM: ./zkevm-erc20

mv ./test/Lock.ts ./test/zkERC20.test.ts;

File: ./test/zkERC20.test.ts

// Imports
// ========================================================
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";

// Tests
// ========================================================
describe("zkERC20", function () {
// We define a fixture to reuse the same setup in every test.
// We use loadFixture to run this setup once, snapshot that state,
// and reset Hardhat Network to that snapshot in every test.
async function deployZkERC20() {
// Contracts are deployed using the first signer/account by default
const [owner, otherAccount] = await ethers.getSigners();
// Make sure in the contract factory that it mateches the contract name in the solidity file
// Ex: contract ZkERC20
const zkERC20Contract = await ethers.getContractFactory("ZkERC20");
const zkERC20 = await zkERC20Contract.deploy();

return { zkERC20, owner, otherAccount };
};

/**
*
*/
describe("Deployment", function () {
/**
*
*/
it("Should deploy with initial 10,000 supply", async function () {
// Setup
const { zkERC20 } = await loadFixture(deployZkERC20);

// Init + Test
expect(await zkERC20.totalSupply()).to.equal(ethers.utils.parseEther(`10000`).toString());
});
});

/**
*
*/
describe("Minting", function () {
/**
*
*/
it("Should mint and increase the supply by 137", async function () {
// Setup
const { zkERC20, owner } = await loadFixture(deployZkERC20);

// Init
await zkERC20.connect(owner).mint(owner.address, ethers.utils.parseUnits('137', 18));

// Init + Test
expect(await zkERC20.totalSupply()).to.equal(ethers.utils.parseEther(`10137`).toString());
});
});
});

Let’s run the Hardhat node and test this contract.

Terminal 1

Running a local node.

# FROM: ./zkevm-erc20

./node_modules/.bin/hardhat node;

# Expected Output:
# Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
#
# Accounts
# ========
#
# WARNING: These accounts, and their private keys, are publicly known.
# Any funds sent to them on Mainnet or any other live network WILL BE LOST.
# ...

Terminal 2

Running our tests.

# FROM: ./zkevm-erc20

./node_modules/.bin/hardhat test;

# Expected Output:
# zkERC20
# Deployment
# ✔ Should deploy with initial 10,000 supply (803ms)
# Minting
# ✔ Should mint and increase the supply by 137
#
# 2 passing (819ms)

Deploying An ERC20 Contract

̶I̶t̶ ̶s̶h̶o̶u̶l̶d̶ ̶b̶e̶ ̶n̶o̶t̶e̶d̶ ̶t̶h̶a̶t̶ ̶c̶u̶r̶r̶e̶n̶t̶l̶y̶ ̶z̶k̶E̶V̶M̶ ̶d̶e̶p̶l̶o̶y̶m̶e̶n̶t̶s̶ ̶a̶r̶e̶n̶’̶t̶ ̶s̶u̶p̶p̶o̶r̶t̶e̶d̶ ̶i̶n̶ ̶H̶a̶r̶d̶h̶a̶t̶ ̶c̶u̶r̶r̶e̶n̶t̶l̶y̶. We can deploy our contracts onHardhat but in this case we’ll use Remix to show how you could do it an alternative way. To deploy your contracts, you’ll need to use the Injected Provider configuration with Ethereum Remix.

Make sure to create an zkERC20.sol file in remix with the contract code we made above.

Remix zkERC20.sol

Switch to the compile section and click the large compile button to get the green checkmark that everything compiled correctly.

Remix Compile Contract

Switch over to the deploy section in left navigation bar, set the Environment to Injected Provider — Metamask. and make sure your wallet is set to the zkEVM network.

Remix Environment zkEVM

When ready, click the deploy button and confirm the transaction in your wallet.

Remix Deploy Contract

Viewing Our Tokens In MetaMask

Open your MetaMask wallet, click on the transaction, and view the transaction on the block explorer. In the block explorer click the contract address to open up the contract. With the contract loaded in the block explorer, copy the contract address.

zkEVM Contract Deployed

Open your MetaMask, click on Assets, and click on Import tokens.

MetaMask Import Tokens

Paste the contract address, and fill the other fields as needed. They might automatically populate, and then click Add custom token.

MetaMask Add Custom Token

You should now be able to follow the prompts and then see the new ZK20 token in your wallet with the correct initial amount.

MetaMask With New ZK20 ERC20 Token

🎉 There you go, you’ve fully deployed an ERC20 contract to Polygon Hermes zkEVM Testnet.

Full Code

Here is the full code for the zkEVM ERC20 token. Just remember that Hardhat deployments aren’t currently supported, yet.

What’s Next

Please look out for the ERC721 tutorial coming soon.

If you got value from this, please give it some love, and please also follow me on twitter (where I’m quite active) @codingwithmanny and instagram at @codingwithmanny.

--

--

Manny

DevRel Engineer @ Berachain | Prev Polygon | Ankr & Web Application / Full Stack Developer