> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tenderly.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Verifying Proxy Contracts

> Follow this step-by-step guide to learn about verifying proxy smart contracts using the Tenderly-Hardhat plugin.

<Info>
  **Works on:** Virtual Environments and public networks (mainnets and testnets). The same plugin handles both; point it at the right network in `hardhat.config.ts`.
</Info>

This guide will walk you through proxy contract verification using `@tenderly/hardhat-tenderly`.

## Requirements

Automatic proxy verification works out of the box on `@tenderly/hardhat-tenderly`:

* **`>= 1.10.0`** (Ethers 5 line)
* **`>= 2.1.0`** (Ethers 6 line)

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
npm update @tenderly/hardhat-tenderly
```

It only works when you deploy proxies with [`@openzeppelin/hardhat-upgrades`](https://docs.openzeppelin.com/upgrades-plugins/api-hardhat-upgrades). [Hardhat Ignition](https://hardhat.org/ignition/docs/getting-started#overview) is **not supported** for proxy verification yet.

If you're on a lower plugin version and can't upgrade, use the [manual workaround](#workaround-for-lower-versions) below.

## Automatic verification

The plugin verifies three proxy patterns: `TransparentUpgradeableProxy`, `UUPSUpgradeableProxy`, and `BeaconProxy`.

<Steps>
  ### Set the auto-populate flag

  Under the hood the plugin drives `@nomicfoundation/hardhat-verify` with the `@openzeppelin/hardhat-upgrades` extension. Setting `TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true` lets the plugin populate `@nomicfoundation/hardhat-verify`'s verification URL for you.

  ```bash title=".env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true
  ```

  ### Write deployment script

  For a clearer view, you can check out [this GitHub repo](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification/ethers-v6) and go to **`scripts/proxy/`** to see the full example.

  <Tabs>
    <Tab title="Automatic">
      Deploy the proxy as usual using `deployProxy` from the **`@openzeppelin/hardhat-upgrades`** extension.

      <Warning>
        You must capture the object returned by the **`waitForDeployment`** function to interact with it further.
      </Warning>

      Under the hood, the automatic verification is implemented by wrapping the **`deployProxy`**, and waiting for completion of the deployment.

      ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}

      async function main() {
          console.log(
              "🖖🏽[ethers] Deploying TransparentUpgradeableProxy with VotingLogic as implementation on Tenderly.",
          );

          const VotingLogic = await ethers.getContractFactory("VotingLogic");
          let proxyContract = await upgrades.deployProxy(VotingLogic);
          proxyContract = await proxyContract.waitForDeployment();

          const proxyAddress = await proxyContract.getAddress();

          console.log("VotingLogic proxy deployed to:", proxyAddress);
          console.log(
              "VotingLogic impl deployed to:",
              await getImplementationAddress(ethers.provider, proxyAddress),
          );
      }

      main().catch((error) => {
          console.error(error);
          process.exitCode = 1;
      });
      ```
    </Tab>

    <Tab title="Manual">
      Verifying the proxy with the manual verification method is done after the proxy contract has been deployed, by calling the **`tenderly.verify()`** function.

      ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}

      async function main() {
          console.log(
              "🖖🏽[ethers] Deploying TransparentUpgradeableProxy with VotingLogic as implementation on Tenderly.",
          );

          const VotingLogic = await ethers.getContractFactory("VotingLogic");
          let proxyContract = await upgrades.deployProxy(VotingLogic);
          proxyContract = await proxyContract.waitForDeployment();

          const proxyAddress = await proxyContract.getAddress();

          console.log("VotingLogic proxy deployed to:", proxyAddress);
          console.log(
              "VotingLogic impl deployed to:",
              await getImplementationAddress(ethers.provider, proxyAddress),
          );

          await tenderly.verify({
              name: ProxyPlaceholderName,
              address: proxyAddress,
          });
      }

      main().catch((error) => {
          console.error(error);
          process.exitCode = 1;
      });
      ```
    </Tab>
  </Tabs>

  ### Run the script

  <Tabs>
    <Tab title="Virtual Environment">
      ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
      TENDERLY_AUTOMATIC_VERIFICATION=true \
      TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \
      npx hardhat run scripts/deploy.ts
      ```
    </Tab>

    <Tab title="Public network">
      When deploying to a public mainnet or testnet, specify whether you want public or private verification.

      ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
      TENDERLY_AUTOMATIC_VERIFICATION=true \
      TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \
      TENDERLY_PRIVATE_VERIFICATION=true \
      npx hardhat run scripts/deploy.ts --network mainnet_base
      ```
    </Tab>
  </Tabs>
</Steps>

## Workaround for lower versions

When using `@tenderly/hardhat-tenderly` at versions **`< 1.10.0`** and **`< 2.1.0`**, this workaround will enable automatic verification.

You need to verify the following:

* The proxy contract (e.g. OpenZeppelin's proxies)
* Implementation behind the proxy
* Any dependencies the implementation has
* New implementation instances deployed with upgrades

The verification process varies depending on the proxy contract type and the implementation.

<figure>
  <img src="https://mintcdn.com/tenderly/XsEZlaGXYskrtN68/images/Verifying%20Proxy%20Contracts%20Doc%20Chart.webp?fit=max&auto=format&n=XsEZlaGXYskrtN68&q=85&s=e9f97aa47100831216c10cc3ed7e7c58" alt="Tenderly Docs" width="2200" height="1219" data-path="images/Verifying Proxy Contracts Doc Chart.webp" />

  <figcaption />
</figure>

### Overview

In this guide, we'll use [an example Hardhat project](https://github.com/Tenderly/tenderly-examples) and the **`@tenderly/hardhat-tenderly`** plugin to demonstrate the verification of OpenZeppelin's [UUPSUpgradeable](https://docs.openzeppelin.com/contracts/4.x/api/proxy#UUPSUpgradeable), [TransparentUpgradeableProxy](https://docs.openzeppelin.com/contracts/4.x/api/proxy#TransparentUpgradeableProxy), and [BeaconProxy](https://docs.openzeppelin.com/contracts/4.x/api/proxy#BeaconProxy) alternatives.

<Warning>
  Proxy contracts need to be [verified manually](/contract-verification/hardhat#manual-verification). Turn off automatic verification like so:

  ```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // use manual verification
  tenderly.setup({ automaticVerifications: false });
  ```
</Warning>

To obtain the address of the deployed implementation, use the **`@openzeppelin/upgrades-core`** package and **`getImplementationAddress`** function.

Verifying the proxy implementation is usually straightforward; verify it just like any other contract.

```typescript title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
await tenderly.verify({
  // the new implementation contract
  name: 'VaultV2',
  // the address where implementation is deployed
  address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
});
```

To verify the proxy instance, you need to complete these two preliminary steps:

1. [Load the exact smart contract of the proxy](#loading-proxy-contracts) depending on the type of proxy you're using, so it gets compiled. You'll need to import the proxy contracts through the compiler by creating a dummy .sol file.
2. [Modify **`hardhat.config.ts`**](#configuring-solidity-compiler-overrides) to specify the settings OpenZepplin contracts were compiled with.

Once these steps are completed, you can proceed to verify the proxy just as you would any other contract.

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
await tenderly.verify({
  name: 'ERC1967Proxy', // or TransparentUpgradeableProxy or BeaconProxy
  address: await proxy.getAddress(),
});
```

