Hashtag Web3 Logo

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.

Local Tests forge test Testnet Deploy Sepolia Verify Etherscan Mainnet ⚠️ Irreversible No undo Monitor Tenderly

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:

  1. Check the contract on Sepolia Etherscan.
  2. Call every function. Transfer tokens. Test edge cases.
  3. 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

StepActionWhy
1Verify on EtherscanUsers need to read and trust the source code
2Test with small amountsConfirm functions work on mainnet
3Record addressesDocument every contract address in your repo
4Transfer ownershipMove admin keys to a multisig (like Gnosis Safe)
5MonitorSet 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 / 5

Why should you deploy to a testnet before mainnet?