跳转到主要内容
适用于: Virtual Environments 和公共网络(主网和测试网)。同一插件同时处理两者;请在 hardhat.config.ts 中将其指向正确的网络。
Tenderly-Hardhat 插件在 Virtual Environments公共网络上验证从 Hardhat 部署的合约。使用自动验证在每次部署时进行验证,或使用手动验证从您的脚本中显式调用 tenderly.verify()
在公共网络上,插件默认公开验证。要私密验证(仅在您的项目/组织内可见),请在 hardhat.config.ts 中设置 privateVerification: true。请参阅配置认证和验证可见性

版本和兼容性

根据您与 Hardhat 一起使用的 Ethers 版本,您需要下载相应版本的 Tenderly-Hardhat 插件。
出于安全性和性能考虑,以及为了获得附加功能,强烈建议切换到 Ethers 6 并使用最新版本的 Tenderly-Hardhat 插件。
您的技术栈Hardhat 版本Tenderly-Hardhat 插件版本
Ethers 6+ 搭配 Hardhat Ignition>=2.19.0 <=2.22.0@tenderly/hardhat-tenderly@^2.3.0
Ethers 6+ 搭配 hardhat-verify>=2.19.0 <=2.22.0@tenderly/hardhat-tenderly@^2.2.1
Ethers 5+ 搭配 hardhat-verify< 2.19.0@tenderly/hardhat-tenderly@1.8.0

验证方法

Tenderly-Hardhat 插件支持三种合约验证方法。
  • 自动验证推荐):只需导入并设置插件。这种方式非常适合部署期间的验证,因为过程是完全自动化的。自动验证适用于普通的 Hardhat 部署设置,以及使用 hardhat-ignition。通过使用 TENDERLY_AUTOMATIC_VERIFICATION 环境变量来控制自动验证。
  • 手动验证(低代码):在您的代码中提供显式验证步骤,适用于部署后验证条件验证(例如仅在测试失败后)或验证工厂合约实例等场景。
  • 详尽手动验证(高代码):提供对验证过程的详细控制,指定库地址、源代码和编译器配置。这对于使用不同编译器版本或设置来验证多个合约是必要的。

示例

探索自动验证过程最好的方法是参考我们 GitHub 仓库中的示例。 所有示例都使用示例 Greeter 合约。直接浏览示例项目:

Tenderly-Hardhat 插件设置

按照下面的步骤安装并初始化 Tenderly-Hardhat 插件。
1
安装
2
安装 Tenderly-Hardhat 插件并将其添加为依赖项。
3
Ethers 6 (plugin v2.x)
yarn add -D @tenderly/hardhat-tenderly@^2.2.2
Ethers 5 (plugin v1.x)
yarn add -D @tenderly/hardhat-tenderly@^1.8.0
4
登录 Tenderly CLI
5
该插件要求您登录 Tenderly CLI
6
macOS
curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-macos.sh | sudo sh
Linux
curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sudo sh
7
使用此命令检查您是否已登录:
8
tenderly whoami
9
要登录,请运行:
10
tenderly login
11
初始化
12
通过简单的 import 语句将 Tenderly-Hardhat 包包含到您的 hardhat.config.tshardhat.config.js 文件中,并调用 setup 方法。
13
请确保在其他包(如 @nomicfoundation/hardhat-toolbox@nomiclabs/hardhat-ethers@openzeppelin/hardhat-upgrades 等)之后导入 @tenderly/hardhat-tenderly
14
在 2.4.0 和 1.10.0 及之前的版本中,需要调用 tdly.setup() 来初始化插件:
tdly.setup({automaticVerifications: !!process.env.TENDERLY_AUTOMATIC_VERIFICATION});
15


const config: HardhatUserConfig = {
solidity: "0.8.19",
};

