Deployment and verification
Networks
Deploy to a testnet before mainnet, always.
| Network | Chain ID | Status |
|---|---|---|
| Ethereum mainnet | 1 | Live |
| Sepolia | 11155111 | Current general-purpose testnet |
| Holesky | 17000 | Staking and validator testing |
| Polygon mainnet | 137 | Live |
| Polygon Amoy | 80002 | Current Polygon testnet |
| Ropsten, Rinkeby, Kovan, Goerli | — | Shut 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.