iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Deploy

Deploying a smart contract is a one-way commit: the bytecode is immutable, the constructor runs once, the address is permanent. Use a deterministic deployer, verify on Etherscan / Sourcify, plan upgradeability up front (or commit to immutability), and keep deploy scripts in source control.

Hardhat / Foundry deploy + verification

EXAMPLE
// ===== Foundry: forge create =====
// Cargo-clean syntax, source goes to Etherscan via verify

forge create src/Token.sol:Token \
  --rpc-url $RPC_URL \
  --private-key $DEPLOYER_KEY \
  --constructor-args 1000000000000000000000 \
  --verify --etherscan-api-key $ETHERSCAN_KEY

// ===== Foundry: a deploy script =====
// script/Deploy.s.sol
pragma solidity ^0.8.24;
import 'forge-std/Script.sol';
import { Token } from '../src/Token.sol';

contract Deploy is Script {
    function run() external {
        uint256 pk = vm.envUint('DEPLOYER_KEY');
        vm.startBroadcast(pk);

        uint256 supply = 1_000 ether;
        Token token = new Token(supply);

        console.log('Token deployed to:', address(token));
        vm.stopBroadcast();
    }
}

// Run
// forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify

// ===== Hardhat: ethers v6 =====
// scripts/deploy.ts
import { ethers, network, run } from 'hardhat';

async function main() {
  const [deployer] = await ethers.getSigners();
  console.log('Deploying from', deployer.address);

  const supply = ethers.parseEther('1000');
  const Token = await ethers.getContractFactory('Token');
  const token = await Token.deploy(supply);
  await token.waitForDeployment();
  const address = await token.getAddress();
  console.log('Token deployed to:', address);

  // Wait a few blocks before verifying (Etherscan indexing lag)
  if (network.name !== 'hardhat' && network.name !== 'localhost') {
    await new Promise((r) => setTimeout(r, 30_000));
    await run('verify:verify', {
      address,
      constructorArguments: [supply],
    });
  }
}

main().catch((e) => { console.error(e); process.exit(1); });

// Run
// npx hardhat run scripts/deploy.ts --network sepolia

// ===== OpenZeppelin Upgradeable proxy =====
// scripts/deploy-upgradeable.ts
import { upgrades, ethers } from 'hardhat';

async function main() {
  const Box = await ethers.getContractFactory('Box');
  const proxy = await upgrades.deployProxy(Box, [42], { kind: 'uups' });
  await proxy.waitForDeployment();
  console.log('Proxy:', await proxy.getAddress());
  // Later upgrade:
  // const BoxV2 = await ethers.getContractFactory('BoxV2');
  // await upgrades.upgradeProxy(proxyAddress, BoxV2);
}

// ===== Verification checklist =====
// - source code verified on Etherscan / Sourcify
// - constructor args MUST match exactly (encoded the same way)
// - compiler version + optimizer settings recorded
// - libraries linked correctly if any
// - proxy admin / owner set to a multisig (not your hot wallet)

// ===== Production deployment checklist =====
// 1) Tests passing AND audits complete
// 2) Test networks deployed + tested (Sepolia / Goerli replacement / Base Sepolia)
// 3) Deploy via a multisig (Gnosis Safe) — not from a hot wallet
// 4) Constructor args double-checked; transferOwnership to multisig
// 5) Source verified on Etherscan + Sourcify
// 6) Frontend, indexer, and subgraph updated with the new address
// 7) Announce the contract address via official channels signed by the multisig
// 8) Set up monitoring (Tenderly, Defender, Forta) for unusual activity

// ===== Cost +chain considerations =====
// L1 mainnet:                     expensive; verify gas budget twice
// L2 (Base, Arbitrum, Optimism):  cheap deploys; same toolchain
// Sidechain (Polygon PoS):         cheap; lower security guarantees
// Testnets:                        free; spotty third-party tool support

// ===== Common pitfalls =====
// - Deploying from a wallet you control; ownership rotation later is risky
// - Hard-coded addresses in constructors (changes per chain) — pass via args
// - Missing verification -> users cannot read the source on Etherscan
// - 'I'll add upgradability later' — you cannot. Decide BEFORE deploy.
// - Re-deploying after a 'small fix' -> users hold the OLD token; coordinate migration

Why it matters

Treat smart-contract deploys like database migrations: plan, test on testnets, verify the source code, and transfer ownership to a multisig immediately. Once you push to mainnet, the bytecode is permanent; the only "rollback" is "deploy a new contract and migrate everyone over", which is far more expensive than testing twice.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// scripts/deploy.ts (Hardhat)
import { ethers } from 'hardhat';
async function main() {
    const Token = await ethers.getContractFactory('MyToken');
    const token = await Token.deploy();
    await token.waitForDeployment();
    console.log('deployed:', await token.getAddress());
}
main().catch(e => { console.error(e); process.exit(1); });
Try it Yourself »

Discussion

Loading…