> ## Documentation Index
> Fetch the complete documentation index at: https://l2docs.whitechain.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Verify a proxy contract

> Verify EIP-1967 and UUPS proxy contracts on Whitechain Sepolia with Blockscout: verify the implementation, verify the proxy, and link the two so the proxy tab exposes the implementation's functions.

<Note>
  Available on Whitechain Sepolia today. The steps use the Blockscout explorer at `https://explorer.testnet.whitechain.io`. For a plain, non-proxy contract, see [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) or [Deploy with Foundry](/build/deploy/deploy-with-foundry).
</Note>

## How proxy verification works

An upgradeable contract is two contracts: a proxy that holds the state and a fixed address, and an implementation (also called the logic contract) that holds the code. The proxy forwards every call to the implementation with `delegatecall`, so users always interact with the proxy address while the logic can be swapped.

Proxy verification is therefore a two-part job. You verify the implementation so its source and ABI are known, and you verify the proxy so its own source is known. Blockscout links the two by reading the EIP-1967 implementation slot from the proxy's storage. Once both are verified and linked, the proxy address gains a `Read/Write proxy` tab that exposes the implementation's functions.

Whitechain runs standard OP Stack Blockscout, which reads these slots automatically:

| Pattern                    | Implementation pointer                                                            | Detected automatically |
| -------------------------- | --------------------------------------------------------------------------------- | ---------------------- |
| EIP-1967 transparent proxy | Storage slot `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` | Yes                    |
| UUPS proxy (ERC1967)       | Same EIP-1967 slot                                                                | Yes                    |
| EIP-1967 beacon proxy      | Beacon slot `0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50`  | Yes                    |

Transparent and UUPS proxies share one slot, so Blockscout cannot tell them apart. It labels every EIP-1967 detection "Transparent Proxy", including UUPS. The label is cosmetic and does not affect the linkage or the proxy tab.

## Before you start

* A proxy and its implementation deployed on Whitechain Sepolia. See [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) or [Deploy with Foundry](/build/deploy/deploy-with-foundry).
* Three values recorded at deploy time: the implementation address, the proxy address, and the ABI-encoded initializer calldata passed as the proxy's second constructor argument. You cannot verify the proxy without the calldata.
* The OpenZeppelin contracts installed, because the proxy source comes from that library. Run `npm install @openzeppelin/contracts` for Hardhat, or `forge install OpenZeppelin/openzeppelin-contracts` for Foundry.
* The Blockscout verifier configured for chain `1874`. Hardhat needs the `chainDescriptors` entry shown on the [Deploy with Hardhat](/build/deploy/deploy-with-hardhat) page. Foundry takes the explorer URL on the command line.

### Hardhat 3 needs the proxy named as a build root

Hardhat 3 only emits artifacts for contracts declared in files under `contracts/`. `ERC1967Proxy` lives in the OpenZeppelin package, so importing it from a local file is not enough: a file that declares no contract of its own produces no artifact. Name it in `solidity.npmFilesToBuild` instead.

```typescript hardhat.config.ts theme={null}
solidity: {
  version: "0.8.30",
  npmFilesToBuild: [
    "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
  ],
},
```

Without this, `hardhat compile` succeeds but no `ERC1967Proxy` artifact exists, and both deployment and verification fail with an artifact-not-found error.

## 1. Verify the implementation contract

Verify the implementation exactly like any standalone contract. It takes no constructor arguments in most upgradeable patterns, because upgradeable contracts use an `initialize` function instead of a constructor.

<Tabs>
  <Tab title="Hardhat">
    ```bash Terminal theme={null}
    npx hardhat verify --network whitechainSepolia <implementation_address>
    ```
  </Tab>

  <Tab title="Foundry">
    ```bash Terminal theme={null}
    forge verify-contract <implementation_address> src/MyContractV1.sol:MyContractV1 \
      --rpc-url https://rpc.testnet.whitechain.io \
      --verifier blockscout \
      --verifier-url https://explorer.testnet.whitechain.io/api/
    ```
  </Tab>
</Tabs>

<Note>
  Foundry's `--verifier-url` ends with `/api/`. Hardhat's `chainDescriptors` `apiUrl` ends with `/api` and no trailing slash. The two tools differ; each form is correct for its own tool.
