Hashtag Web3 Logo

Deploying to Mainnet

8 min
intermediate

The Deployment Path

A deployment creates on-chain code and state. Replacing its behavior later may require an upgrade mechanism or a migration, so test the deployment sequence as well as the contract functions:

Local tests → Testnet deployment → Testnet verification → Mainnet deployment → Mainnet verification

Record the configuration, compiler settings, addresses, and permissions used at each stage.

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

Keep private keys out of source files and version control. If local environment files contain credentials, exclude them from Git and check that they have not already been committed.

# .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

Treat a published private key as compromised. Removing it from a file does not invalidate copies or remove it from repository history.

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

Before broadcasting, verify the chain, sender, constructor arguments, linked addresses, and expected permissions. Compare the resulting deployment with the recorded configuration.

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 / 5

Why should you deploy to a testnet before mainnet?