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

# 部署和验证合约

> 使用 Foundry 或 Hardhat 将 Solidity 合约部署到 Virtual Environment，并进行验证以便使用 Debugger、Gas Profiler 和 Simulator。

使用 Foundry 或 Hardhat 将 Solidity 合约部署到 Virtual Environment。部署在 Virtual Environment 上的合约被称为**虚拟合约（virtual contracts）**，会出现在 **Contracts** 部分的 **Virtual Contracts** 选项卡下。

在部署时验证合约。经过验证的合约会解锁 Debugger、Gas Profiler、Simulator 以及浏览器中更丰富的交易视图。

## 开始之前

* 一个 [Virtual Environment](/virtual-environments/quickstart)。从仪表板复制它的 RPC URL。
* 该 Virtual Environment 上一个[已注资的部署者地址](/virtual-environments/unlimited-faucet)。

在 Virtual Environment 上进行验证不需要单独的 access key：RPC URL 本身即用于身份验证，验证器端点就是该 URL 后附加 `/verify`。

<Warning>
  默认情况下，在 Virtual Environment 上验证的合约是私有的。根据[公共浏览器](/virtual-environments/explorer#public-explorer)的可见性设置，代码可能对外可见。在验证任何您想保密的内容之前，请检查公共浏览器开关和[验证可见性](/virtual-environments/explorer#public-explorer)。
</Warning>

## 部署与验证

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

    Tenderly 的验证器会将 Solidity 编译器附加到已部署字节码上的元数据哈希与您提交的源代码进行匹配。通过显式设置 `cbor_metadata` 和 `bytecode_hash`，将元数据保留在编译输出中：

    ```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` 和 `bytecode_hash = "ipfs"` 是 Foundry 的默认值，但某些模板会剥离它们；`bytecode_hash = "none"` 会破坏验证。固定 `solc_version` 和 optimizer 设置，以便重新验证时编译出相同的字节码。

    如果您的项目使用像 OpenZeppelin 这样的库，请在 `remappings.txt` 中显式声明 remappings：

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

    使用自动检测的 remappings 时，验证载荷可能包含与构建时使用的不同的 remapping 字符串，从而导致字节码不匹配的失败。

    ### 设置环境变量

    ```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
    ```

    在部署之前进行连通性检查。`cast chain-id` 应打印 Virtual Environment 的链 ID，部署者余额应为非零：

    ```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
    ```

    ### 使用 `forge create` 部署

    带上 verify 标志运行 `forge create`。构造函数参数作为原始值传入；Foundry 会对它们进行 ABI 编码，并将编码后的形式提交给验证器：

    ```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>
      将 `--constructor-args` 放在最后。它会贪婪地吞掉命令行的其余部分，因此放在它之后的任何标志都会被视为另一个构造函数参数。
    </Warning>

    ### 使用 Foundry 脚本部署

    使用 `forge script` 在一次运行中部署多个合约。脚本中的每个 `new Foo(...)` 都会自动提交进行验证，构造函数参数会从广播日志中正确获取。导入会被透明处理：验证器会收到一个标准 JSON 载荷，其中包含编译器接触过的每个源文件，因此本地导入和库导入（例如 OpenZeppelin）无需额外步骤即可完成验证。

    使用 `--slow` 让交易一次发送一笔。不加它，广播批处理可能在上一笔交易被确认之前提交下一笔，这在托管 RPC 上不稳定，并且可能与验证步骤发生竞争。

    ```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
    ```

    ### 验证已有的合约

    已经部署的合约可以使用 `forge verify-contract` 验证。与 `forge create` 不同，此命令期望构造函数参数**已经过 ABI 编码**；请使用 `cast abi-encode` 生成它们：

    ```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` 会轮询验证器直到验证完成并打印结果。对于多合约部署，请为每个已部署地址运行一次 `forge verify-contract`。
  </Tab>

  <Tab title="Hardhat">
    <Info>
      `@tenderly/hardhat-tenderly` 插件与 Hardhat 的兼容版本最高为 `2.22.0`。支持的版本矩阵参见 [Hardhat 验证](/contract-verification/hardhat#versions-and-compatibility)。

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

    ### 安装插件和 CLI

    安装 Tenderly Hardhat 插件：

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

    安装 [Tenderly CLI](https://github.com/Tenderly/tenderly-cli) 并登录，以便插件可以进行身份验证：

    <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">
        从[发布页面](https://github.com/Tenderly/tenderly-cli/releases)下载最新的二进制文件，将其添加到 `$PATH`，然后运行：

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

    ### 配置 Hardhat

    将 `virtualMainnet`（或任何为清晰起见以 `virtual` 为前缀的名称）添加到您的 `networks` 块，然后添加一个带有您[项目和用户名 slug](/platform/account/projects/slug) 的 `tenderly` 块。以 `automaticVerifications: true` 调用 `tenderly.setup()`，这样在每次部署时都会验证合约。

    ```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;
    ```

    ### 部署

    针对您定义的网络运行部署脚本。使用 `automaticVerifications: true`，插件会在部署时进行验证。

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

## 检查仪表板

在 Tenderly Dashboard 中，打开 **Contracts** 并选择 **Virtual Contracts** 选项卡。您新部署的合约会显示在那里，并可看到每个合约的验证状态。

## 故障排除

| 症状                                                                 | 可能原因                                                              | 修复                                                                                                                       |
| ------------------------------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 验证器返回 `unauthorized` / `-32004` 响应                                 | 验证器 URL 错误。常见错误是附加了 `/verify/etherscan` 而不是 `/verify`。            | 确保验证器 URL 精确地以 `/verify` 结尾。                                                                                             |
| `Bytecode does not match deployed contract`                        | 提交的源代码编译结果与部署的不同：`optimizer_runs` 不匹配、`solc_version` 不同或元数据哈希被剥离。 | 在 `foundry.toml` 中固定 `solc_version`、`optimizer`、`optimizer_runs` 和 `bytecode_hash`。重新验证前运行 `forge clean && forge build`。 |
| `Failed to deserialize content`                                    | 验证器返回了一个 Foundry 无法解析的错误字符串，通常包裹着上游身份验证或路径错误。                     | 重新检查 URL 并使用 `-vvvv` 重新运行以查看原始响应。                                                                                        |
| 在 `forge create` 上 `Dry run enabled, not broadcasting transaction` | 未传入 `--broadcast`，或被 `--constructor-args` 吞掉。                     | 将 `--constructor-args` 移到最后一个标志。                                                                                         |
| `forge script` 只有第一个合约被验证                                          | RPC 竞态：第二笔交易在第一笔收据之前就已提交。                                         | 添加 `--slow`。                                                                                                             |
| 验证器报告 `OK`，但浏览器仍显示未验证                                              | UI 缓存。                                                            | 硬刷新浏览器页面。API 响应中的状态是权威的。                                                                                                 |

## 后续步骤

* [使用 Foundry 验证代理合约](/virtual-environments/develop/verify-proxy-contracts)：验证 UUPS 实现及其 ERC1967 代理，包括升级。
* [暂存合约](/virtual-environments/ci-cd/stage-contracts)：与您的团队共享 RPC 链接和合约地址。
* [暂存您的 dApp](/virtual-environments/ci-cd/stage-dapps)：将您的前端指向 Virtual Environment。
* [Fork Virtual Environment](/virtual-environments/develop/fork-testnet)：从 Virtual Environment 状态分支出来。
