🤔 Got questions? Schedule an office hours session.
Contract Verification
Hardhat Verification

Hardhat Contract Verification

The Tenderly-Hardhat plugin allows you to automate the process of verifying smart contracts deployed with Hardhat. The plugin supports on public networks (mainnets and testnets) as well as Tenderly’s development infrastructure - Virtual TestNets. Rely on seamless automatic verification, or use manual mode to explicitly verify contracts in deployment scripts.

Contracts deployed to public mainnets and testnets that are verified using the Tenderly-Hardhat plugin are public by default. You need to set the verification mode to private if you don’t want your verification to be publicly visible. When verified, the source code will be visible within your Tenderly workspace.

Versions and compatibility

Depending on the Ethers version you’re using with Hardhat, you need to download the corresponding version of the Tenderly-Hardhat plugin.

⚠️

It’s highly recommended to switch to Ethers 6 and use the latest version of the Tenderly-Hardhat plugin for security and performance reasons, as well as additional features.

Ethers versionHardhat versionCompatible plugin version

Ethers 6 or higher with Hardhat Ignition

>=2.19.0 <=2.22.0

@tenderly/hardhat-tenderly@^2.3.0

Ethers 6 or higher with hardhat-verify plugin.

>=2.19.0 <=2.22.0

@tenderly/hardhat-tenderly@^2.2.1

Ethers 5 or higher with hardhat-verify plugin.

< 2.19.0

@tenderly/hardhat-tenderly@1.8.0

Verification methods

The Tenderly-Hardhat plugin supports three methods for contract verification.

  • Automatic verification (recommended): Simply import and set up the plugin. This method is ideal for verification during deployment because the process is fully automated. Automatic verification works with plain hardhat deployment setup, as well as using hardhat-ignition. Control automatic verification by using the TENDERLY_AUTOMATIC_VERIFICATION environment variable.

  • Manual verification (low-code): Offers an explicit verification step within your code, which is useful for scenarios like post-deployment verification, conditional verification (e.g. only after a failing test), or verifying factory contracts’ instances.

  • Verbose manual verification (high-code): Provides detailed control over the verification process, specifying library addresses, source codes, and compiler configurations. This is necessary for verifying multiple contracts with varying compiler versions or settings.

Examples

The best way to explore the automatic verification process is to go through an example that you can in our GitHub repo.

All the examples use your sample Greeter contract.

Tenderly-Hardhat plugin setup

Follow the steps below to install and initialize the Tenderly-Hardhat plugin.

Install

Install the Tenderly-Hardhat plugin and add it as a dependency.

yarn add -D @tenderly/hardhat-tenderly@^2.2.2

Log into Tenderly CLI

The plugin requires you to be logged into the Tenderly CLI.

example
curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-macos.sh | sudo sh

Use this command to check if you’re logged in already:

tenderly whoami

To log in, run:

tenderly login

Initialize

Include the Tenderly-Hardhat package in your hardhat.config.ts or hardhat.config.js file with a simple import statement and call the setup method.

Make sure to import @tenderly/hardhat-tenderly after other packages, such as @nomicfoundation/hardhat-toolbox, @nomiclabs/hardhat-ethers, @openzeppelin/hardhat-upgrades and similar.

Up to versions 2.4.0 and 1.10.0, it was necessary to call tdly.setup() to initialize the plugin:

 tdly.setup({ automaticVerifications: !!process.env.TENDERLY_AUTOMATIC_VERIFICATION });
hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
 
import * as tdly from "@tenderly/hardhat-tenderly";
 
const config: HardhatUserConfig = {
  solidity: "0.8.19",
};
 
export default config;

Configure

You need to configure Tenderly by passing account information and verification visibility.

  • The privateVerification parameter ensures your source code is kept visible only to your project collaborators or organization.
  • Verification visibility can also be controlled by setting the environment variable TENDERLY_PUBLIC_VERIFICATION.

Configure Tenderly by adding the following code snippet to the hardhat.config.ts file:

hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
 
import * as tdly from "@tenderly/hardhat-tenderly";
 
const config: HardhatUserConfig = {
  solidity: "0.8.19",
  tenderly: {
    username: "Your account slug" ?? "error",
    project: "Your project slug",
 
    // Contract visible only in Tenderly.
    // Omitting or setting to `false` makes it visible to the whole world.
    // Alternatively, configure verification visibility using
    // an environment variable `TENDERLY_PUBLIC_VERIFICATION`.
    privateVerification: process.env.TENDERLY_PUBLIC_VERIFICATION !== 'true',
  },
};
 
export default config;
ParameterExplanationLocation
project
(required)
The project slug where the contracts have been deployed.

