Deploying to Mainnet
8 min
intermediate
The Deployment Path
Deploying a smart contract is not like deploying a web app. You cannot update it after it goes live. The sequence matters:
Local tests → Testnet deployment → Testnet verification → Mainnet deployment → Mainnet verification
Skipping any step is how protocols lose money.
Step 1: Prepare Your Environment
Before touching mainnet, set up your deployment configuration.
Hardhat Config
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
},
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.DEPLOYER_PRIVATE_KEY],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
Critical Rule: Never Expose Keys
Your .env file holds your private key. Your .gitignore must include .env. Period.
# .env (NEVER commit this file)
DEPLOYER_PRIVATE_KEY=0xabc123...
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/your-key
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-key
ETHERSCAN_API_KEY=your-etherscan-api-key
Bots scan every GitHub push for private keys. If yours leaks for even one second, your wallet will be drained.
Step 2: Write the Deployment Script
// scripts/deploy.js
const hre = require("hardhat");
async function main() {
const initialSupply = hre.ethers.parseEther("1000000");
console.log("Deploying SimpleToken...");
const token = await hre.ethers.deployContract("SimpleToken", [initialSupply]);
await token.waitForDeployment();
const address = await token.getAddress();
console.log(`SimpleToken deployed to: ${address}`);
// Wait for 5 block confirmations before verifying
console.log("Waiting for block confirmations...");
await token.deploymentTransaction().wait(5);
// Verify on Etherscan
console.log("Verifying on Etherscan...");
await hre.run("verify:verify", {
address: address,
constructorArguments: [initialSupply],
});
console.log("Deployment and verification complete.");
}
main().catch(console.error);
Step 3: Deploy to Testnet First
npx hardhat run scripts/deploy.js --network sepolia
After deployment:
- Check the contract on Sepolia Etherscan.
- Call every function. Transfer tokens. Test edge cases.
- Have someone else test it. Fresh eyes catch what you missed.
Step 4: Estimate Mainnet Costs
Before deploying to mainnet, estimate the gas cost:
const factory = await hre.ethers.getContractFactory("SimpleToken");
const deployTx = await factory.getDeployTransaction(initialSupply);
const estimatedGas = await hre.ethers.provider.estimateGas(deployTx);
const gasPrice = await hre.ethers.provider.getFeeData();
const costInWei = estimatedGas * gasPrice.gasPrice;
const costInEth = hre.ethers.formatEther(costInWei);
console.log(`Estimated deployment cost: ${costInEth} ETH`);
Typical costs (at 20 gwei gas price):
- Simple ERC-20: ~$30-80
- ERC-721 (NFT): ~$50-150
- Complex DeFi protocol (multiple contracts): ~$2,000-10,000
Step 5: Deploy to Mainnet
npx hardhat run scripts/deploy.js --network mainnet
This is the point of no return. Double-check everything before pressing enter.
Post-Deployment Checklist
| Step | Action | Why |
|---|---|---|
| 1 | Verify on Etherscan | Users need to read and trust the source code |
| 2 | Test with small amounts | Confirm functions work on mainnet |
| 3 | Record addresses | Document every contract address in your repo |
| 4 | Transfer ownership | Move admin keys to a multisig (like Gnosis Safe) |
| 5 | Monitor | Set up alerts for unusual transactions (via Tenderly or OpenZeppelin Defender) |
Foundry Deployment
If you use Foundry instead of Hardhat:
# Deploy
forge create --rpc-url $MAINNET_RPC_URL \
--private-key $DEPLOYER_PRIVATE_KEY \
src/SimpleToken.sol:SimpleToken \
--constructor-args 1000000000000000000000000
# Verify
forge verify-contract $CONTRACT_ADDRESS \
src/SimpleToken.sol:SimpleToken \
--etherscan-api-key $ETHERSCAN_API_KEY
Key takeaways
- Always deploy to a testnet first. Mainnet deployments are irreversible.
- Never expose private keys in code or Git history. Use environment variables and
.gitignore. - Verify your contract on Etherscan so users can audit the source code.
- After mainnet deployment, transfer ownership to a multisig wallet. A single compromised key should never control the protocol.
Quiz: Deploying to Mainnet
1 / 5Why should you deploy to a testnet before mainnet?