Manual Contract Verification

In addition to automatic verification, you can verify your Smart Contracts manually in your Hardhat deployment scripts.

Manual contract verification is applicable in the following situations:

  • Your Smart Contract is already deployed on a blockchain.
  • You want to have more control over verification configuration.

The Tenderly Hardhat plugin verifies contracts publicly by default, unless you configure it to use the private mode.

Manual verification methods

You can choose between two ways to manually verify a contract, including:

  • Simple, where a single function call completes the verification process by accepting two arguments – a contract name and a contract address.
  • Advanced, where you need to specify arguments manually with a high level of detail, which provides more freedom and flexibility throughout the verification process.

Turn off automatic verification: Before deploying a contract using Hardhat, turn off the automatic contract verification in your hardhat.config.ts file by calling

tdly.setup({ automaticVerifications: false });

Simple manual verification

The simple way to manually verify a contract is to call the hre.tenderly.verify() function and provide a reference to the contract you want to verify.

First, deploy the Greeter Smart Contract using Ethers.js. Before the verification code, you have to await for deployment to be confirmed via await greeter.waitForDeployment().

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

After deploying the contract, you can verify it using the Tenderly Hardhat plugin.

To successfully verify the contract using the .verify() method, you need to pass a configuration object. The object consists of 2 fields:

  • the exact name of the Smart Contract as it is in the source file, or a fully qualified contract name like "contracts/A/Token.sol:Token".
  • the address of your deployed Smart Contract

If you want to verify multiple contracts, you need to repeat verification for each one.

The most common reason why verification fails is a mismatch between the given name property and the actual name of the Smart Contract.

Here’s a script employing this method (scripts/manual-simple.ts):

// File: scripts/greeter/manual-simple-public.ts
import { ethers, tenderly } from 'hardhat';
async function main() {
  let greeter = await ethers.deployContract('Greeter', ['Hello, Manual Hardhat!']);
  greeter = await greeter.waitForDeployment();
  const address = await greeter.getAddress();
  console.log('Manual Advanced: {Greeter} deployed to:', address);
    name: 'Greeter',
main().catch(error => {
  process.exitCode = 1;

To perform the verification, run the following:

npx hardhat run scripts/manual-simple.ts --network ropsten

If everything goes well, you should see a similar output in your terminal.

Output of npx hardhat run scripts/manual-simple.ts --network ropsten

Advanced manual verification

Advanced manual verification allows for the highest level of control over verification parameters. It enables you to specify everything from compiler settings to additional details about each contract you want to verify and the libraries your contracts use.

Deployment example

Take a look at the code example below that demonstrates the advanced manual verification approach. The Greeter contract uses the hardhat/console.sol library, so we need to account for that explicitly.

With more power over configuring the verification process, you need to do provide the following key information for each contract you’re verifying (the contracts list):

  • For contracts: Specify the source code of the Greeter Smart Contract and the address it’s deployed to on a specific network (or several networks).
  • For libraries: Specify a list of all the libraries referenced by the contract as additional contracts, with the same information you’d provide to specify a contract. If already deployed, you can paste the address to compiler.settings.libraries parameter like you would paste in solidity compiler input.
// File: scripts/greeter/manual-advanced.ts
import { readFileSync } from 'fs';
import { ethers, tenderly } from 'hardhat';
export async function main() {
  // deploy stuff but later pretend it's been deployed ages ago on Sepolia.
  let greeter = await ethers.deployContract('Greeter', ['Hello, Manual Hardhat!']);
  greeter = await greeter.waitForDeployment(); // await for the contract to be fully deployed
  const greeterAddress = await greeter.getAddress();
  console.log('Manual Simple: {Greeter} deployed to', greeterAddress);
  // pretend it's been deployed ages ago on Sepolia in a different deployment.
  // Hence we know NETWORK_ID=11155111 and the address of the contract (greeterAddress)
  const NETWORK_ID = 11155111;
  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
        // see the warning below
        compiler: {
          version: '0.8.17',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
        networks: {
          [NETWORK_ID]: {
            address: greeterAddress,
main().catch(error => {
  process.exitCode = 1;

As seen in the example above, the two core aspects of using the verifyMultiCompilerApi are:

  • The compiler configuration that was used to compile deployed contracts.
  • The list of contracts undergoing verification.

Let’s explore these in detail.

The Solidity compiler param

compiler field is the same type as the official solidity compiler.

Verification can fail if the compiler config you specified differs significantly from the one actually used to compile the Smart Contract deployed on-chain.


In order to satisfy our own API, we had to modify the compiler object a little bit.
If you wish to specify libraries for the contracts, add an extra addresses param.

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

The list of contracts

The contracts property of the configuration is used to specify all the contracts you’re verifying (lines 26-37) and all the Solidity libraries referenced by the contracts (lines 38-49), in a single function call.

Here’s a breakdown of the contracts property of the advanced verification configuration:

contractToVerifystringThe name of the contract to verify. This can be a short name like Greeter or a fully qualified contract name like contracts/A/Greeter.sol:Greeter
sourcesstringA map of all sources that are needed in order to compile the contract. The key of the map is a source path to the contract.

For Smart Contracts, this is a relative path to the Smart Contract, relative to the contracts directory.
For libraries, this is a relative path and it should match the one in the import statement within the Contract that’s using it.

sources.key.namestringName of the contract. This must short name format.
networksObjectThe set of networks where the contract is deployed. For Libraries that aren’t deployed, pass an empty object {}.
networks.keyintThe ID of a network where contract is deployed (e.g., Mainnet is 1, Rinkeby is 4)
networks.key.addressstringThe address of a contract deployed on a specific network.
compiler.settings.librariesObjectObject in which you specify the library dependencies that are needed in order to compile the contract.

Take a look at the Solidity library overview for more information.

Specifying libraries for Tenderly verification

Alongside the Greeter contract, you also need to send an entry corresponding to hardhat/console.sol Smart Contract, which is in turn a library. The verification process requires the specification of all the libraries referenced by the Smart Contract.

Here are a few guidelines for specifying libraries:

  • Library source: Retrieve the contents of node_modules/hardhat/console.sol and pass them as the code verification parameter.
  • Source path: Pass sourcePath, the path to the library: hardhat/console.sol. It’s a relative path and it should match the path in the import statement in the Greeter contract.
  • Networks: Pass an empty object ({}) for networks because console.sol is deployed alongside the Greeter. This means that the contract isn’t deployed separately, so you only have to make it available for the Tenderly Verification process. In the case the library you’re using is deployed on the network, pass the actual address.

When verifying a contract that uses several libraries, each library must be explicitly specified, including the source code. Include the address of the library if it’s pre-deployed on the network or pass an empty network configuration if the library is linked to your contract at compile time.

Explore the example project in the plugin Git repo to see how to include a separately deployed contract/library. The second example (Calculator contract) demonstrates this case.