</Note>

## 2. Verify the proxy contract

The proxy is `ERC1967Proxy`, `TransparentUpgradeableProxy`, or `BeaconProxy`, and it was deployed with two constructor arguments: the implementation address and the initializer calldata. Pass both when you verify.

<Tabs>
  <Tab title="Hardhat">
    ```bash Terminal theme={null}
    npx hardhat verify --force --network whitechainSepolia \
      <proxy_address> <implementation_address> <init_calldata>
    ```

    `--force` is required here. Hardhat checks whether an address is already verified before submitting, and that check uses Blockscout's legacy `getsourcecode` endpoint. For a detected proxy, that endpoint resolves through to the implementation and returns the implementation's name and source. Hardhat reads that as "the proxy is already verified", skips the submission, and exits successfully while the proxy's own source stays unverified. `--force` submits anyway.
  </Tab>

  <Tab title="Foundry">
    ```bash Terminal theme={null}
    forge verify-contract <proxy_address> \
      lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy \
      --rpc-url https://rpc.testnet.whitechain.io \
      --verifier blockscout \
      --verifier-url https://explorer.testnet.whitechain.io/api/ \
      --constructor-args $(cast abi-encode "constructor(address,bytes)" <implementation_address> <init_calldata>)
    ```
  </Tab>
</Tabs>

<Warning>
  Do not trust an "already verified" message on a proxy address. Confirm it against the API in step 3, which reports the proxy's own verification state rather than the implementation's.
</Warning>

## 3. Confirm the result

Check the API rather than the page, because the page can look correct while the proxy itself is unverified.

```bash Terminal theme={null}
curl -s https://explorer.testnet.whitechain.io/api/v2/smart-contracts/<proxy_address>
```

Confirm four fields:

| Field               | Expected value                                                                     |
| ------------------- | ---------------------------------------------------------------------------------- |
| `is_fully_verified` | `true`                                                                             |
| `name`              | the proxy contract name, for example `ERC1967Proxy`, not the implementation's name |
| `proxy_type`        | `eip1967`                                                                          |
| `implementations`   | an array containing the implementation address and its contract name               |

