Smart Contract Verification on Etherscan
Deployed contract, transaction went through, everything works — and then a request comes from a partner or investor: "Show me the code". On Etherscan instead of sources — bytecode. Verification not done. This isn't the end of the world, but explaining why a public contract is closed is inconvenient.
Why Bytecode Isn't Enough
Etherscan stores deployedBytecode for each contract, but deobfuscating it back to readable Solidity is nontrivial. Tools like Dedaub Decompiler give approximate results, not exact. Without verification you can't:
- call functions through Etherscan Read/Write interface without ABI
- verify that deployed code matches published repository
- pass listing on CoinGecko, CoinMarketCap, most DEX aggregators
- gain user trust — "Contract Source Verified" checkmark on Etherscan became de-facto standard
Technically, verification is replicating compilation on Etherscan's side. The platform takes your source code, compiles with the same parameters (solc version, optimizer runs, via-ir flag) and compares bytecode with deployed. On mismatch — rejection.
Why Verification Sometimes Fails
Compiler version mismatch. Solidity 0.8.19 and 0.8.20 produce different bytecode even on identical source. Hardhat fixes the version in hardhat.config.ts, you must specify it exactly during verification.
Optimizer settings. If contract deployed with optimizer: { enabled: true, runs: 200 }, but verification specifies runs: 1000 — bytecode won't match. This is the most common reason for failure on first attempt.
via-IR pipeline. Starting with Solidity 0.8.13, compilation through intermediate Yul representation is available (viaIR: true). This option changes bytecode. If it was used at deploy — must be specified at verification too.
Flattened contract with conflicts. Some upload flattened file to Etherscan via hardhat flatten. If it has multiple declarations of one library — verification fails with duplicate identifier error.
Immutable variables. immutable values are baked into bytecode at deploy. If verification goes through standard JSON input, immutable values must be specified separately — otherwise mismatch.
How We Verify
We use Hardhat Verify plugin (@nomicfoundation/hardhat-verify) for automatic verification right after deploy:
// hardhat.config.ts
etherscan: {
apiKey: {
mainnet: process.env.ETHERSCAN_API_KEY,
polygon: process.env.POLYGONSCAN_API_KEY,
arbitrumOne: process.env.ARBISCAN_API_KEY,
}
}
After deploy one command:
npx hardhat verify --network mainnet 0xYourContractAddress "ConstructorArg1" "ConstructorArg2"
For proxy contracts (OpenZeppelin TransparentUpgradeableProxy, UUPS) — verification is needed separately for proxy and implementation. Hardhat Verify supports --contract flag for explicit implementation specification.
If contract deployed via Foundry — use forge verify-contract:
forge verify-contract 0xAddress src/MyContract.sol:MyContract \
--chain mainnet \
--constructor-args $(cast abi-encode "constructor(address)" 0xArg) \
--etherscan-api-key $ETHERSCAN_API_KEY
Foundry also supports --verifier sourcify for verification on Sourcify — alternative open registry supported by many chains without their own Etherscan.
Multi-chain verification. Same contract on Ethereum Mainnet, Polygon, Arbitrum, Optimism, BSC must be verified separately on each scanner. Automate via deploy script with post-deploy hook.
Work Process
If contract is already deployed but not verified — recover compilation parameters from deploy artifacts. Hardhat saves deployments/<network>/<Contract>.json with full compiler input. Foundry — out/<Contract>.json with metadata.
If artifacts are lost — analyze bytecode via evmole or whatsabi to recover ABI, then match compiler parameters. Takes longer, but doable for contracts on standard Solidity versions.
Verification time for one contract — 2-4 hours with diagnostics. If contract is simple and artifacts saved — 30-60 minutes.







