Skip to main content

Deployment and verification

Networks

Deploy to a testnet before mainnet, always.

NetworkChain IDStatus
Ethereum mainnet1Live
Sepolia11155111Current general-purpose testnet
Holesky17000Staking and validator testing
Polygon mainnet137Live
Polygon Amoy80002Current Polygon testnet
Ropsten, Rinkeby, Kovan, GoerliShut down or deprecated
Polygon Mumbai (80001)Shut down April 2024

Testnets are retired regularly. A tutorial naming Ropsten or Goerli predates the current set, and its faucet links are dead — check the network is live before spending time on a configuration that cannot connect.

Testnet ETH comes from faucets; see Resources.

RPC providers

A deployment needs a node. Running your own is possible (Geth) but most work goes through a provider:

https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
https://sepolia.infura.io/v3/YOUR_PROJECT_ID

Keep the key server-side or in an untracked environment file. A key in a front-end bundle is public, and while it cannot move funds, it can be used until your quota is exhausted.

Keys

Never commit a mnemonic or private key. Bots scan public repositories continuously and drain funded addresses within minutes of a push.

# .gitignore
.env
secret.json
require("dotenv").config();
const mnemonic = process.env.MNEMONIC;

Use a dedicated deployment account holding only what the deployment needs. For mainnet, prefer a hardware wallet or a multisig over a raw key on a build machine.

Deploying

Foundry:

forge script script/Deploy.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--verify

Hardhat:

npx hardhat ignition deploy ./ignition/modules/MyModule.ts --network sepolia

Truffle (deprecated):

truffle migrate --network sepolia
truffle console --network sepolia

Verification

Verifying publishes the source and lets anyone confirm the deployed bytecode was compiled from it. Block explorers then show the source and a read/write interface. An unverified contract is a black box, and users are right to distrust one.

Verification requires an explorer API key — Etherscan, Polygonscan and most others use the same Etherscan-compatible scheme.

Foundry:

forge verify-contract ADDRESS src/MyContract.sol:MyContract \
--chain sepolia \
--etherscan-api-key $ETHERSCAN_API_KEY \
--constructor-args $(cast abi-encode "constructor(address)" 0xOWNER)

Hardhat:

npm install --save-dev @nomicfoundation/hardhat-verify
npx hardhat verify --network sepolia ADDRESS "constructor arg"

Truffle:

npm install --save-dev truffle-plugin-verify
truffle run verify MyContract --network sepolia

Verification fails unless the compiler version, optimiser setting, optimiser runs and constructor arguments match the deployment exactly. The commonest cause of a mismatch is a different optimiser runs value between the build that was deployed and the one being verified.

Flattening

Older explorers accepted only a single file, so multi-file projects had to be flattened first:

forge flatten src/MyContract.sol > flattened.sol
npx hardhat flatten contracts/MyContract.sol > flattened.sol

npm install -g truffle-flattener
truffle-flattener contracts/MyContract.sol > flattened.sol

Two known limitations of truffle-flattener: it does not handle aliased imports, and flattening duplicates the SPDX licence identifier and pragma lines, which the compiler rejects.

// Not supported by truffle-flattener
import {symbol1 as alias, symbol2} from "./filename.sol";

forge flatten handles aliases and deduplicates the licence and pragma lines, so prefer it even in a Truffle project.

Flattening is largely obsolete — standard-JSON verification uploads the whole source tree with its metadata and is what the plugins above use by default. Only reach for a flattened file if an explorer offers nothing else.

After deploying

  • Record the address, the transaction hash, the compiler version and the constructor arguments. Reconstructing them later for verification is painful.

  • Confirm the deployed bytecode matches what you built:

    cast code ADDRESS --rpc-url $RPC_URL
  • Transfer ownership away from the deployment key if the contract has an owner.

  • On mainnet, deploy from a fresh nonce and check the gas price first. A deployment sent at a bad moment can cost many multiples of its usual price.