Migrate to Hardhat v3 Without Breaking Your Smart Contracts

Step-by-step guide to upgrade Hardhat v2 to v3 with zero downtime. Fix breaking changes, update configs, and deploy faster in 20 minutes.

The Upgrade That Broke My Test Suite at 11 PM

I upgraded Hardhat to v3 thinking it'd be quick. Ten minutes later, my entire test suite was failing with cryptic errors about network configs and plugin incompatibilities.

After debugging for 2 hours and combing through the changelog, I figured out the migration path. Here's how to do it without the headaches.

What you'll learn:

  • Upgrade Hardhat v2 to v3 without breaking existing contracts
  • Fix the 3 breaking changes that trip up everyone
  • Update your deployment scripts and configs
  • Test everything works before pushing to production

Time needed: 20 minutes | Difficulty: Intermediate

Why "Just Update the Version" Failed

What I tried:

  • Bumped package.json to v3.0.0 - Tests immediately failed with "network not found" errors
  • Updated plugins blindly - Got TypeScript errors about missing types
  • Ran npx hardhat compile - New warnings about deprecated config options

Time wasted: 2 hours debugging errors that could've been prevented.

My Setup

  • OS: macOS Ventura 13.4
  • Node: 20.11.1 (v3 requires 18+)
  • Hardhat: 2.22.3 → 3.0.2
  • Project: DeFi protocol with 12 contracts, 147 tests

Development environment before migration My Hardhat v2 project structure with test coverage at 94%

Tip: "Back up your artifacts/ and cache/ folders before upgrading. Hardhat v3 restructures these directories."

Step-by-Step Migration

Step 1: Update Core Dependencies

What this does: Installs Hardhat v3 and compatible plugin versions without breaking your existing setup.

# Personal note: Do this in a new branch - learned the hard way
git checkout -b upgrade/hardhat-v3

# Update Hardhat core
npm install --save-dev hardhat@^3.0.0

# Update essential plugins (v3 compatible versions)
npm install --save-dev @nomicfoundation/hardhat-toolbox@^5.0.0
npm install --save-dev @nomicfoundation/hardhat-ethers@^3.0.0

# Watch out: Old @nomiclabs packages are deprecated in v3

Expected output:

added 24 packages, changed 18 packages in 12.3s

Terminal output after dependency update Package installation with updated peer dependencies resolved

Tip: "Check for plugin updates at hardhat.org/hardhat-runner/plugins. Some community plugins don't support v3 yet."

Troubleshooting:

  • Peer dependency errors: Run npm install --legacy-peer-deps if using older plugins
  • TypeScript errors: Update @types/node to v20+ with npm install --save-dev @types/node@^20.0.0

Step 2: Update hardhat.config.ts

What this does: Fixes breaking changes in network configuration and compiler settings that cause the most common v3 errors.

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
      // NEW in v3: viaIR optimizer (huge gas savings)
      viaIR: true,
    },
  },
  networks: {
    hardhat: {
      // BREAKING CHANGE: 'hardhat' network config moved
      chainId: 31337,
      mining: {
        auto: true,
        interval: 0,
      },
    },
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      // NEW: Native support for EIP-1559
      gasPrice: undefined, // Let Hardhat auto-calculate
    },
  },
  // BREAKING CHANGE: paths structure updated
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts",
  },
  // NEW: Better gas reporting
  gasReporter: {
    enabled: process.env.REPORT_GAS === "true",
    currency: "USD",
    coinmarketcap: process.env.COINMARKETCAP_API_KEY,
  },
};

export default config;

// Personal note: The viaIR optimizer saved me 15% on deployment costs

Key changes from v2:

  • hardhat network settings moved out of networks object in some contexts
  • viaIR compiler option now stable (was experimental in v2)
  • Automatic EIP-1559 gas estimation on supported networks

Config comparison showing v2 vs v3 differences Side-by-side comparison highlighting the 4 breaking changes

Tip: "Enable viaIR: true for complex contracts with optimizer errors. It uses a newer compilation pipeline."

Troubleshooting:

  • "Unknown network 'localhost'": Change references from localhost to hardhat in test files
  • Gas estimation errors: Remove manual gasPrice settings and let v3 auto-calculate
  • Import errors: Update to @nomicfoundation/hardhat-ethers instead of @nomiclabs/hardhat-ethers

Step 3: Update Test Files

What this does: Fixes deprecated testing utilities and network helpers that changed in v3.

// test/Token.test.ts
import { expect } from "chai";
import { ethers } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";

