Hardhat is a full-featured development environment for contract compilation, deployment and verification. The hardhat-verify plugin supports contract verification on OpenScan.AI, which exposes a Blockscout-compatible verification API — no API key required.
A plain
npm install @nomicfoundation/hardhat-verifyinstalls v3, which targets Hardhat 3. If your project is using Hardhat 2, install with the@hh2tag instead (@nomicfoundation/hardhat-verify@hh2). The config format below uses theetherscan.customChainsstructure, which routes verification to a specific explorer instance. Check which Hardhat version applies to you before following the steps below.
1) Install Hardhat
If you are starting from scratch, create an npm project by going to an empty folder, running npm init, and following the instructions. We recommend npm 7 or higher.
Once your project is ready:
npm
npm install --save-dev hardhatyarn
yarn add --dev hardhat2) Create a project
Run npx hardhat in your project folder and follow the instructions to create one (more info here).
3) Install the plugin
Install the hardhat-verify plugin.
npm
npm install --save-dev @nomicfoundation/hardhat-verify@hh2yarn
yarn add --dev @nomicfoundation/hardhat-verify@hh2Note the @hh2 tag above for Hardhat 2 projects — plain npm install @nomicfoundation/hardhat-verify now installs v3 (Hardhat 3), which uses a different config format.
4) Add the plugin reference to your config file
Add the following statement to your hardhat.config.js.
require("@nomicfoundation/hardhat-verify");If using TypeScript, add this to your hardhat.config.ts. More info on using TypeScript with Hardhat is available here.
import "@nomicfoundation/hardhat-verify";Config File
Your basic Hardhat config file (hardhat.config.js or hardhat.config.ts) will be set up to support the network you are working on. In this example we use the XDC Network mainnet and a .ts file.
Here we add an API key value — OpenScan.AI does not require one, however some value is still required by the plugin. You can use any arbitrary string. More info.
In order to use the OpenScan.AI explorer for verification, you have to specify the explorer details under a customChains object. It includes:
chainId— Network chain ID (50 for XDC Network mainnet)apiURL— Block explorer API URLbrowserURL— Block explorer URL
Find an extensive list of ChainIDs at https://chainlist.org/.
Note the network name in customChains must match the network name in the apiKey object.
import { HardhatUserConfig, vars } from "hardhat/config";import "@nomicfoundation/hardhat-toolbox";import "@nomicfoundation/hardhat-verify";
const PRIVATE_KEY = vars.get("PRIVATE_KEY");
const config: HardhatUserConfig = { solidity: "0.8.24", networks: { xdc: { url: "https://erpc.xinfin.network", accounts: [PRIVATE_KEY], }, }, etherscan: { apiKey: { // Is not required by OpenScan.AI. Can be any non-empty string xdc: "abc", }, customChains: [ { network: "xdc", chainId: 50, urls: { apiURL: "https://xdcscan.io/api", browserURL: "https://xdcscan.io", }, }, ], }, sourcify: { enabled: false, },};
export default config;Verifying on multiple chains?
If you deploy to more than one network, you don’t need a separate config block per chain. Define your chains once in an array and generate networks, etherscan.apiKey, and customChains from it:
import { HardhatUserConfig, vars } from "hardhat/config";import "@nomicfoundation/hardhat-toolbox";import "@nomicfoundation/hardhat-verify";
const PRIVATE_KEY = vars.get("PRIVATE_KEY");
// Single source of truth for every chain you deploy to.// Add a new chain by adding one line here — no need to touch// `networks` or `customChains` separately.const CHAINS = [ { network: "xdc", chainId: 50, rpc: "https://erpc.xinfin.network", explorer: "xdcscan.io", },];
const config: HardhatUserConfig = { solidity: "0.8.24", networks: Object.fromEntries( CHAINS.map(({ network, chainId, rpc }) => [ network, { url: rpc, accounts: [PRIVATE_KEY], chainId }, ]), ), etherscan: { // Not required by OpenScan.AI, but the plugin needs *some* non-empty // string per network — the network name in this object must match // the network name used in `customChains` below. apiKey: Object.fromEntries(CHAINS.map(({ network }) => [network, "abc"])), customChains: CHAINS.map(({ network, chainId, explorer }) => ({ network, chainId, urls: { apiURL: `https://${explorer}/api`, browserURL: `https://${explorer}/`, }, })), }, sourcify: { enabled: false, },};
export default config;Run
npx hardhat verify --list-networksat any time to confirm which network identifiers are registered in your config.
Deploy and Verify
For deployment we will use Hardhat Ignition, the built-in Hardhat deployment system.
Deploy
> npx hardhat ignition deploy ./ignition/modules/Lock.ts --network xdc✔ Confirm deploy to network xdc (50)? … yesCompiled 1 Solidity file successfully (evm target: paris).Hardhat Ignition 🚀
Deploying [ LockModule ]
Batch #1 Executed LockModule#Lock
[ LockModule ] successfully deployed 🚀
Deployed Addresses
LockModule#Lock - 0xFE826b33e425f99ce962ACB91752DB41F302EFEAVerify
The plugin requires you to include constructor arguments with the verify task and ensures that they correspond to the expected ABI signature. However, OpenScan.AI ignores those arguments, so you may specify any values that correspond to the ABI.
npx hardhat verify --network <network> DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1"XDC Network mainnet example:
> npx hardhat verify --network xdc 0xFE826b33e425f99ce962ACB91752DB41F302EFEA 1234Successfully submitted source code for contractcontracts/Lock.sol:Lock at 0xFE826b33e425f99ce962ACB91752DB41F302EFEAfor verification on the block explorer. Waiting for verification result...
Successfully verified contract Lock on the block explorer.https://xdcscan.io/address/0xFE826b33e425f99ce962ACB91752DB41F302EFEA#codeIf you’re using the multi-chain config, the same command works unchanged for any chain in your CHAINS array — just swap --network xdc for the network name of the chain you deployed to.
Automatically verified contracts
Sometimes the contract may be automatically verified via the Ethereum Bytecode Database service. In that case you may see a response stating the contract has already been verified.
In that case, you may try to enforce verification using the --force flag*.
It prevents Hardhat from checking if the contract is already verified, and forces it to send a verification request anyway. Notice that it is helpful only if the contract was automatically verified partially. That way, new verification sources would be saved. If the contract was fully verified already, that just returns an error.
npx hardhat verify --network <network> DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1" --force* The flag is available starting from @nomicfoundation/hardhat-verify@2.0.7.
Confirm Verification on OpenScan.AI
- Go to xdcscan.io and paste the contract address into the search bar.
- Scroll down to see the verified status. A green checkmark ✅ means the contract is verified.
- If your screen size is limited, you may need to click the 3 dots to view and click through to the contract.
- Scroll down to see and interact with the contract code.
FAQ
I am using an OpenZeppelin upgrades plugin implementation and receive an error on proxy contract verification. What should I do?
Although you receive an error, the contracts should be verified during the previous steps and you can ignore it. Check in the explorer to make sure the contracts have been verified.
Do I need a real API key for each chain?
No — on the per-instance route shown above, any non-empty string works. Each network still needs its own entry in the apiKey object, since the network name in customChains must match a key in apiKey.
Resources
Learn more about plugin configs, troubleshooting etc. at https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify (Hardhat 2) or https://hardhat.org/docs/plugins/hardhat-verify (Hardhat 3).