In the examples, the project name is project - the name of the default starter project.
File: hardhat.config.ts
username
(required)
The Tenderly organization slug or username for individual accounts.File: hardhat.config.ts
privateVerification
(optional)
default: false
Indicates verification visibility: (public or private) when deploying to public testnets or mainnets.Either set explicitly in `hardhat.config.ts` or pass it as an environment variable when running scripts, e.g.
TENDERLY_PRIVATE_VERIFICATION=true npx hardhat run scripts/deploy.ts --network mainnet
forkedNetwork
(optional)
The ID of the network you forked using Tenderly Forks. This parameter was used in older versions of the plugin for deploying contracts to a Tenderly Forks.File: hardhat.config.ts

Set up your deployment

With the Tenderly-Hardhat plugin set up in your Hardhat project, you can verify contracts deployed to various development and production networks:

hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
 
import * as tdly from "@tenderly/hardhat-tenderly";
 
const config: HardhatUserConfig = {
  solidity: "0.8.19",
  networks: {
    mainnet_base: {
      url: "https://base.gateway.tenderly.co",
      chainId: 8453
    },
    tenderly_base_testnet: {
      // your Tenderly TestNet RPC
      url: "https://virtual.base.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d",
      chainId: 888
    }
  },
  tenderly: {
    username: "Your account name" ?? "error",
    project: "Your project name",
 
    // Contract visible only in Tenderly.
    // Omitting or setting to `false` makes it visible to the whole world.
    // Alternatively, configure verification visibility using
    // an environment variable `TENDERLY_PUBLIC_VERIFICATION`.
    privateVerification: process.env.TENDERLY_PUBLIC_VERIFICATION !== 'true',
  },
};
 
export default config;

In the following sections, you’ll learn how to verify contracts using the different verification methods.

Automatic verification

Automatic verification occurs seamlessly when you deploy a contract using Ethers.js. The automatic verification approach will perform the verification automatically after collecting the compiler settings, the deployed contract’s address, and the source code.

To enable or disable automatic verification, use the TENDERLY_AUTOMATIC_VERIFICATION environment variable recognized by the plugin.

The Tenderly-Hardhat plugin will verify contracts when it detects that the contract has been deployed, and Ethers has received the receipt of the deployment transaction.

To enable this, you need to await for deployment when you deploy contracts using Ethers’ helper methods ethers.deployContract() and ethers.getContractFactory(), by calling waitForDeployment() or deployed() on the contract instance, respectively.

⚠️

You need to capture the reference to the contract object returned by the waitForDeployment() (Ethers 6) and deployed() (Ethers 5) if you intend to interact with that contract.

Modify the deployment script

Modify the Hardhat script to capture the value returned from waitForDeployment().

import { ethers } from "hardhat";
 
export async function main() {
    console.log("🖖🏽[ethers] Deploying and Verifying Greeter in Tenderly");
 
-   const greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
+   let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
 
-   await greeter.waitForDeployment();
+   greeter = await greeter.waitForDeployment();
 
    const greeterAddress = await greeter.getAddress();
    console.log("{Greeter} deployed to", greeterAddress);
}
 
main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

In more compact form:

const greeter = await (
    await ethers.deployContract("Greeter", ["Hello, Hardhat!"])
).waitForDeployment();

Run the script

When running Hardhat scripts, you can set the TENDERLY_AUTOMATIC_VERIFICATION environment variable to true to enable automatic verification on a per-run basis.

Run the script with the commands below. The link to the verified contract will be displayed in the Terminal output.

TENDERLY_AUTOMATIC_VERIFICATION=true \
npx hardhat ignition deploy ./ignition/modules/Lock.ts --network virtual_base --deployment-id deploy-to-virtual-base

Make sure to add --deployment-id with a specific value when running the command.

Verifying proxy contracts

Automatic verification of proxy contracts deployed and upgraded with hardhat-upgrades is possible with the following versions of @tenderly/hardhat-tenderly package:

  • >= 1.10.0
  • >= 2.1.0

For versions 1.x.x < 1.10.0 and 2.x.x < 2.1.0 follow the proxy verification guide.

Manual verification

Low-code verification through tenderly.verify() allows you to be explicit about the exact point of verification within a deployment script or a Hardhat test.

This method is particularly beneficial for:

  • Verifying previously deployed contracts through custom scripts.
  • Verifying instances created by factory contracts.
  • Enhancing test runs by selectively verifying contracts at the end of a failing suite (after), which speeds up execution.

To manually control the verification, disable automatic verification by changing the TENDERLY_AUTOMATIC_VERIFICATION to false when running scripts.

The verify function

The verify() function takes an object where you must provide the:

  • name of the contract
  • address where the contract is deployed
  • libraries (optional) used by the contract

The Tenderly-Hardhat plugin will pick up the existing compiler configuration and contract source code to perform the verification of the contracts you’ve specified.

It’s necessary to invoke verify() after the contract has been deployed.

If you’re performing a verification alongside to deployment, you have to await for waitForDeployment() in the case of Ethers 6, and deployed() in the case of Ethers 5.