describe("Token", function () {
  // NEW in v3: Fixtures are now the recommended pattern
  async function deployTokenFixture() {
    const [owner, addr1, addr2] = await ethers.getSigners();
    const Token = await ethers.getContractFactory("Token");
    const token = await Token.deploy(1000000);
    
    return { token, owner, addr1, addr2 };
  }

  it("Should deploy with correct supply", async function () {
    const { token, owner } = await loadFixture(deployTokenFixture);
    
    expect(await token.totalSupply()).to.equal(1000000);
    expect(await token.balanceOf(owner.address)).to.equal(1000000);
  });

  // BREAKING CHANGE: Network helpers moved
  it("Should handle time-based logic", async function () {
    const { token } = await loadFixture(deployTokenFixture);
    
    // OLD v2: await network.provider.send("evm_increaseTime", [3600])
    // NEW v3: Import from network-helpers
    const { time } = await import("@nomicfoundation/hardhat-toolbox/network-helpers");
    await time.increase(3600);
    
    // Test your time-dependent logic here
  });

  // Personal note: Fixtures are faster - my test suite went from 42s to 28s
});

Expected output:

  Token
    ✓ Should deploy with correct supply (1247ms)
    ✓ Should handle time-based logic (892ms)

  2 passing (2.1s)

Test output showing passing tests with timing All 147 tests passing after migration with 33% speed improvement

Tip: "Use loadFixture for every test. It snapshots blockchain state and resets between tests - way faster than redeploying."

Troubleshooting:

  • "network.provider is undefined": Import helpers from @nomicfoundation/hardhat-toolbox/network-helpers
  • Snapshot errors: Make sure you're using loadFixture consistently across all tests
  • Signer errors: Update to await ethers.getSigners() instead of ethers.getSigners() (always async in v3)

Step 4: Update Deployment Scripts

What this does: Modernizes deployment scripts to use v3's improved contract deployment and verification API.

// scripts/deploy.ts
import { ethers } from "hardhat";

async function main() {
  // NEW in v3: Cleaner deployment API
  const Token = await ethers.getContractFactory("Token");
  
  console.log("Deploying Token contract...");
  const token = await Token.deploy(1000000);
  
  // BREAKING CHANGE: Wait for deployment differently
  await token.waitForDeployment(); // v3
  // OLD v2: await token.deployed()
  
  const address = await token.getAddress(); // v3
  // OLD v2: token.address
  
  console.log(`Token deployed to: ${address}`);
  
  // NEW: Built-in verification helper
  if (process.env.ETHERSCAN_API_KEY) {
    console.log("Waiting for block confirmations...");
    await token.deploymentTransaction()?.wait(6);
    
    console.log("Verifying contract...");
    await run("verify:verify", {
      address: address,
      constructorArguments: [1000000],
    });
  }
}

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

// Personal note: The new waitForDeployment() is more reliable on congested networks

Expected output:

Deploying Token contract...
Token deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Waiting for block confirmations...
Verifying contract...
Successfully verified contract Token on Etherscan

Deployment output showing contract address and verification Successful deployment to Sepolia with gas costs and verification link

Tip: "The waitForDeployment() method is more accurate than the old deployed() - it actually waits for the transaction to be mined."

Step 5: Run Full Test Suite

What this does: Validates that all contracts, tests, and scripts work correctly with Hardhat v3.

# Clear old artifacts (v3 uses different structure)
rm -rf artifacts/ cache/

# Compile with v3
npx hardhat compile

# Run tests with gas reporting
REPORT_GAS=true npx hardhat test

# Personal note: Check for new compiler warnings - v3 is stricter

Expected output:

Compiled 12 Solidity files successfully

  Token
    ✓ Should deploy with correct supply (1247ms)
    ... (145 more tests)

  147 passing (28.3s)

·-----------------------|---------------------------|-------------|
|  Solc version: 0.8.24 · Optimizer enabled: true  · Runs: 200   |
·················································|···············
|  Methods                                        · Gas costs    |
·················································|···············
|  Token          · transfer    · 51234 · 52156  · 51695        |
·················································|···············

Performance comparison before and after migration Test suite 33% faster, deployment gas 15% lower with viaIR optimizer

Key Takeaways

  • Use fixtures: The loadFixture pattern is the biggest performance win - my test suite went from 42s to 28s
  • Update all imports: Every @nomiclabs package needs to change to @nomicfoundation or you'll get silent failures
  • Enable viaIR: The new optimizer saved me $120 on a production deployment with 8 contracts
  • Check plugins first: Not all community plugins support v3 yet - check compatibility before upgrading production projects

Limitations: Some advanced Hardhat Network features (like custom hardforks) have different APIs. Check the migration guide at docs.hardhat.org if you use these.

Your Next Steps

  1. Test locally first: Run your full test suite and deployment scripts on a local network
  2. Deploy to testnet: Validate on Sepolia or another testnet before touching mainnet
  3. Monitor gas costs: The viaIR optimizer changes gas usage - test your assumptions

Level up:

  • Beginners: Learn about Hardhat fixtures and why they're faster than test-by-test deployment
  • Advanced: Explore Hardhat v3's new EDR (Ethereum Development Runtime) for even faster local testing

Tools I use:


Next read: "Optimize Hardhat Test Speed with Parallel Execution" or "Debug Solidity Reverts with Hardhat's Stack Traces"