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

> Верифицируйте контракты, развёрнутые через Foundry, в публичных сетях с помощью API верификации Tenderly — приватно или публично, используя forge verify-contract.

<Info>
  **Работает на:** публичных сетях (mainnet и testnet). Чтобы верифицировать контракты в Virtual Environment, см. [Деплой и верификация контрактов](/virtual-environments/develop/deploy-contracts) и [Верификация прокси-контрактов с помощью Foundry](/virtual-environments/develop/verify-proxy-contracts).
</Info>

Tenderly верифицирует смарт-контракты, развёрнутые через команды Foundry `forge create`, `forge script` и `forge verify-contract`, посредством своего API верификации, совместимого с Etherscan. Верификация бывает либо приватной (исходный код виден только внутри вашего проекта в Tenderly), либо публичной (виден всем, у кого есть ссылка).

## Прежде чем начать

Вам понадобятся [slug](/platform/account/projects/slug) вашего аккаунта и проекта Tenderly, [ключ доступа Tenderly](/platform/account/projects/api-tokens) (Dashboard → Account Settings → Authorization) и профинансированный аккаунт в целевой сети.

Верификатор Tenderly сопоставляет хеш метаданных, который компилятор Solidity добавляет к развёрнутому байткоду, с исходным кодом, который вы отправляете. Сохраняйте метаданные в результатах компиляции и фиксируйте настройки компилятора, чтобы они не изменились между моментом деплоя и моментом верификации:

```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"` ломает верификацию. Расхождение в настройках компилятора между деплоем и верификацией приводит к ошибке `Bytecode does not match deployed contract`.

В примерах ниже используется Base Sepolia (chain ID `84532`). Настройка окружения:

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

## URL верификатора

Каждая команда верификации Foundry принимает `--verifier-url`, указывающий на API верификации Tenderly, и `--etherscan-api-key $TENDERLY_ACCESS_KEY` для аутентификации:

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

Каждый сегмент URL соответствует значению, которое можно посмотреть в Dashboard:

| Сегмент                     | Что это                                                                                                                                |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `account/$TENDERLY_ACCOUNT` | [Slug](/platform/account/projects/slug) вашего аккаунта Tenderly (пользователя или организации). Регистрозависимый.                    |
| `project/$TENDERLY_PROJECT` | Проект, к которому будет привязан верифицированный контракт.                                                                           |
| `network/$NETWORK_ID`       | Сеть, в которой развёрнут контракт, в виде десятичного chain ID (`1` для Ethereum Mainnet, `8453` для Base, `84532` для Base Sepolia). |
| без суффикса                | Приватная верификация: контракт виден только внутри вашего проекта.                                                                    |
| суффикс `/public`           | Публичная верификация: исходный код контракта виден всем, у кого есть ссылка, без входа в Tenderly.                                    |

## Приватная верификация

Приватно верифицированные контракты видны только участникам вашего проекта в разделе **Contracts** в 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` ожидает, что аргументы конструктора **уже закодированы в ABI**; используйте `cast abi-encode`, чтобы получить их. `--watch` опрашивает верификатор, пока верификация не завершится, и печатает результат.

<Note>
  При успехе Foundry печатает строку `URL: https://etherscan.io/address/...`. Это особенность отображения потока, совместимого с Etherscan; контракт был верифицирован в Tenderly, а не в Etherscan. Подтвердите это в Dashboard в разделе **Contracts**.
</Note>

## Публичная верификация

Замените URL верификатора на вариант с суффиксом `/public`; больше ничего менять не нужно. Страница с верифицированным исходным кодом станет доступна всем, у кого есть ссылка, без входа в Tenderly.

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

<Warning>
  Публичная верификация необратима. Публично верифицированный контракт остаётся публичным. Приватная и публичная верификация — независимые записи: чтобы сделать приватно верифицированный контракт публичным, повторно верифицируйте его через URL `/public`.
</Warning>

## Деплой и верификация одной командой

`forge create` и `forge script` принимают те же флаги встроенно, поэтому деплой и верификацию можно запустить одной командой. В отличие от `forge verify-contract`, обе они принимают аргументы конструктора как сырые значения и сами кодируют их в ABI:

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

<Warning>
  Ставьте `--constructor-args` в конце. Он жадно поглощает остаток командной строки, поэтому любой флаг, поставленный после него, будет воспринят как ещё один аргумент конструктора.
</Warning>

