适用于: 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 插件。
验证方法
Tenderly-Hardhat 插件支持三种合约验证方法。
-
自动验证(推荐):只需导入并设置插件。这种方式非常适合部署期间的验证,因为过程是完全自动化的。自动验证适用于普通的 Hardhat 部署设置,以及使用 hardhat-ignition。通过使用
TENDERLY_AUTOMATIC_VERIFICATION 环境变量来控制自动验证。
-
手动验证(低代码):在您的代码中提供显式验证步骤,适用于部署后验证、条件验证(例如仅在测试失败后)或验证工厂合约实例等场景。
-
详尽手动验证(高代码):提供对验证过程的详细控制,指定库地址、源代码和编译器配置。这对于使用不同编译器版本或设置来验证多个合约是必要的。
探索自动验证过程最好的方法是参考我们 GitHub 仓库中的示例。
所有示例都使用示例 Greeter 合约。直接浏览示例项目:
Tenderly-Hardhat 插件设置
按照下面的步骤安装并初始化 Tenderly-Hardhat 插件。
安装 Tenderly-Hardhat 插件并将其添加为依赖项。
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
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
通过简单的 import 语句将 Tenderly-Hardhat 包包含到您的 hardhat.config.ts 或 hardhat.config.js 文件中,并调用 setup 方法。
请确保在其他包(如 @nomicfoundation/hardhat-toolbox、@nomiclabs/hardhat-ethers、@openzeppelin/hardhat-upgrades 等)之后导入 @tenderly/hardhat-tenderly。
在 2.4.0 和 1.10.0 及之前的版本中,需要调用 tdly.setup() 来初始化插件:tdly.setup({automaticVerifications: !!process.env.TENDERLY_AUTOMATIC_VERIFICATION});
const config: HardhatUserConfig = {
solidity: "0.8.19",
};
export default config;
您需要通过传递账户信息和验证可见性来配置 Tenderly。
privateVerification 参数确保您的源代码仅对您的项目协作者或组织可见。
验证可见性也可以通过设置环境变量 TENDERLY_PUBLIC_VERIFICATION 来控制。
通过在 hardhat.config.ts 文件中添加以下代码片段来配置 Tenderly:
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;
在您的 Hardhat 项目中设置了 Tenderly-Hardhat 插件后,您可以验证部署到各种开发和生产网络的合约:
与 hardhat-ignition 一起工作时启用验证,请指定 etherscan 配置。对于 apiUrl,请使用验证 URL:$TENDERLY_VIRTUAL_TESTNET_RPC/verify/etherscan。
With Hardhat Ignition
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
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)返回的合约对象的引用。
Ethers 6
修改 Hardhat 脚本以捕获 waitForDeployment() 返回的值。
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() 返回的值。
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();
运行 Hardhat 脚本时,您可以将 TENDERLY_AUTOMATIC_VERIFICATION 环境变量设置为 true 以按运行启用自动验证。
使用以下命令运行脚本。已验证合约的链接将显示在终端输出中。
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.ts 在 tenderly 部分读取此环境变量。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.x.x < 1.10.0 和 2.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...",
}
},
);
在 hardhat.config.ts 中禁用自动模式
- tdly.setup();
+ tdly.setup({automaticVerifications: false});
const config: HardhatUserConfig = {
这里使用与自动验证相同的示例,但这次使用 tenderly.verify() 方法来验证合约。
Ethers 6
+ 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
+ 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;
});
运行 Hardhat 脚本时,您可以将 TENDERLY_AUTOMATIC_VERIFICATION 环境变量设置为 false 以禁用自动验证。
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() 方法接受一个参数——一个合约对象数组。每个合约对象由以下部分组成:
| | |
|---|
contractToVerify | string | 要验证的合约名称。这可以是短名称如 Greeter,也可以是完全限定的合约名称如 contracts/A/Greeter.sol:Greeter。 |
sources | map | 编译合约所需的所有源代码的映射。映射的键是指向合约的源路径,而值是一个包含名称和 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(), }, }, |
networks | map | 一个包含多个网络上所有部署地址的映射。js { networks: { [NETWORK_ID]: { address: greeterAddress, }, }, } |
libraries | map | 一个包含库部署信息的映射。键是相对于 contracts 文件夹的库源代码的相对路径。js compiler.settings.libraries = { "path/to/lib.sol": { addresses: { "LibName1": "0x...", "LibName2": "0x..." } } } |