A step-by-step guide to verifying your smart contracts on Etherscan using Hardhat
Verifying your smart contracts is a crucial step in ensuring the transparency and trustworthiness of blockchain-based applications. Making the contract’s code publicly accessible and providing proof of deployment on the blockchain allows for independent verification of the contract’s functionality. This not only builds trust among users but also increases the contract’s visibility and credibility.
In addition to that, having a verified contract can make it more easily discoverable by potential users and can help to increase the contract’s visibility and credibility.
In this article, we will explore the process of programmatically verifying smart contracts on Etherscan using the Hardhat. By the end of this guide, you will have a thorough understanding of how to use Hardhat to automate the contract verification process on Etherscan, thus streamlining the smart contract deployment process.
Let’s delve into the details and learn how to verify smart contracts on Etherscan using Hardhat.
The process of programmatically verifying smart contracts on Etherscan can be accomplished using the Hardhat in two different ways.
Writing a custom verification script using Hardhat, which can be run at any time to verify contracts on Etherscan
Implementing a professional approach to automatically verify smart contracts upon deployment to the testnet or mainnet.
As previously discussed in Learn to Deploy Smart Contracts more Professionally with Hardhat article, managing smart contract scripts can present challenges for developers as a project grows in size and complexity. As the project scales, the complexity of managing smart contract scripts also increases, making it a headache for developers to keep track of the changes and maintain the integrity of the project.
In order to fully understand and implement the second approach for automating the verification process, it is recommended that you first read the above article.
Projects Configuration for Verification
Create a new Hardhat Project
Remove the default contract, script and test files provided by Hardhat. These files can be found in the contracts/, scripts/, and test/ folders, they should be removed to avoid confusion and potential errors.
Get the API Key from Etherscan https://etherscan.io/.
Create the
.env
file at the root location.Add the
.env
file inside the.gitignore
file. Make sure you must follow this step because later we will add the PRIVATE_KEY in this file.Create an Etherscan API key variable inside
.env
the file and add your key as a value for this variable like thisETHERSCAN_API_KEY=DR9...
.Install the
dotenv
a package so we can load the env file variables in our scripts. Run the command in the terminalnpm i --save-dev dotenv --force
.Import the dotenv package inside the
hardhat.config.js
orhardhat.config.ts
file like thisrequire(“dotenv”).config()
(JavaScript) orimport “dotenv/config”
(TypeScript).
Add the Sample Smart Contract for Verification
This smart contract will be utilized for verification purposes.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
contract SimpleStorage {
uint private storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
Create a file called
SimpleStorage.sol
inside the contracts folder and add this to it.Compile the smart contract by running the command
npx hardhat compile
Writing a Custom Verification Script
To verify the functionality of this smart contract, it must first be deployed on a specific network. Utilizing the deployed contract’s address, the smart contract can then be verified on Etherscan.
To do this Create a file called verify-simple-storage.js
or verify-simple-storage.js
inside the scripts folder and add this code inside it;
Note: Please note that both JavaScript and TypeScript code will be provided throughout this article. Kindly select the appropriate code examples based on the language being utilized in your implementation.
// JavaScript
import { ethers, network, run } from "hardhat";
async function verifyContract() {
const chainId = network.config.chainId;
const simepleStorageFactory = await ethers.getContractFactory(
"SimpleStorage"
);
const args = [];
console.log(`Deploying...`);
const simpleStorage = await simepleStorageFactory.deploy(args);
await simpleStorage.deployed();
console.log(`Deployed!`);
console.log(`Simple Storage Address: ${simpleStorage.address}`);
console.log(`Waiting for blocks confirmations...`);
await simpleStorage.deployTransaction.wait(6);
console.log(`Confirmed!`);
// * only verify on testnets or mainnets.
if (chainId != 31337 && process.env.ETHERSCAN_API_KEY) {
await verify(simpleStorage.address, args);
}
}
const verify = async (contractAddress, args) => {
console.log("Verifying contract...");
try {
await run("verify:verify", {
address: contractAddress,
constructorArguments: args,
});
} catch (e) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already verified!");
} else {
console.log(e);
}
}
};
verifyContract()
.then(() => process.exit(0))
.catch((error) => {
console.log(error);
process.exit(1);
});
// TypeScript
import { ethers, network, run } from "hardhat";
async function verifyContract(): Promise<void> {
const chainId = network.config.chainId!;
const simepleStorageFactory = await ethers.getContractFactory(
"SimpleStorage"
);
const args: any[] = [];
console.log(`Deploying...`);
const simpleStorage = await simepleStorageFactory.deploy(args);
await simpleStorage.deployed();
console.log(`Deployed!`);
console.log(`Simple Storage Address: ${simpleStorage.address}`);
console.log(`Waiting for blocks confirmations...`);
await simpleStorage.deployTransaction.wait(6);
console.log(`Confirmed!`);
// * only verify on testnets or mainnets.
if (chainId != 31337 && process.env.ETHERSCAN_API_KEY) {
await verify(simpleStorage.address, args);
}
}
const verify = async (contractAddress: string, args: any[]) => {
console.log("Verifying contract...");
try {
await run("verify:verify", {
address: contractAddress,
constructorArguments: args,
});
} catch (e: any) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already verified!");
} else {
console.log(e);
}
}
};
verifyContract()
.then(() => process.exit(0))
.catch((error) => {
console.log(error);
process.exit(1);
});
Let me explain what’s going on; (I am going to explain JavaScript but the Concepts are the same for both).
async function verifyContract() {}
As simple we are creating a verifyContract
JavaScript function.
verifyContract()
.then(() => process.exit(0))
.catch((error) => {
console.log(error);
process.exit(1);
});
Calling it inside the script so the code gets executed when we run the script.
const chainId = network.config.chainId;
Within the function, we utilize the network
object provided by Hardhat to obtain the chainId
of the network. This obtained chainId is then utilized to verify the smart contract exclusively on either testnets or mainnets.
const simepleStorageFactory = await ethers.getContractFactory(
"SimpleStorage"
);
By utilizing the getContractFactory
function from the ethers.js library, we can instantiate a factory object for our smart contract.
The
getContractFactory
function in the Ethereum JavaScript library ethers.js is used to create a contract factory object for a specific contract. This factory object can then be used to deploy new instances of the contract to the Ethereum blockchain. The factory takes the ABI (Application Binary Interface) and bytecode of the contract as input. The ABI is a JSON representation of the contract's interface, which describes the functions and events that the contract exposes. The bytecode is the compiled code of the contract that gets deployed to the blockchain. Once the factory is created, it can be used to deploy new instances of the contract by calling the deploy method and passing in any necessary constructor arguments.
const args = [];
console.log(`Deploying...`);
const simpleStorage = await simepleStorageFactory.deploy(args);
await simpleStorage.deployed();
console.log(`Deployed!`);
console.log(`Simple Storage Address: ${simpleStorage.address}`);
The variable args
represents the list of arguments for the smart contract constructor. However, in this instance, it is left empty as the SimpleStorage
contract does not require any arguments to be passed during instantiation.
We use the deploy
function of the contractFactory to deploy the smart contract, passing in any necessary arguments as specified. This results in the instantiation of the simpleStorage
object, representing the deployed smart contract.
console.log(`Waiting for blocks confirmations...`);
await simpleStorage.deployTransaction.wait(6);
console.log(`Confirmed!`);
We use the wait
function of the deployTransaction
of the smart contract instance to wait for a confirmation of 6 blocks. Waiting for block confirmations before verification of the smart contract ensures that the contract is properly recorded on the blockchain and its bytes codes are available for verification.
// * only verify on testnets or mainnets.
if (chainId != 31337 && process.env.ETHERSCAN_API_KEY) {
await verify(simpleStorage.address, args);
}
This section of the code establishes that the verify
function should only be called under certain conditions: the network must not be hardhat
or localhost
and an Etherscan API key must be present in the environment file. Once these conditions have been met, the verify
function is invoked, passing in the appropriate contract address and arguments.
Why can we not Verify for the Hardhat network?
As Hardhat
is a local network, running exclusively on the user’s machine, it does not have a corresponding public blockchain for Etherscan to utilize for the verification of smart contracts.
solidity: "0.8.17",
networks: {
hardhat: {
chainId: 31337,
},
localhost: {
chainId: 31337,
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
At this step, It is important to ensure that the networks object within the hardhat configuration file contains Hardhat
and Localhost
networks with a chainId of 31337
, and that an etherscan
object is also present.
If you are using the most recent version of Hardhat that includes the @nomicfoundation/hardhat-toolbox
plugin then the verify
task is included by default. However, if this is not the case, it is necessary to install the hardhat-etherscan
plugin and configure it within the Hardhat configuration file in order for the verification process to be performed. Read more about it here Migration away from hardhat-waffle.
await run("verify:verify", {
address: contractAddress,
constructorArguments: args,
});
We utilize the run
function provided by Hardhat to execute the verify
task, passing in the appropriate contract address and smart contract constructor arguments. This step completes the process of verifying the smart contract on the Etherscan.
As seen in the code, we are calling the verify:verify
subtask, which is a part of the larger verify
task. This subtask specifically handles the verification of the smart contract on the Etherscan. It is important to note that the verify
task contains various other subtasks that are not called in this particular instance.
try {
await run("verify:verify", {
address: contractAddress,
constructorArguments: args,
});
} catch (e) {
if (e.message.toLowerCase().includes("already verified")) {
console.log("Already verified!");
} else {
console.log(e);
}
}
We have wrapped the execution of the run
function within a try-catch block to handle any errors that may occur. In the event that the smart contract has already been verified, an error will be thrown and using the error message, we are informing the user that the contract has already been verified.
// JavaScript
npx hardhat run scripts/verify-simple-storage.js --network goerli
// TypeScript
npx hardhat run scripts/verify-simple-storage.ts --network goerli
By executing the above command, we will be able to deploy the smart contract and subsequently verify it on Etherscan after waiting for 6 block confirmations. This allows for easy verification of the smart contract on the Ethereum blockchain.
// JavaScript
goerli: {
chainId: 5,
url: process.env.GOERLI_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
saveDeployments: true,
},
// TypeScript
goerli: {
chainId: 5,
url: process.env.GOERLI_RPC_URL,
accounts: [process.env.PRIVATE_KEY!],
saveDeployments: true,
},
It is important to note that before running the above command, the Goerli network should be added to the networks object in the configuration file. Additionally, the GOERLI_RPC_URL and PRIVATE_KEY environment variables should be defined in the env file to ensure proper deployment and verification on the Goerli network.
The GOERLI_RPC_URL, which is required for connecting to the Goerli network, can be obtained from a platform such as Alchemy. The PRIVATE_KEY, used for signing transactions, can be retrieved from a wallet service like MetaMask.
Note: It is important to keep in mind that the private key should be kept secure, and never shared with anyone. Additionally, it is important to ensure that the env file containing the private key is added to the .gitignore file before committing any code to a version control system, to prevent the private key from being accidentally exposed to the public.
Automated Verification of Smart Contracts
Using the above script approach deploying and verifying smart contracts can be a straightforward process when working with a small number of contracts. However, as the number of contracts increases, this manual approach can become unwieldy and time-consuming for developers. The repetitive nature of running deployment and verification commands for each individual contract can pose a significant challenge for large-scale projects. A more efficient and scalable approach is needed to effectively manage and deploy a large number of smart contracts.
It is important to note that in order to fully grasp this approach, a basic understanding of deploying smart contracts using the hardhat-deploy plugin is required. If you are not familiar with this method, it is recommended that you first read Learn to Deploy Smart Contracts more Professionally with Hardhat article before proceeding.
A best practice is to create a separate file for the verification function. This allows the function to be easily imported and reused across multiple files, making the deployment and verification process more efficient and streamlined.
Create a utils
folder at the root location of the project. Within this folder, create a file named verify.js
or verify.ts
. Add the above verify function inside this file and export it.
// JavaScript
const { verify } = require("../utils/verify");
// TypeScript
import verify from "../utils/verify";
Import the verify function in your deploy file.
// JavaScript
const args = [];
const simpleStorage = await deploy("SimpleStorage", {
from: deployer,
log: true,
args: args,
waitConfirmations: chainId == 31337 ? 1 : 6,
});
// * only verify on testnets or mainnets.
if (chainId != 31337 && process.env.ETHERSCAN_API_KEY) {
await verify(simpleStorage.address, args);
}
// TypeScript
const args: any[] = [];
const simpleStorage: DeployResult = await deploy("SimpleStorage", {
from: deployer,
log: true,
args: args,
waitConfirmations: chainId == 31337 ? 1 : 6,
});
// * only verify on testnets or mainnets.
if (chainId != 31337 && process.env.ETHERSCAN_API_KEY) {
await verify(simpleStorage.address, args);
}
Execute the verification function, providing the deployed contract’s address and any constructor arguments utilized during the deployment process.
npx hardhat deploy --network goerli
With this approach, a single command can be used to deploy and verify all of the smart contracts located within the deploy
folder. This streamlined process eliminates the need for manually deploying and verifying each individual contract, resulting in a more efficient and manageable development process.
Conclusion
In conclusion, verifying smart contracts on the Ethereum blockchain is an essential aspect of building trust and transparency in blockchain-based applications. By making the contract’s code publicly accessible and providing proof of deployment on the blockchain, developers can increase the visibility, credibility, and discoverability of their contracts. Verifying smart contracts is not just a good practice for blockchain-based application development, but it is also a way to increase the user’s trust and adoption of the application.
This comprehensive guide aims to provide assistance to developers of all skill levels and is intended to be beneficial for a wide range of developers. Despite its length, the information provided is intended to be easily understandable and actionable for all readers.
If you found this content helpful, please consider giving it a clap and leaving any feedback for future improvements. Your suggestions and comments are greatly appreciated and will help make these articles even more valuable for you and other readers.
Be sure to follow me to receive updates on my future articles and stay informed of new content.
Thank you
Top comments (0)