> ## 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.

# Deploy and verify contracts

> Deploy Solidity contracts to a Virtual Environment with Foundry or Hardhat, with verification for the Debugger, Gas Profiler, and Simulator.

Deploy Solidity contracts to a Virtual Environment using Foundry or Hardhat. Contracts deployed on a Virtual Environment are called **virtual contracts** and appear in the **Contracts** section under the **Virtual Contracts** tab.

Verify contracts as you deploy them. Verified contracts unlock the Debugger, Gas Profiler, Simulator, and richer transaction views in the explorer.

## Before you begin

* A [Virtual Environment](/virtual-environments/quickstart). Copy its RPC URL from the dashboard.
* A [funded deployer address](/virtual-environments/unlimited-faucet) on that Virtual Environment.

Verification on a Virtual Environment needs no separate access key: the RPC URL itself authenticates, and the verifier endpoint is that URL with `/verify` appended.

<Warning>
  By default, contracts verified on a Virtual Environment are private. Depending on the visibility setting of the [public explorer](/virtual-environments/explorer#public-explorer), the code may be visible externally. Check the public explorer toggle and [verification visibility](/virtual-environments/explorer#public-explorer) before verifying anything you want to keep private.
</Warning>

## Deploy and verify

<Tabs>
  <Tab title="Foundry">
    ### Configure `foundry.toml`

    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 by setting `cbor_metadata` and `bytecode_hash` explicitly:

    ```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. Pin `solc_version` and the optimizer settings so re-verification compiles the same bytecode.

    If your project uses libraries such as OpenZeppelin, declare the remappings explicitly in `remappings.txt`:

    ```bash title="remappings.txt" theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
    forge-std/=lib/forge-std/src/
    ```

    With auto-detected remappings, the verification payload can contain different remapping strings than the build used, which causes bytecode-mismatch failures.

    ### Set environment variables

    ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    export TENDERLY_VIRTUAL_TESTNET_RPC=...   # paste your RPC URL
    export TENDERLY_VERIFIER_URL=$TENDERLY_VIRTUAL_TESTNET_RPC/verify
    export PRIVATE_KEY=...                    # deployer private key
    ```

    Sanity-check the connection before deploying. `cast chain-id` should print the Virtual Environment's chain ID, and the deployer balance should be non-zero:

    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    cast chain-id --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC
    cast balance <DEPLOYER_ADDRESS> --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC
    ```

    ### Deploy with `forge create`

    Run `forge create` with the verify flags. Constructor arguments are passed as raw values; Foundry ABI-encodes them and submits the encoded form to the verifier:

    ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    forge create src/Counter.sol:Counter \
      --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC \
      --private-key $PRIVATE_KEY \
      --broadcast \
      --verify \
      --verifier custom \
      --verifier-url $TENDERLY_VERIFIER_URL \
      --constructor-args 42
    ```

    <Warning>
      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.
    </Warning>

    ### Deploy with a Foundry script

    Use `forge script` to deploy multiple contracts in one run. Every `new Foo(...)` in the script is submitted for verification automatically, with the correct constructor arguments taken from the broadcast log. Imports are handled transparently: the verifier receives a standard-JSON payload containing every source file the compiler touched, so local imports and library imports (such as OpenZeppelin) verify without extra steps.

    Use `--slow` so transactions are sent one at a time. Without it, broadcast batching can submit a transaction before the previous one is confirmed, which is flaky against a hosted RPC and can race the verification step.

    ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    forge script script/Counter.s.sol:CounterScript \
      --rpc-url $TENDERLY_VIRTUAL_TESTNET_RPC \
      --private-key $PRIVATE_KEY \
      --broadcast \
      --slow \
      --verify \
      --verifier custom \
      --verifier-url $TENDERLY_VERIFIER_URL
    ```

    ### Verify an existing contract

    Already-deployed contracts can be verified with `forge verify-contract`. Unlike `forge create`, this command expects constructor arguments **already ABI-encoded**; use `cast abi-encode` to produce them:

    ```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    forge verify-contract $COUNTER_ADDRESS src/Counter.sol:Counter \
      --verifier custom \
      --verifier-url $TENDERLY_VERIFIER_URL \
      --constructor-args $(cast abi-encode "constructor(uint256)" 42) \
      --watch
    ```

    `--watch` polls the verifier until verification finishes and prints the result. For multi-contract deployments, run `forge verify-contract` once per deployed address.
  </Tab>

  <Tab title="Hardhat">
    <Info>
      The `@tenderly/hardhat-tenderly` plugin is compatible with Hardhat up to version `2.22.0`. See [Hardhat verification](/contract-verification/hardhat#versions-and-compatibility) for the supported version matrix.

      ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
      npm install hardhat@2.22.0
      ```
    </Info>

    ### Install the plugin and the CLI

    Install the Tenderly Hardhat plugin:

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

    Install the [Tenderly CLI](https://github.com/Tenderly/tenderly-cli) and log in so the plugin can authenticate:

    <Tabs>
      <Tab title="macOS (Homebrew)">
        ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
        brew tap tenderly/tenderly
        brew install tenderly
        tenderly login
        ```
      </Tab>

      <Tab title="Linux">
        ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
        curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sudo sh
        tenderly login
        ```
      </Tab>

      <Tab title="Manual">
        Download the latest binary from the [release page](https://github.com/Tenderly/tenderly-cli/releases), add it to your `$PATH`, then run:

        ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
        tenderly login
        ```
      </Tab>
    </Tabs>

    ### Configure Hardhat

    Add `virtualMainnet` (or any name prefixed with `virtual` for clarity) to your `networks` block, then add a `tenderly` block with your [project and username slugs](/platform/account/projects/slug). Call `tenderly.setup()` with `automaticVerifications: true` so contracts are verified during every deploy.

    ```typescript title="hardhat.config.ts" showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    import * as tenderly from "@tenderly/hardhat-tenderly";

    tenderly.setup({ automaticVerifications: true });

    const config: HardhatUserConfig = {
      solidity: "0.8.19",
      networks: {
        virtualMainnet: {
          url: process.env.TENDERLY_VIRTUAL_MAINNET_RPC!,
          chainId: 73571, // the Chain ID you selected at Virtual Environment creation
        },
      },
      tenderly: {
        project: "YOUR_PROJECT_SLUG",
        username: "YOUR_USERNAME_SLUG",
      },
    };

    export default config;
    ```

    ### Deploy

    Run your deploy script against the network you defined. With `automaticVerifications: true`, the plugin verifies as it deploys.

    ```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
    npx hardhat run scripts/deploy.ts --network virtualMainnet
    ```
  </Tab>
</Tabs>

## Check the dashboard

In the Tenderly Dashboard, open **Contracts** and select the **Virtual Contracts** tab. Your newly deployed contracts appear there, with verification status visible per contract.

## Troubleshooting

| Symptom                                                           | Likely cause                                                                                                                                           | Fix                                                                                                                                             |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `unauthorized` / `-32004` response from the verifier              | Wrong verifier URL. A common mistake is appending `/verify/etherscan` instead of `/verify`.                                                            | Make sure the verifier URL ends in exactly `/verify`.                                                                                           |
| `Bytecode does not match deployed contract`                       | The submitted source compiled differently than what's deployed: an `optimizer_runs` mismatch, a different `solc_version`, or a stripped metadata hash. | Pin `solc_version`, `optimizer`, `optimizer_runs`, and `bytecode_hash` in `foundry.toml`. 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.                                                                               |
| `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`.                                                                                                                                   |
| Verifier reports `OK` but the explorer still shows unverified     | UI caching.                                                                                                                                            | Hard-refresh the explorer page. The status in the API response is authoritative.                                                                |

## Next steps

* [Verify proxy contracts with Foundry](/virtual-environments/develop/verify-proxy-contracts): verify a UUPS implementation and its ERC1967 proxy, including upgrades.
* [Stage contracts](/virtual-environments/ci-cd/stage-contracts): share the RPC link and contract addresses with your team.
* [Stage your dApp](/virtual-environments/ci-cd/stage-dapps): point your frontend at the Virtual Environment.
* [Fork a Virtual Environment](/virtual-environments/develop/fork-testnet): branch from a Virtual Environment state.