<Steps>
  ### Clone the example repo

  ```sh theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  git clone git@github.com:Tenderly/tenderly-examples.git
  cd contract-verifications
  npm i
  ```

  ### Set up the Tenderly CLI

  ```sh theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  brew tap tenderly/tenderly && brew install tenderly
  tenderly login
  ```

  See the [Tenderly CLI repo](https://github.com/Tenderly/tenderly-cli) for non-Homebrew install options.

  ### Configure Hardhat

  In `hardhat.config.ts`, set `tenderly.username` and `tenderly.project` to your [project and username slugs](/platform/account/projects/slug).

  ### Create a Virtual Environment

  The fastest way to deploy and verify contracts is on a [Virtual Environment](/virtual-environments/overview).

  In the [Tenderly Dashboard](https://dashboard.tenderly.co), open **Virtual Environments** and create a new one. Pick the base network to fork from and a Chain ID, then copy the **Admin RPC URL** from the Virtual Environment's details page.

  Add the RPC URL to your `.env` file:

  ```bash title=".env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  TENDERLY_VIRTUAL_TESTNET_RPC=https://virtual.<network>.rpc.tenderly.co/<your-uuid>
  ```

  Then reference it in `hardhat.config.ts`:

  ```typescript title="hardhat.config.ts" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  networks: {
    virtualMainnet: {
      url: process.env.TENDERLY_VIRTUAL_TESTNET_RPC!,
      chainId: 73571, // the Chain ID you set when creating the Virtual Environment
    },
  },
  ```

  For a full walkthrough see the [Virtual Environment quickstart](/virtual-environments/quickstart).

  ### Run the tests

  ```sh theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  rm -rf .openzeppelin && npx hardhat test --network virtualMainnet
  ```

  <Note>When redeploying contracts, remove the **`.openzeppelin`** folder first. It caches information about proxies and their implementations.</Note>

  ### Load the proxy contracts

  To verify the proxy contract, create a **`DummyProxy.sol`** file and import the OpenZepplin proxy contracts you're working with. In doing so, these contracts are loaded and have passed through the compiler, enabling you to reference the exact contract source of the proxy during verification.

  ```sol theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // SPDX-License-Identifier: MIT
  pragma solidity ^0.8.17;


  abstract contract ERC1967ProxyAccess is ERC1967Proxy {}
  abstract contract UpgradableBeaconAccess is UpgradeableBeacon {}
  abstract contract BeaconProxyAccess is BeaconProxy {}
  abstract contract TransparentUpgradeableProxyAccess is TransparentUpgradeableProxy {}
  ```

  ### Configure Solidity compiler overrides

  <Note>
    The following overrides map was derived for **`@openzeppelin/contracts-upgradeable`** version **`4.9.1`**.
    They may differ for other versions of the package.
  </Note>

  After compiling Openzepplin's proxy contracts, you also need to specify the following:

  * Version of the Solidity compiler that was used to compile the contracts
  * Optimization settings used by Openzepplin's upgrades plugin when performing proxy deployment/upgrades

  <Warning>
    The **`hardhat-tenderly`** plugin uses both the source code of smart contracts and compiler settings
    for verification. If either of these settings is incorrect, the verification will fail.
  </Warning>

  Add the following **`overrides`** map to the **`config.solidity`** section of your Hardhat User config object.

  ```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const config: HardhatUserConfig = {
    solidity: {
      compilers: [{ version: '0.8.18' } /* OTHER COMPILER VERSIONS*/],
      overrides: {
        '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol': {
          version: '0.8.9',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
            },
          },
        },
        '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol': {
          version: '0.8.9',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
            },
          },
        },

        '@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol': {
          version: '0.8.9',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
            },
          },
        },
        '@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol': {
          version: '0.8.9',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
            },
          },
        },
        'contracts/proxy.sol': {
          version: '0.8.9',
          settings: {
            optimizer: {
              enabled: true,
              runs: 200,
            },
          },
        },
      },
    },
    /* OTHER CONFIG */
  };
  ```
</Steps>

## Verify by proxy type

These code samples show how to verify the implementation and the proxy for OpenZeppelin's three proxy patterns. The examples use a proxied `Vault` contract that references an ERC-20 token (`TToken`).

### UUPS proxy

To verify the UUPS proxy and the underlying information, call the **`hardhat-tenderly`** plugin twice:

1. To verify the implementation, you need to provide the following:

* **`name`** of your proxied contract (in our case **`Vault`**)
* Address where the contract was deployed using the **`getImplementationAddress`** method from **`@openzeppelin/upgrades-core`**.

2. To verify the proxy, provide the following:

* **`ERC1967Proxy`** as the proxy contract **`name`**
* Address of the proxy **`proxy.address`**

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
await tenderly.verify(
  {
    name: 'Vault',
    address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
  },
  {
    name: 'ERC1967Proxy',
    address: await proxy.getAddress(),
  },
);
```

#### Complete Code Sample

Here's a complete Hardhat test that does the following:

* Deploys the **`TToken`** (needed for the vault)
* Deploys **`Vault`** as a proxy, initialized with the **`TToken`** contract
* Verifies the proxy (**`ERC1967Proxy`**) instance deployed at **`await proxy.getAddress()`**
* Verifies the implementation instance **`Vault`**, deployed at **`getImplementationAddress(ethers.provider, await proxy.getAddress())`**
* Upgrades the proxy to **`VaultV2`**

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}

describe('Vault', () => {
  it('uups proxy deployment and verification', async () => {
    const VaultFactory = await ethers.getContractFactory('Vault');
    const TokenFactory = await ethers.getContractFactory('TToken');

    let token = await ethers.deployContract('TToken');
    token = await token.waitForDeployment();
    const tokenAddress = await token.getAddress();

    await tenderly.verify({
      name: 'TToken',
      address: tokenAddress,
    });

    let proxy = await upgrades.deployProxy(VaultFactory, [tokenAddress], {
      kind: 'uups',
    });
    await proxy.waitForDeployment();
    const proxyAddress = await proxy.getAddress();

    console.log('Deployed UUPS ', {
      proxy: proxyAddress,
      implementation: await getImplementationAddress(ethers.provider, proxyAddress),
    });

    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddress(ethers.provider, proxyAddress),
      },
      {
        name: 'ERC1967Proxy',
        address: proxyAddress,
      },
    );

    // upgrade
    const vaultV2Factory = await ethers.getContractFactory('VaultV2');
    proxy = (await upgrades.upgradeProxy(proxy, vaultV2Factory, {
      kind: 'uups',
    })) as Vault;

    await proxy.waitForDeployment();

    console.log('Upgraded UUPS ', {
      proxy: proxyAddress,
      implementation: await getImplementationAddress(ethers.provider, proxyAddress),
    });

    await tenderly.verify({
      name: 'VaultV2',
      address: await getImplementationAddress(ethers.provider, proxyAddress),
    });
  });
});
```

### Transparent proxy

To verify the UUPS proxy and the underlying information, call **`hardhat-tenderly`** while passing two contracts: **`Vault`** for the implementation, and **`TransparentUpgradeableProxy`** for the proxy itself.

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
await tenderly.verify(
  {
    name: 'Vault',
    address: await getImplementationAddress(ethers.provider, await proxy.getAddress()),
  },
  {
    name: 'TransparentUpgradeableProxy',
    address: await proxy.getAddress(),
  },
);
```

1. To verify the implementation, provide the following:

* **`name`** of your proxied contract (in our case **`Vault`**)
* Address where the contract was deployed, using the **`getImplementationAddress`** method from **`@openzeppelin/upgrades-core`**.

2. To verify the proxy, provide the following:

* **`TransparentUpgradeableProxy`** as the proxy contract **`name`**
* Address of the proxy **`await proxy.getAddress()`**

#### Complete Code Sample

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}

describe('Vault', () => {
  it('transparent upgradable proxy deployment and verification', async () => {
    const VaultFactory = await ethers.getContractFactory('Vault');
    const TokenFactory = await ethers.getContractFactory('TToken');

    let token = await ethers.deployContract('TToken');
    token = await token.waitForDeployment();
    const tokenAddress = await token.getAddress();

    await tenderly.verify({
      name: 'TToken',
      address: tokenAddress,
    });

    let proxy = await upgrades.deployProxy(VaultFactory, [tokenAddress], {
      kind: 'transparent',
    });
    await proxy.waitForDeployment();
    const proxyAddress = await proxy.getAddress();

    console.log('Deployed transparent', {
      proxy: proxyAddress,
      implementation: await getImplementationAddress(ethers.provider, proxyAddress),
    });

    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddress(ethers.provider, proxyAddress),
      },
      {
        name: 'TransparentUpgradeableProxy',
        address: proxyAddress,
      },
    );

    // upgrade
    const vaultV2Factory = await ethers.getContractFactory('VaultV2');

    proxy = (await upgrades.upgradeProxy(proxy, vaultV2Factory, {
      kind: 'transparent',
    })) as Vault;

    await proxy.waitForDeployment();

    console.log('Upgraded transparent ', {
      proxy: proxyAddress,
      implementation: await getImplementationAddress(ethers.provider, proxyAddress),
    });

    await tenderly.verify({
      name: 'VaultV2',
      address: await getImplementationAddress(ethers.provider, proxyAddress),
    });
  });
});
```

### Beacon proxy

To verify the Beacon proxy and the underlying information, you have to verify two contracts: the **`Vault`** (implementation) and OpenZepplin's **`UpgradableBeacon`**:

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
await tenderly.verify(
  {
    name: 'Vault',
    address: await getImplementationAddressFromBeacon(ethers.provider, await beacon.getAddress()),
  },
  {
    name: 'UpgradeableBeacon',
    address: await beacon.getAddress(),
  },
);
```

#### Complete Code Sample

```ts title="example.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}

describe('Vault', () => {
  it('beacon proxy deployment and verification', async () => {
    const VaultFactory = await ethers.getContractFactory('Vault');
    const TokenFactory = await ethers.getContractFactory('TToken');

    let token = await ethers.deployContract('TToken');
    token = await token.waitForDeployment();
    const tokenAddress = await token.getAddress();

    await tenderly.verify({
      name: 'TToken',
      address: tokenAddress,
    });

    let beacon = (await upgrades.deployBeacon(VaultFactory)) as UpgradeableBeacon;

    await beacon.waitForDeployment();
    const beaconAddress = await beacon.getAddress();

    let vault = await upgrades.deployBeaconProxy(beacon, VaultFactory, [tokenAddress], {
      initializer: 'initialize',
    });
    await vault.waitForDeployment();

    console.log('Deployed beacon ', {
      proxy: beaconAddress,
      implementation: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
      beacon: beaconAddress,
    });

    await tenderly.verify(
      {
        name: 'Vault',
        address: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
      },
      {
        name: 'UpgradeableBeacon',
        address: beaconAddress,
      },
    );

    const vaultV2Factory = await ethers.getContractFactory('VaultV2');

    // upgrade
    vault = await upgrades.deployBeaconProxy(beacon, vaultV2Factory, [tokenAddress]);

    await upgrades.upgradeBeacon(beaconAddress, vaultV2Factory, {});

    console.log('Upgraded beacon ', {
      proxy: beaconAddress,
      implementation: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
      beacon: beaconAddress,
    });

    await tenderly.verify({
      name: 'VaultV2',
      address: await getImplementationAddressFromBeacon(ethers.provider, beaconAddress),
    });
  });
});
```