Для деплоев нескольких контрактов используйте `forge script` с флагом `--slow`; каждый контракт, который развёртывает скрипт, верифицируется автоматически, а аргументы конструктора берутся из broadcast-лога. Без [`--slow`](https://www.getfoundry.sh/reference/forge/script?highlight=slow#forge-script) пакетирование broadcast может отправить транзакцию до подтверждения предыдущей, из-за чего может произойти гонка с шагом верификации:

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

## Верификация контракта, который вы не разворачивали

Когда контракт верифицирован в публичном обозревателе (Etherscan, Basescan), но не в вашем проекте Tenderly, клонируйте верифицированный исходный код локально с помощью [`forge clone`](https://www.getfoundry.sh/reference/forge/clone) и повторно верифицируйте его через API Tenderly:

```bash showLineNumbers theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
forge clone $CONTRACT_ADDRESS -e $ETHERSCAN_API_KEY --chain 1
```

`forge clone` скачивает верифицированный исходный код из Etherscan-совместимого API исходной сети (используйте ключ соответствующего обозревателя для каждой сети), восстанавливает структуру проекта и фиксирует настройки компилятора, которые использовал исходный деплойер. Запустите `forge build` в клонированной директории, чтобы убедиться, что она собирается; ошибки разрешения импортов обычно связаны с `remappings.txt`.

Затем верифицируйте в 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
```

Если верификация падает с ошибкой `Bytecode does not match deployed contract`, передайте исходные настройки компилятора явно. Все они перечислены на странице обозревателя, где контракт уже верифицирован, в разделе исходного кода контракта:

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

### Контракты, живущие в директории `lib`

`forge build` компилирует только то, что достижимо из `src/`. Контракт, который существует только внутри зависимости в `lib/` (прокси, стандартная реализация ERC), никогда не попадает в кеш сборки, и `forge verify-contract` падает с ошибкой:

```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/..."
```

Создайте однострочный файл `src/Imports.sol`, который импортирует контракт, а затем пересоберите:

```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` никогда не отправляется в верификатор; он лишь заставляет компилятор кешировать контракт и его зависимости. После этого верифицируйте, используя полный путь контракта в `lib/`, поскольку исходный код физически находится именно там.

Для `TransparentUpgradeableProxy`, который вы не разворачивали, конструктор имеет вид `(address _logic, address initialOwner, bytes _data)`, и большую его часть можно восстановить из состояния сети:

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

Версия компилятора, число прогонов оптимизатора и версия EVM берутся со страницы обозревателя уже верифицированной реализации. Если реализация нигде не верифицирована, обратитесь к `foundry.toml` или скриптам деплоя разворачивающего проекта.

## Устранение неполадок

| Симптом                                                              | Вероятная причина                                                                                                                                                            | Решение                                                                                                                                                                                                                                    |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `unauthorized`                                                       | Отсутствует или неверный `--etherscan-api-key`, либо ключ доступа был отозван.                                                                                               | Сгенерируйте новый [ключ доступа](/platform/account/projects/api-tokens) и заново задайте `TENDERLY_ACCESS_KEY`.                                                                                                                           |
| `not found`                                                          | Неверный slug аккаунта, slug проекта или chain ID в URL верификатора.                                                                                                        | Перепроверьте каждый сегмент URL. Slug аккаунта регистрозависимый.                                                                                                                                                                         |
| `Bytecode does not match deployed contract`                          | Отправленный исходный код компилируется иначе, чем то, что развёрнуто: несоответствие `optimizer_runs`, другая `solc_version` или версия EVM, либо срезанный хеш метаданных. | Зафиксируйте `solc_version`, `optimizer`, `optimizer_runs` и `bytecode_hash` в `foundry.toml` или передайте `--compiler-version --optimizer-runs --evm-version` явно. Перед повторной верификацией выполните `forge clean && forge build`. |
| `Failed to deserialize content`                                      | Верификатор вернул строку ошибки, которую Foundry не может распарсить, обычно оборачивающую ошибку авторизации или пути от вышестоящего сервиса.                             | Перепроверьте URL и запустите повторно с `-vvvv`, чтобы увидеть сырой ответ.                                                                                                                                                               |
| `Failed to get standard json input - cannot resolve file at lib/...` | Контракт нигде не импортируется из `src/`, поэтому отсутствует в кеше сборки.                                                                                                | Создайте `src/Imports.sol`, который импортирует его, затем `forge build --force`. См. [Контракты, живущие в директории `lib`](#contracts-that-live-in-the-lib-directory).                                                                  |
| `Dry run enabled, not broadcasting transaction` при `forge create`   | Не передан `--broadcast`, либо он проглочен флагом `--constructor-args`.                                                                                                     | Переместите `--constructor-args` в конец списка флагов.                                                                                                                                                                                    |
| Верифицируется только первый контракт при `forge script`             | Гонка на RPC: вторая транзакция была отправлена до получения квитанции первой.                                                                                               | Добавьте `--slow`.                                                                                                                                                                                                                         |
| Верификация успешна, но контракт не появляется в проекте             | Неверный slug проекта в URL верификатора.                                                                                                                                    | Повторно верифицируйте с правильным slug. Контракт попадёт в тот проект, на который указывал URL.                                                                                                                                          |