let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
greeter = await greeter.waitForDeployment();
 
await tenderly.verify({
  name: 'Greeter',
  address: await greeter.getAddress(),
  libraries: {
    AwsomeLib: "0x...",
  }
});

If you’re verifying multiple contracts, the function has variadic arguments:

let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
greeter = await greeter.waitForDeployment();
 
const counter = (await ethers.deployContract("Counter", [42])).waitForDeployment();
 
await tenderly.verify(
  {
    name: 'Greeter',
    address: await greeter.getAddress(),
  },
  {
    name: 'Counter',
    address: await counter.getAddress(),
    libraries: {
        KickAssLib: "0x...",
    }
  },
);

Disable automatic mode hardhat.config.ts

If you prefer to switch off automatic verifications:

hardhat.config.ts
 import { HardhatUserConfig } from "hardhat/config";
 import "@nomicfoundation/hardhat-toolbox";
 
 import * as tdly from "@tenderly/hardhat-tenderly";
- tdly.setup();
+ tdly.setup({ automaticVerirication: false });
 
 const config: HardhatUserConfig = {

Modify the deployment script

We will use the same examples as in automatic verification, but this time we will use the tenderly.verify() method to verify the contract.

import { ethers } from "hardhat";
+ import { tenderly } from "hardhat";
 
async function main() {
    console.log("🖖🏽[ethers] Deploying and Verifying Greeter in Tenderly");
 
-   const greeter = await ethers.deployContract("Greeter", ["Hello, Manual Hardhat!",]);
+   let greeter = await ethers.deployContract("Greeter", ["Hello, Manual Hardhat!",]);
 
-   await greeter.waitForDeployment();
+   greeter = await greeter.waitForDeployment();
 
    const address = await greeter.getAddress();
    console.log("Manual Simple: {Greeter} deployed to:", address);
 
+    await tenderly.verify({
+        address,
+        name: "Greeter",
+    });
}
 
main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

Run the script

When running Hardhat scripts, you can set the TENDERLY_AUTOMATIC_VERIFICATION environment variable to false to disable automatic verification.

TENDERLY_AUTOMATIC_VERIFICATION=false \
npx hardhat run scripts/deploy.ts --network virtual_base

Verbose manual verification

The method verifyMultiCompilerAPI() gives you full control over the verification process, allowing you to specify every detail of the verification process.

This is rarely recommended but can be useful if you’re verifying contracts compiled with different compiler versions or configurations.

The fully controlled verification method allows you to explicitly:

  • Specify the source code of the contracts (sources)
  • Set compilation arguments (compiler)
  • Set linked libraries (libraries)

The following example demonstrates how to perform a fully controlled verification.

await tenderly.verifyMultiCompilerAPI({
  contracts: [
    {
      contractToVerify: 'Greeter',
      sources: {
        'contracts/Greeter.sol': {
          name: 'Greeter',
          code: readFileSync('contracts/Greeter.sol', 'utf-8').toString(),
        },
        'hardhat/console.sol': {
          name: 'console',
          code: readFileSync('node_modules/hardhat/console.sol', 'utf-8').toString(),
        },
      },
      // solidity format compiler with a little modification at libraries param
      compiler: {
        version: '0.8.17',
        settings: {
          optimizer: {
            enabled: true,
            runs: 200,
          },
        },
        libraries: {
          'path/to/lib.sol': {
            addresses: {
              LibName1: '0x...',
              LibName2: '0x...',
            },
          },
        },
      },
      networks: {
        [NETWORK_ID]: {
          address: greeterAddress,
        },
      },
    },
  ],
});

Verification arguments

The verifyMultiCompilerAPI() method takes one argument — an array of contract objects. Each contract object consists of the following:

ParameterTypeDescription
contractToVerifystring

The name of the contract is to be verified. This can be a short name like Greeter or a fully qualified contract name like contracts/A/Greeter.sol:Greeter.

sourcesmap

A map of all sources that are needed to compile the contract. The key of the map is a source path to the contract, whereas the value is an object containing the name and UTF-8-encoded contract source.

sources: {
  'contracts/Greeter.sol': {
    name: 'Greeter',
    code: readFileSync('contracts/Greeter.sol', 'utf-8').toString(),
  },
  'hardhat/console.sol': {
    name: 'console',
    code: readFileSync('node_modules/hardhat/console.sol', 'utf-8').toString(),
  },
},
networksmap

A map containing all deployment addresses on multiple networks.

{
  networks: {
    [NETWORK_ID]: {
    address: greeterAddress,
    },
  },
}
librariesmap

A map containing information about deployments of libraries. The key is the relative path to the library source, relative to the contracts folder.

compiler.settings.libraries = {
  "path/to/lib.sol": {
    addresses: {
        "LibName1": "0x...",
        "LibName2": "0x..."
    }
  }
}