# Verify Contracts via Dashboard Source: https://docs.tenderly.co/contract-verification/dashboard Learn how to verify smart contracts publicly and privately in the browser through the Tenderly Dashboard. **Works on:** public networks (mainnets and testnets). To verify a contract on a Virtual Environment, see [Deploy and verify contracts](/virtual-environments/develop/deploy-contracts). Verify smart contracts from your browser in the Tenderly Dashboard. This is the fastest path when you're not using Hardhat or Foundry; if you are, the [Hardhat](/contract-verification/hardhat) or [Foundry](/contract-verification/foundry) workflows automate this. ## Prerequisites To verify a contract, you need to provide: * **The source code** * **The exact compiler settings** used to compile the deployed version You can upload the source code in several ways through the Dashboard: * **JSON Upload**: paste the JSON metadata generated by the Solidity compiler. * **ABI Upload**: paste the contract ABI. * **Contract Source Upload**: paste source code directly, upload a single source file, or upload an entire project directory. The directory option works best for contracts that import other contracts. ## Public and private verification When verifying a contract, you choose between two visibility modes: * **Public**: the contract source is visible to anyone using Tenderly. * **Private**: the contract source is only visible inside the project you verified it in. The **Verification** column on the Contracts list shows a contract's current status: `Unverified`, `Public`, or `Private`. ## Dashboard contract verification guide After you've [added an unverified contract](/developer-explorer/contracts) to a Tenderly project, here are the steps to verify it using the Dashboard. Open the contract's page. An unverified contract shows a banner with a **Verify Contract** button. Unverified contract banner with Verify Contract button Select **Public** or **Private**. Private is selected by default, and scopes the verified source to the current project. Verify Contract visibility step Pick one of: * **JSON Upload** * **ABI Upload** * **Contract Source Upload**, which opens a submenu: **Paste Source Code**, **Upload Source File**, or **Upload Project Directory**. Verify Contract source code upload method choice Contract Source Upload submenu If you paste source directly, give the file a name and paste the contract source into the editor. Paste source code screen If the upload contains several contracts, pick the one matching the address shown in the previous step. If any imports are missing, the Dashboard prompts you to add them before continuing. Choose a Deployed Contract step Fill in the compiler version, optimizer settings (enabled / runs), EVM version, ViaIR, and library address pairs if your contract uses linked libraries. Any mismatch with the deployed bytecode will fail verification. Compiler Settings step filled in Click **Finish** to submit. The contract's verification status and info panel update immediately. Contract page after successful private verification ## How to verify smart contracts in bulk If you have several unverified contracts in your project, click **Verify All Contracts** on the Contracts list. This opens the **Verify multiple contracts** dialog, listing every unverified contract with a **Make Private** toggle and an **Add source** button per row. Verify multiple contracts dialog Click **Add source** next to each contract and supply the upload, compiler settings, and visibility as in the [single-contract flow](#dashboard-contract-verification-guide) above. The bottom **Verify** button stays disabled until every row has a source, then submits all of them at once. # Smart Contract Verification Using Foundry Source: https://docs.tenderly.co/contract-verification/foundry Verify Foundry-deployed contracts on public networks through Tenderly's verification API, privately or publicly, with forge verify-contract. **Works on:** public networks (mainnets and testnets). To verify contracts on a Virtual Environment, see [Deploy and verify contracts](/virtual-environments/develop/deploy-contracts) and [Verify proxy contracts with Foundry](/virtual-environments/develop/verify-proxy-contracts). Tenderly verifies smart contracts deployed with Foundry's `forge create`, `forge script`, and `forge verify-contract` commands through its Etherscan-compatible verification API. Verification is either private (the source is visible only inside your Tenderly project) or public (visible to anyone with the link). ## Before you begin You need your Tenderly account and project [slugs](/platform/account/projects/slug), a [Tenderly access key](/platform/account/projects/api-tokens) (Dashboard β†’ Account Settings β†’ Authorization), and a funded account on the target network. Tenderly's verifier matches the metadata hash the Solidity compiler appends to deployed bytecode against the source you submit. Keep the metadata in the compiled output, and pin the compiler settings so they can't drift between deploy time and verify time: ```toml title="foundry.toml" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} [profile.default] src = "src" out = "out" libs = ["lib"] solc_version = "0.8.24" optimizer = true optimizer_runs = 200 # Required for Tenderly contract verification: # keep the CBOR metadata so the verifier can match the on-chain bytecode hash. cbor_metadata = true bytecode_hash = "ipfs" ``` `cbor_metadata = true` and `bytecode_hash = "ipfs"` are Foundry's defaults, but some templates strip them; `bytecode_hash = "none"` breaks verification. Compiler-setting drift between deploy and verify causes `Bytecode does not match deployed contract` failures. The examples below use Base Sepolia (chain ID `84532`). Set up the environment: ```bash title=".env" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} BASE_SEPOLIA_RPC=https://sepolia.base.org PRIVATE_KEY=... # a funded key on the target network TENDERLY_ACCOUNT=... # your account slug TENDERLY_PROJECT=... # your project slug TENDERLY_ACCESS_KEY=... # from Account Settings -> Authorization TENDERLY_PRIVATE_VERIFIER_URL=https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT}/project/${TENDERLY_PROJECT}/etherscan/verify/network/84532 TENDERLY_PUBLIC_VERIFIER_URL=${TENDERLY_PRIVATE_VERIFIER_URL}/public ``` ## Verifier URL Every Foundry verification command takes `--verifier-url` pointing at Tenderly's verification API, and `--etherscan-api-key $TENDERLY_ACCESS_KEY` for authentication: ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} https://api.tenderly.co/api/v1/account/$TENDERLY_ACCOUNT/project/$TENDERLY_PROJECT/etherscan/verify/network/$NETWORK_ID ``` Each URL segment maps to a value you can read off the Dashboard: | Segment | What it is | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `account/$TENDERLY_ACCOUNT` | Your Tenderly account (user or organization) [slug](/platform/account/projects/slug). Case-sensitive. | | `project/$TENDERLY_PROJECT` | The project the verified contract is filed under. | | `network/$NETWORK_ID` | The chain the contract is deployed on, as a decimal chain ID (`1` for Ethereum Mainnet, `8453` for Base, `84532` for Base Sepolia). | | no suffix | Private verification: the contract is visible only inside your project. | | `/public` suffix | Public verification: the contract source is visible to anyone with the link, no Tenderly login required. | ## Private verification Privately verified contracts are visible only to your project's members, under **Contracts** in the Dashboard. ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} COUNTER_ADDRESS=0x... # the deployed contract address forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --constructor-args $(cast abi-encode "constructor(uint256)" 7) \ --watch ``` `forge verify-contract` expects constructor arguments **already ABI-encoded**; use `cast abi-encode` to produce them. `--watch` polls the verifier until verification finishes and prints the result. Foundry's output prints a `URL: https://etherscan.io/address/...` line on success. This is a display quirk of the Etherscan-compatible flow; the contract was verified at Tenderly, not Etherscan. Confirm in the Dashboard under **Contracts**. ## Public verification Swap the verifier URL for the `/public`-suffixed variant; nothing else changes. The verified source page becomes reachable by anyone with the link, without a Tenderly login. ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \ --verifier-url $TENDERLY_PUBLIC_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --constructor-args $(cast abi-encode "constructor(uint256)" 7) \ --watch ``` Public verification is irreversible. A contract verified publicly stays public. Private and public verifications are independent records: to make a privately verified contract public, re-verify it against the `/public` URL. ## Deploy and verify in one step `forge create` and `forge script` accept the same flags inline, so deployment and verification run as one command. Unlike `forge verify-contract`, both take constructor arguments as raw values and ABI-encode them for you: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge create src/Counter.sol:Counter \ --rpc-url $BASE_SEPOLIA_RPC \ --private-key $PRIVATE_KEY \ --broadcast \ --verify \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --constructor-args 7 ``` Put `--constructor-args` last. It greedily consumes the rest of the command line, so any flag placed after it is treated as another constructor argument. For multi-contract deployments, use `forge script` with `--slow`; every contract the script deploys is verified automatically with its constructor arguments taken from the broadcast log. Without [`--slow`](https://www.getfoundry.sh/reference/forge/script?highlight=slow#forge-script), broadcast batching can submit a transaction before the previous one is confirmed, which can race the verification step: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge script script/Deploy.s.sol:DeployScript \ --rpc-url $BASE_SEPOLIA_RPC \ --private-key $PRIVATE_KEY \ --broadcast --slow \ --verify \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY ``` ## Verify a contract you didn't deploy When a contract is verified on a public explorer (Etherscan, Basescan) but not in your Tenderly project, clone the verified source locally with [`forge clone`](https://www.getfoundry.sh/reference/forge/clone) and re-verify it through Tenderly's API: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge clone $CONTRACT_ADDRESS -e $ETHERSCAN_API_KEY --chain 1 ``` `forge clone` downloads the verified source from the source chain's Etherscan-compatible API (use the matching explorer's key for each chain), reconstructs the project layout, and pins the compiler settings the original deployer used. Run `forge build` in the cloned directory to confirm it compiles; import-resolution errors usually trace back to `remappings.txt`. Then verify against Tenderly: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge verify-contract $CONTRACT_ADDRESS src/MyContract.sol:MyContract \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --watch ``` If verification fails with `Bytecode does not match deployed contract`, pass the original compiler settings explicitly. All of them are listed on the explorer page where the contract is already verified, under the contract source code section: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge verify-contract $CONTRACT_ADDRESS src/MyContract.sol:MyContract \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --compiler-version v0.8.27+commit.40a35a09 \ --optimizer-runs 10000 \ --evm-version prague \ --constructor-args $ENCODED_ARGS \ --watch ``` ### Contracts that live in the `lib` directory `forge build` only compiles what's reachable from `src/`. A contract that exists solely inside a `lib/` dependency (a proxy, a standard ERC implementation) never enters the build cache, and `forge verify-contract` fails with: ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} Error: Failed to get standard json input - cannot resolve file at "lib/openzeppelin-contracts-upgradeable/lib/..." ``` Create a one-line `src/Imports.sol` that imports the contract, then rebuild: ```solidity title="src/Imports.sol" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; ``` ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} forge build --force ``` `Imports.sol` is never sent to the verifier; it only forces the compiler to cache the contract and its dependencies. Then verify using the contract's full `lib/` path, since that's where the source physically lives. For a `TransparentUpgradeableProxy` you didn't deploy, the constructor is `(address _logic, address initialOwner, bytes _data)`, and most of it can be reconstructed from chain state: ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} # 1. _logic: the implementation address (ERC-1967 slot) cast implementation $PROXY_ADDRESS --rpc-url $RPC_URL # 2. initialOwner: owner of the ProxyAdmin contract cast admin $PROXY_ADDRESS --rpc-url $RPC_URL # returns the ProxyAdmin cast call $PROXY_ADMIN "owner()(address)" --rpc-url $RPC_URL # returns initialOwner # 3. _data: the initialize() calldata from the deployment transaction # (decode the factory calldata on the explorer; initialize(address) is selector 0xc4d66de8) ENCODED_ARGS=$(cast abi-encode "constructor(address,address,bytes)" $LOGIC $INITIAL_OWNER $DATA) forge verify-contract $PROXY_ADDRESS \ lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy \ --verifier-url $TENDERLY_PRIVATE_VERIFIER_URL \ --etherscan-api-key $TENDERLY_ACCESS_KEY \ --compiler-version v0.8.27+commit.40a35a09 \ --optimizer-runs 200 \ --evm-version shanghai \ --constructor-args $ENCODED_ARGS \ --watch ``` Compiler version, optimizer runs, and EVM version come from the explorer page of the already-verified implementation. If the implementation isn't verified anywhere, fall back to the deploying project's `foundry.toml` or deployment scripts. ## Troubleshooting | Symptom | Likely cause | Fix | | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `unauthorized` | Missing or wrong `--etherscan-api-key`, or the access key was revoked. | Generate a new [access key](/platform/account/projects/api-tokens) and re-set `TENDERLY_ACCESS_KEY`. | | `not found` | Wrong account slug, project slug, or chain ID in the verifier URL. | Re-check each URL segment. The account slug is case-sensitive. | | `Bytecode does not match deployed contract` | The submitted source compiled differently than what's deployed: an `optimizer_runs` mismatch, a different `solc_version` or EVM version, or a stripped metadata hash. | Pin `solc_version`, `optimizer`, `optimizer_runs`, and `bytecode_hash` in `foundry.toml`, or pass `--compiler-version --optimizer-runs --evm-version` explicitly. Run `forge clean && forge build` before re-verifying. | | `Failed to deserialize content` | The verifier returned an error string Foundry can't parse, usually wrapping an upstream auth or path error. | Re-check the URL and re-run with `-vvvv` to see the raw response. | | `Failed to get standard json input - cannot resolve file at lib/...` | The contract is never imported from `src/`, so it's missing from the build cache. | Create a `src/Imports.sol` that imports it, then `forge build --force`. See [Contracts that live in the `lib` directory](#contracts-that-live-in-the-lib-directory). | | `Dry run enabled, not broadcasting transaction` on `forge create` | `--broadcast` not passed, or swallowed by `--constructor-args`. | Move `--constructor-args` to be the last flag. | | Only the first contract verifies on `forge script` | RPC race: the second transaction was submitted before the first receipt. | Add `--slow`. | | Verification succeeds but the contract doesn't appear in the project | Wrong project slug in the verifier URL. | Re-verify with the correct slug. The contract is filed under whichever project the URL pointed at. | # Smart Contract Verification Using Hardhat Source: https://docs.tenderly.co/contract-verification/hardhat Learn how to use the Tenderly-Hardhat plugin to perform automatic and manual smart contract verification. **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`. The Tenderly-Hardhat plugin verifies contracts deployed from Hardhat against [Virtual Environments](/virtual-environments/overview) or [public networks](/platform/supported-networks). Use **automatic verification** to verify on every deploy, or **manual verification** to call `tenderly.verify()` explicitly from your scripts. On public networks, the plugin verifies **publicly by default**. To verify privately (visible only inside your project/organization), set `privateVerification: true` in `hardhat.config.ts`. See [Configure authentication and verification visibility](#configure-authentication-and-verification-visibility). ## 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. | Your stack | Hardhat version | Tenderly-Hardhat plugin version | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------ | | [Ethers 6+](https://www.npmjs.com/package/ethers/v/6.0.0), with [Hardhat Ignition](https://hardhat.org/ignition/docs/getting-started#overview) or [hardhat-verify](https://www.npmjs.com/package/@nomicfoundation/hardhat-verify) | **`^2.22.6`** | [`@tenderly/hardhat-tenderly@^2.6.0`](https://www.npmjs.com/package/@tenderly/hardhat-tenderly) | | [Ethers 5+](https://www.npmjs.com/package/ethers/v/5.0.0) with [hardhat-verify](https://www.npmjs.com/package/@nomicfoundation/hardhat-verify) | **`< 2.19.0`** | [`@tenderly/hardhat-tenderly@1.8.0`](https://www.npmjs.com/package/@tenderly/hardhat-tenderly/v/1.8.0) | Verifying against current Virtual Environment RPC URLs (the region and `org/project/slug` forms shown in the dashboard) requires plugin version **2.6.0 or newer**. ## Verification methods The Tenderly-Hardhat plugin supports three methods for contract verification. * **[Automatic 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](https://hardhat.org/ignition/docs/getting-started#overview). Control automatic verification by using the **`TENDERLY_AUTOMATIC_VERIFICATION`** environment variable. * **[Manual verification](#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](#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](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification/). All the examples use the sample `Greeter` contract. Browse the example projects directly: * [Ethers 6 examples](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification/ethers-v6) * [Ethers 5 examples](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification/ethers-v5) ## 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. ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} yarn add -D @tenderly/hardhat-tenderly@^2.6.0 ``` ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} yarn add -D @tenderly/hardhat-tenderly@^1.8.0 ``` ### Log into Tenderly CLI The plugin requires you to be logged into the [Tenderly CLI](https://github.com/Tenderly/tenderly-cli). ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-macos.sh | sudo sh ``` ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sudo sh ``` Use this command to check if you're logged in already: ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} tenderly whoami ``` To log in, run: ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} 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. Only on plugin versions older than 2.4.0 (Ethers 6) and 1.10.0 (Ethers 5) do you need to call **`tdly.setup()`** to initialize the plugin. On current versions the import alone initializes it, and `tdly.setup()` is a no-op: ```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} tdly.setup({automaticVerifications: !!process.env.TENDERLY_AUTOMATIC_VERIFICATION}); ``` ```ts title="hardhat.config.ts" showLineNumbers {2} theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} import "@nomicfoundation/hardhat-toolbox"; import "@tenderly/hardhat-tenderly"; const config: HardhatUserConfig = { solidity: "0.8.19", }; export default config; ``` ### Configure authentication and verification visibility You need to configure Tenderly by passing account information and verification visibility. * The **`privateVerification`** config parameter is the single visibility switch: `true` keeps the source code visible only to your project collaborators or organization; omitted or `false` verifies publicly (the default). * The plugin reads no visibility environment variable. If you want an environment-variable workflow, wire one into `privateVerification` yourself, as below with `TENDERLY_PRIVATE_VERIFICATION` (the convention the [official examples](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification) use). Configure Tenderly by adding the following code snippet to the `hardhat.config.ts` file: ```ts title="hardhat.config.ts" showLineNumbers {3 - 12} theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} const config: HardhatUserConfig = { solidity: "0.8.19", tenderly: { username: "your-account-slug", project: "your-project-slug", // true: contract visible only inside your project. // Omitting or setting to false verifies publicly (the default). // TENDERLY_PRIVATE_VERIFICATION is plain dotenv wiring, not a // variable the plugin reads itself. privateVerification: process.env.TENDERLY_PRIVATE_VERIFICATION === "true", }, }; export default config; ``` ### 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: * **[Public mainnets or testnets](/platform/supported-networks)** via [Node RPC](/node-rpc/overview) * **[Virtual Environments](/virtual-environments/overview)** for development, staging, and demoing When deploying with Hardhat Ignition, set `TENDERLY_AUTOMATIC_VERIFICATION=true` and pass a `--deployment-id`: the plugin verifies every contract from the deployment through `hre.tenderly.verify()`. The `etherscan` configuration below is needed only for the standalone `npx hardhat verify` and `npx hardhat ignition verify` commands on public networks. ```ts title="hardhat.config.ts" showLineNumbers {8-12, 24-38} theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} const config: HardhatUserConfig = { solidity: "0.8.19", networks: { mainnet_base: { url: "https://base.gateway.tenderly.co", chainId: 8453, }, tenderly_base_testnet: { // your Tenderly Virtual Environment RPC url: "https://virtual.base.eu.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d", chainId: 73571, }, }, tenderly: { username: "your-account-slug", project: "your-project-slug", // true: contract visible only inside your project. // Omitting or setting to false verifies publicly (the default). // TENDERLY_PRIVATE_VERIFICATION is plain dotenv wiring, not a // variable the plugin reads itself. privateVerification: process.env.TENDERLY_PRIVATE_VERIFICATION === "true", }, etherscan: { // Tenderly access key (not an Etherscan API key) apiKey: process.env.TENDERLY_ACCESS_KEY, customChains: [ { network: "mainnet_base", chainId: 8453, urls: { // Drop the trailing /public to verify into your project privately. apiURL: "https://api.tenderly.co/api/v1/account/your-account-slug/project/your-project-slug/etherscan/verify/network/8453/public", browserURL: "https://dashboard.tenderly.co", }, }, ], }, }; export default config; ``` ```ts title="hardhat.config.ts" showLineNumbers {9-13, 15-24} theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} const config: HardhatUserConfig = { solidity: "0.8.19", networks: { mainnet_base: { url: "https://base.gateway.tenderly.co", chainId: 8453, }, tenderly_base_testnet: { // your Tenderly Virtual Environment RPC url: "https://virtual.base.eu.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d", chainId: 73571, }, }, tenderly: { username: "your-account-slug", project: "your-project-slug", // true: contract visible only inside your project. // Omitting or setting to false verifies publicly (the default). // TENDERLY_PRIVATE_VERIFICATION is plain dotenv wiring, not a // variable the plugin reads itself. privateVerification: process.env.TENDERLY_PRIVATE_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. Automatic verification is **enabled by default**. To disable it, set the **`TENDERLY_AUTOMATIC_VERIFICATION`** environment variable recognized by the plugin to `false`. 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()`**. ```diff title='deploy.ts' theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} 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: ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} const greeter = await ( await ethers.deployContract("Greeter", ["Hello, Hardhat!"]) ).waitForDeployment(); ``` The best way to explore how the automatic verification process works is to go through an example in [our GitHub repo](https://github.com/Tenderly/hardhat-tenderly/tree/master/examples/contract-verification/ethers-v5). This example uses our sample `Greeter` contract. Modify the Hardhat script to capture the value returned from **`deployed()`**. ```diff title='deploy.ts' theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} export async function main() { console.log("πŸ––πŸ½[ethers] Deploying and Verifying Greeter in Tenderly"); const Greeter = await ethers.getContractFactory("Greeter"); - const greeter = await Greeter.deploy("Hello, Hardhat!"); + let greeter = await Greeter.deploy("Hello, Hardhat!"); - await greeter.deployed(); + greeter = await greeter.deployed(); const greeterAddress = await greeter.address; console.log("{Greeter} deployed to", greeterAddress); } main().catch((error) => { console.error(error); process.exitCode = 1; }); ``` In more compact form: ```typescript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} const greeter = await ( await Greeter.deploy("Hello, Hardhat!") ).deployed(); ``` ### Run the script Automatic verification is enabled by default. The commands below set **`TENDERLY_AUTOMATIC_VERIFICATION`** explicitly so each run's behavior is unambiguous. Run the script with the commands below. The link to the verified contract will be displayed in the Terminal output. ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} 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. ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_AUTOMATIC_VERIFICATION=true \ npx hardhat run scripts/deploy.ts --network virtual_base ``` When deploying to a public mainnet or testnet, specify whether you want public or private verification. Make sure your `hardhat.config.ts` wires this variable into `privateVerification`, as shown above. ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_PRIVATE_VERIFICATION=true \ # Verify privately. Omit to verify publicly (the default). TENDERLY_AUTOMATIC_VERIFICATION=true \ npx hardhat run scripts/deploy.ts --network mainnet_base ``` ### 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](/contract-verification/hardhat-proxy). ## 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. ```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} 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: ```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]); greeter = await greeter.waitForDeployment(); const counter = await (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 verification Automatic verification is enabled by default. Switch it off for a run by setting the `TENDERLY_AUTOMATIC_VERIFICATION` environment variable to `false`: ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_AUTOMATIC_VERIFICATION=false npx hardhat run scripts/deploy.ts --network virtual_base ``` On plugin versions older than 2.4.0 (Ethers 6) and 1.10.0 (Ethers 5), disable it in `hardhat.config.ts` instead: `tdly.setup({ automaticVerifications: false })`. ### Modify the deployment script This uses the same examples as in automatic verification, but this time with the **`tenderly.verify()`** method to verify the contract. ```diff title="deploy.ts" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} + 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; }); ``` ```diff title="deploy.ts" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} + import { tenderly } from "hardhat"; async function main() { console.log("πŸ––πŸ½[ethers] Deploying and Verifying Greeter in Tenderly"); const Greeter = await ethers.getContractFactory("Greeter"); - const greeter = await Greeter.deploy("Hello, Manual Hardhat!"); + let greeter = await Greeter.deploy("Hello, Manual Hardhat!"); - await greeter.deployed(); + greeter = await greeter.deployed(); const address = await greeter.address; 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. ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_AUTOMATIC_VERIFICATION=false \ npx hardhat run scripts/deploy.ts --network virtual_base ``` 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_PRIVATE_VERIFICATION=true \ # Verify privately. Omit to verify publicly (the default). TENDERLY_AUTOMATIC_VERIFICATION=false \ npx hardhat run scripts/deploy.ts --network mainnet_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. ```js theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} 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: | | | | | ------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `contractToVerify` | string | 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`.* | | `sources` | map | 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. `js 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(), }, }, ` | | `networks` | map | A map containing all deployment addresses on multiple networks. `js { networks: { [NETWORK_ID]: { address: greeterAddress, }, }, } ` | | `libraries` | map | A map containing information about deployments of libraries. The key is the relative path to the library source, relative to the `contracts` folder. `js compiler.settings.libraries = { "path/to/lib.sol": { addresses: { "LibName1": "0x...", "LibName2": "0x..." } } } ` | # Verifying Proxy Contracts Source: https://docs.tenderly.co/contract-verification/hardhat-proxy Follow this step-by-step guide to learn about verifying proxy smart contracts using the Tenderly-Hardhat plugin. **Works on:** public networks (mainnets and testnets). To verify proxy contracts on a [Virtual Environment](/virtual-environments/overview), follow [Verify proxy contracts with Foundry](/virtual-environments/develop/verify-proxy-contracts). 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`. ### 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. The handoff authenticates with your [access key](/platform/account/projects/api-tokens), so `TENDERLY_ACCESS_KEY` must be set as well. ```bash title=".env" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true TENDERLY_ACCESS_KEY=your-access-key ``` ### 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. Deploy the proxy as usual using `deployProxy` from the **`@openzeppelin/hardhat-upgrades`** extension. You must capture the object returned by the **`waitForDeployment`** function to interact with it further. 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; }); ``` 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: "TransparentUpgradeableProxy", address: proxyAddress, }); } main().catch((error) => { console.error(error); process.exitCode = 1; }); ``` ### Run the script Automatic verification is enabled by default, so no extra variable is needed to turn it on. Specify whether you want [public or private verification](/contract-verification/hardhat#configure-authentication-and-verification-visibility): ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} TENDERLY_AUTOMATIC_POPULATE_HARDHAT_VERIFY_CONFIG=true \ TENDERLY_PRIVATE_VERIFICATION=true \ # Verify privately. Omit to verify publicly (the default). npx hardhat run scripts/deploy.ts --network mainnet_base ``` ## 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.
Tenderly Docs
### 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. Proxy contracts need to be [verified manually](/contract-verification/hardhat#manual-verification). On the plugin versions this workaround targets (older than 2.4.0 on the Ethers 6 line and 1.10.0 on the Ethers 5 line), turn off automatic verification in `hardhat.config.ts`: ```typescript title="hardhat.config.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}} // use manual verification tdly.setup({ automaticVerifications: false }); ``` 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(), }); ``` ### 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..rpc.tenderly.co/ ``` 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 ``` When redeploying contracts, remove the **`.openzeppelin`** folder first. It caches information about proxies and their implementations. ### 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 The following overrides map was derived for **`@openzeppelin/contracts-upgradeable`** version **`4.9.1`**. They may differ for other versions of the package. 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 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. 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 */ }; ``` ## 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 Transparent 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), }); }); }); ``` # Smart Contract Verification Source: https://docs.tenderly.co/contract-verification/overview Verify a smart contract on Tenderly to decode its transactions, events, and state changes, and debug execution against the original Solidity source. Contract verification submits the source code of a deployed contract so Tenderly can match it against the on-chain bytecode. Once verified, Tenderly can decode that contract's transactions, events, and state changes in: * **Decoded transactions**: call traces, events, state changes, and gas usage become human-readable. * **Debugger**: step through execution against the original Solidity, set priorities, and comment on traces. * **Gas profiler**: line-level gas attribution against the source. * **Sharing**: share verified contracts with collaborators and auditors. Without verification you'll see raw bytecode in every Tenderly tool that touches that contract. ## Pick a method For verification on **public networks** (mainnets and testnets), pick the guide that matches your tooling: * [Dashboard](/contract-verification/dashboard): browser-only verification from the Tenderly Dashboard. * [Foundry](/contract-verification/foundry): `forge verify-contract` and deploy-and-verify with `forge create` / `forge script`. * [Hardhat](/contract-verification/hardhat): the `@tenderly/hardhat-tenderly` plugin, automatic or manual. * [Hardhat (proxy contracts)](/contract-verification/hardhat-proxy): UUPS, Transparent, and Beacon proxies. To verify contracts on a **Virtual Environment**, see [Deploy and verify contracts](/virtual-environments/develop/deploy-contracts) and [Verify proxy contracts with Foundry](/virtual-environments/develop/verify-proxy-contracts) under Virtual Environments. ## Public vs private visibility On **public networks** you choose between two visibility modes: * **Public verification.** Source is visible to everyone on Tenderly and propagated to public verification registries. * **Private verification.** Source is visible only inside your Tenderly project and organization. On **Virtual Environments** verifications are always scoped to your project and organization. There is no public mode; see [Deploy and verify contracts](/virtual-environments/develop/deploy-contracts). | Who can see the verified source | Public verification (public network) | Private verification (public network) | Virtual Environment | | ------------------------------- | :----------------------------------: | :-----------------------------------: | :-----------------: | | All Tenderly users | βœ“ | | | | Project collaborators | βœ“ | βœ“ | βœ“ | | Organization members | βœ“ | βœ“ | βœ“ | How to switch to private mode depends on the method: * **Hardhat**: set `tenderly.privateVerification: true` in `hardhat.config.ts`. See [Hardhat setup](/contract-verification/hardhat#configure-authentication-and-verification-visibility). * **Foundry**: append `/public` to the verifier URL to verify publicly, omit it to verify privately. See [Foundry verifier URL](/contract-verification/foundry#verifier-url). * **Dashboard**: toggle **Make Private** in the verification dialog. See [Dashboard verification](/contract-verification/dashboard#public-and-private-verification). # Commenting & Prioritizing Traces Source: https://docs.tenderly.co/debugger/commenting Comment on any execution trace and set priorities so your team can flag and discuss the calls that matter during debugging. Collaboration can be complex and time-consuming when you go through various contracts and Execution Traces ([**Function Trace and Call Trace**](/debugger/execution-overview)). That is why you can comment on any trace, as well as set priorities. Click the speech-bubble icon next to a trace node in the execution tree to open its comment panel on the right side of the Debugger. The panel header shows the number of comments on that trace (for example, "0 comments on `exactInputSingle`"), and you can post a reply from the box at the bottom of the panel. When you select a trace, you can clearly prioritize it. In the top right of the trace window you can see the option ***Set priority*** right next to [*View in Debugger*](/debugger/overview) button - clicking on it opens a dropdown from which you can select the priority you want for that particular trace and it will stay marked for all users that have access to the project until manually changed. Selecting *None* for the priority of a trace removes the markings. Lastly, clicking on the three dots next to *Set priority* opens up a dropdown where you can [view the contract source](/simulator-ui/editing-contract-source). All the comment and priority features persist through both Transaction Overview and Debugger views in your dashboard. You can delete your own comments from the comment panel. # Dev Toolkit Browser Extension Source: https://docs.tenderly.co/debugger/dev-toolkit Dev Toolkit browser extension gives one-click access to Tenderly debugging tools on any block explorer to analyze on-chain data. Tenderly Dev Toolkit is a browser extension that gives you one-click access to Tenderly’s powerful exploration and debugging tools on any block explorer. Open any transaction in the Tenderly Dashboard to explore, analyze, and debug on-chain data in a human-readable format.