export default config;
16
配置认证和验证可见性
17
您需要通过传递账户信息和验证可见性来配置 Tenderly。
18
  • privateVerification 参数确保您的源代码仅对您的项目协作者或组织可见。
  • 验证可见性也可以通过设置环境变量 TENDERLY_PUBLIC_VERIFICATION 来控制。
  • 19
    通过在 hardhat.config.ts 文件中添加以下代码片段来配置 Tenderly:
    20
    
    
    const config: HardhatUserConfig = {
    solidity: "0.8.19",
    tenderly: {
    username: "Your account slug" ?? "error",
    project: "Your project slug",
    
    // Contract visible only in Tenderly.
    // Omitting or setting to `false` makes it visible to the whole world.
    // Alternatively, configure verification visibility using
    // an environment variable `TENDERLY_PUBLIC_VERIFICATION`.
    privateVerification: process.env.TENDERLY_PUBLIC_VERIFICATION !== 'true',
    },
    };
    
    export default config;
    
    21
    设置您的部署
    22
    在您的 Hardhat 项目中设置了 Tenderly-Hardhat 插件后,您可以验证部署到各种开发和生产网络的合约:
    23
  • 通过 Node RPC 部署到 公共主网或测试网
  • Virtual Environments 用于开发、暂存和演示
  • 24
    与 hardhat-ignition 一起工作时启用验证,请指定 etherscan 配置。对于 apiUrl,请使用验证 URL:$TENDERLY_VIRTUAL_TESTNET_RPC/verify/etherscan
    25
    With Hardhat Ignition
    hardhat.config.ts
    
    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.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d",
    chainId: 888
    }
    },
    tenderly: {
    username: "Your account slug",
    project: "Your project slug",
    
    // Mainnet contract verification visible only in Tenderly.
    // Omitting or setting to `false` makes it visible to the whole world.
    // Alternatively, configure verification visibility using
    // an environment variable `TENDERLY_PUBLIC_VERIFICATION`.
    privateVerification: process.env.TENDERLY_PUBLIC_VERIFICATION !== 'true',
    },
    etherscan:{
    apiKey: "YOUR_API_KEY",
    customChains: [
    {
    network: "tenderly_base_testnet",
    chainId: 888,
    urls:{
    apiURL: `https://virtual.base.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d/verify/etherscan`,
    browserURL: "https://virtual.base.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d"
    }
    }
    ]
    },
    };
    
    export default config;
    
    Standard setup
    hardhat.config.ts
    
    
    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.rpc.tenderly.co/872ac073-1de1-4422-b01d-8d057781d77d",
    chainId: 888
    }
    },
    tenderly: {
    username: "Your account name",
    project: "Your project name",
    
    // Mainnet contract verification visible only in Tenderly.
    // Omitting or setting to `false` makes it visible to the whole world.
    // Alternatively, configure verification visibility using
    // an environment variable `TENDERLY_PUBLIC_VERIFICATION`.
    privateVerification: process.env.TENDERLY_PUBLIC_VERIFICATION !== 'true',
    },
    };
    
    export default config;
    
    在以下部分中,您将了解如何使用不同的验证方法验证合约。

    自动验证

    在使用 Ethers.js 部署合约时会无缝进行自动验证。自动验证方法将在收集编译器设置、已部署合约的地址和源代码后自动执行验证。 要启用或禁用自动验证,请使用插件识别的 TENDERLY_AUTOMATIC_VERIFICATION 环境变量。 当 Tenderly-Hardhat 插件检测到合约已部署,且 Ethers 已收到部署交易的收据时,将验证合约。 要启用此功能,您需要在使用 Ethers 的辅助方法 ethers.deployContract()ethers.getContractFactory() 部署合约时 await 部署,方法是分别在合约实例上调用 waitForDeployment()deployed()
    如果您打算与该合约交互,需要捕获 waitForDeployment()(Ethers 6)和 deployed()(Ethers 5)返回的合约对象的引用。
    1
    修改部署脚本
    2
    Ethers 6
    修改 Hardhat 脚本以捕获 waitForDeployment() 返回的值。
    deploy.ts
    
    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;
    });
    
    更紧凑的形式:
    const greeter = await (
    await ethers.deployContract("Greeter", ["Hello, Hardhat!"])
    ).waitForDeployment();
    
    Ethers 5
    探索自动验证过程如何工作的最好方法是浏览我们 GitHub 仓库中的示例。此示例使用我们的示例 Greeter 合约。修改 Hardhat 脚本以捕获 deployed() 返回的值。
    deploy.ts
    
    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;
    });
    
    更紧凑的形式:
    const greeter = await (
    await Greeter.deploy("Hello, Hardhat!")
    ).deployed();
    
    3
    运行脚本
    4
    运行 Hardhat 脚本时,您可以将 TENDERLY_AUTOMATIC_VERIFICATION 环境变量设置为 true 以按运行启用自动验证。
    5
    使用以下命令运行脚本。已验证合约的链接将显示在终端输出中。
    6
    Hardhat Ignition (Virtual Environment)
    TENDERLY_AUTOMATIC_VERIFICATION=true \
    npx hardhat ignition deploy ./ignition/modules/Lock.ts --network virtual_base --deployment-id deploy-to-virtual-base
    
    运行命令时,请务必添加带有特定值的 --deployment-id
    Hardhat (Virtual Environment)
    TENDERLY_AUTOMATIC_VERIFICATION=true \
    npx hardhat run scripts/deploy.ts --network virtual_base
    
    Public network
    部署到公共主网或测试网时,指定您需要公开还是私密验证。请确保您的 hardhat.config.tstenderly 部分读取此环境变量。
    TENDERLY_PUBLIC_VERIFICATION=true \ # Verify publicly. Omit to verify privately.
    TENDERLY_AUTOMATIC_VERIFICATION=true \
    npx hardhat run scripts/deploy.ts --network mainnet_base
    

    验证代理合约

    使用 hardhat-upgrades 部署和升级的代理合约的自动验证在以下版本的 @tenderly/hardhat-tenderly 包中可用:
    • >= 1.10.0
    • >= 2.1.0
    对于版本 1.x.x < 1.10.02.x.x < 2.1.0,请按照代理验证指南进行操作。

    手动验证

    通过 tenderly.verify() 进行的低代码验证使您能够在部署脚本或 Hardhat 测试中显式指定验证的确切时点。 此方法特别适合:
    • 通过自定义脚本验证之前部署的合约。
    • 验证工厂合约创建的实例。
    • 通过在失败测试套件末尾(after)选择性地验证合约来增强测试运行,从而加快执行速度。
    要手动控制验证,请在运行脚本时将 TENDERLY_AUTOMATIC_VERIFICATION 更改为 false 以禁用自动验证。

    verify 函数

    verify() 函数接受一个对象,您必须在其中提供:
    • 合约的 name
    • 合约部署的 address
    • 合约使用的 libraries(可选)
    Tenderly-Hardhat 插件将获取现有的编译器配置和合约源代码,以验证您指定的合约。
    必须在合约部署之后调用 verify()如果您在部署时执行验证,需要在 Ethers 6 中 await waitForDeployment(),在 Ethers 5 中 await deployed()
    let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
    greeter = await greeter.waitForDeployment();
    
    await tenderly.verify({
      name: 'Greeter',
      address: await greeter.getAddress(),
      libraries: {
        AwsomeLib: "0x...",
      }
    });
    
    如果您要验证多个合约,该函数具有可变参数:
    let greeter = await ethers.deployContract("Greeter", ["Hello, Hardhat!"]);
    greeter = await greeter.waitForDeployment();
    
    const counter = (await ethers.deployContract("Counter", [42])).waitForDeployment();
    
    await tenderly.verify(
      {
        name: 'Greeter',
        address: await greeter.getAddress(),
      },
      {
        name: 'Counter',
        address: await counter.getAddress(),
        libraries: {
            KickAssLib: "0x...",
        }
      },
    );
    
    1
    hardhat.config.ts 中禁用自动模式
    2
    如果您希望关闭自动验证:
    3
    
    - tdly.setup();
    + tdly.setup({automaticVerifications: false});
    
    const config: HardhatUserConfig = {
    
    4
    修改部署脚本
    5
    这里使用与自动验证相同的示例,但这次使用 tenderly.verify() 方法来验证合约。
    6
    Ethers 6
    deploy.ts
        + 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;
      });
    
    Ethers 5
    deploy.ts
        + 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;
      });
    
    7
    运行脚本
    8
    运行 Hardhat 脚本时,您可以将 TENDERLY_AUTOMATIC_VERIFICATION 环境变量设置为 false 以禁用自动验证。
    9
    Virtual Environment
    TENDERLY_AUTOMATIC_VERIFICATION=false \
    npx hardhat run scripts/deploy.ts --network virtual_base
    
    Public network
    部署到公共主网或测试网时,指定您需要公开还是私密验证。
    TENDERLY_PUBLIC_VERIFICATION=false \ # Omit or set to true to verify publicly.
    TENDERLY_AUTOMATIC_VERIFICATION=false \
    npx hardhat run scripts/deploy.ts --network mainnet_base
    

    详尽手动验证

    方法 verifyMultiCompilerAPI() 让您完全控制验证过程,允许您指定验证过程的每一个细节。 通常不推荐这样做,但如果您正在验证使用不同编译器版本或配置编译的合约,它会很有用。 完全受控的验证方法允许您显式:
    • 指定合约的源代码(sources
    • 设置编译参数(compiler
    • 设置链接的库(libraries
    以下示例演示了如何执行完全受控的验证。
    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,
            },
          },
        },
      ],
    });
    

    验证参数

    verifyMultiCompilerAPI() 方法接受一个参数——一个合约对象数组。每个合约对象由以下部分组成:
    contractToVerifystring要验证的合约名称。这可以是短名称如 Greeter,也可以是完全限定的合约名称如 contracts/A/Greeter.sol:Greeter
    sourcesmap编译合约所需的所有源代码的映射。映射的键是指向合约的源路径,而值是一个包含名称和 UTF-8 编码合约源代码的对象。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(), }, },
    networksmap一个包含多个网络上所有部署地址的映射。js { networks: { [NETWORK_ID]: { address: greeterAddress, }, }, }
    librariesmap一个包含库部署信息的映射。键是相对于 contracts 文件夹的库源代码的相对路径。js compiler.settings.libraries = { "path/to/lib.sol": { addresses: { "LibName1": "0x...", "LibName2": "0x..." } } }