Then open the proxy on the [explorer](https://explorer.testnet.whitechain.io). Under `Contract` there are three tabs:

| Tab                   | What it shows                                                                                                                                  |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `Code`                | The proxy's own verified source, with an exact-match marker and the decoded constructor arguments.                                             |
| `Read/Write contract` | The proxy's own functions. A single tab with an All, Read, Write filter.                                                                       |
| `Read/Write proxy`    | The implementation's functions, called through the proxy. Shows a banner naming the detected pattern and a link to the implementation address. |

The implementation address has only `Code` and `Read/Write contract`, and no proxy tab. That is correct: it is not a proxy.

Open `Read/Write proxy` and check that the functions listed are the implementation's, not the proxy's. The proxy's own ABI has 8 entries and no state getters. A UUPS implementation adds its own functions alongside the inherited `upgradeToAndCall`, `proxiableUUID`, `owner`, `transferOwnership`, `renounceOwnership`, and `UPGRADE_INTERFACE_VERSION`.

<Note>
  `/api/v2/smart-contracts/<address>/methods-read` and `methods-write` return 404 on this Blockscout build. To check the proxy state from a script, read `proxy_type` and `implementations` from `/api/v2/smart-contracts/<address>`.
</Note>

## 4. Verify again after an upgrade

An upgrade points the proxy at new code, so the verification you already did no longer covers it. Two things happen when you call `upgradeToAndCall`.

The proxy stays verified. Its own source and constructor arguments do not change, so you never re-verify the proxy itself. Blockscout re-reads the EIP-1967 slot on each page load and follows the new implementation automatically.

The new implementation is unverified. Until you verify it, the API returns `implementations` with a `name` of `null`, and the `Read/Write proxy` tab cannot show the new functions. Verify the new implementation exactly as in step 1, then confirm the linkage as in step 3.

```bash Terminal theme={null}
npx hardhat verify --network whitechainSepolia <new_implementation_address>
```

<Note>
  Every implementation you deploy stays on chain at its own address, verified independently. Only the address in the EIP-1967 slot is live. Earlier implementations remain verified but unreferenced, which is expected and needs no cleanup.
</Note>

## Worked example on Whitechain Sepolia

A UUPS pair on Whitechain Sepolia, verified with the steps above and then upgraded once, for comparison against your own output.

| Item                        | Value                                                                                                                                     |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Proxy                       | [`0x9890f99ab68065020ebe6ef6e64831451e4ad98d`](https://explorer.testnet.whitechain.io/address/0x9890f99ab68065020ebe6ef6e64831451e4ad98d) |
| Live implementation (BoxV2) | [`0x8f4f72dd4421bc39af3b3ff145e0702f6ac95fb9`](https://explorer.testnet.whitechain.io/address/0x8f4f72dd4421bc39af3b3ff145e0702f6ac95fb9) |
| EIP-1967 slot on the proxy  | `0x0000000000000000000000008f4f72dd4421bc39af3b3ff145e0702f6ac95fb9`, which decodes to the live implementation address                    |
| Proxy `proxy_type`          | `eip1967`                                                                                                                                 |
| Proxy source path           | `npm/@openzeppelin/contracts@5.6.1/proxy/ERC1967/ERC1967Proxy.sol`                                                                        |
| Compiler, every contract    | `v0.8.28+commit.7893614a`, EVM `cancun`, optimizer disabled                                                                               |
| Upgrade cost                | 38,548 gas for `upgradeToAndCall`                                                                                                         |

The proxy was verified once, before any upgrade, and is still verified now. Every contract reports `is_fully_verified: true` and `verified_via_sourcify: false`, so Blockscout verified them natively.

Two earlier implementations remain on chain, verified and no longer referenced by the slot: BoxV1 [`0xc6d4397ba81e5f74e49d77f52b1d6c364a7a060b`](https://explorer.testnet.whitechain.io/address/0xc6d4397ba81e5f74e49d77f52b1d6c364a7a060b) and an earlier BoxV2 [`0x92b00d7db1aef4801f81167ffe58b2d35f0b9b4c`](https://explorer.testnet.whitechain.io/address/0x92b00d7db1aef4801f81167ffe58b2d35f0b9b4c).

## Troubleshooting

<AccordionGroup>
  <Accordion title="`hardhat verify` reports the proxy is already verified, but its source is missing">
    Blockscout's legacy `getsourcecode` resolves through the proxy to the implementation, satisfying Hardhat's pre-check. Re-run with `--force`, then confirm `name` and `is_fully_verified` on the proxy through the API.
  </Accordion>

  <Accordion title="Artifact for `ERC1967Proxy` not found">
    Hardhat 3 emits artifacts only for contracts declared under `contracts/`. Add the proxy to `solidity.npmFilesToBuild`, as shown in "Before you start".
  </Accordion>

  <Accordion title="`hardhat verify` exits with code 1 although Blockscout succeeded">
    The toolbox enables several verifiers. A different verifier in the same run failed, for example Etherscan without an API key. Read the per-verifier output. A Blockscout success is independent of the others.
  </Accordion>

  <Accordion title="The banner says &#x22;Transparent Proxy&#x22; but the contract is UUPS">
    Both patterns use the same EIP-1967 slot, so Blockscout cannot distinguish them. The label is cosmetic and the linkage is correct.
  </Accordion>

  <Accordion title="Verification reports a compiler mismatch">
    The solc version, EVM version, or optimizer setting differs from the deployed bytecode. Compare `artifacts/build-info` against the values the explorer reports, then verify again with the matching settings.
  </Accordion>

  <Accordion title="No `Read/Write proxy` tab">
    The implementation is not verified, or the slot is non-standard. Verify the implementation first, then reload the proxy page. Blockscout re-reads the slot on load.
  </Accordion>

  <Accordion title="Hardhat rejects the network configuration at startup">
    The private key in `.env` has no `0x` prefix. Add the prefix, or normalize it in `hardhat.config.ts` before passing it to `accounts`.
  </Accordion>
</AccordionGroup>

## Related

* [Deploy with Hardhat](/build/deploy/deploy-with-hardhat)
* [Deploy with Foundry](/build/deploy/deploy-with-foundry)
* [Block explorer overview](/build/block-explorer/overview)
* [Connect to Whitechain Sepolia](/learn/get-started/connect-wallet)
